Invocation & Envelope
How a call is built, and what comes back. One grammar covers every platform action; one output contract covers every result. Everything on this page also rides every discovery response as the invocation, envelope, and pagination scaffolding.
The grammar
omni-cli <platform> <resource> <verb> [--flag value ...] [--json]
Every action parameter binds as a long flag --<name>. There are no positional arguments and no short flags, and all values are passed as strings — a param's type in discovery is a semantic and validation hint, not a shell type. The action's argv field gives the exact <platform> <resource> <verb> triple; never guess flag spelling — read the action's tier-3 discovery entry.
Global flags
| Flag | Type | What it does |
|---|---|---|
--context |
string | work, personal, or both. Omit to auto-resolve when exactly one context is logged in — agents should pass it explicitly. |
--json |
bool | Machine-readable JSON output. Agents should always set it. |
--verbose |
bool | Diagnostic logging to stderr (never secrets). |
Hosts
Platforms with host_required (a self-hosted server URL, or a Jira site including /rest/api/3) need a host. The host is saved at login and applied automatically to every action — omit --host on action commands; an explicit value overrides the saved host for that single call (exceptional). Omit host entirely for hosted platforms.
The output contract
Success — exit 0, and stdout carries the bare platform payload, projected to the action's output_schema. It is not wrapped in an {ok, data} envelope; if the platform returns an array, stdout is an array.
Failure — non-zero exit, and the error goes to stderr: a structured JSON object when --json is set, human-readable text otherwise.
{
"ok": false,
"error": "rate_limited",
"platform": "github",
"context": "work",
"message": "GitHub rejected the request: rate limit exceeded",
"remedy": "wait and re-issue; the request was rejected before applying",
"upstream": {
"http_status": 429,
"action": "github:issue.list",
"mutating": false,
"idempotent": true,
"retry_after_seconds": 30
}
}
upstream is present only when a platform request actually failed (never on local, usage, or auth errors): http_status (0 for a transport failure with no response), action (<platform>:<action-id>), mutating, idempotent, and retry_after_seconds (0 unless the server sent a Retry-After). It exists so the caller can decide whether re-issuing is safe.
Control flow
Branch on the process exit code — never parse stdout's shape to detect failure. Exit 0: parse stdout against output_schema. Non-zero: read the error object from stderr; the code-to-meaning table is in Exit Codes (and ships in the CLI as omni-cli errors).
Execute-once: retries are yours
The CLI executes each action exactly once and never retries — not even reads. There is no backoff, no Retry-After sleep, and deliberately no retryable field: retry-safety is a judgment about side effects, and the caller owns it. The rule:
- A
rate_limited(12) orupstream_error(9) on an idempotent action (upstream.idempotenttrue) is safe to re-issue, optionally afterupstream.retry_after_seconds. - Never blindly re-issue a non-idempotent action (
upstream.idempotentfalse) after an indeterminate failure — a create or append may already have applied.
Pagination
The CLI returns a single page and never auto-paginates — paging is the caller's job. An action with a pagination block in its discovery entry pages; one without returns a complete set. To advance, re-call the action passing the prior response's next-page value via the param named by pagination.cursor_param; stop when that value is empty or absent.
Where the next-page value lives depends on pagination.next.from:
header— the cursor is lifted from a response header into a top-levelnext_cursor: an array body is wrapped as{items, next_cursor}, an object body gets anext_cursorsibling. An emptynext_cursormeans last page.body— the cursor is already in the named response field.body_total— page by number until the named total field is reached.body_empty— page by number until the named array field comes back empty.
omni-cli github issue list --repo octo/hello --json
# → { "items": [ ... ], "next_cursor": "2" }
omni-cli github issue list --repo octo/hello --page 2 --json
# → { "items": [ ... ], "next_cursor": "" } # empty: last page
Read this page as markdown — the copy your coding agent fetches.