feat: add claudebox list, with snapshot labels #9

Merged
dan-claude-bot merged 1 commit from feat/list-command into main 2026-07-13 20:43:00 +00:00
dan-claude-bot commented 2026-07-13 20:27:10 +00:00 (Migrated from github.com)

Closes #7.

status was the only way to see your boxes, and it was misnamed, silently
ignored the <box> argument its own help advertised, and could not show you a
snapshot label — the one thing the README's headline snapshot→clone flow
requires you to know. Meanwhile claudebox list, the command everyone actually
types, printed the help and exited 0.

What's here

$ claudebox list
NAME     STATE    TYPE  SNAPSHOTS
work     RUNNING  VM    2
feature  STOPPED  VM    0
scratch  RUNNING  CT    0

$ claudebox list work
NAME       work
STATE      RUNNING
TYPE       VM
IPV4       10.216.10.42

SNAPSHOTS
  authed        2026/07/11 09:40 UTC
  post-clone    2026/07/12 14:02 UTC

Clone one:  claudebox new --name <new> --from work/authed
  • list <box> closes the snapshot gap: labels, plus the exact --from line to
    copy — the two halves of the README workflow finally meet.
  • Empty host says so (no boxes yet — create one with: claudebox new --name work);
    an unknown box is an error on stderr, not empty output.
  • --json passes Incus's JSON straight through; --remote is honored.
  • status keeps working as a deprecated alias (stderr note), so nobody's muscle
    memory breaks. README updated.

Notes on the implementation

  • Every read is filtered by the existing user.claudebox=1 tag, so an Incus
    instance claudebox didn't mint is never listed and never reached into — an
    untagged instance is "no such box", not someone else's VM we start reporting on.
  • The table columns (nstS) are all in Incus's own default column set, and none
    of those fields can contain a comma or newline, so a plain -F, split is safe.
    IPv4 is deliberately fetched separately: a box running docker has several
    addresses, which Incus emits as a quoted multi-line CSV field — inlining it
    garbles the table. The detail view takes the first address and strips Incus's
    (iface) suffix.
  • No new dependencies (awk only — notably not column, which isn't installed
    on a stock Debian 13 cloud image).

Testing

⚠️ Not exercised against a real Incus daemon — I develop inside a claudebox,
which has no incus and no nested virt. I drove every path against a stubbed
incus reproducing its CSV output (including the multi-address docker case, a
stopped box with no IP, a snapshot-less box, an empty host, and --remote), and
asserted the exact argv reaching incus:

incus list r: user.claudebox=1 --format csv --columns nstS
incus list r:work --format csv --columns 4
incus snapshot list r:work --format csv

shellcheck bin/claudebox adds no new findings (the two standing ones, SC2034
and SC2015, are #8's). Worth a real smoke on a host with Incus before merge
the thing I can't prove is that Incus's actual CSV matches the fixture.

🤖 Generated with Claude Code

Closes #7. `status` was the only way to see your boxes, and it was misnamed, silently ignored the `<box>` argument its own help advertised, and could not show you a snapshot label — the one thing the README's headline snapshot→clone flow requires you to know. Meanwhile `claudebox list`, the command everyone actually types, printed the help and exited 0. ## What's here ```console $ claudebox list NAME STATE TYPE SNAPSHOTS work RUNNING VM 2 feature STOPPED VM 0 scratch RUNNING CT 0 $ claudebox list work NAME work STATE RUNNING TYPE VM IPV4 10.216.10.42 SNAPSHOTS authed 2026/07/11 09:40 UTC post-clone 2026/07/12 14:02 UTC Clone one: claudebox new --name <new> --from work/authed ``` - `list <box>` closes the snapshot gap: labels, plus the exact `--from` line to copy — the two halves of the README workflow finally meet. - Empty host says so (`no boxes yet — create one with: claudebox new --name work`); an unknown box is an error on stderr, not empty output. - `--json` passes Incus's JSON straight through; `--remote` is honored. - `status` keeps working as a deprecated alias (stderr note), so nobody's muscle memory breaks. README updated. ## Notes on the implementation - Every read is filtered by the existing `user.claudebox=1` tag, so an Incus instance claudebox didn't mint is never listed and never reached into — an untagged instance is "no such box", not someone else's VM we start reporting on. - The table columns (`nstS`) are all in Incus's own default column set, and none of those fields can contain a comma or newline, so a plain `-F,` split is safe. **IPv4 is deliberately fetched separately**: a box running docker has several addresses, which Incus emits as a quoted multi-line CSV field — inlining it garbles the table. The detail view takes the first address and strips Incus's `(iface)` suffix. - No new dependencies (`awk` only — notably not `column`, which isn't installed on a stock Debian 13 cloud image). ## Testing ⚠️ **Not exercised against a real Incus daemon** — I develop inside a claudebox, which has no `incus` and no nested virt. I drove every path against a stubbed `incus` reproducing its CSV output (including the multi-address docker case, a stopped box with no IP, a snapshot-less box, an empty host, and `--remote`), and asserted the exact argv reaching `incus`: ``` incus list r: user.claudebox=1 --format csv --columns nstS incus list r:work --format csv --columns 4 incus snapshot list r:work --format csv ``` `shellcheck bin/claudebox` adds no new findings (the two standing ones, SC2034 and SC2015, are #8's). **Worth a real smoke on a host with Incus before merge** — the thing I can't prove is that Incus's actual CSV matches the fixture. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
dan-claude-bot commented 2026-07-13 20:41:33 +00:00 (Migrated from github.com)

Force-pushed: list no longer takes a box. Per the discussion on #11, overloading list <box> as the detail view was the wrong shape — and it is what sent people reaching for incus info in the first place.

  • claudebox list — all your boxes. No positional.
  • claudebox info <box> — one box: state, type, IP, snapshot labels, --from line.
  • claudebox list <box> — an error that points at info, rather than a silent surprise.

status still forwards to list. README and the (still hand-written) help follow; #10 rebased on top.

Also worth noting for anyone hitting this: claudebox list <box> printing the help with exit 0 is what main does with any unknown command — it is the headline symptom in #7, not a regression here. Nothing installable has list until this merges.

Force-pushed: **`list` no longer takes a box.** Per the discussion on #11, overloading `list <box>` as the detail view was the wrong shape — and it is what sent people reaching for `incus info` in the first place. - `claudebox list` — all your boxes. No positional. - `claudebox info <box>` — one box: state, type, IP, snapshot labels, `--from` line. - `claudebox list <box>` — an error that points at `info`, rather than a silent surprise. `status` still forwards to `list`. README and the (still hand-written) help follow; #10 rebased on top. Also worth noting for anyone hitting this: `claudebox list <box>` printing the **help with exit 0** is what `main` does with *any* unknown command — it is the headline symptom in #7, not a regression here. Nothing installable has `list` until this merges.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: heavy-duty/box#9
No description provided.