Postguy
Reference

UI control channel

The opt-in local HTTP channel that lets postguy-cli inspect and drive the running desktop app: launch, control file, endpoints, set keys, command ids and limits.

The control channel lets a script or an agent look at and steer a running Postguy window. You can read which tabs are open, run a command, switch a panel, or take a screenshot. It is off by default. It exists only when you launch the app with --control FILE. You reach it with postguy-cli ui.

Anyone who can read the control file can drive the app. Keep it in a private folder. The app writes it owner-only on Unix. On Windows the file gets default permissions, with no owner-only enforcement.

Launch

postguy --workspace ~/postguy-workspace --control /tmp/postguy-control.json
postguy-cli ui --control /tmp/postguy-control.json inspect
  • The server binds to 127.0.0.1 on an ephemeral port, never to another address.
  • A fresh random token is generated on every launch (64 hexadecimal characters). It is never logged.
  • If the channel cannot start, the app still runs. It reports control channel disabled: ... on stderr and in the status bar.
  • On exit the app deletes the control file, but only if it still contains this launch's token.

The control file

{"port": 53211, "token": "<64 hex characters>", "pid": 4242}
KeyMeaning
portThe loopback port.
tokenThe bearer token for this launch.
pidThe app's process id.

Parent folders are created. A stale file is removed first, so a planted symlink is never followed. The new file is created with create_new and mode 0600 on Unix.

Protocol

The protocol is plain HTTP/1.1 over TCP, one request per connection (Connection: close). Every request needs this header:

Authorization: Bearer <token>
HTTPpostguy-cli uiBody or query
GET /inspectinspectnone
POST /commandcommand ID{"id": "<command id>"}
POST /setset KEY=VALUE{"key": "...", "value": ...}
GET /screenshot?path=<percent-encoded path>screenshot --out FILEquery path
POST /clickclick X Y{"x": 1, "y": 2}
POST /keykey NAME{"key": "..."}
POST /typetype TEXT{"text": "..."}

Any other method or path is a 400 unknown control endpoint.

Status codes and limits

StatusWhen
200Success; the body is the JSON result.
400Bad body, unknown endpoint, or the app refused the request (the body is {"error": "..."}).
401Missing or wrong token: {"error":"missing or invalid control token"}.
429More than 16 concurrent connections.
503The UI queue is unavailable.
504The UI did not answer within 20 seconds.
  • Request bodies are read up to 1 MiB.
  • The server waits at most 10 seconds for the request to arrive.
  • The postguy-cli ui client waits up to 30 seconds for the response.
  • The client connects to 127.0.0.1 only and refuses a control file that has group or other permission bits on Unix.

inspect

inspect returns the state of the window:

{
  "tabs": [{"id": 1, "title": "Welcome", "dirty": false}],
  "active": 1,
  "panel": "welcome",
  "sidebar": true,
  "console": false,
  "right": false,
  "twoPane": false,
  "status": "",
  "consoleRows": 0,
  "engine": true
}
KeyMeaning
tabsOpen tabs with id, title and the unsaved-changes flag dirty.
activeThe active tab's id.
panelThe kind of the active tab: request, environment, collection, settings, cookies, trash, runner, history or welcome.
sidebar, console, right, twoPaneWhether the sidebar, the Console, the code snippet panel and the two-pane layout are on.
statusThe status bar text.
consoleRowsNumber of Console rows.
engineWhether the workspace engine is running.

set

postguy-cli ui set KEY=VALUE parses VALUE as JSON and falls back to a plain string. So console=true sends a boolean, and url=https://example.com sends a string.

KeyValue
tabsettings, globals, cookies, trash or runner to open that tab; otherwise the title of an open tab (case-insensitive) to activate it.
sidebar, console, right, two-paneBoolean. Shows or hides the panel.
themelight, dark or system. Saves the setting.
urlSets the active request tab's URL and marks the tab as changed.
openA collection id (opens its detail tab) or a request id (opens the request).
request-tabparams, authorization, headers, body, scripts, tests, settings or docs.
detail-taboverview, authorization, variables, scripts, runs or assets.

An unknown key is refused with this error (400):

unknown key `x`; keys are tab, console, right, two-pane, sidebar, theme, url, open, request-tab, detail-tab

command

postguy-cli ui command ID runs one command from the app's command registry. This is the same table that drives the command palette, the keyboard shortcuts and the menus. It returns {"ok":true,"command":"<id>"}. An unknown id is an error. Shortcuts for these commands are on Keyboard shortcuts.

GroupCommand ids
Createrequest.new, collection.new, environment.new, import.open, workspace.export
Requestrequest.send, request.cancel
Tabstab.close, tab.reopen, tab.next, tab.previous
Viewview.sidebar, view.console, view.right, view.two-pane, console.clear, view.zoom-in, view.zoom-out, view.zoom-reset, view.fullscreen
Gosettings.open, globals.open, cookies.open, runner.open, trash.open
Editedit.undo, edit.redo, edit.cut, edit.copy, edit.paste, edit.delete
Windowwindow.minimize, window.zoom, window.fill, window.center, window.previous-size, window.close
Window placementwindow.left-half, window.right-half, window.top-half, window.bottom-half, window.top-left, window.top-right, window.bottom-left, window.bottom-right, window.left-third, window.center-third, window.right-third, window.left-two-thirds, window.right-two-thirds
App and helpapp.quit, help.docs, help.github
window.close and app.quit close the app. help.docs and help.github open your browser. Commands act on the live window exactly as the matching menu item or shortcut would.

screenshot

postguy-cli ui --control /tmp/postguy-control.json screenshot --out shots/home.png --root /tmp/work

The app captures the next frame, writes it as a PNG (mode 0600 on Unix, parent folders created) and replies {"ok":true,"path":"..."}.

  • Only one screenshot can be in progress: a second request gets a screenshot is already in progress.
  • If the windowing system does not deliver the frame within 10 seconds, the reply is the screenshot did not arrive from the windowing system.
  • --root DIR is applied by the CLI, to the --out path only. A path outside the root is refused with the usual is outside the allowed directory message.
  • The app itself writes to whatever path it receives, so a direct HTTP client is not confined.
  • Without --root, --out is used as given.

click, key and type

The CLI accepts click X Y, key NAME and type TEXT (X and Y are numbers) and forwards them. The app refuses all three with a 400:

synthetic clicks are not supported; use `command` or `set`
synthetic keys are not supported; use `command` or `set`
synthetic typing is not supported; use `command` or `set`

postguy-cli ui exits 2 with ui: {"error":"..."}. The reason is that safe egui has no supported way to inject raw pointer and keyboard events into a running window. Use command and set instead. This is the first entry in Known gaps. f.ui-control is partial in Feature status.

Safety summary

  • Off unless --control FILE is given.
  • Loopback only, token on every request, token file owner-only on Unix, fresh token per launch.
  • The channel never touches the network beyond 127.0.0.1.
  • postguy-cli ui never guesses a token and never connects to another address.
Copyright © 2026