HEDDLE

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_kind discriminator, 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, same output_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 run
  • heddle thread cd
  • heddle shell init
  • heddle shell completion
  • heddle shell prompt
  • heddle complete
  • heddle auth login
  • heddle auth derive-agent
  • heddle 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.