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:
Thales Maciel 2026-04-26 15:02:08 -03:00
parent 41ced66a54
commit 35bfac3f13
No known key found for this signature in database
GPG key ID: 33112E6833C34679
6 changed files with 251 additions and 30 deletions

View file

@ -25,18 +25,43 @@ import (
func (d *deps) newVMCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "vm",
Short: "Manage virtual machines",
RunE: helpNoArgs,
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(
d.newVMCreateCommand(),
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(),
@ -169,8 +194,16 @@ Three modes:
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",
Use: "kill <id-or-name>...",
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,8 +353,21 @@ func (d *deps) newVMCreateCommand() *cobra.Command {
)
cmd := &cobra.Command{
Use: "create",
Short: "Create a VM",
Args: noArgsUsage("usage: banger vm create"),
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)
if err != nil {
@ -427,8 +473,15 @@ 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",
Use: "show <id-or-name>",
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 {
@ -477,9 +530,17 @@ func (d *deps) newVMActionCommand(use, short, method string, aliases ...string)
func (d *deps) newVMDeleteCommand() *cobra.Command {
return &cobra.Command{
Use: "delete <id-or-name>...",
Aliases: []string{"rm"},
Short: "Delete a VM",
Use: "delete <id-or-name>...",
Aliases: []string{"rm"},
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 {
@ -559,8 +620,22 @@ 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",
Use: "ssh <id-or-name> [ssh args...]",
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,8 +662,29 @@ 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",
RunE: helpNoArgs,
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(
d.newVMWorkspacePrepareCommand(),
@ -723,8 +819,18 @@ func (d *deps) newVMWorkspaceExportCommand() *cobra.Command {
func (d *deps) newVMLogsCommand() *cobra.Command {
var follow bool
cmd := &cobra.Command{
Use: "logs <id-or-name>",
Short: "Show VM logs",
Use: "logs <id-or-name>",
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 {