Advanced
Agents and remote control
Use the semantic remote API to inspect and control a running Gloom desktop or TUI session.
gloom remote drives a Gloom app running on this machine, terminal (TUI) or desktop. Scripts and local agents read the screen, open panes, pick tickers and arrange layouts by name.
gloom # one terminal: the app
gloom remote help # another: the agent guide
gloom remote get app://snapshot --json # what is on screenLeave --app out when one app runs. Pass --app tui or --app desktop when both do.
| You are | Use |
|---|---|
| A script or local agent on the same machine as the app | gloom remote: no account, no network |
| Claude Code, Codex or another assistant connected to Gloom Cloud | The MCP server with Terminal control: the same operations, one MCP for data and the terminal, and the app asks you before an assistant takes over and before anything leaves the app |
| Anything that needs Cloud data with no app open | The MCP server without Terminal control |
For headless data, reports and a screenshot of a function with no app open, use the CLI.
Commands
| Command | Use |
|---|---|
gloom remote help | Agent-focused usage from the running app |
gloom remote schema | Resource URIs, operations, request and response shapes |
gloom remote get <resource> | Read one resource |
gloom remote call <operation> [json] [--dry-run] | Invoke one operation |
gloom remote patch <resource> <json-patch> [--expect-rev rev] [--dry-run] | JSON Patch a mutable resource |
gloom remote batch <json> [--dry-run] | Run a multi-step workflow |
gloom remote watch <resource> [--interval ms] [--limit n] | Poll for changes |
All take --app tui|desktop and --json. --dry-run on call only echoes the payload, with no check of the operation or its fields; on patch it returns the patched result without saving.
When no app answers
With --json the reason is in error.code, with message and details (what to do), and the command exits 1:
error.code | Means, and what to do |
|---|---|
app_not_running | No app of this profile runs. Start gloom in another terminal, or open the desktop app |
app_ambiguous | The terminal and desktop apps both run, and neither is the one started last. Pass --app tui or --app desktop |
app_unreachable | The app's port does not answer from here, but nothing proves it gone: usually a sandbox that blocks localhost. Run the command where it can reach 127.0.0.1, or start Gloom again |
An endpoint file left by an app that exited without quitting is removed once its process is gone and its port refuses. An unreachable app's file is kept.
Asked for the desktop app while only the terminal app runs:
{ "ok": false, "error": { "code": "app_not_running", "message": "The Gloom desktop app is not running.", "details": "The terminal app is: pass --app tui, or leave --app out." } }Anything else that fails, such as an operation the app refuses, is cli_error.
Read the screen
app://snapshot is what is on screen in one read:
| Key | Holds |
|---|---|
app | Focus and command bar. Read app.focusedPaneId; app.activePanel is deprecated and always left |
config | Settings and the layouts |
panes | Each pane, where it is, and whether it has focus |
commandBar, form | The command bar, and an open form or the setup dialog |
ui | The labelled UI controls |
references | Where to read what the snapshot leaves out |
It leaves out what an agent reads once or rarely:
| Left out | Read it from |
|---|---|
| Every resource and operation with its input | gloom remote schema, once |
| Recipes and caveats | gloom remote help (app://remote/help) |
What each pane loaded (runtimeState, a saved layout's paneState) | app://pane-state/{paneId}, app://panes, app://config |
| Unlabelled UI nodes | ui://tree |
app://snapshot/full has all of it inline, the shape the snapshot had before 0.20.5. It is often several times the size: 413 KB against 73 KB with nine panes open.
gloom remote get app://pane-templates --json
gloom remote get ui://tree --jsonpane.screenshot returns a pane as a PNG (base64 in image.data, with its pixel size): the focused pane, or paneId from app://panes. Desktop only: the terminal app draws text.
gloom remote call pane.screenshot '{}' --app desktop --jsonFirst-run setup
On a new profile the setup dialog covers the app. app://form (and the snapshot's form) reads "open": true, "kind": "setup", with its step, title, fields by id and buttons:
{ "open": true, "kind": "setup", "step": "PORTFOLIO", "title": "What do you hold?",
"fields": [{ "id": "ticker", "label": "Ticker", "value": "" }, { "id": "shares" }, { "id": "avgCost" }, { "id": "currency" }],
"buttons": [{ "label": "Skip" }, { "label": "Continue", "disabled": true, "shortcut": "Enter" }] }While it is open:
app.searchandapp.openCommandBarrefuse, andapp.closeDialogdoes not close it.- A change made behind it still runs, and its answer carries
warningssaying the setup dialog covers it. - Its own controls match first in
ui.invokeMatching.
Add a ticker and continue, or skip a step:
gloom remote batch '{ "type": "batch", "requests": [
{ "type": "call", "operation": "ui.invokeMatching", "input": { "role": "form-field", "metadata": { "fieldId": "ticker" }, "action": "setValue", "input": "NVDA" } },
{ "type": "call", "operation": "ui.invokeMatching", "input": { "role": "form-field", "metadata": { "fieldId": "ticker" }, "action": "submit" } },
{ "type": "call", "operation": "ui.invokeMatching", "input": { "role": "form", "action": "submit" } }
] }' --json
gloom remote call ui.invokeMatching '{ "role": "button", "label": "Skip", "action": "press" }' --json- Submit on the Ticker field adds the position;
{ "role": "form", "action": "submit" }presses the button Enter would. - Skip leaves one step unfilled. A new profile has two steps, Portfolio and Desks.
- A step can change after the call answers, so read
app://formagain before the next call. - When setup is run again,
form.actionsincludescancel, which skips all of it.
Open panes
When you know the pane, use a pane operation.
gloom remote call pane.createFromTemplate '{ "templateId": "market-heatmap-pane" }' --json
gloom remote call ticker.pin '{ "symbol": "NVDA", "forceNewPane": true }' --jsonFor command-bar ranking and aliases, search then activate.
gloom remote batch '{
"type": "batch",
"include": ["panes", "commandBar"],
"requests": [
{ "type": "call", "operation": "app.search", "input": { "mode": "ticker", "query": "NVDA" } },
{ "type": "call", "operation": "commandBar.activateResult", "input": { "index": 0 } }
]
}' --jsonArrange the workspace
gloom remote call layout.setGrid '{ "columns": 2 }' --json
gloom remote call layout.focusRegion '{ "region": "right" }' --json
gloom remote call layout.closeFloating '{}' --json
gloom remote call app.switchPanel '{ "panel": "right" }' --jsonremote patch works on what remote schema marks patchable: app://config, app://layout/current, app://pane-state/{paneId} and app://pane-settings/{paneId}.
Read the resource and its rev first, then pass --expect-rev, so a stale edit fails instead of overwriting.
Semantic UI fallback
No app-level operation? Target UI nodes by role, label, contained text, metadata, or action.
gloom remote call ui.invokeMatching '{ "role": "button", "label": "Open Help", "action": "press" }' --jsonUse it sparingly; higher-level operations survive UI changes.
Concepts
| Concept | Meaning |
|---|---|
| App kind | tui and desktop are separate apps; --app picks one |
| Resources | State to read: app://snapshot, app://form, app://panes, app://pane-templates, app://commands, app://capabilities, ui://tree |
| Operations | Changes: app.search, commandBar.activateResult, pane.createFromTemplate, pane.focus, ticker.navigate, layout.setGrid, ui.invokeMatching |
| Capabilities | Plugin service operations, for plugin-owned data and side effects. The UI goes through app operations |
- Every operation changes only the local app, except
capability.invoke, which runs a plugin capability and can reach external services. watchlist.addandwatchlist.removeask in the app first and answer with what happened. Nothing else asks.