CLI output contracts
This page describes the v0.3.0 release candidate. Version 0.3.0 uses recipe schema version 4 and exposes the execution-mode facet.
awf separates human output from machine output so scripts never need to parse presentation text.
Use --json whenever another process consumes a command result.
Stream and exit-code rules
A JSON command that completes with a normal result writes exactly one JSON value to stdout and writes nothing to stderr.
status --json and doctor --json also write their versioned report to stdout when the completed inspection is unhealthy.
In that case, stderr remains empty and the process exits with code 1, so consumers can parse the report and act on its health fields.
An operational, validation, or syntax failure represented by the error schema writes exactly one JSON object to stderr and leaves stdout empty.
Exit code 0 means normal completion, 1 means an unhealthy report or an operational or validation failure, and 2 means invalid command syntax.
On POSIX systems, an interrupted process uses 130 for SIGINT and 143 for SIGTERM after requesting safe cancellation of the active operation.
An interrupted JSON command writes one versioned INTERRUPTED object to stderr and leaves stdout empty.
Windows forced termination has platform-defined process status and cannot promise those POSIX exit codes.
Human output and errors are sanitized before they reach the terminal.
The complete command-by-command meanings are listed in the CLI exit-code reference.
Schema ownership
The following table identifies the version field that owns each machine-readable result.
| Command or mode | Top-level JSON value | Version contract |
|---|---|---|
context --json | Project-context report | Top-level schema_version: 1 |
list --json | Array of generated catalog recipe records | Every recipe has schema_version: 4 |
show --json | Recipe record | Recipe schema_version: 4 |
show --open --json | Documentation opener result | Top-level schema_version: 1 |
Applied install, update, or remove | Installation manifest | Manifest schema_version: 2 |
Lifecycle --dry-run --json | Command result with nested plan | plan.schema_version: 1 |
status --json | Status report | Top-level schema_version: 1 |
validate --json | Validation report | Top-level schema_version: 1 |
doctor --json | Diagnostic report | Top-level schema_version: 1 |
init --json | Configuration result | Top-level schema_version: 1 |
manifest --json | Installation manifest | Manifest schema_version: 2 |
Any failed command with --json | Error object | Top-level schema_version: 1 |
Executable schemas
The CLI package exports strict Zod schemas for every machine-readable result through one public subpath.
The core package remains the canonical schema owner for recipe and manifest data, and the CLI contract registry exposes those same schema instances instead of maintaining duplicates.
Import the parser through the public package subpath:
import { parseCliOutput } from "@kauanpolydoro/agentic-workflows/output-contract";
const report = JSON.parse(stdout);
parseCliOutput("status", report);Available contracts are catalog_list, recipe, manifest, context, lifecycle_plan, status, doctor, init, validation, documentation_open, and error.
parseCliOutput returns the parsed, inferred record when validation succeeds and throws a Zod validation error when the value does not satisfy that contract.
Use catalog_list for list --json, recipe for show --json, and manifest for applied lifecycle results or manifest --json.
The package smoke test imports this public subpath from an installed tarball, and subprocess automation validates real list, show, lifecycle, status, validation, diagnostic, initialization, documentation, and failure results against the schemas.
Automation examples
These shell examples assume a global installation.
Replace awf with npx awf when the CLI is pinned as a project dependency.
In Bash, retain command failure and parse only successful stdout:
context_json="$(awf context --json)" || exit $?
printf '%s\n' "$context_json" | jq -r '.project_root'In PowerShell, capture the process status before interpreting the JSON:
$contextJson = awf context --json
$awfExitCode = $LASTEXITCODE
if ($awfExitCode -ne 0) { exit $awfExitCode }
$context = $contextJson | ConvertFrom-Json
$context.project_rootFor Node.js automation, pin the package locally, execute its JavaScript entrypoint without a shell, and validate the result with the public contract export:
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { parseCliOutput } from "@kauanpolydoro/agentic-workflows/output-contract";
const cli = fileURLToPath(import.meta.resolve("@kauanpolydoro/agentic-workflows"));
const result = spawnSync(process.execPath, [cli, "status", "--json"], {
cwd: process.cwd(),
encoding: "utf8",
});
if (result.error) throw result.error;
if (result.status === 0 || (result.status === 1 && result.stdout.trim() !== "")) {
const report = parseCliOutput("status", JSON.parse(result.stdout));
if (result.status === 1) {
console.error(
`Unhealthy installations: ${report.summary.drifted + report.summary.invalid}`,
);
process.exitCode = 1;
}
} else {
const error = parseCliOutput("error", JSON.parse(result.stderr));
throw new Error(`[${error.code}] ${error.message}\nNext: ${error.remediation}`);
}This distinction lets automation consume a completed unhealthy report without confusing it with a command failure.
Lifecycle plan version 1
Every lifecycle dry run exposes the same nested plan fields.
{
"schema_version": 1,
"operation": "install",
"dry_run": true,
"requires_force": false,
"changes": {},
"proposed_files": []
}operation is one of install, update, or remove.
proposed_files is present only when the command supports --show-content and that option was requested.
The changes object records applicable create, replace, unchanged, retire, remove, modified, and missing file sets.
An unchanged managed file is not rewritten during an update.
The surrounding command result retains its command-specific fields for compatibility, but new automation should read the versioned plan object.
Filtered diagnostics
doctor --failures-only --json retains summary counts for every check while returning only warning and failure records in checks.
The diagnostic report retains projectContext.root, projectContext.source, and projectContext.reason so automation can audit how the root was selected.
Diagnostic checks may include structured data and an actionable remediation without requiring consumers to parse detail.
Every diagnostic check has schema version 1 and always retains remediation and data, using null when no value applies.
The top-level diagnostic status is pass or fail, healthy is its boolean equivalent, and exit_code predicts the process result.
status --failures-only --json retains summary counts for every installation while returning only drifted and invalid records in installations.
Every status --json result includes project_context.project_root, project_context.selection_source, project_context.project_root_fallback, and project_context.reason so automation can verify the root before interpreting installation records.
The status context uses snake-case field names to match context --json, while doctor --json retains its existing projectContext contract.
The public normalizeProjectContext helper validates and maps all four compatible version 1 shapes to the canonical context field names:
import { normalizeProjectContext } from "@kauanpolydoro/agentic-workflows/output-contract";
const context = normalizeProjectContext(contract, report);
console.log(context.project_root, context.selection_source, context.project_root_fallback);The accepted contract names are context, status, doctor, and init.
This additive helper avoids changing the strict command records or breaking existing version 1 consumers.
Error schema version 1
Every JSON error includes schema_version, error, message, code, command, retryable, help_url, and remediation.
Known operational failures use a stable code such as NOT_FOUND, CONFLICT, MODIFIED_FILE, or INVALID_PATH.
| Code family | Meaning | Expected next action |
|---|---|---|
CONFLICT | Existing configuration, installation, or lifecycle ownership blocks the request. | Inspect ownership or the conflicting path before retrying. |
MODIFIED_FILE | A managed file differs from its retained hash. | Review the dry run and preserve the edit or approve --force. |
MISSING_FILE, NOT_FOUND | A requested workflow, path, or retained file is absent. | Verify the workflow ID, target, and selected project root. |
INVALID_PATH | The path violates containment, type, or symbolic-link rules. | Choose a real project-local path. |
FILE_TOO_LARGE | A bounded input exceeds its documented limit. | Reduce that input before retrying. |
INVALID_RECIPE, INVALID_MANIFEST | Retained data does not satisfy its strict contract. | Run strict validation and diagnostics. |
INTERRUPTED | Safe cancellation was requested before normal completion. | Follow remediation and inspect lifecycle state before retrying. |
UNKNOWN_ERROR | The failure did not map to a known operational class. | Use offline command help and awf doctor, then retain diagnostics for a report. |
awf.* syntax diagnostics | Commander rejected an incompatible or malformed invocation. | Correct the command line and retry; the process exits with code 2. |
Unexpected failures use UNKNOWN_ERROR instead of omitting the field.
Structured failures include details with fields such as remediation, suggestions, affected paths, or validation issues.
The command field identifies the command boundary that failed, and help_url points to its published reference section.
Current CLI errors retain a command-specific offline reference in details.help_command.
details is the existing extensible record in schema version 1, so earlier parsers continue accepting this addition without weakening the strict top-level contract.
The retryable field is deliberately conservative and is true only when retrying later can be safe without changing the request.
The remediation field always contains a next action, while a more specific remediation retained in details takes precedence.
An active lifecycle-lock conflict is the only currently retryable failure.
When its record is valid, details contains the sanitized pid and acquiredAt, and remediation requires verifying that owner and timestamp before manual removal.
The lock ownership token is never included in output.
Interruption uses code INTERRUPTED, retains details.signal, and preserves the same stream-isolation rules as every other JSON failure.
For example, an unsafe install target produces one stderr object and leaves stdout empty:
{
"schema_version": 1,
"error": "AwfError",
"message": "Target must stay inside the project root.",
"code": "INVALID_PATH",
"command": "install",
"retryable": false,
"help_url": "https://kauanpolydoro.github.io/agentic-workflows/guide/cli-reference#awf-install-workflow-id",
"remediation": "Choose a real, project-local path without symbolic-link or traversal boundaries.",
"details": {
"help_command": "awf install --help"
}
}Consumers should branch on code, not on the human-readable message.
New optional fields may be added within the same schema version.
Removing a field, changing its meaning, or changing its type requires a new schema version.