#!/usr/bin/env bash # claudebox — trust-less, isolated Incus VMs with Claude Code, creds-free. # The command surface is the CMDS table below: it is the single source of truth # for what exists, what it looks like, what the help says, and what runs. The # help cannot drift from the code, because it is rendered from the same rows. set -euo pipefail root="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/.." && pwd)" remote=""; mode="auto"; name=""; from=""; force=0; json=0; want_help=0 inst="" # the resolved Incus instance, set by the 'box' precondition die() { echo "claudebox: $*" >&2; exit 1; } # 1 = it went wrong usage_error() { echo "claudebox: $*" >&2; echo "try 'claudebox help'." >&2; exit 2; } # 2 = you asked wrong version() { echo "claudebox $(cat "$root/VERSION" 2>/dev/null || echo unknown) ($root)"; } # --------------------------------------------------------------------------- # The command table. # # verb ^ synopsis args ^ preconditions ^ one-line summary ^ action ^ ok message # # Fields are ^-separated because a synopsis may contain '|' ([--vm|--container]). # # preconditions (comma-separated): # box first positional is a box: resolve it, and REFUSE if the instance # isn't tagged user.claudebox=1 — the boundary, enforced, not assumed # arg2 a second positional is required # stopped the box must not be running # confirm destructive: prompt unless --force # # action: # incus: run `incus [rest...]` # fn: call a shell function (it has real work to do) # # ok message: printed on success; {} = the box, {1} = the second positional. # # Adding a thin verb is one row. If a request can't be expressed as a row and # doesn't enforce a claudebox invariant, it is incus's job, not ours — that is # what `claudebox incus` is for. CMDS=( "new^--name [--from [/]] [--vm|--container]^^Mint a box: fresh from cloud-init, or --from an existing box/snapshot^fn:cmd_new^" "list^[--json]^^List your boxes^fn:cmd_list^" "info^ [--json]^box^One box: state, type, IP, and its snapshot labels^fn:cmd_info^" "shell^^box^Open a shell in a box, as the claude user^fn:cmd_shell^" "exec^ -- ^box^Run a command inside a box^fn:cmd_exec^" "snapshot^ [