JSON output & exit codes

Every mani command accepts the global --json flag. With it, the command prints exactly one JSON object to stdout — the envelope — and sets a differentiated process exit code. This is the contract to script against: branch on the structured ok/code and on the exit code, never on the human-readable text.

A command's human-readable output is unchanged whether or not you pass --json; --json only swaps the rendered stdout for the single envelope object. (Nothing extra is printed to stdout in --json mode, so stdout is always parseable as one object. mani schema is the natural exception — its plain output is already the raw JSON payload, which --json simply wraps in the envelope.)

The envelope

A success carries the command's result under value:

{ "ok": true, "value": <command-specific JSON> }

A failure carries a classified error:

{ "ok": false, "error": { "code": "<code>", "message": "<human message>" } }

The value shape is per-command — it's the result described for that verb in mani schema. The envelope only ever carries non-secret anchors (ids, epochs, counts, fingerprints); no token, one-time code, key, recovery phrase, master password, or .env plaintext is ever placed in value or in a message.

Exit codes

A success exits 0. Each failure code maps to a stable, differentiated exit code so a caller that can't parse JSON can still branch on $?:

codeexitmeaning
(success)0ok
internal1unclassified failure — the catch-all, never a security signal
invalid_args2bad flags / usage / a structurally invalid argument
not_found3the target is absent or you're not allowed to see it exists (folded on purpose, so "absent" is indistinguishable from "forbidden")
needs_unlock4no key / this machine isn't enrolled / the master password is required — run mani init, mani device enroll, or unlock
conflict5a CAS append lost the race / a stale parent / a conflict copy was written
auth_expired6the session was rejected or expired — run mani login
integrity7a signature, content-address, AEAD tag, or format check failed

This table is emitted live under error_codes in mani --json schema — treat that as the source of truth if it ever disagrees with this page.

Branching in a script

Branch on .ok and .error.code from the envelope:

out=$(mani --json vault ls)
if [ "$(echo "$out" | jq -r '.ok')" = "true" ]; then
  echo "$out" | jq -r '.value[].name'
else
  case "$(echo "$out" | jq -r '.error.code')" in
    auth_expired) echo "session expired — a human must run: mani login" >&2 ;;
    needs_unlock) echo "locked — a human must unlock (master password)" >&2 ;;
    *)            echo "$out" | jq -r '.error.message' >&2 ;;
  esac
fi

Or, without jq, branch on the exit code alone:

mani --json sync >/dev/null
case $? in
  0) echo "synced" ;;
  5) echo "conflict copy written — merge by hand, then re-run mani sync" >&2 ;;
  6) echo "session expired — run mani login" >&2 ;;
  *) echo "sync failed" >&2 ;;
esac

See also