JSON output
Add --format json to any command and it prints one JSON object instead of a table, on the same stream it would have used anyway: success on stdout, errors on stderr. Nothing about the command's behavior changes; only the rendering does.
The envelope
Every non-streaming command wraps its payload in the same three fields: a schema version, the verb name, and the data itself. Here's shep flock --format json against a two-sheep flock:
{
"schema_version": 1,
"command": "flock",
"data": [
{
"id": 0,
"name": "web",
"status": "online",
"pid": 40968,
"restarts": 0,
"uptime_ms": 1019,
"fold": "backend",
"out_file": "/tmp/shepdemo/logs/web-0-out.log",
"err_file": "/tmp/shepdemo/logs/web-0-err.log",
"cpu_percent": null,
"memory_bytes": 1212416,
"dog": null,
"lambs": null,
"last_exit": null,
"smit": null,
"instance": 0,
"handshook": null
},
{
"id": 1,
"name": "worker",
"status": "online",
"pid": 40969,
"restarts": 0,
"uptime_ms": 1017,
"fold": "backend",
"out_file": "/tmp/shepdemo/logs/worker-0-out.log",
"err_file": "/tmp/shepdemo/logs/worker-0-err.log",
"cpu_percent": null,
"memory_bytes": 1212416,
"dog": null,
"lambs": null,
"last_exit": null,
"smit": null,
"instance": 0,
"handshook": null
}
]
}command is the verb you typed, spelled out in full: run it as shep ls --format json and commandstill reads "flock", because that's what the daemon answered to, not the alias that got you there. data is always the same shape for a given command: a single object for something like shep ping, an array for anything that lists multiple sheep.
Errors get their own envelope
A failed command prints a different, smaller shape to stderr, nocommand key, because the command never produced a result:
{
"schema_version": 1,
"error": {
"code": "not_found",
"message": "the daemon reported NotFound: selector matched no registered sheep"
}
}code is the same word that appears inerror[<code>]: … under the default table format, and it's the same taxonomy as the process exit code: 3for not_found, 4 forinvalid_config, and so on. A script can switch onerror.code without parsing English, and the exit status confirms it independently.
What schema_version promises
It's at 1 today, and it moves only for a breaking change to some command's data shape: a field renamed, removed, or given a different type. Adding a new field to an existing payload does not bump it: a consumer that reads the keys it cares about and ignores the rest survives an additive release untouched. The contract that's actually worth writing code against:
- Check
schema_versiononce, up front, rather than per field: it's the one thing that tells you whether the shape you coded against still applies. - Don't assume every key is present on every row.
dogisnullfor a plain sheep.lambsisnullfromshep flock(the plain listing never walks the process tree) but a populated array (empty if the sheep has spawned no children of its own) from bothshep describeandshep fold, since fold renders the same per-sheep detail the table format's Lambs section shows. Check the key itself, not which command you ran, if you're writing something generic. last_exitisnulluntil a sheep's process has stopped at least once under this daemon, and afterwards holds one of two shapes:{"code": 1, "signal": null}for a normal exit, or{"code": null, "signal": 15}for one ended by a signal. Both are never set at once, and both beingnullis not a state the daemon produces. It is the field that distinguishes a process that started and crashed from one that never started at all, which thestatusalone cannot: both readerrored.cpu_percentreadsnull, not0, when a sample wasn't available yet. A fresh sheep a few hundred milliseconds old, most often. Table format prints the same case as-, for the same reason: a confident zero would be a claim shep can't back up.handshookis about dogs and isnullfor every sheep.falseis the one to act on: the dog's process is up and the shepherd has never heard from it, so it is not doing its job.statusstill readsonlinethere, truthfully — it describes the process. Table format shows this row assilent.smitisnulluntil a dog attaches one over the client protocol'sSetSmitrequest. shep stores and reports the string verbatim, and never parses it — seeterminology.
shep bleats --format json is the one exception. A log follow has no end, so there's nothing to close an envelope around: it prints one JSON object per line, newline-delimited, with nocommand or data wrapper.
{"schema_version":1,"id":0,"name":"web","stream":"out","instance":null,"line":"…"}instance is the sheep's slot within its app, when that app has more than one instance registered; nullotherwise, and also null for a backlog line read from a log file several instances share, since nothing in that file says which one wrote it.
Table format is not a lesser version of this
The default --format table and --format jsonrender from the same underlying data (there's no separate code path that could drift between them) but table adds human formatting JSON doesn't: uptime_ms: 1019 becomes 1s,memory_bytes: 1212416 becomes 1.2M. If you're piping shep's output anywhere, reach for--format json and do the formatting yourself rather than parsing the table's columns.