diff --git a/README.md b/README.md index 4909cc7..8322b43 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # box **Headless, trust-less, throwaway dev VMs.** One command mints a fresh, -network-isolated Incus box from a **template**; the flagship template is -`claude` — Debian 13 with Claude Code installed. The box is the product — -you log in and work; destroying it loses nothing you didn't push. +network-isolated Incus box from a **template**; the coding-agent templates +ship a CLI agent on Debian 13 — `claude` (Claude Code), `codex` (OpenAI +Codex), `grok` (xAI Grok). The box is the product — you log in and work; +destroying it loses nothing you didn't push. **Strictly creds-free.** A box ships with everything installed and **no** -credentials — no Claude token, no git PAT, nothing. You authenticate +credentials — no agent token, no git PAT, nothing. You authenticate interactively *inside* the box. The tool never stores or injects a secret. That means there's nothing shared or committed, so it's safe for multiple operators out of the box. @@ -18,8 +19,8 @@ nobody home — not a box with the safety off. **The tool knows nothing about your projects.** You just `git clone` inside a box. A repo can ship an optional [`.box/`](docs/box-recipe.md) -runbook that Claude Code reads and acts on — there is no `install` step and no -host-run setup. See [docs/box-design.md](docs/box-design.md) for the +runbook that the box's coding agent reads and acts on — there is no `install` +step and no host-run setup. See [docs/box-design.md](docs/box-design.md) for the design rationale. > **0.5.0**: two new templates (`codex`, `grok`), `box expose` — a @@ -66,23 +67,26 @@ legacy box remains. ## Quick start ```sh -box new --name work --template claude # a creds-free Claude box (~10 min cold) +box new --name work --template claude # a creds-free coding-agent box (~10 min cold) box shell work # enter as the template's user ``` -Inside the box, authenticate as needed: +Pick whichever coding-agent template you like — `claude`, `codex`, `grok` — or +`blank` for none. Inside the box, authenticate as needed. The `claude` template +looks like this; the others follow the same shape with their own login step: ```sh claude # then run /login — copy the URL (press c), open it # in YOUR browser, paste the code back. No host CLI needed. gh auth login # or drop a PAT in — your git credentials, your call git clone https://github.com/you/project && cd project -claude # if the repo has .box/, Claude reads it and sets up +claude # if the repo has .box/, the agent reads it and sets up ``` ## Templates -The claude box is one template among several. What ships today: +No coding agent is special — each is one template among several, and adding +another is just another directory. What ships today: | Template | What's in it | | --- | --- | @@ -122,7 +126,7 @@ box snapshot work authed # checkpoint after you've logged in box new --name feature --from work/authed # clone the authed state into a new box ``` -`--from` copies the whole box (Claude login, git creds, clones and all) while +`--from` copies the whole box (agent login, git creds, clones and all) while preserving isolation. You can also `box new --name x --from work` to clone a box's live state, or roll a box back with `box restore work authed`. @@ -233,8 +237,9 @@ enforces it, layer by layer: that door only ever opens onto the host's own loopback (`127.0.0.1`), never the network. -The VM is the trust boundary: whatever runs inside — Claude, or anything a -template ships — can run arbitrary code and touch nothing you care about. +The VM is the trust boundary: whatever runs inside — the coding agent, or +anything a template ships — can run arbitrary code and touch nothing you care +about. ### Measured, not claimed @@ -265,9 +270,10 @@ inherit. ## Recipes: the `.box/` convention A repo that wants to be easy to stand up in a box ships an optional `.box/` -folder — a runbook Claude reads and follows (install deps, start services, -template env, seed data, smoke-test). It is agent-facing documentation, not a -host-executed script. See [docs/box-recipe.md](docs/box-recipe.md). +folder — a runbook the box's coding agent reads and follows (install deps, +start services, template env, seed data, smoke-test). It is agent-facing +documentation, not a host-executed script. See +[docs/box-recipe.md](docs/box-recipe.md). ## Uninstall @@ -280,6 +286,6 @@ rm -rf ~/.local/share/box ~/.local/bin/box # the CLI itself ## Non-goals - **No unattended/CI bring-up.** The flow is interactive (log in, clone, ask - Claude). Reproducible-by-construction provisioning is out of scope. + the agent). Reproducible-by-construction provisioning is out of scope. - **No credential storage or injection by the tool.** Boxes are creds-free; snapshots are the reuse mechanism, not a secrets store. diff --git a/docs/box-design.md b/docs/box-design.md index efc41ad..834165a 100644 --- a/docs/box-design.md +++ b/docs/box-design.md @@ -1,28 +1,30 @@ # box design `box` is a CLI that mints and manages **trust-less, network-isolated VMs -with Claude Code installed**. It is infrastructure, not a project provisioner. +with a coding agent installed** (`claude`, `codex`, `grok`, or `blank` for +none). It is infrastructure, not a project provisioner. See issue #3 for the full reframe and rationale. This doc captures the durable design decisions. ## Principle: separate the tool from the agent -- **The tool** mints isolated boxes with Claude installed but **unauthenticated**. +- **The tool** mints isolated boxes with the agent installed but **unauthenticated**. It knows nothing about projects, secrets, recipes, or memory. -- **The agent** (Claude Code, inside the box) reads an optional `.box/` - runbook in a cloned repo and acts on it. The recipe's consumer is the - reasoning agent, not host machinery. +- **The agent** (Claude Code, Codex, Grok — whichever template, inside the box) + reads an optional `.box/` runbook in a cloned repo and acts on it. The recipe's + consumer is the reasoning agent, not host machinery. ## Boxes are strictly creds-free `box new --name ` launches a blank box: everything installed, **no** -git credentials and **no** Claude credentials. The operator authenticates +git credentials and **no** agent credentials. The operator authenticates interactively *inside* the box: -- **Claude** — `claude` → `/login` (paste-a-code OAuth: copy the URL, open it in - your own browser, paste the code back). Works because the box is outbound-only; - the tool never handles a token. +- **The coding agent** — e.g. `claude` → `/login` (paste-a-code OAuth: copy the + URL, open it in your own browser, paste the code back); `codex` and `grok` + have their own login step. Works because the box is outbound-only; the tool + never handles a token. - **Git** — the operator adds their own PAT / `gh auth login` inside the box. The tool stores and injects **no** credentials, ever. This dissolves the @@ -36,15 +38,16 @@ snapshots, not a secrets store: - `box snapshot [label]` — checkpoint after login + clone. - `box new --name --from [/]` — clone an existing box or snapshot (authed state and all). Isolation is preserved: the clone keeps - the `claude-dev` profile + `claudenet` + ACL. + the `box-net` profile + `boxnet` + ACL. - `box restore ` — roll a box back to a checkpoint. Log in once → snapshot → spin up authed boxes from it. ## The box announces itself to the agent -cloud-init installs a global `~/.claude/CLAUDE.md` in every box telling Claude it -is running in a box (trust-less, ephemeral, creds-free) and to treat a +cloud-init installs a global agent-context file in every coding-agent box +(`~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md`, `~/.grok/AGENTS.md`) telling the +agent it is running in a box (trust-less, ephemeral, creds-free) and to treat a repo's `.box/` folder as its bootstrap runbook. No "tell it" step, no host execution. @@ -62,7 +65,7 @@ forever — and wrapping them one at a time grows a worse `incus`. The rule: > **box owns a command when it must enforce an invariant Incus cannot see:** > the `user.box=1` boundary (never touch an instance we didn't mint), the -> isolation stack (`claude-dev` profile + `claudenet` + ACL), or the creds-free +> isolation stack (`box-net` profile + `boxnet` + ACL), or the creds-free > snapshot→clone workflow. Everything else is Incus's job. The rule cuts both ways, and that's the point: @@ -92,7 +95,7 @@ warns and proceeds — from there the trust boundary is yours to keep. ## Isolation -Dedicated NAT bridge `claudenet` + Incus `claude-isolate` ACL dropping all +Dedicated NAT bridge `boxnet` + Incus `box-isolate` ACL dropping all RFC1918/CGNAT/link-local egress, plus host-firewall rules blocking instance → host. Entry is `incus exec` over the local socket — no inbound path. The VM is the trust boundary. @@ -101,12 +104,12 @@ the trust boundary. That last clause is the one that was assumed and turned out to be false, so it is spelled out here with the mechanism, and `drill/` tests it on every run. -- **Box → host, LAN, RFC1918, CGNAT, link-local:** the `claude-isolate` ACL. +- **Box → host, LAN, RFC1918, CGNAT, link-local:** the `box-isolate` ACL. - **Box → box: an nftables *bridge-family* rule** (`host/box-firewall.sh`). It cannot be an ACL rule. Two boxes on one bridge share an L2 segment, so their frames are *switched* between bridge ports and never traverse the netfilter path an L3 ACL lives on — the ACL looked airtight (it drops - `10.0.0.0/8`, which contains `claudenet`) while box→box was in fact wide open. + `10.0.0.0/8`, which contains `boxnet`) while box→box was in fact wide open. A live probe found box A's SYN arriving at box B. The bridge family's forward hook fires exactly on port-to-port frames, which on this bridge means box→box and nothing else: gateway traffic and routed egress are delivered locally, not diff --git a/docs/box-recipe.md b/docs/box-recipe.md index 6d4f345..6544c93 100644 --- a/docs/box-recipe.md +++ b/docs/box-recipe.md @@ -1,14 +1,15 @@ # The `.box/` convention -`box` mints trust-less, creds-free, isolated VMs with Claude Code already -installed (`box new/shell/snapshot/restore/exec/down/start/rm/status`). The -tool knows **nothing** about your project. There is no `install` step and no -host-run setup script. +`box` mints trust-less, creds-free, isolated VMs with a coding agent already +installed (`box new/shell/snapshot/restore/exec/down/start/rm/status`) — the +`claude`, `codex`, and `grok` templates each ship a CLI agent. The tool knows +**nothing** about your project. There is no `install` step and no host-run +setup script. A project makes itself easy to stand up inside a box by shipping an optional `.box/` folder. This folder is **agent-facing documentation** — read and -acted on by Claude Code (the reasoning agent) running inside the box. It is not -shell that the host executes. +acted on by the box's coding agent (the reasoning agent) running inside the +box, whichever template you minted. It is not shell that the host executes. > The folder was named `.claudebox/` before the 0.5.0 rename. Repos that still > ship `.claudebox/` keep working — the agent is told to read either — but new @@ -26,15 +27,17 @@ shell that the host executes. ## How it's consumed -Every box ships a global `~/.claude/CLAUDE.md` telling Claude it is inside a -box and to treat a repo's `.box/` folder as its bootstrap runbook. -So the whole flow is: +Every coding-agent box ships a global agent-context file — `~/.claude/CLAUDE.md` +for `claude`, `~/.codex/AGENTS.md` for `codex`, `~/.grok/AGENTS.md` for `grok` — +telling the agent it is inside a box and to treat a repo's `.box/` folder as its +bootstrap runbook. So the whole flow is (shown with `claude`; the other agents +follow the same shape): ``` box new # get a box box shell # get in git clone && cd -claude # Claude reads .box/ and brings the project up +claude # the agent reads .box/ and brings the project up ``` The operator can also just say: *"set this project up per .box"*. @@ -45,9 +48,9 @@ Author everything here for a reasoning agent. - **`.box/SETUP.md`** — the prose runbook. Prerequisites, how to install deps, how to start services, how to template the env, how to seed data, and - how to smoke-test. Written as instructions to Claude. -- **Helper scripts** (e.g. `.box/dev-up.sh`) that the runbook tells Claude - to run. Claude decides to run them; the host never does. + how to smoke-test. Written as instructions to the agent. +- **Helper scripts** (e.g. `.box/dev-up.sh`) that the runbook tells the agent + to run. The agent decides to run them; the host never does. - **`.box/env.template`** — example env the runbook explains how to fill. Staging values the operator pastes in. **Never commit real secrets.** - **`.box/compose.yml`** — optional services the runbook starts. @@ -70,7 +73,7 @@ This is a Node service backed by Postgres. 6. Smoke-test: `curl -sf localhost:3000/health` should return `{"ok":true}`. ``` -That's it — Claude reads it top to bottom and adapts if reality differs. +That's it — the agent reads it top to bottom and adapts if reality differs. ## Guidance @@ -79,4 +82,4 @@ That's it — Claude reads it top to bottom and adapts if reality differs. - **Never put real credentials in `.box/`.** Templates and staging placeholders only. The operator pastes real values at runtime. - **No `.box/` is fine.** The operator can stand the project up by hand, - or let Claude infer the steps from the repo's `README` / `CLAUDE.md`. + or let the agent infer the steps from the repo's `README` / `CLAUDE.md`. diff --git a/drill/README.md b/drill/README.md index cdb41e9..d7a06cf 100644 --- a/drill/README.md +++ b/drill/README.md @@ -84,9 +84,9 @@ a **design veto** for #16, caught before the code is written. ## What it does not check -`claude /login` — it's interactive by design, and the box is creds-free by -design. The drill confirms Claude Code is installed and runnable; authenticating -is yours. +agent login (e.g. `claude /login`) — it's interactive by design, and the box is +creds-free by design. The drill confirms each coding-agent template's CLI is +installed and runnable; authenticating is yours. If the host has no `/dev/kvm`, box falls back to container mode. The drill still runs, but it says loudly that **the VM trust boundary was not validated**