CLI reference
Machine output
Heddle is meant to be driven by programs, and it says so in a stable contract rather than in the shape of its prose. Three output modes, one discriminator field, a structured error envelope, and per-verb JSON schemas. This page is what an agent needs before it parses anything.
The three modes
Verbatim from heddle help output-formats: “text is the default, always. There is no TTY/pipe
auto-detection — the default never switches under you, so scripts and
humans see the same thing until a flag says otherwise.”
--output text- The default on every command, including when stdout is a pipe. Treat text output as human-facing and unstable. Never parse it.
--output json- The full machine contract: a stable
output_kinddiscriminator, exit codes, and recovery templates. --output json-compact- Only the decision-surface fields —
output_kind,status/coordination_status,blockers,next_action,changed_paths,conflicts. Fewer tokens, sameoutput_kind, so callers can still dispatch on it.
Dispatch on output_kind, never on the presence or absence
of a field. It is the discriminator the contract guarantees.
The error envelope
Failures are not a message. They are a structured object on stderr, and
it is the single most useful thing the CLI gives an automated caller.
Running heddle status --output json in a directory with no
repository:
stderr, on a directory with no Heddle repository
json$ heddle status --output json 2>&1 >/dev/null{ "error": "repository not found at /tmp/probe", "exit_code": 78, "kind": "repository_not_found", "hint": "Run `heddle init /tmp/probe` to initialize the requested repository.", "preserved": "no repository objects, refs, metadata, or worktree files were changed", "unsafe_condition": "no Heddle repository was found at '/tmp/probe'", "would_change": "the command cannot inspect or change repository state until initialization", "primary_command": "heddle init /tmp/probe", "primary_command_template": { "argv_template": ["<abs path to heddle>", "init", "/tmp/probe"], "agent_may_fill": false, "required_inputs": [] }, "recovery_commands": ["heddle init /tmp/probe"]} Three of those fields are the fail-closed contract, and they are worth
naming individually: preserved tells you what was not touched, unsafe_condition why the command
refused, and would_change what it would have done. Together
they let a caller distinguish “this did nothing” from “this did half a
thing”. argv_template is directly executable.
Branch on kind, the typed discriminator — never on the error string. Exit-code classification works the same way;
see Exit codes.
Schemas, and what has none
heddle schemas <verb> prints the JSON Schema for a
verb that emits JSON; with no argument it prints the registered list.
The verb is the joined subcommand path — status, fsck repair git — and a handful of flag-qualified variants
are registered as distinct verbs in their own right, because they emit
a different shape: log --reflog, log --timeline, land --threads, undo --list, undo --recover.
The command catalog itself is machine-readable: heddle help --output json lists every command with its
output modes, op-id behaviour and declared exit codes. This site keeps a
checked-in copy of it and diffs the reference pages against it on every
build.
9 leaf commands have no JSON contract at all and only ever emit text:
heddle runheddle thread cdheddle shell initheddle shell completionheddle shell promptheddle completeheddle auth loginheddle auth derive-agentheddle semantic index
Idempotent replay
Mutating commands accept --op-id <UUID>, or the HEDDLE_OPERATION_ID environment variable. Verbatim from heddle help operation-ids: “Replaying the same id with
the same body returns the recorded outcome; with a different body it
returns a typed conflict.” Without an id, dedup is bypassed and the
call executes normally.
The flag is hidden on every command's --help on purpose —
it shows up in the machine catalog instead, where each command advertises
its own op_id_behavior. Every command that supports it in
this release reports explicit_replay, meaning the caller
must supply the id. The dedup store is file-backed locally, with a
seven-day default retention.
This is the mechanism that makes a retry on exit 75 safe to
write: same id, same body, at-most-once effect.
Putting it together
A loop that cannot silently swallow a failure
bash# capture BOTH streams — the envelope is on stderr$ heddle status --output json$ heddle diff --output json$ heddle capture -m 'why this change'$ heddle commit$ heddle push --op-id 3f7c1a24-9f0e-4a11-9e6c-2b8d0e5f7a31# exit 75 → replay the same op-id. exit 76 → escalate. Ground truth: heddle 0.12.0.