Apps¶
The five browser apps (Observer, Continuum, Code, Entity and Flow Editor) can be
installed, started, stopped and updated from the gateway: from the console's
Apps page, from the HTTP API below, or with abstractgateway apps. Nobody has
to open a terminal or install Node.js by hand. The Apps page also lists the
desktop app, the Assistant (see The Assistant).
On a card, a plain user sees one button for the state the app is in:
| State | Buttons |
|---|---|
| Not installed | Install |
| Installing | a progress bar (one row per part, e.g. "Code in the browser" and "Code in the terminal") and Cancel |
| Installed (running or not) | Open, and Open in Terminal right beside it when the app's terminal version is installed |
| A newer version is published | Update to x.y.z beside Open (an admin; its tooltip: "Install the newest Flow Editor (0.8.0); a running app restarts on it") |
| Failed | the reason, Show details, and Install again |
The status badge beside the name is the start/stop control, one click as Install is (an admin):
| Badge | A click | Its tooltip |
|---|---|---|
| Running (the gateway started it) | stops it (POST /apps/{id}/stop) |
"Running — click to stop" |
| Stopped (installed, not running) | starts it without opening a tab (POST /apps/{id}/launch) |
"Stopped — click to start" |
| Stopped unexpectedly / Keeps crashing | starts it again | "Stopped unexpectedly — click to start" |
| Running, started outside the gateway | nothing: the badge is disabled | "Started outside the gateway — stop it where it was started" |
| any badge, for a user who is not an admin | nothing: the badge is disabled | "Only an admin can start or stop apps" |
| Starting… / Stopping… / Not installed | a plain pill, not a control | — |
While its request runs the badge reads "Stopping…" or "Starting…"; the result
(or the refusal, with Show details) shows on the card as for every other
action. There is no separate Stop or Start button: Open stays (it starts a
stopped app, then opens it). The Assistant's badge: see
The Assistant. The gateway sends the badge on
every row (status_control, see HTTP API), so the web console and the
terminal console say the same words.
Show log, versions, addresses and commands are under Technical details.
Updates¶
Every app row says when a newer version is published: the browser apps from
the npm registry (dist-tags.latest), the Assistant from PyPI
(<pypi_url>/abstractassistant/json, info.version). One cache serves the
npm registry, PyPI and GitHub (the terminal apps' releases): an answer is
reused for 10 minutes, a failure for 1 minute, then the registry is asked
again, so a version published while the gateway runs shows up within 10
minutes without a restart (Check again asks the gateway at once, still
through that cache). update_available is true when that version is newer
than the installed one (semantic versions, prereleases before their release).
- An app the gateway installed (browser apps, the Assistant) gets Update to x.y.z: one click starts the update as a job, the same one as Install for that version. A running browser app restarts on the new version. The Assistant: see The Assistant.
- An app started outside the gateway shows "Latest 0.8.0 · Started outside the gateway — update it where it was installed" and has no update action: its process belongs to whoever started it. An Assistant installed from a source checkout likewise shows "Latest x.y.z · Installed from a source checkout — update it there".
- The button's label and tooltip come from the gateway (
update_label,update_tipon the row), so the web console and the terminal console show the same words; in the terminal consoleuupdates the selected app, with the tooltip as its confirmation. - When the installer upgrades the framework with no gateway running, it leaves
the apps' versions in
<data dir>/apps-upgrade.pendingand the gateway brings each installed app UP to that version at its next start; an app you already updated past it from the Apps page is kept as it is.
What happens when you click Install¶
- Node.js. The apps are small Node.js servers. If the gateway finds
Node.js 18 or newer on the machine, it uses it. Otherwise it installs
Node.js 24 for you in its own data folder (
<data dir>/runtime/node/, about 56 MB to download, no administrator rights). This is the Node.js build published on PyPI asnodejs-wheel-binaries, the same one the installer's--with-appsoption gets withuv tool install nodejs-wheel. The download is checked against PyPI's sha256 checksum. - The app. The gateway downloads
@abstractframework/<name>from the npm registry into<data dir>/apps/<app>/<version>/and checks it against the registry's sha512 integrity hash. Apps that need other npm packages get them through npm (with its cache in<data dir>/apps/npm-cache/); Code needs none and is unpacked directly. - The terminal app, too. When the app also runs in a terminal and a
ready-made download exists for this computer (Code, see
Terminal versions), the same Install installs it
right after the browser app: ONE job whose
partsare the two rows the card shows (installtheninstall-tui, eachwaiting,running,done,failed,cancelledorskipped). Cancel stops both. If the terminal part fails, the browser app stays installed and the job says so ("Code is installed for the browser, but its terminal app did not install: …"); with Technical details on, the card offers "Install terminal app" to try that part again. Without a ready-made download for this computer, Install installs the browser app only and the terminal app's command stays under Technical details. The row'sinstall_parts(["web"]or["web", "tui"]) says in advance what Install covers.
Install only installs: nothing starts and no tab opens. The card then shows
Open, which starts the app on a free port when it is stopped, waits until
it answers, and opens it in a new tab, already signed in, at
<the gateway's address>/apps/<app>/ (see
Apps are served through the gateway).
Every step is a job with a percentage, downloaded bytes and a plain message.
When a step fails, the job says why in one sentence and carries the full log
in its details field (<data dir>/apps/jobs/<job>.log).
Running apps¶
- An app started from the gateway is a child process of the gateway. It stops when the gateway stops (even if the gateway is killed: the app watches the pipe the gateway holds open and exits when it closes).
- An app you started stays enabled: the next time the gateway starts, it
starts the app again, on the same port when that port is free. Stop the app
to turn this off (click its Running badge in the console, or
abstractgateway apps stop <app>). Nothing is registered with launchd, systemd or the login items; the gateway itself starts the apps. - If an app exits unexpectedly, the gateway restarts it (after 1, 2 then 4 seconds). After more than 3 crashes in a minute it stops trying and shows "crash_loop" with the app's log.
- Each app writes its output to
<data dir>/logs/apps/<app>.log. - Apps always listen on
127.0.0.1: browsers reach them through the gateway (next section), never directly. Ports: the app's usual port when it is free, else the first free port in 3100-3199. The usual ports are the framework's stack port map (scripts/start-local.sh): Observer 3001, Continuum 3002, Code 3003, Entity 3004, Flow 3005.
Apps are served through the gateway¶
Every browser app opens at /apps/<app>/ on the gateway's own address:
http://127.0.0.1:8080/apps/observer/ on the gateway machine,
https://gateway.example.com/apps/flow/ behind a reverse proxy, the same
path through a tunnel. One address and one port serve the console, the API
and every app, so a remote or headless gateway needs nothing more than the
one address it already has.
The gateway relays each request to the app's own server on 127.0.0.1
(app_proxy.py):
- Signed in, per app. Every request under
/apps/<app>/needs a valid gateway session for that app: the app's own session cookie, set by the one-time handover below withPath=/apps/<app>/. Without one, opening a page sends the browser to the console (/console#apps?open=<app>&path=…), which signs you in and opens the app on the page you asked for; any other request gets 401app_sign_in_required. A WebSocket without one is refused. A request or WebSocket whoseOriginis not the gateway's own address (another site, or another port on the same host) is refused too (403cross_origin). - Public assets. What a browser fetches without cookies answers without
a session: the web app manifest (
manifest.webmanifest,manifest.json,site.webmanifest),favicon*,icon*,apple-touch-icon*andicons/<file>at the app's root, GET/HEAD only. They are relayed with no cookie and returned only when the app answers 200/304 with a manifest or image type, with noSet-Cookie. Pages, scripts,sw.jsand everything underapi/stay signed-in only. - Streaming. Responses are relayed as they arrive (live updates, server-sent events), and WebSockets frame by frame.
- What the app receives. The path with
/apps/<app>removed, plusX-Forwarded-Prefix: /apps/<app>,X-Forwarded-For: <the browser's address>(written by the gateway, never passed through from the browser),X-Forwarded-ProtoandX-Forwarded-Host, always both. A request whoseHostis not a host name or address the gateway can pass on (browsers accept some, such asa_b.example.com, that it cannot) is refused with 400invalid_hostand never reaches the app. It receives only its own cookies (<app prefix>_*): never the console's session cookie, never anAuthorizationheader. It can set only its own cookies in return. - Only apps that say they can. An app announces that it can be served
this way with a header on every response:
X-AbstractFramework-App: <app>; mount=1(the abstractuic app-server kit does it). The row'smountedandapp_path(/apps/<app>/) say whether it does. An older app version does not: it would read every visitor as a browser on the gateway machine (the gateway is its local peer), so the gateway never serves it under/apps/<app>/(409not_mountable), and it opens by its own port from the gateway machine only (409app_loopback_onlyfrom anywhere else, with the hint to update it).
How an app uses these headers (base path, the browser's address, cookie
paths, the identity header) is the app-server kit's contract:
abstractuic app-server.
Apps started outside the gateway¶
An app can also run without the gateway having started it: the framework's
development stack (scripts/start-local.sh), npx @abstractframework/observer,
a global npm install, a service. The gateway finds such an app by asking the
usual ports on this machine (3001-3005, then 3000 and 3007) for their start
page and reading its title
("AbstractObserver", "AbstractContinuum", "AbstractCode", "AbstractEntity",
"AbstractFlow"). None of the apps has an address that says who it is without
a gateway sign-in, and the start page is plain HTML, so this costs one short
local request per port (half a second at most, all ports at once, remembered
for 5 seconds). The version is read from the package.json of the program
listening on the port, when this computer shows which program that is.
Such an app is listed as installed and running, with source: "external",
managed: false, its url, port and version, and one action: Open.
Opening it works exactly like opening an app the gateway started (the
one-time sign-in link below, and /apps/<app>/ when it announces it can be
served there): the app's server reads the same sign-in cookies whoever
started it. The gateway does not stop, update or show the
log of an app it did not start: its Running badge is disabled with the
tooltip "Started outside the gateway — stop it where it was started", and
Technical details says "Started outside the gateway on port 3001" (when a newer version is
published, the card says "Latest x.y.z · Started outside the gateway — update
it where it was installed"), and POST /apps/{id}/stop
answers 409 started_outside_gateway. Starting the gateway's own copy while
an outside one runs does nothing (the app is already running).
Who may install¶
Installing an app (or Node.js, or a terminal version) runs software on the
gateway machine, so it follows the gateway's allow_engine_install setting.
With no saved choice, installs are allowed for someone at the gateway machine
itself, whatever address the gateway listens on (a browser or the tray on that
machine, including one that uses its network address), and for everyone when
the gateway listens on this machine only. A browser on another computer needs
an admin to turn the setting on. How "at the gateway machine" is decided is
in configuration.md.
Opening an app signed in¶
The console asks the gateway for a one-time link
(POST /api/gateway/apps/{id}/open, sending the browser's origin) and
opens it in a new tab. The link (/apps/handover/<code>) works once, for two
minutes, and only on the address it was made for. The gateway creates a
browser session for the person who clicked, puts it in the app's own sign-in
cookies (Path=/apps/<app>/), and answers with a relative redirect to
/apps/<app>/, so it lands on whatever address the browser used (a LAN
address, a reverse proxy, a tunnel). The app's server finds the session and
the app opens connected. The gateway token never appears in the page, the
link or the browser's storage.
The answer also carries app_path (/apps/<app>/…) and app_url (the
origin you sent, or the address the request came in on, plus app_path).
origin must be exactly scheme://host[:port] (400 invalid_origin
otherwise).
An older app version that cannot be served through the gateway keeps the
direct link to its own port (app_path: null, cookies at Path=/), which
works from the gateway machine only; from another computer the gateway says
so (409 app_loopback_only) instead of producing a broken link.
Landing somewhere inside the app¶
POST /api/gateway/apps/{id}/open accepts an optional path: where inside
the app the browser lands after the handover, for example /#new, Entity's
creation form. The path is bound to the one-time code when the link is made
(the link itself carries nothing), and it must stay inside the app: it starts
with a single /, and a second leading slash (//host), a full address, a
backslash, a space or a control character is refused with 400
invalid_app_path before any link is made. Without path the browser lands
on the app's start page.
An app with nothing in it yet¶
Each app row carries content_summary: a small fact about what the app holds
on this gateway, or null for apps that report nothing. Entity reports {"entities_count": n}: the number of entries
GET /api/gateway/entities lists (counted from the same entity registry,
without reading any entity), null when it cannot be known, including for an
Entity started outside the gateway whose page names another gateway. When the
count is exactly 0, the Entity card's button reads Create your first
entity and opens the app on /#new through the handover above; with one
entity or more, or an unknown count, it reads Open. The guide's Apps step
shows the same card.
Terminal versions¶
Some apps also run in a terminal: Code (abstractcode, a
Rust terminal app from the abstractcode repository); Flow Editor, Observer,
Continuum and Entity are browser apps only. The gateway's own console also
has a terminal twin, abstractgateway-console, which the console's Done step
mentions once.
Every app row therefore lists its interfaces: interfaces[0] is the browser
app (it mirrors the row's own fields), and apps with a terminal version have a
second entry of kind "tui":
{"kind": "tui", "name": "Code in the terminal", "binary": "abstractcode",
"installed": true, "version": "0.7.0", "source": "gateway", "path": "/home/me/.local/bin/abstractcode",
"latest_version": "0.7.0", "update_available": false,
"install_available": false, "install_method": "release_binary", "install_blocked_reason": null,
"install_command": "cargo install abstractcode", "download_page": "https://github.com/lpalbou/abstractcode/releases",
"launch_available": true, "launch_blocked_reason": null, "launch_mode": "terminal",
"command": "abstractcode --gateway http://127.0.0.1:8080",
"signin_command": null, "active_job": null}
- One folder, shared with the installer. Terminal apps go where uv put
the gateway's own commands (
uv tool dir --bin, usually~/.local/bin, on Windows%USERPROFILE%\.local\bin), which the installer puts onPATH; the gateway reads it from the receipt uv writes into its environment (uv-receipt.toml). The installer buildsabstractcodeand the terminal console into the same folder (cargo install --rootits parent), soabstractcoderuns by name whichever of the two installed it. A gateway that is not a uv tool install (a development checkout, pip) uses<data dir>/apps/bin/, offPATH(commandthen carries the full path). A receipt uv did not write (no[tool]table, noentrypointslist, a relativeinstall-path) is ignored and<data dir>/apps/bin/is used. - Found by presence. The gateway never imports or runs an app to detect
it: it looks for the binary in that folder, in
<data dir>/apps/bin/(where gateways before 0.7.1 put it; the next install or update moves it and removes the old copy), onPATHand in~/.cargo/bin, and accepts it only when its--helpnames the program (PyPI's unrelated Python packageabstractcodeinstalls a script with the same name).sourcesays where it was found (gateway: one of the gateway's folders,path: elsewhere). - Install (
install_method).release_binary: Code publishes prebuilt binaries for macOS (Apple silicon and Intel), Linux (x86_64 and arm64, glibc) and Windows (x86_64) on its GitHub release, with aSHA256SUMSfile. The card's Install (andinstall-tuion its own) downloads the archive for this computer, checks it againstSHA256SUMSand against the sha256 digest GitHub reports for the file (both must agree), unpacks the single binary into the folder above, makes it executable and runs--versionbefore it replaces anything. It replaces only a copy of the app itself (its own earlier install or the installer's build). When another program already holds the name there, for example the older Pythonabstractcodefrom PyPI, the install stops withforeign_binary, names the file and leaves it: runuv tool uninstall abstractcode(or remove the file), then install again. About 3 MB, no administrator rights, no terminal.cargo: there is no prebuilt binary for this computer (musl Linux, Windows on ARM, other CPUs), or the program is published as source only (the gateway console, crates.io). Theninstall_availableisfalse,install_blocked_reasonstarts with "Needs the Rust toolchain" andinstall_commandis the exact command to copy; the console shows no Install button. - Open (
launch_mode).terminal: the caller is an admin on the gateway machine itself, and "Open in Terminal" opens a new terminal window there (macOS Terminal; on Linux the first ofx-terminal-emulator,gnome-terminal,konsole,xfce4-terminal,kitty,alacritty,xtermwhen a desktop session exists; Windowscmd).copy: the browser is on another computer (or the caller is not an admin); with Technical details on, the card showscommand(this gateway's address as the browser reaches it) andsignin_command(abstractcode login --gateway <url> --token <your token>) to copy. A terminal is never opened for a remote browser. How the card presents each case: console.md.
Signed in, without a token anywhere it could leak¶
The terminal window runs a small launcher script
(<data dir>/apps/terminal/open-code-<random>.command, mode 0700) that holds
a one-time code (two minutes, single use) and deletes itself as its first
line. It runs tui_signin.py from the gateway package (standard library only,
python -I), which trades the code at POST /apps/tui-handover for a bearer
token and then becomes the terminal app with the token in the app's
environment (ABSTRACTCODE_GATEWAY_TOKEN, which Code prefers over its
saved login). The token
- is new for each launch and is not the admin token;
- acts as the person who clicked (their identity and role, never more);
- is accepted only from this machine (a loopback socket peer);
- lives in the gateway's memory only, so it stops working when the gateway restarts (open the app again from the console);
- is never in a command line, a file, the page, or a URL.
From a terminal¶
Everything above works without the console:
abstractgateway apps list # each app; Code also gets a "terminal:" line (installed, where, what to run)
abstractgateway apps install-tui code # the same job: release binary, SHA256SUMS + GitHub digest, --version check
abstractgateway apps tui-command code # the one-time sign-in line for THIS machine (2 minutes, works once) + the plain command
install-tui prints the job's progress; when there is no prebuilt binary for
the computer it prints the reason and cargo install abstractcode and exits 2.
tui-command makes the same one-use, self-deleting launcher as "Open in
Terminal" (it holds a single-use code, never a token) but opens no window: run
the printed line in a terminal on the gateway machine. From another computer
it prints the command and the abstractcode login line instead.
/apps/tui-handover sits outside /api/gateway like the browser handover:
the code in its body is its only credential, it answers only loopback socket
peers without proxy headers, and a browser handover code does not work there
(nor the reverse).
How the apps are served¶
Each app runs its own small server, started by the gateway, rather than being served as static files by the gateway. The app's server does real work:
- it holds the sign-in (
/api/connection/gateway, HttpOnly session cookies, CSRF) and forwards/api/*to the gateway, which is how live updates (server-sent events) reach the page; - Observer reveals local folders, Flow keeps its connection file, and Continuum proxies the agora hub.
The gateway serves them all on its own address at /apps/<app>/ (above). It
gives each server its port and gateway URL as launch flags (--port,
--host, --gateway-url), which win over the environment and over any
settings file the app keeps. An installed version older than the flags
(Observer 0.1.14, Code 0.5.0 or Entity 0.2.2 and earlier), or a global
install of unknown version started from the tray, also gets the legacy
environment (PORT, HOST, <APP>_GATEWAY_URL), so it still listens on the
port the gateway chose. The bind address is always 127.0.0.1.
Settings¶
Four settings control the apps: apps.node (Node.js for apps),
apps.ports (Ports for apps), apps.npm_registry (npm registry) and
apps.pypi_url (Node.js download index). apps.host (Where apps
listen) is deprecated: apps always listen on 127.0.0.1 and open
through the gateway. It accepts only a loopback address; an older saved
0.0.0.0 (or ABSTRACTGATEWAY_APPS_HOST) is ignored with one warning in the
gateway's log, and clearing it removes the warning. Change them from the Apps page (the toolbar gear, Apps settings), the
terminal console (Runtimes → Runtime knobs → Edit apps settings) or the
CLI:
abstractgateway apps config get [NAME] [--json]
abstractgateway apps config set ports 3100-3199 # "" clears back to the default
A saved value applies at the next app start or download. Values, defaults and the environment-variable fallbacks are listed in configuration.md.
Install, update, start and stop need an admin, and installs follow Who may install. Any signed-in user can list the apps and open a running one (as themselves).
Without internet¶
- Apps already installed keep working offline.
- Installing needs the npm registry (and PyPI for Node.js and the Assistant). When it cannot be reached, the Apps page says so on each app and the Install button is off; a job that loses the network fails with "The npm registry (registry.npmjs.org) is not reachable…". The gateway does not ship app copies: the five packages are about 6 MB, but four of them need npm dependencies (Flow alone installs to about 130 MB), which would have to ship too.
Command line¶
The same actions, through the running gateway:
abstractgateway apps list # Node.js, every app, installed/latest, URL
abstractgateway apps install code # the browser app and, where available, its terminal app
abstractgateway apps install code --launch # ... and start it
abstractgateway apps install assistant # the desktop Assistant, into the gateway's Python
abstractgateway apps launch assistant # open it on this computer
abstractgateway apps launch observer
abstractgateway apps open observer # prints a one-time signed-in link
abstractgateway apps logs observer --tail 50
abstractgateway apps update flow
abstractgateway apps stop observer
abstractgateway apps runtime # install Node.js only
abstractgateway apps install-tui code # Code's terminal version (see "Terminal versions")
abstractgateway apps tui-command code # one-time signed-in launch line for this machine
abstractgateway apps jobs [JOB_ID]
--url, --token and --data-dir work as for abstractgateway models: by
default the command finds the gateway running for this data dir and uses its
admin token on a loopback URL.
The Assistant (a desktop app)¶
AbstractAssistant (PyPI abstractassistant) is the framework's desktop
companion: a menu-bar app with a chat palette and hands-free voice
conversations. It is a Python app, not a browser app, so its card
(kind: "desktop", id assistant, after the five browser apps) has no address
or port. The full abstractframework install already includes it, in the same
Python environment as the gateway; a gateway-only install may not.
- Found by presence (nothing is imported or started to find it): the
abstractassistantcommand next to the gateway's own Python (or onPATH), the installed package (importlib.util.find_spec, without importing it; a folder that merely has the package's name does not count), and on macOSAbstractAssistant.appin/Applicationsor~/Applications. - One Assistant: the installed package. When the package is installed (its
command, else this Python running it), the card describes, opens and watches
that one; the app bundle is used only when no package is installed. The
version is that Assistant's (the package's, or the app's
Info.plistfor the bundle). It is Running when a process of that Assistant runs (its command,python -m abstractassistant…, or — for the bundle — the app's own program). When the OTHER one runs (for example an olderAbstractAssistant.appnext to the installed package), the card is not "Running": it says so in one sentence — "Another Assistant is running: /Applications/AbstractAssistant.app 0.5.0 — quit it to use 0.13.0" — and Open still starts the installed one. The tray's "Launch Assistant" uses the same detection (apps_desktop.detect_assistant) and shows the same sentence under it, so the tray and the console always agree. - Its badge (the card's start/stop control): Running for the
Assistant this gateway opened — a click quits it (
POST /apps/assistant/stop, SIGTERM: it closes cleanly), tooltip "Running — click to quit"; Running for one started anywhere else (its menu, the app bundle, a terminal) — disabled, "Started outside the gateway — stop it where it was started", andPOST /apps/assistant/stopanswers 409started_outside_gatewaywithout touching it; Stopped — a click opens it (the same as Open), tooltip "Stopped — click to start", or disabled with the reason from another computer ("The Assistant runs on the gateway's computer: open it there."). - Install installs
abstractassistantinto the gateway's own Python as a job (uv pip install --python <gateway python> abstractassistant, or pip when there is no uv), with everyabstract*package the gateway runs pinned to its current version (name==versionrequirements in the same command), so installing the Assistant never changes the gateway. The same rule as the other installs decides who may install (see Who may install). - Open starts it on the gateway's computer: its command (or this Python
running it), or
open -a AbstractAssistant.appwhen only the app exists, as a separate process with none of the gateway's tokens, secrets or keys in its environment. A running Assistant is not started twice: the app is brought to the front (or, when it was started from its command, the card says its icon is in the menu bar). An Assistant opened from here (the console or the menu bar icon) is connected to this gateway and signed in as you automatically: the gateway starts it with--gateway-url <address> --gateway-handover-file <file>, where the file (readable by you only, in<data dir>/handover/) holds a one-time code valid for two minutes and the id of the user who clicked Open ({schema: "abstractgateway.desktop_handover.v1", code, base_url, expires_at, user_id}). The Assistant reads and deletes the file, trades the code for a remembered sign-in and keeps it. No code or token is ever on its command line or in its environment. An Assistant that is already running cannot receive a code: if it is not signed in, quit it and open it again from here. - Update to x.y.z (when PyPI has a newer
abstractassistant) runs the install job withabstractassistant==<latest>and the same pins (the Assistant itself is never pinned to its old version). An Assistant this gateway opened is quit and opened again on the new version, signed in as the admin who clicked Update ("a running app restarts on it"). An Assistant the gateway did not open (started from its command or a script such asscripts/start-local.sh, or the other one of the two above) is left alone: the tooltip, the job's result and the card say "Quit it and open it again to run 0.14.0" until that process ends. When PyPI cannot be reached, Technical details say "PyPI is not reachable: …" and there is no Update. The gateway remembers which Assistant it opened only while it runs. The update must land: when the installer finishes but the requested version is not the one installed afterwards, the job fails with "Asked for 0.14.0, but 0.13.0 is still installed — not updated." (the installer output under Show details) and nothing is restarted. - Installed from a source checkout (an editable install: the package's
direct_url.jsonsaysdir_info.editable: true, asscripts/build.shinstalls it): the card shows the version the checkout's ownpyproject.tomldeclares ([project].version, in the folderdirect_url.jsonnames; the editable install's metadata goes stale as the checkout moves on, and when the checkout declares no readable version the card shows none, with the reason) and, when PyPI has a newer one, "Latest x.y.z · Installed from a source checkout — update it there", with no Update (installing a PyPI wheel would replace the checkout);POST /apps/assistant/updaterefuses with the same sentence. - From another computer the card says "The Assistant runs on the gateway's computer: open it there." with no button: it is a desktop app for that computer's screen.
- Technical details show its version, where it was found and the launch command (or the install command when it is not installed).
HTTP API¶
All routes are under /api/gateway/apps and need a signed-in principal, except desktop-handover.
| Method and path | Who | What |
|---|---|---|
GET /apps?latest=true |
any user | Node.js status, one row per app (kind web with interfaces[], see "Terminal versions", and install_parts; then the Assistant, kind desktop with desktop {location, found_by, launch_command, install_command, launch_available, launch_blocked, launch_blocked_reason, other_running, restart_note, started_by_gateway, source_checkout, checkout_path, version_reason, latest_error}); every row has latest_version, update_available, update_label and update_tip (see Updates) and status_control {label, tone, busy, action, enabled, tip} — the status badge for THIS caller: action stop or launch is what a click posts, enabled false with tip as the reason, tip null for a plain pill (the badge table at the top of this page) and console_tui (the gateway console's terminal app). gateway_url is where the app servers reach the gateway (on its machine); browser_gateway_url is the address the caller uses (e.g. https://<host>.ts.net behind tailscale serve), the one to show in any command or link. latest=false skips the npm registry, PyPI and GitHub release lookups (cached 10 minutes). |
POST /apps/runtime/install |
admin | Install Node.js (a job), or job: null when one is already usable. |
POST /apps/{id}/install {"version"?, "launch"?, "with_terminal"?} |
admin | ONE job: Node.js if needed, download, check, dependencies, then the terminal app when the row's install_parts has "tui" (with_terminal: false skips it); the job's parts are its child rows. Starts nothing unless launch: true. For assistant: installs abstractassistant into the gateway's Python (every abstract* package is pinned to its current version in the same command). |
POST /apps/{id}/update {"version"?} |
admin | A job: install the latest (or given) version; a running app is restarted on it. For assistant: abstractassistant==<version> from PyPI with the gateway's pins; the Assistant this gateway opened reopens signed in as the caller, any other running Assistant is left alone ("Quit it and open it again to run x.y.z"). A row started outside the gateway has no update action. |
POST /apps/{id}/launch |
admin | Start the app (waits until it answers) and mark it enabled. For assistant: open it on the gateway's computer, signed in as the caller, {ok, app, already_running, signed_in_by_gateway, message}; from another computer 409 not_on_gateway_machine, and nothing starts. |
POST /apps/desktop-handover {"code"} |
no sign-in; this computer only | The Assistant trades its one-time code for a remembered sign-in: {base_url, session_id, csrf_token, user_id, expires_at}. Answered only for a direct caller on this computer (no proxy headers, no app-server session header): 403 otherwise; 410 for a used or expired code. |
POST /apps/{id}/stop |
admin | Stop the app and mark it disabled. 409 started_outside_gateway for an app the gateway did not start. For assistant: quit the Assistant this gateway opened, {ok, app, message}; any other running Assistant: 409 started_outside_gateway, nothing is quit. |
POST /apps/{id}/open {"remember"?, "path"?, "origin"?} |
any user | A one-time open_url (relative to the gateway) that opens the running app signed in, at path inside the app when given (e.g. /#new): {open_url, mounted, app_path, app_url, expires_in_s}. app_path is /apps/<id>/… for an app served through the gateway (else null); app_url is origin (default: this request's address) + app_path. 400 invalid_app_path / invalid_origin. 409 desktop_app for the Assistant (use /launch). |
GET /apps/{id}/logs?tail=200 |
admin | The end of the app's log. |
GET /apps/jobs, GET /apps/jobs/{job} |
any user | Jobs: state (queued, running, succeeded, failed, cancelled), percent, bytes_done, bytes_total, message, steps, parts (the child rows of an Install that covers two parts), details (full log on failure). |
POST /apps/jobs/{job}/cancel |
admin | Cancel a job. |
POST /apps/{id}/install-tui |
admin | A job: download, check and place the app's prebuilt terminal version (or update the gateway's copy). 409 toolchain_required with command when only a source build exists. |
POST /apps/{id}/launch-tui |
admin, on the gateway machine | Open the terminal version in a new terminal window, signed in as the caller: {ok, app_id, interface: "tui", terminal, version, message, expires_in_s}. From another computer (non-loopback peer or Host, or any proxy header): 409 not_on_gateway_machine with command and signin_command, and nothing opens. |
POST /apps/{id}/tui-command |
admin, on the gateway machine | The same one-use launcher as launch-tui without opening a window: {ok, app_id, interface, version, signin_command, command, expires_in_s}. From another computer: 409 not_on_gateway_machine with command and signin_command. |
POST /apps/tui-handover {"code"} (outside /api/gateway) |
the launcher script, loopback only | Trade a launcher's one-time code for {token, token_env, url_env, gateway_url, gateway_flag, user}. 403 loopback_only, 410 handover_expired. |
The apps themselves, outside /api/gateway:
| Method and path | Who | What |
|---|---|---|
GET /apps/handover/<code> |
the one-time code | Sets the app's sign-in cookies and redirects: relative 303 to /apps/<id>/… (cookies Path=/apps/<id>/), or to the app's own port for an older app. 410 used or expired, 400 another address. |
any /apps/<id>/… (HTTP, SSE, WebSocket) |
the app's gateway session | The app, relayed (see Apps are served through the gateway). Without a session: 303 to /console#apps?open=<id>&path=… for a page load, else 401 app_sign_in_required. 400 invalid_host, 404 unknown_app, 403 cross_origin, 409 not_running / not_mountable, 502 app_not_answering. /apps/<id> redirects to /apps/<id>/. |
Errors are {"ok": false, "reason", "message", "hint"?, "details"?} with
404 (unknown_app, also for a terminal route on a browser-only app), 409
(not_installed, node_missing, not_running, app_loopback_only,
toolchain_required, foreign_binary, not_on_gateway_machine, no_terminal,
started_outside_gateway), 403
(installs_not_allowed), 502 (integrity_mismatch), 503
(network_unavailable, no_free_port) or 500 (launch_failed, with the
app's output in details). The terminal errors also carry command (and,
where it helps, install_command or signin_command) so the console can
offer the command to copy instead.