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 $?:
| code | exit | meaning |
|---|---|---|
| (success) | 0 | ok |
internal | 1 | unclassified failure — the catch-all, never a security signal |
invalid_args | 2 | bad flags / usage / a structurally invalid argument |
not_found | 3 | the target is absent or you're not allowed to see it exists (folded on purpose, so "absent" is indistinguishable from "forbidden") |
needs_unlock | 4 | no key / this machine isn't enrolled / the master password is required — run mani init, mani device enroll, or unlock |
conflict | 5 | a CAS append lost the race / a stale parent / a conflict copy was written |
auth_expired | 6 | the session was rejected or expired — run mani login |
integrity | 7 | a 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
fiOr, 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 ;;
esacSee also
mani schema— discover every verb, its params, and its scope.- Automation & agents — the consent rules for driving
maniunattended.