Solitary

The dashboard

Every cell and its state in one live view.

solitary dashboard is the same operations as the CLI in one live view: every cell, its state as it changes, and the actions that apply to the selected one.

 solitary
 ╭─────────────────────╮╭──────────────────────────────────╮
 │ cells               ││ claude                           │
 │ › ● claude  running ││ image   build:./Containerfile    │
 │   ○ demo    stopped ││ machine 4 cpus · 4GiB · 40GiB    │
 ╰─────────────────────╯│ ports   all reach host localhost │
                        │ network 2 allowed                │
                        │         github.com               │
                        │         api.anthropic.com        │
                        │ vpn     none                     │
                        │ inbox   0 files · 0 B            │
                        │ outbox  2 files · 4.1 MiB        │
                        │ secrets 2 of 2 set               │
                        ╰──────────────────────────────────╯
 ↑↓ move · ⏎ shell · u up · s stop · e secrets · n network · t traffic · d rm · q quit

It does nothing the commands cannot, and the slow ones it runs as those commands: pressing u runs solitary up --detach, so a build prints what a build prints and a cell missing a secret asks for it the way it always does. The dashboard steps out of the way and comes back rather than owning a second, worse version of each.

Keys

KeyAction
↑ ↓ / k jMove between cells
⏎Shell into the selected cell
uBring it up (solitary up --detach)
bRebuild it, after a confirmation
sStop it
dDestroy its machine, after a confirmation
eManage its secrets
nShow the whole allow list
tFollow its network traffic
rRefresh
qQuit

The list appears as soon as Lima says which machines are up. A running machine is then asked what its container is doing, each on its own, and shows loading until it answers — a machine that is busy or unreachable can take the better part of a minute to, and the rest of the list does not wait on it. A loading cell can be looked at, its allow list included, but not acted on: what u, s or ⏎ would do depends on the answer. After that, every refresh shows what the machine last said while it is asked again.

The provision row

A cell whose vm.provision changed since its machine was provisioned says so in its detail view. It is the one setting stopping and starting the cell does not apply: the old script has already run, and only d then u — destroying the machine and its disk — gets one built by the current script alone. Every other change to vm, ports or network is applied by a restart, so the dashboard leaves those to up.

The inbox and outbox rows

What is waiting to go in and what is waiting to come out, for the selected cell — the same two directories send and fetch work on, counted rather than listed:

 inbox   0 files · 0 B
 outbox  2 files · 4.1 MiB

They are read out of the machine, so they say machine not running for a cell that is not up, and asking the machine… for the moment between selecting a running cell and the answer arriving. An outbox with something in it is the cue to run solitary fetch; the dashboard does not move files itself, because where they land is the host's business and the cell's names are not to be trusted with it.

The VPN row

Every cell has one, so a cell with no tunnel is as plain to read as a cell with one. It is refreshed while the view is open:

ShownMeans
vpn noneNo tunnel configured.
vpn configuredThe cell is not running, so there is nothing to ask.
vpn asking the machine…Running, and the first answer has not come back yet.
vpn down — this cell reaches nothingThe tunnel is not up, and the kill switch is therefore dropping everything.
vpn up, no recent handshakeUp but silent. From inside the cell this is indistinguishable from a broken network, which is why it is said here.
vpn up · handshake 12s agoWorking, with the peer it is actually talking to and the bytes moved in each direction.

The peer address is read live rather than taken from the configuration: a provider that balances one hostname across servers moves it between handshakes.

The traffic view

t follows what the selected cell's network is doing, as it happens: every name it asks about, what it resolved to, and every connection the firewall refused.

 ╭─────────────────────╮╭─────────────────────────────────────────────────╮
 │ cells               ││ traffic · claude                                │
 │ › ● claude  running ││ 12:07:53 query    api.github.com ×2             │
 ╰─────────────────────╯│ 12:07:53 resolved api.github.com → 140.82.121.6 │
                        │ 12:07:53 refused  example.com                   │
                        │ 12:07:53 denied   1.1.1.1:443                   │
                        ╰─────────────────────────────────────────────────╯
 ↑↓ scroll · G live · / filter · b refused only · c clear · esc back
KeyAction
↑ ↓ / k jScroll
pgup pgdown / ctrl+b ctrl+f / spacePage
g / GOldest / back to live
/Filter as you type; ⏎ keeps it, esc clears it
bRefused connections only
cClear what is shown
escBack to the cell list

It reads the machine's log, where both halves of the allow list record what they did — so a cell cannot see, let alone edit, what is recorded about it. Repeats fold into a count, since one lookup answers with every address a name has.

This is the fastest way to find what an allow list is missing: a refused line names it.

Secrets

e manages the selected cell's secrets: which names are set, which are not, and a masked field to set or rotate one (↑↓ to move, ⏎ to set a value, esc to go back).

A name the cell declared required: false is marked optional, and its description is shown under it. The count in the detail pane covers every declared name, but only a missing required one colours it: a cell running without an optional secret is doing what it was asked to.

Values are never displayed. The dashboard reads only whether each declared name has one.

On this page