cli: rewrite help text for AI-driven discovery
Frontier models tend to discover a CLI by running --help, scanning the Long description, and inferring the dominant workflow from the examples. Today's banger help reads like a man page index — every verb has a one-line Short and nothing else. This rewrites the groups (banger, vm, vm workspace, image, kernel, system, ssh-config) so each landing page answers "what is this for, what's the 80% command, what comes next" in three to ten lines, with runnable examples. Also disambiguates the near-twin lifecycle commands so a model reading the subcommand index can tell stop/kill/delete apart at a glance: start Start a stopped VM stop Stop a running VM gracefully restart Stop then start a VM kill Force-kill a VM (use when 'vm stop' hangs) delete Stop a VM and remove its disks (irreversible) vm create / vm ssh / vm logs / vm show pick up Long descriptions and examples for the same reason. No behaviour changes; help text only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
41ced66a54
commit
35bfac3f13
6 changed files with 251 additions and 30 deletions
|
|
@ -22,7 +22,30 @@ func NewBangerCommand() *cobra.Command {
|
|||
func (d *deps) newRootCommand() *cobra.Command {
|
||||
root := &cobra.Command{
|
||||
Use: "banger",
|
||||
Short: "Manage development VMs and images",
|
||||
Short: "Run development sandboxes as Firecracker microVMs",
|
||||
Long: strings.TrimSpace(`
|
||||
banger runs disposable development sandboxes as Firecracker microVMs.
|
||||
Each sandbox boots in a few seconds, gets its own root filesystem and
|
||||
network, and exits on demand.
|
||||
|
||||
The most common workflow is one command:
|
||||
|
||||
banger vm run bare sandbox, drops into ssh
|
||||
banger vm run ./repo ships a repo into /root/repo, drops into ssh
|
||||
banger vm run ./repo -- make test ships a repo, runs the command, exits with its status
|
||||
|
||||
For a longer-lived VM, use 'banger vm create' to provision and
|
||||
'banger vm ssh <name>' to attach. 'banger ps' lists running VMs;
|
||||
'banger vm list --all' shows stopped ones too.
|
||||
|
||||
First-time setup, in order:
|
||||
sudo banger system install install the systemd services
|
||||
banger doctor confirm the host is ready
|
||||
banger image pull debian-bookworm fetch a default image
|
||||
|
||||
Run 'banger <command> --help' for any subcommand. Run 'banger doctor'
|
||||
to diagnose host readiness problems.
|
||||
`),
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
RunE: helpNoArgs,
|
||||
|
|
@ -46,6 +69,20 @@ func (d *deps) newDoctorCommand() *cobra.Command {
|
|||
return &cobra.Command{
|
||||
Use: "doctor",
|
||||
Short: "Check host and runtime readiness",
|
||||
Long: strings.TrimSpace(`
|
||||
Check that the host has everything banger needs to boot guests:
|
||||
required tools (mkfs.ext4, debugfs, dmsetup, ip, iptables, ...), KVM
|
||||
access, daemon reachability, and per-feature preflight (NAT, DNS
|
||||
routing, work-disk seeding).
|
||||
|
||||
Run 'banger doctor':
|
||||
- after 'banger system install' to confirm the install took
|
||||
- after upgrading the host kernel or banger itself
|
||||
- when 'banger vm run' fails with an unclear error
|
||||
|
||||
Exit code is non-zero if any check fails. Warnings are reported but
|
||||
do not fail the run.
|
||||
`),
|
||||
Args: noArgsUsage("usage: banger doctor"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
report, err := d.doctor(cmd.Context())
|
||||
|
|
|
|||
|
|
@ -15,7 +15,27 @@ import (
|
|||
func (d *deps) newImageCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "image",
|
||||
Short: "Manage images",
|
||||
Short: "Pull and manage banger images (rootfs + kernel + work-seed)",
|
||||
Long: strings.TrimSpace(`
|
||||
A banger image bundles a rootfs.ext4, a kernel, an optional initrd
|
||||
+ modules, and an optional work-seed (the snapshot used to populate
|
||||
each new VM's /root). Most users only need 'banger image pull
|
||||
<catalog-name>' for the cataloged paths (see internal/imagecat),
|
||||
or 'banger image pull <oci-ref>' for an OCI image.
|
||||
|
||||
Subcommands:
|
||||
pull fetch a bundle by catalog name OR pull an OCI image
|
||||
register point banger at an existing local rootfs (advanced)
|
||||
promote copy a registered image's files into banger's managed dir
|
||||
list show what's installed
|
||||
show print one image's full record as JSON
|
||||
delete remove an image (no VMs may reference it)
|
||||
`),
|
||||
Example: strings.TrimSpace(`
|
||||
banger image pull debian-bookworm
|
||||
banger image pull docker.io/library/alpine:3.20 --kernel-ref generic-6.12
|
||||
banger image list
|
||||
`),
|
||||
RunE: helpNoArgs,
|
||||
}
|
||||
cmd.AddCommand(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,30 @@ import (
|
|||
func (d *deps) newKernelCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "kernel",
|
||||
Short: "Manage the local kernel catalog",
|
||||
Short: "Pull and manage Firecracker-compatible kernels",
|
||||
Long: strings.TrimSpace(`
|
||||
Banger boots guests with a separate kernel artifact (vmlinux, plus
|
||||
optional initrd + modules). Kernels are tracked by name in a local
|
||||
catalog so multiple images can share one.
|
||||
|
||||
Most users never run these commands directly: 'banger image pull'
|
||||
auto-pulls the kernel referenced by the catalog entry. Use these
|
||||
commands when you want to inspect what's installed, switch a VM to
|
||||
a different kernel via 'image register --kernel-ref', or import a
|
||||
kernel built locally with scripts/make-*-kernel.sh.
|
||||
|
||||
Subcommands:
|
||||
pull download a cataloged kernel by name
|
||||
list show what's installed (or --available for the catalog)
|
||||
show inspect one entry as JSON
|
||||
rm remove a local kernel
|
||||
import register a kernel built from scripts/make-*-kernel.sh
|
||||
`),
|
||||
Example: strings.TrimSpace(`
|
||||
banger kernel list --available
|
||||
banger kernel pull generic-6.12
|
||||
banger kernel import void-kernel --from build/manual/void-kernel
|
||||
`),
|
||||
RunE: helpNoArgs,
|
||||
}
|
||||
cmd.AddCommand(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package cli
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"banger/internal/config"
|
||||
"banger/internal/daemon"
|
||||
|
|
@ -21,7 +22,7 @@ func newSSHConfigCommand() *cobra.Command {
|
|||
)
|
||||
cmd := &cobra.Command{
|
||||
Use: "ssh-config",
|
||||
Short: "Manage the optional `ssh <name>.vm` shortcut",
|
||||
Short: "Enable plain 'ssh <name>.vm' from any terminal",
|
||||
Long: `Banger keeps a self-contained SSH client config under its own config
|
||||
directory (never touching ~/.ssh/config on its own). Opt in to the
|
||||
convenience shortcut that lets you run 'ssh <name>.vm' from any
|
||||
|
|
@ -30,7 +31,15 @@ terminal, bypassing 'banger vm ssh':
|
|||
banger ssh-config # print status + copy-paste snippet
|
||||
banger ssh-config --install # add an Include line to ~/.ssh/config
|
||||
banger ssh-config --uninstall # remove banger's Include from ~/.ssh/config
|
||||
|
||||
After --install, 'ssh agent.vm' works the same as 'banger vm ssh
|
||||
agent', including for tools like rsync, scp, and editor remotes.
|
||||
`,
|
||||
Example: strings.TrimSpace(`
|
||||
banger ssh-config --install
|
||||
ssh agent.vm
|
||||
rsync -avz ./code agent.vm:/root/repo/
|
||||
`),
|
||||
Args: noArgsUsage("usage: banger ssh-config [--install|--uninstall]"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if install && uninstall {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,33 @@ func (d *deps) newSystemCommand() *cobra.Command {
|
|||
var purge bool
|
||||
cmd := &cobra.Command{
|
||||
Use: "system",
|
||||
Short: "Install and manage banger's system services",
|
||||
Short: "Install banger's owner-daemon and root-helper systemd units",
|
||||
Long: strings.TrimSpace(`
|
||||
Banger ships as two services: an owner-user daemon for
|
||||
orchestration and a narrow root helper for bridge/tap, NAT, and
|
||||
Firecracker launch. 'banger system' installs, restarts, inspects,
|
||||
and removes them.
|
||||
|
||||
First-run flow (must be run as root):
|
||||
|
||||
sudo banger system install --owner $USER install both services
|
||||
banger system status confirm they're up
|
||||
banger doctor check host readiness
|
||||
|
||||
After 'install', the owner user can run 'banger ...' day to day
|
||||
without sudo. Subsequent invocations:
|
||||
|
||||
sudo banger system restart bounce both services
|
||||
sudo banger system uninstall remove services + binaries
|
||||
sudo banger system uninstall --purge also delete /var/lib/banger
|
||||
|
||||
See docs/privileges.md for the full trust model.
|
||||
`),
|
||||
Example: strings.TrimSpace(`
|
||||
sudo banger system install --owner alice
|
||||
banger system status
|
||||
sudo banger system uninstall --purge
|
||||
`),
|
||||
RunE: helpNoArgs,
|
||||
}
|
||||
installCmd := &cobra.Command{
|
||||
|
|
|
|||
|
|
@ -25,7 +25,32 @@ import (
|
|||
func (d *deps) newVMCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "vm",
|
||||
Short: "Manage virtual machines",
|
||||
Short: "Manage Firecracker microVM sandboxes",
|
||||
Long: strings.TrimSpace(`
|
||||
Lifecycle commands for banger's microVMs.
|
||||
|
||||
For most cases you want 'banger vm run' — it creates, starts,
|
||||
provisions ssh, and drops you into the guest in one command. Use
|
||||
'vm create' / 'vm start' / 'vm ssh' separately when you want a
|
||||
longer-lived VM you'll come back to.
|
||||
|
||||
Quick reference:
|
||||
banger vm run ephemeral sandbox; --rm to delete on exit
|
||||
banger vm run ./repo -- make test ship a repo, run a command, exit
|
||||
banger vm create --name dev persistent VM; pair with 'vm ssh'
|
||||
banger vm ssh <name> open a shell in a running VM
|
||||
banger vm stop <name> | vm restart graceful lifecycle
|
||||
banger vm kill <name> force-kill if stop hangs
|
||||
banger vm delete <name> stop + remove disks
|
||||
banger ps / banger vm list running / all VMs (use --all)
|
||||
banger vm logs <name> guest console + daemon log
|
||||
banger vm workspace prepare/export ship a repo in, pull diffs back
|
||||
`),
|
||||
Example: strings.TrimSpace(`
|
||||
banger vm run -- uname -a
|
||||
banger vm run ./project -- npm test
|
||||
banger vm create --name agent && banger vm ssh agent
|
||||
`),
|
||||
RunE: helpNoArgs,
|
||||
}
|
||||
cmd.AddCommand(
|
||||
|
|
@ -33,10 +58,10 @@ func (d *deps) newVMCommand() *cobra.Command {
|
|||
d.newVMRunCommand(),
|
||||
d.newVMListCommand(),
|
||||
d.newVMShowCommand(),
|
||||
d.newVMActionCommand("start", "Start a VM", "vm.start"),
|
||||
d.newVMActionCommand("stop", "Stop a VM", "vm.stop"),
|
||||
d.newVMActionCommand("start", "Start a stopped VM", "vm.start"),
|
||||
d.newVMActionCommand("stop", "Stop a running VM gracefully", "vm.stop"),
|
||||
d.newVMKillCommand(),
|
||||
d.newVMActionCommand("restart", "Restart a VM", "vm.restart"),
|
||||
d.newVMActionCommand("restart", "Stop then start a VM", "vm.restart"),
|
||||
d.newVMDeleteCommand(),
|
||||
d.newVMPruneCommand(),
|
||||
d.newVMSetCommand(),
|
||||
|
|
@ -170,7 +195,15 @@ func (d *deps) newVMKillCommand() *cobra.Command {
|
|||
var signal string
|
||||
cmd := &cobra.Command{
|
||||
Use: "kill <id-or-name>...",
|
||||
Short: "Send a signal to a VM process",
|
||||
Short: "Force-kill a VM (use when 'vm stop' hangs)",
|
||||
Long: strings.TrimSpace(`
|
||||
Send a signal directly to the firecracker process. Default is
|
||||
SIGTERM; pass --signal SIGKILL when the VM is stuck and a graceful
|
||||
'vm stop' has already failed.
|
||||
|
||||
This skips the normal stop sequence (no flush, no clean shutdown).
|
||||
Prefer 'banger vm stop' for routine teardown.
|
||||
`),
|
||||
Args: minArgsUsage(1, "usage: banger vm kill [--signal SIGTERM|SIGKILL|...] <id-or-name>..."),
|
||||
ValidArgsFunction: d.completeVMNames,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
|
@ -320,7 +353,20 @@ func (d *deps) newVMCreateCommand() *cobra.Command {
|
|||
)
|
||||
cmd := &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a VM",
|
||||
Short: "Create a VM (without entering it)",
|
||||
Long: strings.TrimSpace(`
|
||||
Create a microVM in the 'running' state and return its summary.
|
||||
Unlike 'banger vm run', this does NOT open an ssh session — pair it
|
||||
with 'banger vm ssh <name>' when you want to attach.
|
||||
|
||||
Use 'vm create' for a longer-lived VM you'll come back to. Use
|
||||
'vm run' for one-shot sandboxes (especially with --rm).
|
||||
`),
|
||||
Example: strings.TrimSpace(`
|
||||
banger vm create --name agent
|
||||
banger vm create --name big --vcpu 8 --memory 16384
|
||||
banger vm create --no-start --name spare # provision but leave stopped
|
||||
`),
|
||||
Args: noArgsUsage("usage: banger vm create"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
params, err := vmCreateParamsFromFlags(cmd, name, imageName, vcpu, memory, systemOverlaySize, workDiskSize, natEnabled, noStart)
|
||||
|
|
@ -428,7 +474,14 @@ func selectVMListVMs(vms []model.VMRecord, showAll, latest bool) []model.VMRecor
|
|||
func (d *deps) newVMShowCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "show <id-or-name>",
|
||||
Short: "Show VM details",
|
||||
Short: "Print full VM record as JSON",
|
||||
Long: strings.TrimSpace(`
|
||||
Emit the complete VM record (spec, runtime state, image reference,
|
||||
created/updated timestamps) as a single JSON object. Suitable for
|
||||
piping into 'jq' or feeding into automation.
|
||||
|
||||
For human-readable summaries use 'banger ps' or 'banger vm stats'.
|
||||
`),
|
||||
Args: exactArgsUsage(1, "usage: banger vm show <id-or-name>"),
|
||||
ValidArgsFunction: d.completeVMNameOnlyAtPos0,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
|
@ -479,7 +532,15 @@ func (d *deps) newVMDeleteCommand() *cobra.Command {
|
|||
return &cobra.Command{
|
||||
Use: "delete <id-or-name>...",
|
||||
Aliases: []string{"rm"},
|
||||
Short: "Delete a VM",
|
||||
Short: "Stop a VM and remove its disks (irreversible)",
|
||||
Long: strings.TrimSpace(`
|
||||
Stop the VM if it's running, then remove its work disk, system
|
||||
overlay, snapshot, and metadata. Frees host disk space. The
|
||||
operation is irreversible — anything written inside the guest is
|
||||
lost.
|
||||
|
||||
Use 'banger vm prune' to bulk-delete every VM that isn't running.
|
||||
`),
|
||||
Args: minArgsUsage(1, "usage: banger vm delete <id-or-name>..."),
|
||||
ValidArgsFunction: d.completeVMNames,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
|
@ -560,7 +621,21 @@ func (d *deps) newVMSetCommand() *cobra.Command {
|
|||
func (d *deps) newVMSSHCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "ssh <id-or-name> [ssh args...]",
|
||||
Short: "SSH into a running VM",
|
||||
Short: "Open an interactive ssh session to a running VM",
|
||||
Long: strings.TrimSpace(`
|
||||
Connect to a running VM as root over the host bridge. Trailing
|
||||
arguments are passed through to the underlying 'ssh' command, so
|
||||
'-- -L 8080:localhost:8080' forwards a port and '-- echo hi' runs
|
||||
a single command and exits.
|
||||
|
||||
To run a one-shot command without holding a session, prefer
|
||||
'banger vm run --rm -- <command>' over 'vm ssh -- <command>'.
|
||||
`),
|
||||
Example: strings.TrimSpace(`
|
||||
banger vm ssh agent
|
||||
banger vm ssh agent -- uname -a
|
||||
banger vm ssh agent -- -L 8080:localhost:8080 -N
|
||||
`),
|
||||
Args: minArgsUsage(1, "usage: banger vm ssh <id-or-name> [ssh args...]"),
|
||||
ValidArgsFunction: d.completeVMNameOnlyAtPos0,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
|
@ -587,7 +662,28 @@ func (d *deps) newVMSSHCommand() *cobra.Command {
|
|||
func (d *deps) newVMWorkspaceCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "workspace",
|
||||
Short: "Manage repository workspaces inside a running VM",
|
||||
Short: "Ship a host repo into a guest, pull diffs back",
|
||||
Long: strings.TrimSpace(`
|
||||
Two-step pattern for round-tripping a working tree through a guest
|
||||
VM:
|
||||
|
||||
prepare Copy a local git repo into the guest at /root/repo
|
||||
(or any path you choose). Default ships tracked files
|
||||
only; pass --include-untracked to ship the rest.
|
||||
export Capture every change inside the guest workspace as a
|
||||
host-readable patch. Non-mutating: the guest's working
|
||||
tree is left untouched.
|
||||
|
||||
This is the supported flow for AI agents and CI runners that want
|
||||
to evaluate code changes inside a sandbox without touching the
|
||||
host checkout. 'banger vm run ./repo -- <cmd>' is shorthand for
|
||||
prepare + run + delete.
|
||||
`),
|
||||
Example: strings.TrimSpace(`
|
||||
banger vm workspace prepare agent ../repo
|
||||
banger vm ssh agent -- bash -lc 'cd /root/repo && make test'
|
||||
banger vm workspace export agent --base-commit <commit> > out.patch
|
||||
`),
|
||||
RunE: helpNoArgs,
|
||||
}
|
||||
cmd.AddCommand(
|
||||
|
|
@ -724,7 +820,17 @@ func (d *deps) newVMLogsCommand() *cobra.Command {
|
|||
var follow bool
|
||||
cmd := &cobra.Command{
|
||||
Use: "logs <id-or-name>",
|
||||
Short: "Show VM logs",
|
||||
Short: "Show guest console + per-VM daemon log",
|
||||
Long: strings.TrimSpace(`
|
||||
Print the firecracker console log (kernel + early init output) and
|
||||
the per-VM daemon log (lifecycle stages, errors). Pass -f to follow
|
||||
new lines as they arrive — useful while a VM is starting up or
|
||||
hanging on boot.
|
||||
`),
|
||||
Example: strings.TrimSpace(`
|
||||
banger vm logs agent
|
||||
banger vm logs agent -f
|
||||
`),
|
||||
Args: exactArgsUsage(1, "usage: banger vm logs [-f] <id-or-name>"),
|
||||
ValidArgsFunction: d.completeVMNameOnlyAtPos0,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue