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.
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.
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.
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.
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.
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 code | Meaning |
|---|---|
0 |
success |
1 |
user-facing failure (validation, not-found, conflicting state) |
2 |
internal failure (logged to .sinaris/logs/error-*.log) |
| Command | Purpose | Stdin? |
|---|---|---|
init |
Initialize the workspace (assets + database + templates) | — |
hub |
Start the local web hub | — |
- 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.
-p / --port <int>(default8394).- Prints the URL on startup; keeps running until
Ctrl+C.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| Sub-command | Stdin? | Notes |
|---|---|---|
hook run |
— | --platform, --hook, --payload-file; no JSON output option because it streams hook decisions for the host |
| 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.
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.
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.
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.
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.