CLI Control (control Omniscio from scripts and hotkeys)
CLI Control is the local-only HTTP door into Omniscio: how to turn it on, copy or regenerate its token, drive the app from a script or a hotkey, and what it refuses to do without your say-so. This overview covers what the server is, where its switch and token live, and the walkthrough for calling it.
What it is
CLI Control is a tiny HTTP server that Omniscio runs on your own machine at 127.0.0.1:19519. It lets any tool that can make an HTTP request — AutoHotKey, PowerShell, curl, Python, Bash, an external AI like Claude Code — navigate Omniscio, launch sessions with a pre-typed prompt, bring the window to front, pop a project or integration out into its own window, or create/schedule/toggle cron jobs. It's on by default, localhost-only (cannot be reached from other machines), and any mutation (starting a session, creating a cron job) requires a token that Omniscio auto-generates and stores at ~/.amc/cli-token with OS-level file-permission hardening. Read-only endpoints like /ping and /status are open, so your scripts can check health without handling the token.
Where to find it
CLI Control lives in Settings → CLI Control, which is where you switch the server on or off, read and copy its token, regenerate that token, and decide which outside requests must wait for your approval. It is not a panel you open to use; it is a door other programs knock on, and that Settings page is where you choose how far it opens.
How it behaves
How to use it
- Confirm it's on and copy your token. Open Settings → CLI Control. CLI Control ships enabled. The Token field shows a hex string you can copy; use Regenerate to invalidate the old one at any time. A small "Created N ago" line under the token shows its age and turns amber once it is over 90 days old, nudging you to Regenerate (the token itself never auto-expires). A fresh token is also mirrored to
~/.amc/cli-token(%USERPROFILE%\.amc\cli-tokenon Windows). - Call the read-only endpoints.
GET http://127.0.0.1:19519/pingreturns{ok: true}.GET /statuslists your projects and integrations.GET /cron/jobs,/cron/jobs/:id,/cron/jobs/:id/runs,/cron/runs/upcomingreturn cron state — these requireAuthorization: Bearer <token>(they surface private cron data) and share a per-token 60/min read budget. No token is needed for/pingand/status. - Navigate or focus.
GET /inbox,/focus,/project/<name>(fuzzy-matched, case-insensitive), or/superprompt/<id>. As of the CLI-server hardening these need a valid bearer token (Authorization: Bearer <token>) or a same-origin request from Omniscio's own UI — a header-less, tokenless call is now rejected with403, closing a hole where any local program (or a malicious web page) could steal window focus or force navigation without your token. Hotkey macros must send theAuthorizationheader. (/pingand/statusstay open with no token.) - Launch a session or create a cron.
GET /project/<name>/new?token=<YOUR_TOKEN>&prompt=<url-encoded prompt>spawns a session and auto-sends the prompt. The spawn is created by the background engine and is durable — as of 2026-06-04 it no longer depends on the Omniscio window being responsive, so a200means the session was actually created, and a spawn survives a busy/frozen UI or an app restart mid-spawn (a rapid burst is paced, returning202for the queued ones). By default the new session opens silently in the background — Omniscio does NOT come to the foreground. That's the right default for agent-driven calls (the user is working in another app and shouldn't be yanked away). Hotkey-driven workflows that DO want focus must opt in with&focus=true(only the literal stringtrueopts in;focus=yes/focus=1are ignored)./focusand/window/openbring the window forward;/project/<name>(activate-project, no/new),/inbox,/settings/open, and the note / mind-map / Gmail-thread / saved-prompt routes SWITCH which screen you're looking at — and the "don't take over the user's screen" rule covers both. (a) Foreground: an agent-initiated/focus//window/open(a call sendingX-AMC-Source-Session-Id) is auto-blocked while a different app owns the foreground (returnsfocusSuppressed: true+ an inbox note you can silence via its "Turn off these notices" button or Settings → Notifications → "Screen-grab block notices"); it still foregrounds when you're already in Omniscio, or when you opt in at Settings → CLI Control → "Let agents bring Omniscio to the front" (default off). (b) View-switch: an agent-initiated activate-project //inbox/ setting / note / mind-map / Gmail-thread / saved-prompt request no longer changes your view directly — while you're IN Omniscio it drops a click-to-go toast ("…wants to switch to X — Go there") that navigates only when you click, and while you're in another app it drops the same inbox note; either way it never moves you on its own, unless you opt in at Settings → CLI Control → "Let agents switch your view directly" (default off). A human's own hotkey, notification click, phone tap, or the in-app UI is never affected./cron/jobssupportsPOST(create),PATCH /cron/jobs/:id(update),DELETE /cron/jobs/:id(delete),POST /cron/jobs/:id/run(fire now), andPOST /cron/jobs/:id/toggle(enable/disable). All cron mutations requireAuthorization: Bearer <token>and are capped at 10 per minute. There is no ceiling on how many cron jobs an install may hold — theMAX_CRON_JOBSrow-count cap (200, later 500) was removed on 2026-09-06 because it measured the wrong quantity: it counted every row in the table (inactive, rejected, spent one-shots, wake schedules), so an install with 199 dead rows was refused a create while one with 199 jobs firing every five minutes was not. What replaced it is the create-RATE policy above, which is what a runaway caller actually runs into; the single source is cron-job-create-rate.ts. - Pop a project or integration into its own window.
POST /window/openwith a JSON body{ "projectId": "<id>" }opens (or focuses) that project — a real project or an integration like Tasks — in its own themed desktop window, the HTTP half of the in-app "Open in new window" button.projectIdtakes a project UUID, a friendly alias, or an integration id such astasks-v2. It's bearer-authed, shares the 10/min mutation cap, and is idempotent (a second call just focuses the existing window, returningstate: "already-open"). Full details — id resolution, response shape, and the404/409(a dedicated window like KMS has its own route) /400(no panel to show) cases — live on the omniscio-control windows surface. (Yes, this is supported: earlier notes that called a CLI pop-out "out of scope" predate this route.)- POST variant for Unicode-safe prompts.
POST /project/<name>/newexists alongside the GET form because Windows mangles non-ASCII chars (em-dash, arrows, accented chars, emoji, non-Latin scripts) in the cmd.exe / Git Bash argv pipeline before curl can encode them — they arrive at the spawned session as?or U+FFFD replacement chars. The POST variant takes the prompt as a JSON body field, which streams from the socket as raw bytes and bypasses argv encoding entirely. Auth is header-only (Authorization: Bearer <token>— query-string?token=...is rejected with 401), Content-Type must beapplication/json(charset suffix allowed), body schema is{ prompt?: string, name?: string, provider?: ProviderId, focus?: boolean }(.strict()). Empty body is allowed for a source-less call (no prompt → blank session), but a prompt-less agent spawn — one sendingX-AMC-Source-Session-Id— is rejected400("a spawned session needs a prompt"): an agent always has a task for the child it spawns, so a missing prompt is a caller bug that would otherwise leave a dead empty session. Only literal booleantrueopts into focus.providerforces the engine for this one session; omit it and the session uses the project's configured default provider (the one set viaPATCH /project/:iddefaultProvider, gated by the show-alternate-providers master toggle), falling back to Claude — the same provider the UI's + New button would pick, so a CLI / cron / recipe spawn into a project defaulted to e.g. Gemini now correctly starts on Gemini instead of silently using Claude.namestamps an exact session title instead of letting the AI titler infer one. The 1 MB body cap matches the other mutations, but/newis deliberately not on the 10/min mutation bucket — spawns are serialized by a FIFO spawn pacer at one every 30 s (≈2/min), and a burst is queued and released in order (each queued spawn returns202) up to a 50-deep runaway backstop that returns429. Validation errors are self-explanatory: a malformed JSON body returns400with the parser’s own message, the character position, and a short snippet of the body around the break (echoed only to the caller, never logged), and an unrecognized body field returns400with a did-you-mean hint (Unexpected field: message — Did you mean ‘prompt’?— common aliases likemessage/text/taskmap toprompt, and close typos suggest the nearest accepted field). An optionalX-Client-Request-Idheader (≤64 chars) makes a retry safe — reuse the same id and a duplicate POST returns200 { idempotent: true }instead of spawning a second session (without it,/newdoes not dedup). That replay is a receipt, not a bare acknowledgement: it carries the ORIGINAL spawn'sspawnId, plusstatusandsessionIdonce the session exists — so a caller that timed out and retried learns WHICH session its first call made instead of being told only that something happened. Source required by default: withrequireSpawnSourceSessionon (the default), both/newforms ANDPOST /agent/sessionsrefuse a spawn that sends no source with400— passX-AMC-Source-Session-Id: $AMC_SESSION_ID(an Omniscio-spawned agent has it in env) so the child links back to its origin, or the user turns the setting off in Settings → CLI Control for anonymous script/hotkey spawns. A scoped agent token can't spawn by default: the/newforms accept the global CLI token and an in-app session, but an agent's scoped per-session token (the$AMC_CLI_TOKENOmniscio injects into every spawned session) is refused (401/403) unless the user opts in at Settings → CLI Control → "Let agents spawn sessions with their own token" (allowAgentTokenSpawn, default off) — so a compromised MCP server / npm dep reading an agent's env can't spend money by spawning paid sessions; the narrowest per-MCP-server token never spawns. (The separatePOST /agent/sessionsagent-driven-spawn path has its ownagentDrivenSessionsEnabledgate.) - Confirming a spawn —
GET /spawn/:spawnId. Every/newreply (200 immediate, 202 queued, 202 starting) carries a durablespawnId; this route resolves it. Response{ ok: true, spawnId, status, sessionId, error }wherestatusiscreated(the session exists —sessionIdnames it),pending(accepted, not driven yet),failed(the driver gave up, or its caller withdrew it —errorsays why), orunknown(no spawn by that id). Resolved from the session's ownorigin_spawn_idanchor, so it is exact. Confirm with this, never by listing/sessionsand taking the newest — aperf:instant-new-sessionprewarm shell is indistinguishable from a fresh spawn (both blank +ready), so a timestamp guess picks the wrong one. Read-only, on the 60/min read budget; the global CLI token sees any spawn, an agent's scoped token only spawns it originated (403otherwise). - Withdrawing a queued spawn —
DELETE /spawn/:spawnId. A spawn paced into the queue (202) can be taken back before it starts. The spawn pacer gives each caller a share of its 50-deep queue measured from the queue itself, so a request you no longer want holds a slot you cannot use — and until this route there was no way to give one back. Withdrawing cancels the request itself, not just its place in line, so nothing can start it later —GET /spawn/:spawnIdthen answersfailedwith theerror"withdrawn by its caller". Answers200withwithdrawn(how many queued entries were removed),cancelled(whether the spawn is withdrawn for good —falseif it had already begun starting, or there was nothing to cancel) andqueued(whether it is still waiting); an id that already fired or was never queued is a harmless200no-op, never a404. The global CLI token may withdraw any spawn; an agent's scoped token only one it originated, never an operator's or a cron's (403otherwise). On the mutation budget.
- POST variant for Unicode-safe prompts.
TOKEN=$(cat ~/.amc/cli-token)
# Unicode-safe — em-dash, arrows, emoji, Chinese, accents all survive intact
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"prompt":"Em-dash — and arrow → and emoji 🚀 and Chinese 你好"}' \
http://127.0.0.1:19519/project/MyProject/new
# Hotkey workflow that wants the window in front
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"prompt":"continue from where we left off","focus":true}' \
http://127.0.0.1:19519/project/MyProject/new
- Hand the token to an external AI safely. The
omniscio-controlskill bundle reads~/.amc/cli-tokenautomatically and POSTs with approval gating enforced server-side — see omniscio-control.md (the consolidated entry covering cron, automations, settings, sessions, recipes, projects, tags, away-mode, keybindings, and pending actions). - Trigger pre-approved recipes.
POST /recipes/runlets an authenticated agent fire a multi-step Claude workflow without opening Omniscio. Two orthogonal gates apply: the recipe must haveagentTriggerable.enabledset in the Recipe Editor's "Agent triggering" card (others return 403 — an agent cannot trigger an arbitrary recipe) AND the recipe must have cleared the authoring approval gate (approvalStatus∉{pending, rejected}; pending/rejected recipes return 400 even when the agent-trigger flag is on). A 30-second cooldown per(recipeId, projectId)tuple prevents accidental retry-loop double-fires; the engine still enforces its singleton lock so concurrent triggers return 409.GET /recipes/triggerableis the discovery endpoint — agents call it first to see what's flagged. Full request/response shapes, error codes, and cost-cap behavior: agent-trigger-recipes.md.
For agents
Implementation
The HTTP listener binds to 127.0.0.1 (never 0.0.0.0) in /src/main/services/cli/cli-server.ts (the flat services/cli-server.ts is now a re-export shim; the impl + per-surface cli/cli-server-*-routes.ts files live under cli/), which also declares every route, enforces the per-token 10/min cron mutation limit, and hard-codes requiresApproval: true on AI-created jobs so clients can't override it. The token is generated in /src/main/services/config-store/accessors-runtime-state.ts via crypto.randomBytes(16).toString('hex') on first launch, stored encrypted in config.json, and mirrored to disk by /src/main/services/cli/cli-token-file.ts. Disk hardening: Unix writes with mode: 0o600 via fs.writeFile; Windows runs icacls <path> /inheritance:r /grant:r "<user>:F" (5-second timeout so antivirus scans can't hang startup). The settings UI is src/renderer/src/features/settings/sections/cli-control/CliControlSettings.tsx (show/hide + copy + regenerate). Regeneration flows through the CLI_TOKEN_REGENERATE IPC handler in /src/main/ipc/cli-token-handlers.ts, which rewrites the file in place — no restart needed. Session-spawn routing calls through to the same process manager that GUI launches use; cron mutation routing Zod-validates the body and hands off to /src/main/db/queries-cron-jobs.ts for persistence. User-facing marketing page: /docs/intro-sandbox/cli-control.html. The complete route inventory is machine-derived, not written here. Every registered route — with the gating tier read out of its own handler, its feature id and its source file — is in the CLI Control Server route gating catalog (2,917 routes across the hub and its path-range shards), regenerated by npm run cli-gating:reindex. Read that rather than treating this page, or any hand-written list, as the inventory of record: the generated one cannot miss a route or invent one, which a hand-kept list always can.
Related
- omniscio-control.md — the skill bundle that uses
/cron/jobs,/automation/*,/settings,/keybindings/*,/recipe/*, etc. from external AIs - create-cron-job-with-ai.md — user-facing walkthrough for the cron surface specifically
- agent-trigger-recipes.md —
POST /recipes/run+GET /recipes/triggerablein detail (agent-fired recipe runs) - bug-report-intake.md —
PATCH /project/:id/bug-intakeopt-in details and the email triage pipeline it controls - inbox-alerts.md —
POST /alertin detail: how an agent drops a persistent row in the user's inbox - mobile-remote-access.md — a different server for remote (non-localhost) access from your phone
The CLI route gating catalog— the complete, generated route inventory (every route + its gating tier)
This page is split across three parts: part 2 covers the endpoints that change projects, sessions, project docs, away-mode rules and recipes, and part 3 covers the ones that read and drive what you are looking at — coaching data, bookmarks, inbox alerts, screen capture and on-screen highlighting.
Last verified 2026-09-28