# CLI Reference

The `sandbox` CLI is your primary authoring tool. Run any command from inside a sandbox directory — the CLI searches upward from your current directory to find `sandbox.yaml`.

## Version requirements

This reference covers Sandbox CLI v1.27.0. Check the installed version before using the newer workflows:

```
sandbox version
```

| Available since | Author-facing changes |
| --- | --- |
| v1.25 | Borrowed `sandbox attach` terminals, including `--ux`; corrected Bashp quote tracking inside nested command and parameter substitutions. |
| v1.26 | Read-only, AST-based Bashp MCP tools: `bashp_find`, `bashp_explain`, and `bashp_resolve`, with paginated results and retrievable expansions. |
| v1.27 | Background live reload for every VM selected by `sandbox start`, including scripts, assets, and local library dependencies. |

Devbox and Nix installations use the repository's pinned CLI. See [updating the CLI](#sandbox-update) if that pin predates the feature you need.

## Command overview

| Command | What it does |
| --- | --- |
| `sandbox init` | Scaffold a new sandbox directory |
| `sandbox build` | Build VM disk images |
| `sandbox shell` | Start VMs and open an interactive shell |
| `sandbox attach` | Open a separate terminal in an already-running VM |
| `sandbox setup` | Run `setup.sh`, then open a shell |
| `sandbox answer` | Run `setup.sh` + `answer.sh`, then open a shell |
| `sandbox score` | Run `setup.sh` + `answer.sh` + `score.sh`, then open a shell |
| `sandbox test` | Run the full lifecycle non-interactively and report results |
| `sandbox exec` | Run a single command in a running VM |
| `sandbox instructions` | Preview the candidate instructions with live reload |
| `sandbox validate` | Run static checks without building |
| `sandbox start` | Start VMs and independent background source watchers |
| `sandbox stop` | Stop selected watchers, then stop and delete their VMs |
| `sandbox mcp` | Expose Bashp discovery, explanation, and resolution to agents |
| `sandbox version` | Print the installed CLI version |

---

## sandbox init

Scaffold a new sandbox directory.

```
sandbox init [itemname]
```

Without `itemname`, the interactive wizard runs. With `itemname`, a single-VM scaffold is created immediately.

| Flag | Default | Description |
| --- | --- | --- |
| `--type` | auto-detected | `exam` or `lab`. Detected from the git remote URL if omitted. |

```
sandbox init                        # interactive wizard
sandbox init foobar                 # single-VM fast path
sandbox init --type exam foobar
```

---

## sandbox build

Build VM disk images from `build.sh` and the base image.

```
sandbox build [PATH]
```

| Flag | Default | Description |
| --- | --- | --- |
| `--vms` | all | Comma-separated list of VM names to build. |
| `--no-refresh-images` | false | Skip the registry check and use the local cache. |
| `-q, --quiet` | auto | Suppress Lima and image-helper output; local builds are quiet by default. |

```
sandbox build
sandbox build --vms cp,worker
sandbox build --no-refresh-images
```

Base images are cached locally after the first pull. `sandbox shell` runs `sandbox build` automatically if images are not yet built.

---

## sandbox shell

Start VMs and open an interactive shell. Builds images first if needed. Plain `sandbox shell` does not execute `setup.sh`; use `sandbox setup` when you need the configured starting state.

```
sandbox shell [PATH]
```

| Flag | Default | Description |
| --- | --- | --- |
| `--target` | first VM | VM to open the shell in. |
| `--vms` | all | VMs to start. |
| `--ux` | false | SSH as the configured user to replicate the candidate experience. |

```
sandbox shell
sandbox shell --target worker
sandbox shell --vms cp,worker --target cp
sandbox shell --ux
```

This shell owns the VM session. Exiting it deletes all VMs it started and disconnects any attached terminals. Its live-reload watcher follows only the target VM while the owning shell remains open.

For independently attached terminals and automatic reload on every VM, use [background start and stop](#sandbox-start-and-stop).

---

## sandbox setup / answer / score / test

These commands create an owned VM session and run lifecycle scripts in sequence. `setup`, `answer`, and `score` then open an interactive shell; `test` prints results and exits.

| Command | Runs | Then |
| --- | --- | --- |
| `sandbox setup` | `setup.sh` | Opens a shell |
| `sandbox answer` | `setup.sh` + `answer.sh` | Opens a shell |
| `sandbox score` | `setup.sh` + `answer.sh` + `score.sh` | Opens a shell |
| `sandbox test` | `setup.sh` → score → `answer.sh` → score | Prints results, no shell |

`sandbox test` is the acceptance test. It passes when scoring returns zero checks before the answer is applied, and full marks after.

All accept `--target` and `--vms`. Lifecycle scripts run on every selected VM; `--target` chooses the interactive shell. Interactive modes watch only the target VM until their owning shell exits. `sandbox test` has no live-reload watcher.

Stop any background VMs for this sandbox before starting an owned session. Exiting an owning shell or completing `test` deletes its VMs; exiting an attached terminal does not.

```
sandbox setup                       # verify the environment starts correctly
sandbox answer                      # verify your reference solution works
sandbox score                       # verify scoring sees the correct result
sandbox test                        # run the full lifecycle and report
```

---

## sandbox attach

Available since v1.25. Open a borrowed terminal in an existing VM without building images, starting or stopping VMs, running lifecycle scripts, or creating a watcher.

```
sandbox attach [PATH] --target VM
```

| Flag | Default | Description |
| --- | --- | --- |
| `--target` | required | Exact VM name from `sandbox.yaml`. |
| `--ux` | false | Connect as the configured VM user and exercise the platform SSH experience. |

```
sandbox attach --target cp
sandbox attach --target worker --ux
```

You can attach to VMs owned by either `sandbox shell` or `sandbox start`. Closing an attachment closes only that connection. An owning shell must remain open; background VMs remain available until `sandbox stop`.

`--ux` temporarily installs a unique SSH key for the configured user and normally removes it on exit. Cleanup is best-effort after abrupt process or transport failure.

---

## sandbox exec

Run a single command in a running VM. The VM must be started first. This command does not run lifecycle preparation or create a watcher; an existing background watcher continues independently.

```
sandbox exec [PATH] -- COMMAND [ARGS...]
```

| Flag | Default | Description |
| --- | --- | --- |
| `--target` | first VM | VM to run the command in. |

```
sandbox exec -- whoami
sandbox exec -- cat /etc/os-release
sandbox exec --target worker -- systemctl status kubelet
```

---

## sandbox instructions

Start a local preview server for the candidate instructions with live reload.

```
sandbox instructions [PATH]
```

| Flag | Default | Description |
| --- | --- | --- |
| `-o, --open` | false | Open the browser automatically. |
| `-p, --port` | random | Port to serve on. |

```
sandbox instructions
sandbox instructions -o
sandbox instructions -p 3000 -o
```

Uses the production instructions image, so the preview matches exactly what candidates see. Requires Docker.

---

## sandbox validate

Validate the sandbox without building VM images. Base-image manifest checks can contact the registry, so a registry error is not necessarily a local source error.

```
sandbox validate [PATH]
```

Checks:

- `sandbox.yaml` schema
- Each tool's `targetHost` references a known VM
- Required scripts exist in each VM's source directory, have executable permissions, and have shebangs
- `score.sh` ends with `scoring::report`
- Instructions `sourcePath` resolves to a Markdown file or a directory with `index.md`
- Each base image has a reachable registry manifest
- `dist/` contains no git-tracked files

```
sandbox validate
sandbox validate ./items/foobar
```

---

## sandbox start and stop

Since v1.27, `start` launches one independent source watcher for every selected VM and returns after the VMs and watchers are ready. Build the images first; `start` does not build missing disks.

```
sandbox build --vms cp,worker
sandbox start --vms cp,worker
```

Both commands accept an optional sandbox path. Omitting `--vms` selects all VMs declared in the blueprint.

Open separate terminals in the same sandbox directory:

**Control-plane terminal**

```
sandbox attach --target cp
```

**Worker terminal**

```
sandbox attach --target worker
```

Edit source files in your editor while either terminal is open or after closing both. The watchers are independent of attachments and of the completed `start` command.

### What reloads

For each started VM, the CLI recompiles Bashp scripts and copies assets into `dist/<vm>/`, mounted in the guest at `/sandbox`.

| Host source | What changes in the running VM |
| --- | --- |
| `<vm_dir>/scripts/*.sh` | Compiled Bash at `/sandbox/scripts/<name>.sh`. |
| `<vm_dir>/assets/` | Files under `/sandbox/assets/`, including additions, renames, and deletions. |
| `<vm_dir>/libs/` or `<vm_dir>/scripts/libs/` | The compiled scripts that inline those helpers, even when the lifecycle source itself is unchanged. |
| The nearest existing ancestor `libs/`, or the sandbox's `bashp-packages/libs/` | Compiled scripts in every started VM watching that shared library root. |
| Sandbox `metadata.yaml` | Regenerated script settings from the updated metadata. |

Here, `<vm_dir>` is the VM's source directory, normally its named directory such as `cp/`; a configured `sourceDir` override is respected. A script can call a local helper that itself calls a vendored or bundled helper: the dependency chain is inlined into the generated Bash. See the [library reload example](https://skills.staging.lf-cert.cloud/docs/sandbox/lifecycle-scripts/#bashp-helpers-and-local-libraries).

Reload updates files, not VM state

Neither `start` nor a reload executes `setup.sh`, `answer.sh`, or `score.sh`. Functions already loaded into a shell remain unchanged until you source or execute the regenerated script again. Asset changes update `/sandbox/assets/`, not copies previously installed elsewhere in the image.

Run a regenerated script explicitly when its effects are needed:

```
sandbox exec --target cp -- bash /sandbox/scripts/setup.sh
```

Changes to `sandbox.yaml`, the base image, or files installed by `build.sh` are not live configuration or image updates. To apply image-time changes, stop the affected VMs, rebuild them, and start them again.

### Errors and shutdown

`start` prints a watcher log path for each VM. Check that log when a source edit does not appear. Compilation failures retain the previous compiled files, and a later valid edit is retried automatically. Successful publication replaces individual files atomically, then removes stale entries; it is not an atomic snapshot of the entire directory tree.

Starting an already-running selected VM fails before changing its mounted files. If startup partially fails, successful VMs retain their watchers; watcher-startup failure triggers cleanup of only that newly started VM, with any cleanup failure reported.

Stop one VM and its watcher without interrupting the others:

```
sandbox stop --vms worker
```

Stop all remaining VMs and their watchers:

```
sandbox stop
```

`stop` waits for selected watchers to finish any in-flight reload before deleting their VMs. A watcher also exits when its VM stops or disappears. Use `sandbox exec` for commands or `sandbox attach` for terminals; neither takes ownership of these background watchers.

---

## sandbox cache

Manage locally cached base images.

```
sandbox cache ls       # list cached images and sizes
sandbox cache df       # show total disk usage
sandbox cache prune    # remove orphaned cache objects
sandbox cache rm <image-ref>
```

Cache location:

- macOS: `~/Library/Caches/sandbox-cli/images/`
- Linux: `~/.cache/sandbox-cli/images/`

---

## sandbox mcp

The v1.26 MCP interface gives coding agents read-only Bashp discovery, source-grounded explanations, and resolution of saved scripts or unsaved buffers. The server reads and parses files; it does not execute shell commands, run analyzers, or make network requests.

Start it with the content repository as its working directory. Most clients launch this stdio server themselves:

```
sandbox mcp
```

For clients using `.mcp.json`, the server entry is:

```
{
  "mcpServers": {
    "bashp": {
      "command": "sandbox",
      "args": ["mcp"]
    }
  }
}
```

### Find, explain, resolve

These are MCP tool calls, not `sandbox` subcommands:

| Tool | Purpose |
| --- | --- |
| `bashp_find` | Search by name or intent; omit `query` to browse namespaces, or supply only `namespace` to browse its helpers. |
| `bashp_explain` | Explain exact helper names in a batch, with selected source, dependencies, potential effects, and limitations. |
| `bashp_resolve` | Check a saved `file` or an inline `script`; `mode: "expand"` retrieves the generated Bash in chunks. |

For a saved lifecycle script, use the same file context throughout:

1. Discover a helper:

**bashp_find arguments**

```
{
  "query": "install apt packages",
  "file": "items/hello-image/host1/scripts/build.sh"
}
```

2. Inspect the selected implementation before using it:

**bashp_explain arguments**

```
{
  "names": ["install::apt_packages"],
  "level": "full",
  "file": "items/hello-image/host1/scripts/build.sh"
}
```

3. Save the script and resolve its dependencies:

**bashp_resolve arguments**

```
{
  "file": "items/hello-image/host1/scripts/build.sh"
}
```

For an unsaved buffer, provide `script` instead of `file`; `context_path` selects the item, VM, and library context:

**bashp_resolve arguments for an unsaved buffer**

```
{
  "script": "#!/usr/bin/env bashp\ninstall::apt_packages curl jq\n",
  "context_path": "items/hello-image/host1/scripts/build.sh",
  "mode": "check"
}
```

The server identifies the selected source and any shadowed helper implementations. Local overrides can therefore have a different contract from a bundled helper with the same name. An `include_only` artifact is not a callable function.

Optional MCP resources are `bashp://guide`, `bashp://namespaces`, and `bashp://namespace/{ns}`. Clients without resource browsing can use `bashp_find` instead.

### Scope and result handling

The startup workspace and explicit `--include-dir` roots define filesystem access:

```
sandbox mcp --include-dir ./libs
```

Client roots can narrow that access. A `file` or `context_path` argument selects context but cannot grant access to another checkout.

Resolution returns `scope: "bashp_resolution"` and a status:

- `pass`: applicable resolution checks completed without an established error or blocking uncertainty.
- `fail`: an established error exists, such as a missing static helper or include.
- `incomplete`: dynamic references, unreadable dependencies, or the dependency-closure limit prevent a complete answer.

Inspect diagnostics, `evaluated`, and `not_evaluated` along with the status. A resolution `pass` does not replace Skills lint, ShellCheck, shfmt, content validation, `sandbox validate`, or `sandbox build && sandbox test`; it cannot prove guest files or runtime behavior.

Results have structured JSON and an equivalent JSON text fallback. When `next` is present, call its named `tool` with exactly its `arguments`; continuation calls contain only `cursor`. Follow continuations until `next` is null. After `stale_cursor`, discard the partial result and restart the original request against the changed sources.

An expansion page is not executable by itself. Reassemble chunks in `byte_offset` order and verify the complete byte count and SHA-256 before treating the result as generated Bash. Expansion refuses environment-sourced `SANDBOX_SETTING_*` values rather than disclosing them.

### Upgrading older MCP clients

v1.26 removed the old tools and resource below without compatibility aliases. Update client configuration, tool allowlists, and authoring instructions that still name them:

| Removed surface | Replacement |
| --- | --- |
| `bashp_search`, `bashp_list_namespaces`, `bashp_list_functions` | `bashp_find` |
| `bashp_get_function`, `bashp_get_dependencies` | `bashp_explain` |
| `bashp_validate_script`, `bashp_preprocess` | `bashp_resolve` |
| `bashp://catalog` | Namespace resources or `bashp_find` |

---

## sandbox update

Update the CLI to the latest release.

```
sandbox update
sandbox update --force    # reinstall even if already on latest
```

For Devbox/Nix-managed installations, including managed Codespaces, use the repository's toolchain upgrade process. `sandbox update` does not replace the managed CLI. Run `sandbox version` afterward to confirm the version actually selected by your environment.
