Terminal output
At a terminal, shep draws a box-drawn table that fits your window, colours a sheep's status, restart count, exit code, CPU and memory, and shows status as a face too. Piped into anything else, it prints the same plain columns it always has. You do not have to configure that; it is the default both ways.
What a terminal shows
shep flock in a 120-column window. Every colour here says something a number alone would take a second glance to notice: STATUS carries a face and the word; RESTARTS and EXIT turn a colour once a sheep has actually restarted or crashed; CPU and MEM ramp with how much of each a sheep is using; ID and FOLD are muted throughout, since they never change; and every - is muted too, so an absent value does not compete with a real one:
CPU and MEM have two tiers each, not a gradient. The question they answer is whether one sheep is unusual for this flock, not how much it is using: 160M and 4G both land in the same tier, and on a flock where everything is large the whole column is one colour and tells you nothing. A third tier would have to be the fault colour, which would paint a healthy 4G service as though it had broken. Use--format json when the exact number is the thing you need.
┌────┬──────┬───────────────┬───────┬──────────┬──────┬──────┬──────┬────────┬─────────┬──────┐ │ ID │ NAME │ STATUS │ PID │ RESTARTS │ EXIT │ CPU │ MEM │ UPTIME │ FOLD │ SMIT │ ├────┼──────┼───────────────┼───────┼──────────┼──────┼──────┼──────┼────────┼─────────┼──────┤ │ 1 │ api │ (x.x) errored │ - │ 15 │ 1 │ - │ - │ 0s │ - │ - │ │ 0 │ web │ (o.o) online │ 68630 │ 0 │ - │ 0.0% │ 3.0M │ 18s │ backend │ - │ └────┴──────┴───────────────┴───────┴──────────┴──────┴──────┴──────┴────────┴─────────┴──────┘
SMIT is a marker a dog attaches to a sheep over the client protocol — - for every sheep here because none carries one. See terminology.
The faces
Five faces across six statuses, because two of them mean the same thing to someone glancing at a table:
(o.o) grazingonline(o~o) wakingstarting(>_<) flinchingwaiting to restart(-.-) asleepstopping, stopped(x.x) startlederrored(?_?) puzzledsilent, dogs onlysilent is not a lifecycle status and no sheep ever wears it. See the dogs table.
They are ASCII rather than emoji on purpose. An emoji is double-width, inconsistently so across terminals, and cannot take a foreground colour, which would turn every column measurement into guesswork.
The EXIT column
A sheep that is not running shows why it stopped: an exit code, or a signal name. A running one shows -, the same conventionPID and CPU use for a value that has no honest answer yet.
It tells two failures apart that status alone cannot.errored with fifteen restarts says only that a sheep is not up. EXIT 1 says it started and crashed; an empty EXIT on that same row says it never started at all.
Multi-instance apps group
An app with instances above 1 gets a header row above its slots, in full or plain: web ×3in NAME, and a ↳ :0 row beneath it per instance, which is also what web:0 selects. A single-instance app is unchanged, no group row and no suffix:
┌────┬────────┬──────────────┬───────┬──────────┬──────┬──────┬──────┬────────┬──────┬──────┐ │ ID │ NAME │ STATUS │ PID │ RESTARTS │ EXIT │ CPU │ MEM │ UPTIME │ FOLD │ SMIT │ ├────┼────────┼──────────────┼───────┼──────────┼──────┼──────┼──────┼────────┼──────┼──────┤ │ │ web ×3 │ (o.o) online │ │ 0 │ │ 0.0% │ 9.1M │ 33s │ - │ - │ │ 0 │ ↳ :0 │ (o.o) online │ 74802 │ 0 │ - │ 0.0% │ 3.0M │ 33s │ │ │ │ 1 │ ↳ :1 │ (o.o) online │ 74803 │ 0 │ - │ 0.0% │ 3.0M │ 33s │ │ │ │ 2 │ ↳ :2 │ (o.o) online │ 74804 │ 0 │ - │ 0.0% │ 3.0M │ 33s │ │ │ └────┴────────┴──────────────┴───────┴──────────┴──────┴──────┴──────┴────────┴──────┴──────┘
The group row rolls up rather than picking a winner: STATUSis the shared word when every instance agrees, else a count per state like 2 up, 1 down. RESTARTS, CPUand MEM sum across the instances, and UPTIMEis the minimum, time since the last disruption. ID andPID are blank, since neither has a single answer, andFOLD and SMIT sit only on the group row and stay blank on the instance rows beneath it, since both are per-app already. When a selector or filter matches only some of an app's instances, the count and rollups describe the rows actually listed, not the app's true size.
bare and --format json stay one line per instance instead, so they stay greppable. bare suffixes the name, web:2, and only for an app with more than one instance:
ID NAME STATUS PID RESTARTS EXIT CPU MEM UPTIME FOLD SMIT 0 web:0 online 74802 0 - 0.0% 3.0M 38s - - 1 web:1 online 74803 0 - 0.0% 3.0M 38s - - 2 web:2 online 74804 0 - 0.0% 3.0M 38s - -
JSON rows carry the slot as its own field, "instance": 0, rather than folding it into the name.
The dogs table
Dogs get their own table, under a Dogs caption, wherever one appears beside a flock listing. Every column the two tables share sits in the same place, and each table's own columns come last:
ID NAME STATUS PID RESTARTS EXIT CPU MEM UPTIMEFOLD SMITSOURCE┌────┬────────┬──────────────┬───────┬──────────┬──────┬─────┬──────┬────────┬────────┬──────┐ │ ID │ NAME │ STATUS │ PID │ RESTARTS │ EXIT │ CPU │ MEM │ UPTIME │ FOLD │ SMIT │ ├────┼────────┼──────────────┼───────┼──────────┼──────┼─────┼──────┼────────┼────────┼──────┤ │ 0 │ golbat │ (o.o) online │ 48182 │ 0 │ - │ - │ 3.0M │ 2s │ backed │ - │ └────┴────────┴──────────────┴───────┴──────────┴──────┴─────┴──────┴────────┴────────┴──────┘ Dogs ┌────┬────────────┬──────────────┬───────┬──────────┬──────┬─────┬──────┬────────┬─────────┐ │ ID │ NAME │ STATUS │ PID │ RESTARTS │ EXIT │ CPU │ MEM │ UPTIME │ SOURCE │ ├────┼────────────┼──────────────┼───────┼──────────┼──────┼─────┼──────┼────────┼─────────┤ │ 1 │ log-rotate │ (o.o) online │ 48207 │ 0 │ - │ - │ 6.6M │ 2s │ adopted │ └────┴────────────┴──────────────┴───────┴──────────┴──────┴─────┴──────┴────────┴─────────┘
FOLD and SMIT are missing from the dogs table because they are impossible for a dog rather than merely empty: a dog belongs to no fold, and a smit is a mark a dog paints on a sheep. A column that reads - on every row teaches nothing.
Colours match too, column for column. Same face and status colour, same restart colour above zero, same ramps for CPU and MEM, sameEXIT. So the same dog reads the same way undershep dogs, under shep flock, and aftershep restart log-rotate.
A dog that never answers reads silent
A dog is a peer as well as a process. It dials back to the shepherd and keeps a connection open, and one that never manages it is not doing its job however alive it looks. So online becomessilent, in amber, with a puzzled face instead of a grazing one.
The usual cause is a version skew: cargo install replaced the binary on disk and the process still running is the old one. Readshep bleats <dog> for the refusal, thenshep restart <dog>.
Only online is replaced. starting already says the connection is not up yet, and a dog is silent for a moment every time it starts. --format json carries the fact ashandshook, beside a status that still readsonline — true of the process, which is what that field has always described.
SOURCE says whose code is running
It is the only column in shep carrying a trust distinction, so the two values do not look alike. built-in is shep running its own code, and is muted like any other label you read past.adopted is a third-party binary running at the shepherd's own trust level, from a path you supplied, with no sandbox around it. That gets the same colour a restart count above zero gets: worth a glance, not a fault, because you chose it.
enable, disable, adopt andrehome print a one-row confirmation with the same treatment. Their STATUS holds either a status or a sentence saying why no shepherd answered, and only a status is coloured; a sentence is left plain, because a colour repeating the words beside it is decoration rather than information.
Where else colour appears
Every table shep prints follows one rule: a colour carries information, or the column does not get one. Beyond the flock and the dogs:
trigger, signal, whisperOUTCOME by what happened: it worked, nothing to report, a config gap you can close, or it failedflush, startupRESULT the same way. absent is muted, not marked: it is the state the verb was asked to producebarksSINKS, so an alert that failed to deliver does not read like one that arrivedkillSOCKET_REMOVED, because a false is what the next boot has to contend withimportREUSE_PORT when it is true, which is work you have to doSeven tables carry no colour at all, deliberately: describe's lamb tree, delete's id list, unset's count, the two saved-roll tables, the KV store, andshep dogs --available. Each is either pure data shep has no opinion about, or a single column that is also the whole content, where muting would fade the table and distinguish nothing.
The roll listing is the one table with a STATUS column and no colour on it. Everything in a saved roll is stopped by definition, so the cell reads stopped on every row of every rendering, and a colour that never varies distinguishes nothing.
After a lifecycle command
start, stop, restart,reload, delete and stock all print the whole flock afterwards, not only the sheep they touched. The question after starting one app is usually what everything else is doing, and the exit code already said whether the one you named worked.
┌────┬────────┬─────────┬───────┬──────────┬─────────┬─────┬────────┬────────┬────────┬──────┐ │ ID │ NAME │ STATUS │ PID │ RESTARTS │ EXIT │ CPU │ MEM │ UPTIME │ FOLD │ SMIT │ ├────┼────────┼─────────┼───────┼──────────┼─────────┼─────┼────────┼────────┼────────┼──────┤ │ 4 │ golbat │ online │ 14459 │ 5 │ - │ - │ 416.0K │ 0s │ backed │ - │ │ 5 │ koji │ stopped │ - │ 2 │ SIGTERM │ - │ - │ 0s │ backed │ - │ └────┴────────┴─────────┴───────┴──────────┴─────────┴─────┴────────┴────────┴────────┴──────┘ Dogs ┌────┬────────────┬────────┬───────┬──────────┬──────┬──────┬──────┬────────┬─────────┐ │ ID │ NAME │ STATUS │ PID │ RESTARTS │ EXIT │ CPU │ MEM │ UPTIME │ SOURCE │ ├────┼────────────┼────────┼───────┼──────────┼──────┼──────┼──────┼────────┼─────────┤ │ 6 │ log-rotate │ online │ 14110 │ 1 │ - │ 0.0% │ 6.6M │ 41s │ adopted │ └────┴────────────┴────────┴───────┴──────────┴──────┴──────┴──────┴────────┴─────────┘
That is the same output shep flock gives, dogs table and all, so shep restart log-rotate and shep flockdraw the same dog the same way. See the dogs table for why it is a second table rather than another row.
delete is the one verb whose listing cannot show what it did, since the rows are gone. It names the ids it removed on stderr and prints what is left on stdout.
notice[delete]: deleted 1 sheep, id 5
--format json is not widened. A script that runsshep stop web --format json asked aboutweb, so data holds the rows forweb. Use shep flock --format json when you want the flock.
Row order
Every listing shep prints is sorted by name, then by id. That coversshep flock, every lifecycle verb above,shep lookout, and the tablessignal, whisper and triggerprint.
Ids are handed out in registration order, which is a fact about your shell history rather than about the flock, and they move: adelete all followed by a fresh start renumbers everything. Names do not. The id breaks ties, so the four instances of an app stocked to four stay in a fixed order instead of shuffling between refreshes, and it is still what you type atshep stop 11.
Narrow windows
The table measures your terminal and drops columns until it fits, least useful first, and names what it hid. The same flock at 60 columns:
┌────┬──────┬────────┬───────┬──────────┬──────┬────────┐ │ ID │ NAME │ STATUS │ PID │ RESTARTS │ MEM │ UPTIME │ ├────┼──────┼────────┼───────┼──────────┼──────┼────────┤ │ 1 │ api │ (x.x) │ - │ 15 │ - │ 0s │ │ 0 │ web │ (o.o) │ 68630 │ 0 │ 3.0M │ 18s │ └────┴──────┴────────┴───────┴──────────┴──────┴────────┘ CPU, EXIT, FOLD, SMIT hidden. Widen the window, or use --format json.
Two things narrowed there. The status word went before any whole column did, leaving the face, which is five columns rather than fifteen. Then SMIT, FOLD, EXITand CPU dropped, in that order, and the footer says so.SMIT goes first: it is by far the widest column, so dropping it buys back the most room.
Nothing is ever hidden silently, and --format json always carries every field regardless of how wide your window is. If you are scripting against shep, that is the surface to use.
ID, NAME and STATUS never drop. Below the width those three need, the table prints wider than the window rather than losing the columns that say which sheep a row is about.
What a pipe gets
Redirect it and the boxes, colour and faces all go away. This is not a setting; a non-terminal stdout forces it, and so does--format json:
ID NAME STATUS PID RESTARTS EXIT CPU MEM UPTIME FOLD SMIT 1 api errored - 15 1 - - 0s - - 0 web online 68630 0 - 0.0% 3.0M 5m 10s backend -
None of the pretty rendering survives a pipe, which is the point. The columns themselves do move between releases though. EXITand SMIT are both recent, so parse--format json instead if a script depends on the shape.
Choosing how much
Three levels, set with shep style:
fullsheep, boxes, colour. The default.plainboxes and colour, no facesbareexactly what a pipe gets$ shep style notice[style]: full (from the default) $ shep style plain notice[style]: wrote plain to /tmp/docdemo/home/shep.toml $ shep style notice[style]: plain (from shep.toml)
With no argument it reports the level in force and where it came from, which is what you want when a shell profile and a config file disagree. Four places can decide, and the last one wins:
fullshep.toml[style] level$SHEP_STYLEper shell--styleper command$ SHEP_STYLE=full shep style notice[style]: full (from $SHEP_STYLE)
NO_COLOR
The NO_COLOR conventionis honoured at every level, and it is a separate axis rather than a fourth level. It removes colour and leaves the layout alone, sofull under NO_COLOR still draws boxes and still shows faces:
│ 1 │ api │ (x.x) errored │ - │ 15 │ 1 │ - │ - │ 0s │ - │
An empty NO_COLOR= counts as unset, per the convention.
Where sheep appear
Beyond the status column, three moments get art, each one a screen with nothing else on it: a flock with nothing in it yet, a flock that is entirely stopped, and shep muster. The count is capped, so mustering forty processes does not paint a field.
Never on an error, and never after kill,delete or stop. Errors keep their colour, because red is information, and lose everything else. A sheep beside a failure makes it harder to read, and a cheerful one after deleting a service is simply wrong.
Following logs
shep bleats prints the tail of each log before it starts following, so a sheep that has already died still tells you why rather than showing you an empty screen while the reason sits in a file you had no particular reason to open.
api | FATAL: port in use api | FATAL: port in use
--lines says how much, counted per stream, and defaults to fifteen. --lines 0 follows without replaying anything.
A multi-instance app's prefix carries the slot, so lines from two instances stay tellable apart:
web:0 | listening web:1 | listening web:2 | listening
Shared via merge_logs instead, the backlog read from the one file is printed unattributed, web |, since nothing in the file itself says which instance wrote a line. Lines read live after that come from the daemon's own per-sheep events and carry their slot as normal.