@_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 +203 -0
- package/dist/cbox +0 -0
- package/package.json +20 -0
- package/raycast/cbox-attach-session.sh +41 -0
- package/raycast/cbox-kill-session.sh +11 -0
- package/raycast/cbox-list-sessions.sh +9 -0
- package/raycast/cbox-run-prompt.sh +22 -0
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,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
|
+
"
|