Omniscio documentation
Browse all documentation
  1. Getting Started13
  2. Sessions & Agents115
  3. Inbox & Notifications59
  4. Projects & Tasks95
  5. Automation & Scheduling75
  6. Knowledge & Memory26
  7. AI Features60
  8. Integrations100
  9. Plugins & Marketplace33
  10. Cloud & Teams56
  11. Settings & Customization58
  12. Account & Billing28
  13. Troubleshooting84
  14. CLI & API Reference22
  15. Legal & Policies4
  16. Uncategorised22

CLI Control (control Omniscio from scripts and hotkeys) (part 2)

Part 2 of the CLI Control page: the endpoints an outside script or AI uses to change things on your behalf — driving a running session, registering, editing or deleting a project, adding and removing project docs, away-mode rules, and firing a recipe — plus how the approval queue and the rate caps treat each one.

What it is

This is part 2 of the CLI Control (control Omniscio from scripts and hotkeys) page. It covers the endpoints an outside script or AI uses to change things on your behalf — driving a session that is already running, registering, reordering, editing or deleting a project, adding and removing files in a project's docs folder, away-mode rules, and firing a recipe run.

Beyond cron, automations, settings, sessions, recipes, and keybindings, the same server hosts three more surfaces that an external AI (or a script) can drive on the user's behalf. Two of them (project DELETE, every away-mode mutation, recipe run) land as pending rows in the same cli_pending_actions queue that gates settings PATCH (and session lifecycle only when its approval toggle is on — by default lifecycle applies immediately) — they show up in Omniscio's inbox as approve/reject cards and only have a side effect once the user clicks Approve. Cosmetic project edits (rename, recolor, pin, divider, reorder) are NOT gated because they're personal preferences that the user can revert without consequence.

When several approval cards pile up (e.g. an agent queues many project.docs_upload rows at once), they don't have to be approved one at a time: the Approvals inbox section header shows an "Approve all N" button (one confirm, then it approves the whole batch), and an arbitrary subset can be multi-selected and approved together (mobile selection bar / desktop right-click "Approve N selected"). Bulk approval is exactly N single approves — each card still runs its OWN approve-time re-validation independently (a tampered row is rejected on its own while the rest go through), so it never weakens the two-gate check described below. Two cards are never part of a bulk gesture: the delete-old-branches card (worktree.stale_branch_retire), whose approval retires every branch in its batch, and the auto-lander's preservation-override card (autolander.preservation_overlap_review), whose approval lets a branch its safety check refused land. "Approve all", "Reject all" and the selection actions all skip them and leave them waiting, so each is only ever decided on its own card.

Where to find it

Same surface as the parent page: every endpoint here is served by the same local address, and the token you copy from Settings → CLI Control is the one they all take. The ones that change something you would have to live with — deleting a project, uploading or removing a docs file, editing an away-mode rule, running a recipe — do not act the moment they arrive: they queue an approval card in your inbox, and the family-wide switches for that are in Settings → CLI Control → Approval requirements.

How it behaves

Session actions — /session/*

Drive an existing session directly. All four apply immediately and are IDOR-bound — the regular ~/.amc/cli-token acts on any session; a minted in-app-session token may act only on its own session. They join the existing lifecycle (pause / unpause / snooze / archive), recovery (restart / nudge / move-account), and rename routes on the same server.

  • POST /session/:id/message (auth, 200 OK) — billable. Send the user's next turn to a session. Body { text }. Unlike /nudge and /peer-message (which mark the turn Omniscio-injected), this is a real user message. Spawns / resumes the session as needed; 409 if the session is paused (unpause it first), and 409 inactive_target_needs_confirmation if it is archived — re-send with "confirmInactiveTarget": true only when it genuinely matters (owner rule 2026-09-24). If the target is mid-turn, this kills and respawns it immediately — no confirmation flag, unlike peer-message's opt-in confirmInterruptTurn (see peer-message-interrupt-and-wake.md). Shares the 120/hr peer-message bucket; an X-Client-Request-Id header makes a retry safe.
  • POST /session/:id/move (auth, 200 OK) — move a session to another project. Body { targetProjectId }. A started session keeps its working directory (pinned so --resume still resolves); sentinel / tool-panel targets are rejected (400), and a same-project move is 409. Free.
  • POST /session/:id/schedule-response (auth, 200 OK) — billable at delivery. "Send Later": queue the user's next turn for a future time. Body { text, sendAt (strict ISO-8601, ≥ ~30 s out), force? } (text-only). 409 if one is already scheduled unless force:true (the body then carries the existing text + time). Scheduling is free; the eventual delivery is the paid turn.
  • POST /session/:id/interrupt (auth, 200 OK) — stop the current turn (kills the child process) while keeping the session alive and re-sendable — distinct from pause (a status) and archive (a terminal close). 409 if the session isn't running. Free.

Full request/response shapes + curl examples live on the omniscio-control sessions surface — see omniscio-control.md. Because /message and /schedule-response spend money (a real AI turn), an agent calls them only when the user explicitly asks.

Project management — /project/*

  • POST /project/create (auth, 201 Created) — register an existing folder on disk as a new Omniscio project. Body { name, folderPath, color?, dividerId? }. The folder must already exist; the route returns 400 otherwise. Emits PROJECTS_CHANGED. Persists immediately, no inbox round-trip — there's no destructive side effect to gate.

  • POST /project/reorder (auth, 200 OK) — bulk-update sidebar display order. Body { orderedIds: [<projectId>, ...] } — each id a non-empty string (not necessarily a UUID), up to 1000; an empty array validates and is a no-op. Emits PROJECTS_CHANGED. Cosmetic, immediate.

  • PATCH /project/:id (auth, 200 OK) — apply cosmetic edits. Optional fields: name, color (nullable — pass null to clear the project tint, mirrors the EditProjectDialog "No color" button), hideBranchInHeader, isPinned, dividerId (nullable — pass null to remove the project from its current group), defaultProvider (a pickable provider id — e.g. "claude", "codex", "gemini", "grok", "gpt" — or null — sets this project's default AI provider override, mirrors the EditProjectDialog provider radios. The route validates against the provider registry's full pickable set (PICKABLE_PROVIDER_ID_VALUES = every provider the new-session picker offers), which grows automatically as new pickable providers ship — so the accepted set is exactly whatever is currently pickable, not a fixed list (don't hard-code a count against this: it has drifted 9 → 11 → 15 → 19 → 20 as providers shipped); null clears the override and falls back to Claude). Two more optional fields mutate project STATE (beyond the cosmetic set above): isolationEnabled (boolean — toggles this project's git-worktree isolation, applied immediately via setProjectIsolation) and deployProfileId (a deploy-profile id string, or null to clear the project's deploy-profile association, applied via setProjectDeployProfile). One more optional field: cloudMachineType — the SIZE of cloud machine this project launches. One of "small", "default", "large", "xlarge" (the vendor's own four sizes), or null to clear back to the vendor default. The absent-vs-null distinction is the same one color carries: omitting the key leaves the stored size untouched, while an explicit null clears it — so a caller that sends only { "name": … } cannot accidentally resize a project. "default" is stored as itself, NOT as null, even though both launch identically (neither passes --type to the provider), because the row has to keep saying a person CHOSE the standard size. The value reaches the launch as the provider's --type; "default" and null produce byte-identical commands by design. Send only the fields you want to change. Emits PROJECTS_CHANGED. Cosmetic, immediate. defaultProvider writes through the global projectDefaultProviders Record setting and triggers SETTINGS_CHANGED as well so the EditProjectDialog radios + sidebar "!" cue update without an app refresh.

    TOKEN=$(cat ~/.amc/cli-token)
    
    # Set this project's default provider to Codex
    curl -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
      -d '{"defaultProvider":"codex"}' \
      http://127.0.0.1:19519/project/<projectUuid>
    
    # Clear the override (fall back to Claude)
    curl -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
      -d '{"defaultProvider":null}' \
      http://127.0.0.1:19519/project/<projectUuid>
    
  • DELETE /project/:id (auth, 202 Accepted) — approval-gated. Queues a project.delete row in cli_pending_actions and emits CLI_PENDING_CHANGED. The actual delete (which cascades to sessions) doesn't happen until the user approves in the inbox. Idempotency on the X-Client-Request-Id request header (DELETE bodies are spec-discouraged) — same id within 30 days returns the existing pending row with idempotent: true.

  • PATCH /project/:id/bug-intake (auth, 202 Accepted) — approval-gated. Queues a project.bug_intake_update row that enables, changes, or disables the project's bug-report intake slug. Body { enabled: true, slug: "<a-z0-9-, 1-64>" } to enable/change, or { enabled: false } to disable. Slug uniqueness is enforced case-insensitively against other non-deleted projects (409 at queue and apply time). Approval-gated because flipping intake on opens an external email side channel and auto-spawns Claude sessions on incoming reports — see bug-report-intake.md. Idempotency via X-Client-Request-Id header.

Project docs (.claude/docs/) — /project/:id/docs

Lets an external agent inspect, add, and remove files in a project's .claude/docs/ folder — the per-project knowledge folder that gets auto-injected into every session in that project (see project-docs-auto-injection.md for what the folder does). Mutations are approval-gated; the read endpoint is open to authenticated callers.

  • GET /project/:id/docs (auth, 200 OK) — list current files in the project's .claude/docs/. Response: { ok: true, files: [{ name, sizeBytes, ext, modifiedAt }, ...], ragFiles: [ ...same entry shape... ], totalBytes, ragTotalBytes, quotaBytes, truncated }. Docs live in TWO buckets: files/totalBytes are the always bucket (top-level .claude/docs/, auto-injected into the first message of every session, counted against quotaBytes); ragFiles/ragTotalBytes are the rag bucket (.claude/docs/rag/, surfaced as an on-demand table-of-contents the agent reads selectively and NOT counted against the always-bucket quotaBytes). ext is the lower-cased extension including the leading dot (e.g. .md); truncated flips to true once 1000 files are listed (older entries are dropped). Returns 404 for unknown project, 401 without bearer token, 429 when the per-bearer read budget (60/min) is exhausted.
  • POST /project/:id/docs (auth, 202 Accepted) — approval-gated. Queues a project.docs_upload row. Body { sourcePath: "<absolute path to a regular file>", destFilename: "<safe filename>", bucket?: "always" | "rag" } (bucket defaults to "always"; pass "rag" to route the file into the uncapped .claude/docs/rag/ bucket instead of the always-bucket) — the agent writes the file to a %TEMP%/scratch directory FIRST and passes its absolute path; Omniscio copies it into .claude/docs/<destFilename> only after approval. Validation at submit time: filename rules (ASCII printable, single extension, no path separators, not a Windows reserved device like con.txt/PRN.log), source must be a regular file (symlinks rejected — security), source ≤ 25 MB per file, project total ≤ 100 MB after the add, destination must not already exist. Returns 400 for filename / source / size violations, 409 for dest already exists or pending-action cap, 413 for size/quota, 404 for unknown project. Idempotency via X-Client-Request-Id header.
  • DELETE /project/:id/docs/:filename (auth, 202 Accepted) — approval-gated. Queues a project.docs_delete row. Accepts an optional ?bucket=always|rag query param (default always) selecting which bucket to delete from — an unknown value returns 400. The filename must pass the same validateFilename rules; the file must exist at submit time (returns 404 otherwise so the caller doesn't queue an approval for a no-op). Idempotency via X-Client-Request-Id header.

The two-gate validation matters: filename rules and existence checks run at BOTH submit time (so a misshapen request fast-fails with 400/404 instead of polluting the inbox) AND approve time (so a tampered queue row can't sneak ..\..\evil.dat past). Path-based upload (sourcePath) is deliberate — base64 in the request body would force the agent to load the entire file into memory and would balloon the JSON payload past most rate-limit / log-line thresholds, while a path-based handoff is constant-cost regardless of file size.

TOKEN=$(cat ~/.amc/cli-token)

# List
curl -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:19519/project/<projectUuid>/docs

# Upload (queue approval)
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"sourcePath":"/tmp/notes.md","destFilename":"notes.md"}' \
  http://127.0.0.1:19519/project/<projectUuid>/docs

# Delete (queue approval)
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:19519/project/<projectUuid>/docs/notes.md

Away-mode rules — /away-mode/rules

  • GET /away-mode/rules (auth, 200 OK) — list all away-mode rules. Auth-gated despite being read-only — returns 401 without the bearer token.
  • POST /away-mode/rules (auth, 202 Accepted) — approval-gated. Queues an away_mode.create row. Body { conditionType: "keyword" | "project" | "tag" | "time", conditionValue, responseText, snippetId? }. Emits CLI_PENDING_CHANGED.
  • PATCH /away-mode/rules/:id (auth, 202 Accepted) — approval-gated. Queues an away_mode.update row. Same body fields as POST (all optional). The route 404s if the rule no longer exists, so dispatchers don't waste approvals on dead targets.
  • DELETE /away-mode/rules/:id (auth, 202 Accepted) — approval-gated. Queues an away_mode.delete row. No body. Same 404 fast-fail as PATCH.

All three away-mode mutations accept X-Client-Request-Id (header, ≤64 chars) for idempotent retries; same id within 30 days returns the existing pending row.

Recipe runs — /recipe/configs/:id/run

  • POST /recipe/configs/:id/run (auth, 202 Accepted) — approval-gated. Queues a recipe.run row in cli_pending_actions. Body fields: customMessage? (≤10 000 chars; overrides the recipe's default kickoff prompt) and variables? (object of string-keyed values that fill the recipe's parameter slots). Returns 400 if the recipe lacks a homeProjectId (CLI v1 doesn't run virtual-project recipes), 404 if the id is unknown. Emits CLI_PENDING_CHANGED. Idempotency via X-Client-Request-Id header.

The recipe also needs approvalStatus: "approved" for the eventual run to actually start — but this endpoint enqueues the run regardless. The recipe engine itself refuses to spawn for an unapproved recipe; the user approves the recipe once (in the inbox) before the first run will actually fire.

Approvals, caps and rate limits

All approval-gated mutations above share the same cap on outstanding pending actions and the same 10/min rate-limit bucket as cron, automations, settings PATCH, session lifecycle, keybindings, and recipe authoring. A 409 means either the cap is full (list GET /cli-pending?status=pending to see what's queued) or — for POST /project/create only — the folder is already registered as a project.

Related

The endpoints that watch and drive what is on your screen are in part 3, and how to switch CLI Control on and call it is on the parent page. Firing a recipe from an agent has its own page in agent-trigger-recipes.md, and the folder this part uploads into is explained in project-docs-auto-injection.md.

Last verified 2026-09-28