Sinaris CLI Protocol Reference
This document is the canonical reference for Sinaris's structured CLI surface. The entire command tree is defined declaratively via [CliCommand] attributes and lives behind a framework-agnostic CommandRegistry — System.CommandLine is just an adapter. Anything documented here is machine-discoverable at runtime via sinaris meta list --format json and sinaris meta info --format json.
Self-describing surface
The protocol layer is the single source of truth:
| Discovery method | Output | Intended consumer |
|---|---|---|
sinaris --version | <version> (built YYYY-MM-DD) — stamped at compile time | humans, release verification |
sinaris meta info --format json | {version, buildDate, commandCount, executableCount} | health checks, CI |
sinaris meta list --format json | [{id, name, path, parentId, executable, description}] | scripts, grep-style filters |
sinaris meta list | One line per command: <id> (<full path>) | humans |
sinaris meta schema | Full nested schema (commands, options, args, stdin, enum allowedValues, metadata) | AI / MCP tools, code generators |
--version and the version / buildDate fields all come from a single CliToolInfo DI singleton — the build date is stamped into the assembly via <AssemblyMetadata Include="BuildDate" /> in Sinaris.Cli.csproj and may be overridden by CI/release pipelines (set the BuildDate MSBuild property) to guarantee deterministic timestamps across reproducible builds.
Every command described below also appears in meta schema — so when this doc drifts from reality, meta schema is correct.
Output format convention
All commands that produce structured data accept --format text|json (alias -f). text is the human default; json is the machine-stable contract and is what AI agents, hook scripts, and MCP tools should request.
sinaris task list --format json
sinaris meta info -f json
sinaris meta schema # defaults to --format jsonThere is exactly one way to ask for JSON output (--format json); no command exposes a legacy --json flag — the protocol enum OutputFormat ({Text, Json}) is the single source of truth across the platform.
Error envelope
Failures under --format json emit a structured envelope on stdout with a non-zero exit code, so AI agents and MCP tools always parse JSON regardless of success/failure:
{
"ok": false,
"error": {
"code": "notFound",
"message": "task 'missing-id' not found.",
"details": { "id": "missing-id" }
}
}details is optional and omitted when empty. code is one of the protocol taxonomy values defined by CliErrorCode:
| Code | Meaning |
|---|---|
validation | Input validation failed (missing/empty/malformed argument or stdin). |
notFound | Requested entity does not exist. |
conflict | Operation rejected because of conflicting state (already claimed, wrong status, ...). |
internal | Unhandled exception bubbled out of the handler. |
cancelled | Command was cancelled (Ctrl+C / SIGTERM). |
unknown | Fallback when the failure mode is not classified. |
The envelope is also produced by the adapter for failures that happen before a handler runs (workspace not initialized, cancellation, uncaught exceptions). The adapter recovers --format from the parsed token stream so AI agents always observe the same shape regardless of the failure origin.
Successful commands under --format json emit on stdout in two shapes:
- Data commands (list / show / create / claim / ...): the existing JSON payload (e.g.
[{...},{...}]fortask list --format json) — unchanged by this contract so existing AI clients keep working. The schema is the contract. - State-changing commands without a data payload (
task complete,task delete,agent register, ...): a success envelope{"ok":true,"message":"...","data":{"id":"..."}}—datais optional.
--format text (the human default) keeps the long-standing Error: ... line on stderr for failures, and the prior plain-text message on stdout for successes. Exit codes are the same in both formats.
Cancellation
Long-running commands such as hub bind their work to a CancellationToken supplied by the adapter, so Ctrl+C / SIGTERM always exits cleanly without dropping in-flight state.
Streaming output
Long-running commands write progress through ICommandOutput (stdout/stderr). This is the same interface tests use, which means subcutaneous tests assert on the exact bytes a user would see — no Console.WriteLine shortcuts allowed.
Exit codes
| Exit code | Meaning |
|---|---|
0 | success |
1 | user-facing failure (validation, not-found, conflicting state) |
2 | internal failure (logged to .sinaris/logs/error-*.log) |
Command tree
Root-leaf commands
| Command | Purpose | Stdin? |
|---|---|---|
init | Initialize the workspace (assets + database + templates) | — |
hub | Start the local web hub | — |
init
- In an interactive shell, first-time
initasksSelect target platform: 1) Copilot / 2) Cursor (default). - Re-running
initkeeps the installed platform and regenerates missing or outdated managed assets. --forceregenerates managed hook, command, skill, and instruction assets, but only after explicit interactive confirmation.- If the workspace database cannot be migrated,
initbacks it up under the user-level Sinaris backup directory for the current workspace and rebuilds it only after explicit interactive confirmation. - In non-interactive environments (CI, piped stdin), first-time
initand every destructive path fail loudly withvalidation. No platform default, asset overwrite, or database rebuild is inferred silently.
hub
-p / --port <int>(default8394).- Prints the URL on startup; keeps running until
Ctrl+C.
meta — protocol introspection
| Sub-command | Purpose |
|---|---|
meta info | Version + command counts |
meta list | Enumerate every command Id |
meta schema | Export the full nested schema (defaults to JSON) |
All three accept --format text|json (alias -f). meta list also accepts --executable-only to filter out group nodes. meta schema additionally accepts --indented false for a compact single-line JSON payload.
task — task management (16 leaves)
| Sub-command | Stdin? | Notes |
|---|---|---|
task create | — | -t <title> required; -p priority, --parent, --phase optional (--plan deprecated alias) |
task list | — | -s <status>, --tree |
task next | — | preview without claiming |
task queue-status | — | inspect claimable count, next task, and auto-claim session state |
task claim | — | --session <id> to attribute the claim |
task complete <id> | — | --session <id> required for owned tasks; -m <message> must include Criteria: and Risk: when context snapshots are active |
task fail <id> | — | --session <id> required for owned tasks; -m <message> must include Criteria: and Risk: when context snapshots are active |
task release <id> | — | back to Todo |
task retry <id> | — | reset failed task |
task decision-request <id> | — | pause a task for a user decision; -m, optional -c, optional --session |
task decision-resume <id> | — | resume a paused task with a user decision; -d, optional -c, optional --session |
task batch | yes | JSON array piped on stdin; --phase <phase-id> optional (--plan deprecated alias) |
task show <id> | — | |
task edit <id> | — | -t, -d, -p |
task delete <id> | — | only Todo / Failed tasks |
task log <id> | — | activity log |
Every task sub-command accepts --format json (alias -f json) for machine output.
proposal — demand proposals
| Sub-command | Stdin? | Notes |
|---|---|---|
proposal template | — | print proposal markdown template |
proposal next | — | allocate canonical proposal ID/path; --slug required |
proposal check | — | validate proposal markdown; --path, optional `--stage draft |
proposal publish | — | register proposal markdown into the workspace database |
proposal status | — | --detail includes gate-derived next action and blockers |
proposal prepare-design <id> | — | verify proposal can enter phase design |
proposal assign-milestone <id> | — | --milestone <id> |
proposal reject <id> | — | optional --reason |
Every proposal sub-command accepts --format json (alias -f json) for machine output.
milestone — project milestones
| Sub-command | Stdin? | Notes |
|---|---|---|
milestone next | — | allocate canonical milestone ID/path; --slug, optional --after |
milestone save | — | register authored milestone markdown; --path, optional --source-proposal |
milestone list | — | |
milestone show <id> | — |
Every milestone sub-command accepts --format json (alias -f json) for machine output.
phase — project phases
| Sub-command | Stdin? | Notes |
|---|---|---|
phase save | — | register authored phase markdown; --path, --milestone, optional --proposal, --title |
phase save accepts --format json (alias -f json) for machine output.
agent — agent session lifecycle
| Sub-command | Stdin? | Notes |
|---|---|---|
agent register | — | --session, --type (copilot/cursor/windsurf/aider/...) |
agent alias | — | --session, --alias |
agent auto-claim | — | --session, --enabled; toggles automatic task claiming for a recorded session |
agent register-copilot-session | optional | resolves the current Copilot CLI session; --cwd, --payload-file, --source |
agent diagnose-hooks | — | hook health |
agent heartbeat | — | --session <id> |
agent disconnect | — | --session <id> |
agent release-tasks | — | release everything claimed by a session |
agent list | — | |
agent resume | — | execute platform-specific resume command; --latest, --dry-run |
Every agent sub-command accepts --format json (alias -f json) for machine output.
guide — guided design workflow
| Sub-command | Stdin? | Notes |
|---|---|---|
guide template <kind> | — | goal / context / design / milestones / decision / open-question |
guide context | — | ProjectContext bundle for agent consumption |
guide check | — | candidate ProjectContext markdown checked against confirmed goals; --candidate <file> |
guide validate | — | validates .sinaris/context files |
guide publish | — | registers validated guide context into execution context |
guide status | — | summary of context state |
Every guide sub-command accepts --format json (alias -f json) for machine output.
evaluator — delivery confidence
| Sub-command | Stdin? | Notes |
|---|---|---|
evaluator list | — | list evaluator journeys; optional --tags |
evaluator status | — | delivery status summary |
evaluator validate [journeyId] | — | validate journey markdown files |
evaluator enable <journeyId> | — | enable a journey |
evaluator disable <journeyId> | — | disable a journey |
evaluator mark-needs-update <journeyId> | — | mark stale; optional --reason |
evaluator mark-current <journeyId> | — | mark current; optional --reason |
evaluator retire <journeyId> | — | retire journey; optional --reason |
evaluator run | — | run journeys; --scope, optional --agent, --delivery-gate |
evaluator runs | — | list runs; optional --journey-id, --limit, --offset |
evaluator show <runId> | — | show run detail |
evaluator explain <runId> | — | explain run failure |
evaluator submit <runId> | — | submit result JSON from --file |
evaluator cancel <runId> | — | cancel running run; optional --reason |
evaluator abandon <runId> | — | abandon stale running run; optional --reason |
evaluator retry <runId> | — | retry run; optional --agent |
evaluator confirm <runId> | — | confirm human-review run; optional --journey, --summary |
evaluator reject <runId> | — | reject human-review run; optional --journey, --checkpoint, --summary |
Every evaluator sub-command accepts --format json (alias -f json) for machine output.
hook — host hook runner
| Sub-command | Stdin? | Notes |
|---|---|---|
hook run | — | --platform, --hook, --payload-file; no JSON output option because it streams hook decisions for the host |
recovery — agent-facing workspace recovery inspection
| Sub-command | Notes |
|---|---|
recovery status | compare active DB resources, current .sinaris/ documents, and available backups |
recovery inspect | inspect --backup latest or a named/path backup and emit a recovery manifest for sinaris-recovery |
Every recovery sub-command accepts --format json (alias -f json) for machine output. These commands are intended for the sinaris-recovery Agent command/SKILL; init should tell users to run the Agent command, not hand-enter recovery CLI arguments.
AI / MCP integration patterns
Pattern 1: discover the surface
sinaris meta list --format json | jq -r '.[] | select(.executable) | "\(.path)\t\(.description)"'This is the recommended way for AI agents to bootstrap themselves — no hard-coded command list, no scraping --help.
Pattern 2: pipe in JSON, parse JSON out
cat tasks.json | sinaris task batch --phase phase-001 --format json | jq '.[].id'Commands accept stdin only where it is semantically meaningful: task batch and agent register-copilot-session.
Pattern 3: drive an MCP / code-gen tool from the full schema
sinaris meta schema > sinaris-schema.json
# or pipe directly:
sinaris meta schema | jq '.commands[] | select(.name=="task") | .commands[].name'meta schema is the recommended entry point for any tool that needs the complete option / argument / stdin / enum surface for every command — it is the same schema the generator emits at compile time, only serialized at runtime so external consumers stay in lock-step with the actual binary.
Commands that consume structured JSON on stdin (task batch) self-describe their payload in the schema. Example shape:
{
"stdin": {
"parameter": "stdin",
"description": "JSON array of task descriptions",
"required": false,
"schema": {
"kind": "array",
"items": {
"kind": "object",
"type": "BatchTaskRequest",
"properties": [
{ "name": "title", "type": "string", "required": true },
{ "name": "description", "type": "string", "required": false },
{ "name": "priority", "type": "int", "required": true }
]
}
}
}
}AI agents should treat the presence of stdin.schema as the signal that a typed JSON payload is expected and use the property list to construct the body.
Implementation contract
A new command is added by writing a single attributed class — the source generator handles everything else:
[CliCommand("Manage widgets", Category = "Widgets")]
public sealed partial class WidgetCommands
{
private readonly WidgetCommandHandler _handler;
public WidgetCommands(WidgetCommandHandler handler) => _handler = handler;
[CliCommand("Create a widget")]
public Task<CliCommandResult> CreateAsync(
[CliOption("-n", Description = "Widget name")] string name,
[CliOption(Description = "Output format")] OutputFormat format = OutputFormat.Text,
CancellationToken cancellationToken = default)
=> _handler.CreateAsync(name, format, cancellationToken);
[CliCommand("Load widgets from JSON on stdin")]
public Task<CliCommandResult> LoadAsync(
[CliOption("-f", Description = "Output format")] OutputFormat format = OutputFormat.Text,
[CliStandardInput("JSON array of widget descriptions", typeof(WidgetDto[]))] string stdin = "")
=> _handler.LoadAsync(stdin, format);
}Handlers compose results through CliResults:
return CliResults.Ok($"Widget '{id}' created.", format,
data: new Dictionary<string, string> { ["id"] = id });
return widget is null
? CliResults.NotFound($"widget '{id}' not found.", format)
: CliCommandResult.Success(OutputFormatter.FormatWidget(widget, format));Conventions enforced by the generator (diagnostics SINARIS001–SINARIS009):
XxxCommands(plural) → command group; one leaf per[CliCommand]method.XxxCommand(singular) → root-level leaf; exactly one[CliCommand]method.- Method names:
CreateAsync→create,RegisterCopilotSessionAsync→register-copilot-session. - Parameter types:
bool / int / long / double / float / byte / char / string, anyenum, nullable variants of the above. Type-injected by the runtime:ICommandOutput,CancellationToken. - Async return: every method must be
Task<CliCommandResult>.
Everything else (option name kebab-casing, --help text, default values, enum AllowedValues, args-builder for tests) is generated automatically.