Commands API (CIP)
The Commands API (/api/commands) is the client-facing contract of SupaCloud’s
Command-Interaction-Protocol (CIP). It exposes the same typed command set the web
terminal and the Telegram/Discord bots use, so a new surface — a bot, a CLI, a mobile or
voice client — drives SupaCloud commands without re-implementing parsing, option lists or
validation. The registry, option providers and the resolve state machine all live once in
the server (server/src/services/command_registry/); this API is a thin contract over
them (ADR 0049 §2.2).
Endpoints
Section titled “Endpoints”| Method | Path | Purpose |
|---|---|---|
GET |
/api/commands |
List the command descriptors available to the caller. |
GET |
/api/commands/options/{provider_id}?q= |
Resolve a named option provider’s choices, filtered by q. |
POST |
/api/commands/{word}/resolve |
Step the resolver: ready | need_param | invalid. |
POST |
/api/commands/{word}/invoke |
Resolve and, when ready, return the rendered reply. |
GET /api/commands
Section titled “GET /api/commands”Returns the array of command descriptors the caller can run. Each descriptor carries:
wordandaliases— the canonical word plus alternates (e.g.f→follow).category— the discovery bucket (control,tasks,resources,admin).description_i18n— an i18n key you resolve to your own locale.scope—task|project|workspace|global.surfaces— where the command is offered (always includesapihere).params— the ordered, typed parameters (name,description_i18n,required,kind,options).
A parameter’s kind is one of enum, ref (with a ref_kind such as task/project/
model), free_text, int_range (with min/max) or attachment. Its options is
static (a closed value list), dynamic (resolved by a named provider) or none.
GET /api/commands/options/{provider_id}
Section titled “GET /api/commands/options/{provider_id}”Resolves the live choice list for a dynamic parameter against your tenancy. Provider ids
include recent_tasks, models, efforts, projects and agents. The optional q
query is a case-insensitive substring filter (empty = the full, capped list). An
unregistered provider id returns 404.
Each Choice is { value, label, hint?, glyph? } — value is the wire value you bind
into an invocation; label is the display text.
POST /api/commands/{word}/resolve
Section titled “POST /api/commands/{word}/resolve”Body: { "args": { "<param>": "<value>", … } } (omit args for a bare invocation). The
response status is one of:
ready— every required param is bound and valid;inputis the resolved{ word, args }invocation.need_param— a required param is still missing; the body carriesparam,description_i18n,free_text(whether to also offer a free-text entry) and the enumeratedoptions.invalid— a supplied value failed validation; the body carriesparam,valueand a render-readyhint.
Drive a picker by calling resolve after each captured value until it returns ready.
POST /api/commands/{word}/invoke
Section titled “POST /api/commands/{word}/invoke”Same body as resolve. When the invocation is under-specified or invalid, invoke
returns the same need_param / invalid guidance (never a half-run command). When it
is fully resolved, it returns status: "executed" with the resolved invocation and a
rendered plan:
{ "status": "executed", "invocation": { "word": "status", "args": {} }, "plan": { "text": "Resolved /status", "html": false, "media": [], "affordances": [ { "label": "/status", "action": { "word": "status", "args": {} } } ] }}The plan is a surface-neutral reply: text/html body, optional media (image,
screenshot, file or audio — carried by url or inline data), and affordances
(next actions, each carrying a replayable CommandInvocation a client renders as a native
button / component / numbered key).
The full request/response schemas are in the generated
Web API reference (commands tag).