@_mehrad/cbox 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,203 @@
1
+ # cbox — Claude Sandbox CLI
2
+
3
+ Run Claude Code in a throwaway Docker container. One-shot automation or persistent interactive sessions that survive terminal disconnects.
4
+
5
+ ## Requirements
6
+
7
+ - [Docker](https://docs.docker.com/get-docker/) (running)
8
+ - [tmux](https://github.com/tmux/tmux) (required for `session`, `attach`)
9
+ - `ANTHROPIC_API_KEY` environment variable set
10
+
11
+ ## Install
12
+
13
+ ```sh
14
+ bun install -g @_mehrad/cbox # global install
15
+ bunx @_mehrad/cbox # no-install, always latest
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ```sh
21
+ # One-shot: run a prompt and stream output
22
+ cbox "refactor the auth module to use JWT"
23
+
24
+ # Interactive: persistent Claude Code session
25
+ cbox session --mount .
26
+
27
+ # Mount current directory and run a task from a file
28
+ cbox run -f task.md --mount .
29
+
30
+ # JSON output for scripting
31
+ cbox run -j "list all TODO comments" --mount ./src
32
+ ```
33
+
34
+ ## Commands
35
+
36
+ ### One-shot mode
37
+
38
+ ```sh
39
+ cbox "<prompt>" # implicit run
40
+ cbox run "<prompt>" # explicit (same behaviour)
41
+ cbox run -f task.md # prompt from file
42
+ cbox run -j "<prompt>" # JSON output
43
+ cbox run --mount . "<prompt>" # mount cwd read-write
44
+ cbox run --mount ./src:ro "<prompt>" # mount read-only
45
+ cbox run --env KEY=VALUE "<prompt>" # pass env var into container
46
+ cbox run --no-config "<prompt>" # skip mounting ~/.claude
47
+ cbox run --no-browser "<prompt>" # skip agent-browser (lighter image)
48
+ ```
49
+
50
+ Streams Claude Code's stdout/stderr directly. Exits with the container's exit code.
51
+
52
+ ### Interactive session mode
53
+
54
+ ```sh
55
+ cbox session # start interactive session
56
+ cbox session --mount . # mount cwd read-write
57
+ cbox session --mount ./src:ro # mount read-only
58
+ cbox session --name refactor-auth # named session
59
+ cbox session --env KEY=VALUE # pass env var
60
+ cbox session --no-config # skip mounting ~/.claude
61
+ cbox session --no-browser # lighter container
62
+ ```
63
+
64
+ Creates a tmux session on the host, starts Docker inside it. Attaching/detaching from tmux leaves the container running. Reconnect anytime with `cbox attach`.
65
+
66
+ ### Session management
67
+
68
+ ```sh
69
+ cbox list # list active sessions
70
+ cbox attach <id|name> # reattach to a session
71
+ cbox kill <id|name> # stop container + tmux + prune registry
72
+ cbox kill --all # kill everything
73
+ ```
74
+
75
+ `cbox list` cross-references live sessions against `docker ps` and automatically prunes dead entries.
76
+
77
+ ### Image management
78
+
79
+ ```sh
80
+ cbox build # force rebuild Docker image
81
+ ```
82
+
83
+ The image is built automatically on first run and cached. It rebuilds when the CLI version changes or `mcpPackages` in config changes.
84
+
85
+ ## Flags
86
+
87
+ | Flag | Commands | Description |
88
+ |---|---|---|
89
+ | `--mount <path>` or `--mount <path>:ro` | `run`, `session` | Mount a host path into `/workspace` (default: read-write) |
90
+ | `--env KEY=VALUE` | `run`, `session` | Pass an environment variable into the container (repeatable) |
91
+ | `--no-config` | `run`, `session` | Skip mounting `~/.claude` (fully isolated container) |
92
+ | `--no-browser` | `run`, `session` | Skip agent-browser (smaller, faster startup) |
93
+ | `-f, --file <path>` | `run` | Read prompt from a file instead of argument |
94
+ | `-j, --json` | `run` | Wrap output in a JSON envelope |
95
+
96
+ ## Mounts
97
+
98
+ `--mount .` resolves to the absolute path of your cwd at invocation time and mounts it as `-v /abs/path:/workspace:rw` inside the container. All mounts land at `/workspace`.
99
+
100
+ ```sh
101
+ cbox run --mount . # /workspace = cwd (read-write)
102
+ cbox run --mount ./src:ro # /workspace = ./src (read-only)
103
+ cbox run --mount /abs/path # /workspace = /abs/path (read-write)
104
+ ```
105
+
106
+ ## Tools Inside the Container
107
+
108
+ | Tool | Description |
109
+ |---|---|
110
+ | `claude` | Claude Code with `--dangerously-skip-permissions` |
111
+ | `agent-browser` | Headless browser automation (Chrome baked in) |
112
+ | Host skills | Mounted from `~/.claude` (unless `--no-config`) |
113
+ | MCP servers | Host config mounted read-only; `localhost` URLs rewritten to `host.docker.internal` |
114
+ | Local MCP packages | Installed at image build time via `mcpPackages` in config |
115
+
116
+ ## JSON Output (`-j`)
117
+
118
+ ```json
119
+ {
120
+ "output": "...",
121
+ "exitCode": 0,
122
+ "error": null
123
+ }
124
+ ```
125
+
126
+ On failure:
127
+
128
+ ```json
129
+ {
130
+ "output": "",
131
+ "exitCode": 1,
132
+ "error": "Claude Code exited with code 1"
133
+ }
134
+ ```
135
+
136
+ Always valid JSON — safe to pipe into `jq`.
137
+
138
+ ## Configuration
139
+
140
+ `~/.config/cbox/config.json`:
141
+
142
+ ```json
143
+ {
144
+ "defaultMountMode": "rw",
145
+ "terminalApp": "Terminal",
146
+ "mcpPackages": ["@my-org/my-mcp-server"]
147
+ }
148
+ ```
149
+
150
+ | Field | Default | Description |
151
+ |---|---|---|
152
+ | `defaultMountMode` | `"rw"` | Default mount mode (`"rw"` or `"ro"`) |
153
+ | `terminalApp` | `"Terminal"` | Terminal for Raycast attach (`"Terminal"` or `"iTerm"`) |
154
+ | `mcpPackages` | `[]` | npm packages for local-process MCP servers (installed in image) |
155
+
156
+ Adding or removing a package in `mcpPackages` triggers an automatic image rebuild on the next run.
157
+
158
+ ## Session Registry
159
+
160
+ Active sessions are tracked at `~/.config/cbox/sessions.json`. Each session entry records its ID, name, tmux session name, container name, mount path, and creation time. Writes are atomic (temp file + rename).
161
+
162
+ ## Raycast Integration
163
+
164
+ Four Script Commands live in `raycast/`:
165
+
166
+ | Script | Behaviour |
167
+ |---|---|
168
+ | `cbox-list-sessions.sh` | Searchable list of active sessions |
169
+ | `cbox-attach-session.sh` | Opens Terminal and runs `cbox attach <id>` |
170
+ | `cbox-kill-session.sh` | Runs `cbox kill <id>` with confirmation |
171
+ | `cbox-run-prompt.sh` | Runs `cbox run -j "<input>"`, shows output inline |
172
+
173
+ Copy the scripts into your Raycast Script Commands directory. The terminal app used by `cbox-attach-session.sh` is configurable via `terminalApp` in config.
174
+
175
+ ## MCP Servers
176
+
177
+ **Network MCP servers** (configured with `localhost`/`127.0.0.1` URLs) are reachable from inside the container — cbox patches the config transparently at runtime, replacing those URLs with `host.docker.internal`. The host config is never modified; the patched copy lives in a temp file for the duration of the run.
178
+
179
+ Network MCP servers must already be running on the host before invoking `cbox`.
180
+
181
+ **Local process MCP servers** run inside the container as Claude Code child processes. Install them by adding their npm package names to `mcpPackages` in config and running `cbox build`.
182
+
183
+ ## Security
184
+
185
+ - `ANTHROPIC_API_KEY` is passed as an environment variable reference — the value is never embedded in command strings, shell history, or tmux state.
186
+ - `~/.claude` is mounted **read-only**. Claude Code inside the container cannot modify your host config.
187
+ - MCP config patching writes to a system temp directory. Your `~/.claude/settings.json` is never touched.
188
+
189
+ ## Docker Image
190
+
191
+ Based on `node:20-bookworm-slim` with Chrome dependencies for headless browser support. Images are tagged `cbox:<version>` or `cbox:<version>-<mcpHash>`.
192
+
193
+ Old images are left in place after upgrades. Clean them up with `docker image prune`.
194
+
195
+ ## Building from Source
196
+
197
+ ```sh
198
+ git clone https://github.com/mehrad/cbox
199
+ cd cbox
200
+ bun install
201
+ bun run build # produces dist/cbox
202
+ bun test # run test suite
203
+ ```
package/dist/cbox ADDED
Binary file
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@_mehrad/cbox",
3
+ "version": "0.1.0",
4
+ "description": "Claude Sandbox CLI — run Claude Code in Docker",
5
+ "bin": {
6
+ "cbox": "./dist/cbox"
7
+ },
8
+ "scripts": {
9
+ "start": "bun run src/index.ts",
10
+ "build": "bun build --compile --minify --sourcemap src/index.ts --outfile dist/cbox",
11
+ "test": "bun test"
12
+ },
13
+ "dependencies": {
14
+ "commander": "^14.0.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/bun": "latest",
18
+ "typescript": "^5"
19
+ }
20
+ }
@@ -0,0 +1,41 @@
1
+ #!/bin/bash
2
+
3
+ # @raycast.schemaVersion 1
4
+ # @raycast.title CSB Attach Session
5
+ # @raycast.mode silent
6
+ # @raycast.packageName cbox
7
+ # @raycast.description Attach to a Claude Sandbox session in a new terminal window
8
+ # @raycast.argument1 { "type": "text", "placeholder": "Session ID or name" }
9
+
10
+ SESSION="$1"
11
+
12
+ # Validate session id/name to prevent osascript injection
13
+ if ! echo "$SESSION" | grep -qE '^[a-zA-Z0-9_-]+$'; then
14
+ echo "Error: invalid session ID or name: $SESSION"
15
+ exit 1
16
+ fi
17
+
18
+ TERM_APP=$(python3 -c "
19
+ import json, os
20
+ cfg = os.path.expanduser('~/.config/cbox/config.json')
21
+ try:
22
+ with open(cfg) as f:
23
+ data = json.load(f)
24
+ print(data.get('terminalApp', 'Terminal'))
25
+ except:
26
+ print('Terminal')
27
+ " 2>/dev/null || echo "Terminal")
28
+
29
+ if [ "$TERM_APP" = "iTerm" ]; then
30
+ osascript -e "tell application \"iTerm\"
31
+ create window with default profile
32
+ tell current session of current window
33
+ write text \"cbox attach $SESSION\"
34
+ end tell
35
+ end tell"
36
+ else
37
+ osascript -e "tell application \"Terminal\"
38
+ do script \"cbox attach $SESSION\"
39
+ activate
40
+ end tell"
41
+ fi
@@ -0,0 +1,11 @@
1
+ #!/bin/bash
2
+
3
+ # @raycast.schemaVersion 1
4
+ # @raycast.title CSB Kill Session
5
+ # @raycast.mode silent
6
+ # @raycast.packageName cbox
7
+ # @raycast.description Kill a Claude Sandbox session
8
+ # @raycast.argument1 { "type": "text", "placeholder": "Session ID or name" }
9
+
10
+ SESSION="$1"
11
+ cbox kill "$SESSION" && echo "Session $SESSION killed."
@@ -0,0 +1,9 @@
1
+ #!/bin/bash
2
+
3
+ # @raycast.schemaVersion 1
4
+ # @raycast.title CSB List Sessions
5
+ # @raycast.mode fullOutput
6
+ # @raycast.packageName cbox
7
+ # @raycast.description List active Claude Sandbox sessions
8
+
9
+ cbox list
@@ -0,0 +1,22 @@
1
+ #!/bin/bash
2
+
3
+ # @raycast.schemaVersion 1
4
+ # @raycast.title CSB Run Prompt
5
+ # @raycast.mode fullOutput
6
+ # @raycast.packageName cbox
7
+ # @raycast.description Run a one-shot Claude Code prompt and show output
8
+ # @raycast.argument1 { "type": "text", "placeholder": "Your prompt" }
9
+
10
+ PROMPT="$1"
11
+ RESULT=$(cbox run -j "$PROMPT" 2>&1)
12
+ echo "$RESULT" | python3 -c "
13
+ import sys, json
14
+ try:
15
+ data = json.load(sys.stdin)
16
+ if data.get('error'):
17
+ print('Error:', data['error'])
18
+ else:
19
+ print(data.get('output', ''))
20
+ except:
21
+ print(sys.stdin.read())
22
+ "