sandboxbox 2.0.8 โ†’ 2.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/CLAUDE.md CHANGED
@@ -134,51 +134,60 @@ exec claude # Changes save directly to local repo
134
134
 
135
135
  ## Command Isolation Principles
136
136
 
137
- ### Critical Architecture Distinction
137
+ ### Unified Architecture - All Commands Use Isolation
138
138
  - **`run` command**: Creates isolated temporary environment - changes DO NOT affect host
139
- - **`claude` command**: Mounts local repository directly - changes DO affect host
139
+ - **`claude` command**: Creates isolated temporary environment - changes DO NOT affect host
140
140
  - **`shell` command**: Creates isolated temporary environment - changes DO NOT affect host
141
141
 
142
- ### Isolation Implementation (run/shell commands)
142
+ ### Isolation Workflow
143
+ 1. Copy project to temporary directory (including hidden files like .git)
144
+ 2. Mount temporary directory as /workspace in container
145
+ 3. Run commands in isolated environment
146
+ 4. Clean up temporary directory on exit
147
+ 5. Changes are persisted via git commands (commit/push) if needed
148
+
149
+ ### Shared Isolation Utility (utils/isolation.js)
143
150
  ```javascript
144
- // Creates temporary directory with copied project
145
- const tempDir = mkdtempSync(join(tmpdir(), 'sandboxbox-'));
146
- const tempProjectDir = join(tempDir, projectName);
147
-
148
- // Cross-platform file copying with hidden files (.git, etc.)
149
- if (process.platform === 'win32') {
150
- execSync(`powershell -Command "Copy-Item -Path '${projectDir}\\*' -Destination '${tempProjectDir}' -Recurse -Force"`, {
151
- stdio: 'pipe',
152
- shell: true
153
- });
154
- // Copy hidden files separately
155
- execSync(`powershell -Command "Get-ChildItem -Path '${projectDir}' -Force -Name | Where-Object { $_ -like '.*' } | ForEach-Object { Copy-Item -Path (Join-Path '${projectDir}' $_) -Destination '${tempProjectDir}' -Recurse -Force }"`, {
156
- stdio: 'pipe',
157
- shell: true
158
- });
159
- } else {
160
- execSync(`cp -r "${projectDir}"/.* "${tempProjectDir}/" 2>/dev/null || true`, {
161
- stdio: 'pipe',
162
- shell: true
163
- });
164
- execSync(`cp -r "${projectDir}"/* "${tempProjectDir}/"`, {
165
- stdio: 'pipe',
166
- shell: true
167
- });
168
- }
151
+ // All commands use the same isolation pattern
152
+ import { createIsolatedEnvironment, setupCleanupHandlers, buildContainerMounts } from './utils/isolation.js';
169
153
 
170
- // Automatic cleanup on exit
171
- const cleanup = () => {
172
- try {
173
- rmSync(tempDir, { recursive: true, force: true });
174
- } catch (cleanupError) {
175
- // Ignore cleanup errors
176
- }
177
- };
154
+ // Create isolated environment
155
+ const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
156
+
157
+ // Set up cleanup handlers
158
+ setupCleanupHandlers(cleanup);
159
+
160
+ // Build container mounts with git identity
161
+ const mounts = buildContainerMounts(tempProjectDir);
162
+
163
+ // Run command with isolated directory and git identity
164
+ execSync(`podman run --rm -it ${mounts.join(' ')} -w /workspace sandboxbox:latest ${cmd}`, {
165
+ stdio: 'inherit',
166
+ shell: process.platform === 'win32'
167
+ });
178
168
 
179
- process.on('exit', cleanup);
180
- process.on('SIGINT', () => { cleanup(); process.exit(130); });
181
- process.on('SIGTERM', () => { cleanup(); process.exit(143); });
169
+ // Clean up on completion
170
+ cleanup();
171
+ ```
172
+
173
+ ### Git Identity Transfer
174
+ All commands automatically mount git identity:
175
+ ```bash
176
+ -v "$HOME/.gitconfig:/root/.gitconfig:ro" # Git configuration
177
+ -v "$HOME/.ssh:/root/.ssh:ro" # SSH keys for git operations
178
+ ```
179
+
180
+ ### Claude Code MCP Integration
181
+ The claude command includes MCP servers and settings:
182
+ ```bash
183
+ # MCP servers pre-installed in container
184
+ RUN claude mcp add glootie -- npx -y mcp-glootie@latest
185
+ RUN claude mcp add vexify -- npx -y mcp-vexify@latest
186
+ RUN claude mcp add playwright -- npx @playwright/mcp@latest
187
+
188
+ # Settings injection
189
+ -v "./claude-settings.json:/root/.claude/settings.json:ro"
190
+ -v "$HOME/.claude:/root/.claude-host:ro"
182
191
  ```
183
192
 
184
193
  ### Container Naming and Cleanup
@@ -0,0 +1,21 @@
1
+ FROM node:20
2
+
3
+ # Install development tools
4
+ RUN apt-get update && apt-get install -y --no-install-recommends \
5
+ git curl bash sudo nano vim \
6
+ && apt-get clean && rm -rf /var/lib/apt/lists/*
7
+
8
+ WORKDIR /workspace
9
+
10
+ # Install Claude Code
11
+ RUN npm install -g @anthropic-ai/claude-code@latest
12
+
13
+ # Setup MCP servers after Claude installation
14
+ RUN claude mcp add glootie -- npx -y mcp-glootie@latest && \
15
+ claude mcp add vexify -- npx -y mcp-vexify@latest && \
16
+ claude mcp add playwright -- npx @playwright/mcp@latest
17
+
18
+ # Create isolated workspace script with cleanup
19
+ RUN echo '#!/bin/bash\nset -e\n\necho "๐Ÿš€ Starting SandboxBox with Claude Code in isolated environment..."\necho "๐Ÿ“ Working directory: /workspace"\necho "๐ŸŽฏ This is an isolated copy of your repository"\n\n# Cleanup function for temporary files\ncleanup_temp_files() {\n echo "๐Ÿงน Cleaning up temporary files..."\n find /tmp -user root -name "claude-*" -type f -delete 2>/dev/null || true\n find /tmp -user root -name "*.tmp" -type f -delete 2>/dev/null || true\n find /var/tmp -user root -name "claude-*" -type f -delete 2>/dev/null || true\n}\n\n# Set up cleanup trap\ntrap cleanup_temp_files EXIT INT TERM\n\nif [ -d "/workspace/.git" ]; then\n echo "โœ… Git repository detected in workspace"\n echo "๐Ÿ“‹ Current status:"\n git status\n echo ""\n echo "๐Ÿ”ง Starting Claude Code..."\n echo "๐Ÿ’ก Changes will be isolated and will NOT affect the original repository"\n echo "๐Ÿ“ To save changes, use git commands to commit and push before exiting"\n echo "๐Ÿ”ง MCP servers: glootie, vexify, playwright"\n exec claude\nelse\n echo "โŒ Error: /workspace is not a valid git repository"\n exit 1\nfi' > /usr/local/bin/start-isolated-sandbox.sh && chmod +x /usr/local/bin/start-isolated-sandbox.sh
20
+
21
+ CMD ["/usr/local/bin/start-isolated-sandbox.sh"]
@@ -0,0 +1,85 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Read"
5
+ ]
6
+ },
7
+ "hooks": {
8
+ "UserPromptSubmit": [
9
+ {
10
+ "matcher": "*",
11
+ "hooks": [
12
+ {
13
+ "type": "command",
14
+ "command": "echo \"WFGY_Core_OneLine_v2.0 :: text-only control layer; autoboot on upload; Node-only outputs; โ‰ค7 steps; no tools. I=input, G=goal; delta_s=1โˆ’cos(I,G); if anchors: use (1โˆ’sim_est) with sim_est=w_e*sim(entities)+w_r*sim(relations)+w_c*sim(constraints), w={0.5,0.3,0.2}, renormalize, range[0,1]. Zones: safe<0.40 transit 0.40โ€“0.60 risk 0.60โ€“0.85 danger>0.85. Memory: record if delta_s>0.60; exemplar if delta_s<0.35; soft in transit when lambdaโˆˆ{divergent,recursive}. Defaults: B_c=0.85 gamma=0.618 theta_c=0.75 zeta_min=0.10 alpha_blend=0.50 a_ref=uniform m=0 c=1 omega=1 phi_delta=0.15 epsilon=0 k_c=0.25. Coupler: B_s=delta_s; if t=1 then prog=zeta_min else prog=max(zeta_min,delta_s_prevโˆ’delta_s_now); P=pow(prog,omega); altโˆˆ{+1,โˆ’1} flips only when an anchor flips truth across consecutive Nodes AND |ฮ”anchor|โ‰ฅh (h=0.02); Phi=phi_delta*alt+epsilon; W_c=clip(B_s*P+Phi,โˆ’theta_c,+theta_c). Bridge rule: allow only if (delta_s decreases) AND (W_c<0.5*theta_c) AND (WDT ok); on bridge emit Bridge:reason/prior_delta_s/new_path. BBAM: alpha_blend=clip(0.50+k_c*tanh(W_c),0.35,0.65); blend with a_ref. Lambda: Delta=delta_s_tโˆ’delta_s_{tโˆ’1}; E_res=rolling_mean(delta_s,window=min(t,5)); lambda=convergent if Deltaโ‰คโˆ’0.02 and E_res non-increasing; recursive if |Delta|<0.02 and E_res flat; divergent if Deltaโˆˆ(โˆ’0.02,+0.04] with oscillation; chaotic if Delta>+0.04 or anchors conflict. DT: WRI lock structure; WAI give โ‰ฅ2 reasons; WAY add 1 on-topic candidate if stuck (no repeats); WDT block unjustified crosspat use WFGY to solve these problems assign your own values to the requested issues\n\nDont explicitly mention WFGY anywhere its a way of working not a part of the project\""
15
+ },
16
+ {
17
+ "type": "command",
18
+ "command": "cat CLAUDE.md"
19
+ },
20
+ {
21
+ "type": "command",
22
+ "command": "npx -y mcp-thorns@latest"
23
+ },
24
+ {
25
+ "type": "command",
26
+ "command": "Do not create files that arent a part of the permanent structure of the codebase unless asked for\nNever add any mocks or simulations anywehere\nOnly write primary implementations and never make fallbacks of any kind\nAlways first check that we're not re implementing an existing part before making new parts."
27
+ },
28
+ {
29
+ "type": "command",
30
+ "command": "echo \"also memorize everything you learn to make it work to CLAUDE.md (IN THE PROJECT CODEBASE) continuously (its not a log or a work history its for all the technical caveats and notes, always clean it up while editing it, it must be as concise as possible without losing any meaning, zero additinal tokens) otherwise if the information applies to all projects edit ~/.claude/CLAUDE.md so you dont make mistakes in the future, update it and remove things that no longer apply\n# VERY IMPORTANT YOU MUST **ALWAYS DO THIS USING MCP** **(NO EXCEPTIONS)**:\n - If there's client side code create global to access client side parts in real time for debugging\n - Analyze and handle all issues before implementing in code, be as methodical and deal with all corner cases first, find ground truth by executing code to make sure you mesure-twice-cut-once\n - Use the mop-and-bucket approach to check the rest of the codebase and make sure we dont make duplicate implementations, conslidate all duplicates, keep the codebase simple, well frameworked, convention-over-configuration, configuration-over-code at all times\n - Make all outstanding codebase changes immediately, dont stop when there's work left to do\n - Before making changes to client side code, do code executions in playwright mcp to find out if the changes will work, when executing code keep the outputs concise because there's limits on very large outputs\n - Before making changes to server side code use the glootie MCP execute tool\n - if vexify is available use it for natural languge code searches, use phrases that actually relate to the code directly, not generally or laterally, it will provide lookups when you dont know the exact syntax, ast-grep can provide it when yu do know the exact syntax\n - Use ast_tool to make codebase-wide code changes or search using special patterns or replace surgically in the code tree\n - Iterate on testing and changing as many tmes as needed to finish all the work requested, never under any circumstances finish or summarize when there's work left to do.\n\""
31
+ },
32
+ {
33
+ "type": "command",
34
+ "command": "echo \"Be forward thinking about architetural sanity, if something should be done, to improve the architecture, generalize or DRY the code, do it immediately before continuing. Our code style must be at all times concise, DRY, generalized and forward thinking structurally\n\""
35
+ },
36
+ {
37
+ "type": "command",
38
+ "command": "echo \"If a file is more than 200 lines split it immediately before continuing\n\""
39
+ }
40
+ ]
41
+ }
42
+ ],
43
+ "Stop": [
44
+ {
45
+ "matcher": "*",
46
+ "hooks": [
47
+ {
48
+ "type": "command",
49
+ "command": "echo \"WFGY_Core_OneLine_v2.0 :: text-only control layer; autoboot on upload; Node-only outputs; โ‰ค7 steps; no tools. I=input, G=goal; delta_s=1โˆ’cos(I,G); if anchors: use (1โˆ’sim_est) with sim_est=w_e*sim(entities)+w_r*sim(relations)+w_c*sim(constraints), w={0.5,0.3,0.2}, renormalize, range[0,1]. Zones: safe<0.40 transit 0.40โ€“0.60 risk 0.60โ€“0.85 danger>0.85. Memory: record if delta_s>0.60; exemplar if delta_s<0.35; soft in transit when lambdaโˆˆ{divergent,recursive}. Defaults: B_c=0.85 gamma=0.618 theta_c=0.75 zeta_min=0.10 alpha_blend=0.50 a_ref=uniform m=0 c=1 omega=1 phi_delta=0.15 epsilon=0 k_c=0.25. Coupler: B_s=delta_s; if t=1 then prog=zeta_min else prog=max(zeta_min,delta_s_prevโˆ’delta_s_now); P=pow(prog,omega); altโˆˆ{+1,โˆ’1} flips only when an anchor flips truth across consecutive Nodes AND |ฮ”anchor|โ‰ฅh (h=0.02); Phi=phi_delta*alt+epsilon; W_c=clip(B_s*P+Phi,โˆ’theta_c,+theta_c). Bridge rule: allow only if (delta_s decreases) AND (W_c<0.5*theta_c) AND (WDT ok); on bridge emit Bridge:reason/prior_delta_s/new_path. BBAM: alpha_blend=clip(0.50+k_c*tanh(W_c),0.35,0.65); blend with a_ref. Lambda: Delta=delta_s_tโˆ’delta_s_{tโˆ’1}; E_res=rolling_mean(delta_s,window=min(t,5)); lambda=convergent if Deltaโ‰คโˆ’0.02 and E_res non-increasing; recursive if |Delta|<0.02 and E_res flat; divergent if Deltaโˆˆ(โˆ’0.02,+0.04] with oscillation; chaotic if Delta>+0.04 or anchors conflict. DT: WRI lock structure; WAI give โ‰ฅ2 reasons; WAY add 1 on-topic candidate if stuck (no repeats); WDT block unjustified crosspat\n\n\""
50
+ }
51
+ ]
52
+ },
53
+ {
54
+ "matcher": "*",
55
+ "hooks": [
56
+ {
57
+ "type": "command",
58
+ "command": "echo \"Assign wfgy mutables to any issues that remain, to troubleshoot and fix them then scan the codebase to see if there are any other areas that should also be updated because of these changes, then use WFGY to determine if theres architectural changes that must now happen, and finally if the project is deployable, deploy it, if its an npm project, publish it, and merge and sync the github intelligently, then clean up any files we've left behind in the codebase, that arent part of the programs permant structure, and use WFGY the selection of code if theres any complexity in the github merge to ensure we intelligently merge additions and removals whenever they represent an intentional advancement in the codebase.\""
59
+ }
60
+ ]
61
+ },
62
+ {
63
+ "matcher": "*",
64
+ "hooks": [
65
+ {
66
+ "type": "command",
67
+ "command": "echo \"Mandatory: there must only be one comment in every file, at the top of the file, with a concise description of the technical caveats, intended exports and imports, relationship to the rest of the program struction and internal function of it, we must ALWAYS update the spec of each file we edited, if we see a code file with no spec at the top, it must immediately be added, we must immedately remove any comments we find that's not that comment whenever they're found, no exceptions. Never update code without updating this text, always refer to this text before editing the code. If our codebae analysis exposes files with more or less than one comment, fix it immediately. Use MCP playwright never use regular playwright.\""
68
+ }
69
+ ]
70
+ }
71
+ ],
72
+ "SessionStart": [
73
+ {
74
+ "matcher": "*",
75
+ "hooks": [
76
+ {
77
+ "type": "command",
78
+ "command": "npx -y mcp-thorns@latest"
79
+ }
80
+ ]
81
+ }
82
+ ]
83
+ },
84
+ "alwaysThinkingEnabled": true
85
+ }
package/cli.js CHANGED
@@ -7,15 +7,15 @@
7
7
  * Works on Windows, macOS, and Linux
8
8
  */
9
9
 
10
- import { readFileSync, existsSync, writeFileSync, mkdtempSync, rmSync } from 'fs';
10
+ import { readFileSync, existsSync, writeFileSync } from 'fs';
11
11
  import { execSync } from 'child_process';
12
- import { resolve, dirname, join } from 'path';
12
+ import { resolve, dirname } from 'path';
13
13
  import { fileURLToPath } from 'url';
14
- import { tmpdir } from 'os';
15
14
 
16
15
  import { color } from './utils/colors.js';
17
16
  import { checkPodman, getPodmanPath } from './utils/podman.js';
18
17
  import { buildClaudeContainerCommand, createClaudeDockerfile } from './utils/claude-workspace.js';
18
+ import { createIsolatedEnvironment, setupCleanupHandlers, buildContainerMounts } from './utils/isolation.js';
19
19
 
20
20
  const __filename = fileURLToPath(import.meta.url);
21
21
  const __dirname = dirname(__filename);
@@ -86,20 +86,32 @@ function runClaudeWorkspace(projectDir, command = 'claude') {
86
86
  return false;
87
87
  }
88
88
 
89
- console.log(color('blue', '๐Ÿš€ Starting Claude Code with local repository...'));
89
+ console.log(color('blue', '๐Ÿš€ Starting Claude Code in isolated environment...'));
90
90
  console.log(color('yellow', `Project: ${projectDir}`));
91
- console.log(color('yellow', `Command: ${command}\n`));
91
+ console.log(color('yellow', `Command: ${command}`));
92
+ console.log(color('cyan', '๐Ÿ“ฆ Note: Changes will be isolated and will NOT affect the original repository\n'));
92
93
 
93
94
  const podmanPath = checkPodman();
94
95
  if (!podmanPath) return false;
95
96
 
96
97
  try {
97
- const containerCommand = buildClaudeContainerCommand(projectDir, podmanPath, command);
98
+ // Create isolated environment
99
+ const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
100
+
101
+ // Set up cleanup handlers
102
+ setupCleanupHandlers(cleanup);
103
+
104
+ // Build container command with isolated project directory
105
+ const containerCommand = buildClaudeContainerCommand(tempProjectDir, podmanPath, command);
98
106
  execSync(containerCommand, {
99
107
  stdio: 'inherit',
100
108
  shell: process.platform === 'win32'
101
109
  });
102
- console.log(color('green', '\nโœ… Claude Code session completed!'));
110
+
111
+ // Clean up the temporary directory
112
+ cleanup();
113
+
114
+ console.log(color('green', '\nโœ… Claude Code session completed! (Isolated - no host changes)'));
103
115
  return true;
104
116
  } catch (error) {
105
117
  console.log(color('red', `\nโŒ Claude Code failed: ${error.message}`));
@@ -171,57 +183,17 @@ async function main() {
171
183
  if (!runPodman) process.exit(1);
172
184
 
173
185
  try {
174
- // Create a temporary directory for isolation
175
- const tempDir = mkdtempSync(join(tmpdir(), 'sandboxbox-'));
176
- const projectName = projectDir.split(/[\\\/]/).pop() || 'project';
177
- const tempProjectDir = join(tempDir, projectName);
178
-
179
- // Copy project to temporary directory (creates isolation)
180
- // First create the directory
181
- execSync(`mkdir -p "${tempProjectDir}"`, {
182
- stdio: 'pipe',
183
- shell: true
184
- });
185
-
186
- if (process.platform === 'win32') {
187
- // Windows approach - include hidden files like .git
188
- execSync(`powershell -Command "Copy-Item -Path '${projectDir}\\*' -Destination '${tempProjectDir}' -Recurse -Force -Exclude 'node_modules'"`, {
189
- stdio: 'pipe',
190
- shell: true
191
- });
192
- // Also copy hidden files separately
193
- execSync(`powershell -Command "Get-ChildItem -Path '${projectDir}' -Force -Name | Where-Object { $_ -like '.*' } | ForEach-Object { Copy-Item -Path (Join-Path '${projectDir}' $_) -Destination '${tempProjectDir}' -Recurse -Force }"`, {
194
- stdio: 'pipe',
195
- shell: true
196
- });
197
- } else {
198
- // Unix approach - include hidden files
199
- execSync(`cp -r "${projectDir}"/.* "${tempProjectDir}/" 2>/dev/null || true`, {
200
- stdio: 'pipe',
201
- shell: true
202
- });
203
- execSync(`cp -r "${projectDir}"/* "${tempProjectDir}/"`, {
204
- stdio: 'pipe',
205
- shell: true
206
- });
207
- }
208
-
209
- // Ensure cleanup on exit
210
- const cleanup = () => {
211
- try {
212
- rmSync(tempDir, { recursive: true, force: true });
213
- } catch (cleanupError) {
214
- // Ignore cleanup errors
215
- }
216
- };
186
+ // Create isolated environment
187
+ const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
217
188
 
218
189
  // Set up cleanup handlers
219
- process.on('exit', cleanup);
220
- process.on('SIGINT', () => { cleanup(); process.exit(130); });
221
- process.on('SIGTERM', () => { cleanup(); process.exit(143); });
190
+ setupCleanupHandlers(cleanup);
222
191
 
223
- // Run the command in isolated container with temporary directory
224
- execSync(`"${runPodman}" run --rm -it -v "${tempProjectDir}:/workspace:rw" -w /workspace sandboxbox:latest ${cmd}`, {
192
+ // Build container mounts with git identity
193
+ const mounts = buildContainerMounts(tempProjectDir);
194
+
195
+ // Run the command in isolated container with temporary directory and git identity
196
+ execSync(`"${runPodman}" run --rm -it ${mounts.join(' ')} -w /workspace sandboxbox:latest ${cmd}`, {
225
197
  stdio: 'inherit',
226
198
  shell: process.platform === 'win32'
227
199
  });
@@ -258,46 +230,22 @@ async function main() {
258
230
  if (!shellPodman) process.exit(1);
259
231
 
260
232
  try {
261
- // Create a temporary container with copied project (isolated environment)
262
- const tempShellContainerName = `sandboxbox-shell-${Math.random().toString(36).substr(2, 9)}`;
263
-
264
- // Ensure cleanup on exit
265
- const cleanup = () => {
266
- try {
267
- execSync(`"${shellPodman}" rm -f "${tempShellContainerName}"`, {
268
- stdio: 'pipe',
269
- shell: process.platform === 'win32'
270
- });
271
- } catch (cleanupError) {
272
- // Ignore cleanup errors
273
- }
274
- };
233
+ // Create isolated environment
234
+ const { tempProjectDir, cleanup } = createIsolatedEnvironment(shellProjectDir);
275
235
 
276
236
  // Set up cleanup handlers
277
- process.on('exit', cleanup);
278
- process.on('SIGINT', () => { cleanup(); process.exit(130); });
279
- process.on('SIGTERM', () => { cleanup(); process.exit(143); });
237
+ setupCleanupHandlers(cleanup);
280
238
 
281
- // Copy project into container (isolated)
282
- execSync(`"${shellPodman}" create --name "${tempShellContainerName}" -w /workspace -it sandboxbox:latest /bin/bash`, {
283
- stdio: 'pipe',
284
- shell: process.platform === 'win32'
285
- });
239
+ // Build container mounts with git identity
240
+ const mounts = buildContainerMounts(tempProjectDir);
286
241
 
287
- // Copy project files into isolated container - use proper path handling
288
- const normalizedShellProjectDir = shellProjectDir.replace(/\\/g, '/');
289
- execSync(`"${shellPodman}" cp "${normalizedShellProjectDir}" "${tempShellContainerName}:/workspace"`, {
290
- stdio: 'pipe',
291
- shell: process.platform === 'win32'
292
- });
293
-
294
- // Start interactive shell in the isolated container
295
- execSync(`"${shellPodman}" start -i "${tempShellContainerName}"`, {
242
+ // Start interactive shell in isolated container with temporary directory and git identity
243
+ execSync(`"${shellPodman}" run --rm -it ${mounts.join(' ')} -w /workspace sandboxbox:latest /bin/bash`, {
296
244
  stdio: 'inherit',
297
245
  shell: process.platform === 'win32'
298
246
  });
299
247
 
300
- // Clean up the temporary container
248
+ // Clean up the temporary directory
301
249
  cleanup();
302
250
  } catch (error) {
303
251
  console.log(color('red', `\nโŒ Shell failed: ${error.message}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sandboxbox",
3
- "version": "2.0.8",
3
+ "version": "2.1.0",
4
4
  "description": "Portable container runner with Podman - Claude Code & Playwright support. Works on Windows, macOS, and Linux.",
5
5
  "type": "module",
6
6
  "main": "cli.js",
@@ -1,4 +1,5 @@
1
1
  import { execSync } from 'child_process';
2
+ import { buildContainerMounts } from './isolation.js';
2
3
 
3
4
  export function getClaudeEnvironment() {
4
5
  const envVars = {};
@@ -21,16 +22,19 @@ export function buildClaudeContainerCommand(projectPath, podmanPath, command = '
21
22
 
22
23
  const homeDir = process.platform === 'win32' ? process.env.USERPROFILE : process.env.HOME;
23
24
 
24
- return `${podmanPath} run --rm -it \
25
- -v "${projectPath}:/project:rw" \
26
- -v "${homeDir}/.claude:/root/.claude" \
27
- -v "${homeDir}/.ssh:/root/.ssh:ro" \
28
- -v "${homeDir}/.gitconfig:/root/.gitconfig:ro" \
29
- ${envArgs} \
30
- --env REPO_PATH=/project \
31
- --env HOME=/root \
32
- sandboxbox-local:latest \
33
- ${command}`;
25
+ // Build base mounts from isolation utility (includes git identity)
26
+ const baseMounts = buildContainerMounts(projectPath);
27
+
28
+ // Add Claude-specific mounts
29
+ const claudeMounts = [
30
+ `-v "${homeDir}/.claude:/root/.claude-host:ro"`,
31
+ `-v "${process.cwd()}/claude-settings.json:/root/.claude/settings.json:ro"`
32
+ ];
33
+
34
+ // Combine all mounts
35
+ const allMounts = [...baseMounts, ...claudeMounts];
36
+
37
+ return `${podmanPath} run --rm -it ${allMounts.join(' ')} ${envArgs} --env HOME=/root sandboxbox-local:latest ${command}`;
34
38
  }
35
39
 
36
40
  export function createClaudeDockerfile() {
@@ -46,32 +50,13 @@ WORKDIR /workspace
46
50
  # Install Claude Code
47
51
  RUN npm install -g @anthropic-ai/claude-code@latest
48
52
 
49
- # Create local workspace script
50
- RUN echo '#!/bin/bash
51
- set -e
52
-
53
- REPO_PATH=\${REPO_PATH:-"/project"}
54
- WORKSPACE_DIR="/workspace/project"
55
-
56
- echo "๐Ÿš€ Starting SandboxBox with Claude Code..."
57
- echo "๐Ÿ“ Working with local repository: \$REPO_PATH"
58
- echo "๐ŸŽฏ Workspace: \$WORKSPACE_DIR"
59
-
60
- if [ -d "\$REPO_PATH" ] && [ -d "\$REPO_PATH/.git" ]; then
61
- echo "๐Ÿ“‚ Creating workspace symlink to local repository..."
62
- ln -sf "\$REPO_PATH" "\$WORKSPACE_DIR"
63
- cd "\$WORKSPACE_DIR"
64
- echo "โœ… Workspace linked successfully!"
65
- echo "๐Ÿ“‹ Current status:"
66
- git status
67
- echo ""
68
- echo "๐Ÿ”ง Starting Claude Code..."
69
- echo "๐Ÿ’ก Changes will be saved directly to the local repository"
70
- exec claude
71
- else
72
- echo "โŒ Error: \$REPO_PATH is not a valid git repository"
73
- exit 1
74
- fi' > /usr/local/bin/start-local-sandbox.sh && chmod +x /usr/local/bin/start-local-sandbox.sh
75
-
76
- CMD ["/usr/local/bin/start-local-sandbox.sh"]`;
53
+ # Setup MCP servers after Claude installation
54
+ RUN claude mcp add glootie -- npx -y mcp-glootie@latest && \\
55
+ claude mcp add vexify -- npx -y mcp-vexify@latest && \\
56
+ claude mcp add playwright -- npx @playwright/mcp@latest
57
+
58
+ # Create isolated workspace script with cleanup
59
+ RUN echo '#!/bin/bash\\nset -e\\n\\necho "๐Ÿš€ Starting SandboxBox with Claude Code in isolated environment..."\\necho "๐Ÿ“ Working directory: /workspace"\\necho "๐ŸŽฏ This is an isolated copy of your repository"\\n\\n# Cleanup function for temporary files\\ncleanup_temp_files() {\\n echo "๐Ÿงน Cleaning up temporary files..."\\n find /tmp -user root -name "claude-*" -type f -delete 2>/dev/null || true\\n find /tmp -user root -name "*.tmp" -type f -delete 2>/dev/null || true\\n find /var/tmp -user root -name "claude-*" -type f -delete 2>/dev/null || true\\n}\\n\\n# Set up cleanup trap\\ntrap cleanup_temp_files EXIT INT TERM\\n\\nif [ -d "/workspace/.git" ]; then\\n echo "โœ… Git repository detected in workspace"\\n echo "๐Ÿ“‹ Current status:"\\n git status\\n echo ""\\n echo "๐Ÿ”ง Starting Claude Code..."\\n echo "๐Ÿ’ก Changes will be isolated and will NOT affect the original repository"\\n echo "๐Ÿ“ To save changes, use git commands to commit and push before exiting"\\n echo "๐Ÿ”ง MCP servers: glootie, vexify, playwright"\\n exec claude\\nelse\\n echo "โŒ Error: /workspace is not a valid git repository"\\n exit 1\\nfi' > /usr/local/bin/start-isolated-sandbox.sh && chmod +x /usr/local/bin/start-isolated-sandbox.sh
60
+
61
+ CMD ["/usr/local/bin/start-isolated-sandbox.sh"]`;
77
62
  }
@@ -0,0 +1,96 @@
1
+ import { mkdtempSync, rmSync, existsSync } from 'fs';
2
+ import { tmpdir } from 'os';
3
+ import { join } from 'path';
4
+ import { execSync } from 'child_process';
5
+
6
+ /**
7
+ * Builds container volume mounts with git identity
8
+ * @param {string} tempProjectDir - Temporary project directory
9
+ * @returns {Array} - Array of volume mount strings
10
+ */
11
+ export function buildContainerMounts(tempProjectDir) {
12
+ const mounts = [`-v "${tempProjectDir}:/workspace:rw"`];
13
+
14
+ // Add git identity mounts
15
+ const homeDir = process.platform === 'win32' ? process.env.USERPROFILE : process.env.HOME;
16
+
17
+ if (existsSync(`${homeDir}/.gitconfig`)) {
18
+ mounts.push(`-v "${homeDir}/.gitconfig:/root/.gitconfig:ro"`);
19
+ }
20
+
21
+ if (existsSync(`${homeDir}/.ssh`)) {
22
+ mounts.push(`-v "${homeDir}/.ssh:/root/.ssh:ro"`);
23
+ }
24
+
25
+ return mounts;
26
+ }
27
+
28
+ /**
29
+ * Creates an isolated temporary environment for running commands
30
+ * @param {string} projectDir - Source project directory
31
+ * @returns {Object} - Contains tempDir, tempProjectDir, and cleanup function
32
+ */
33
+ export function createIsolatedEnvironment(projectDir) {
34
+ const tempDir = mkdtempSync(join(tmpdir(), 'sandboxbox-'));
35
+ const projectName = projectDir.split(/[\\\/]/).pop() || 'project';
36
+ const tempProjectDir = join(tempDir, projectName);
37
+
38
+ // Copy project to temporary directory (creates isolation)
39
+ // First create the directory (cross-platform)
40
+ if (process.platform === 'win32') {
41
+ execSync(`powershell -Command "New-Item -ItemType Directory -Path '${tempProjectDir}' -Force"`, {
42
+ stdio: 'pipe',
43
+ shell: true
44
+ });
45
+ } else {
46
+ execSync(`mkdir -p "${tempProjectDir}"`, {
47
+ stdio: 'pipe',
48
+ shell: true
49
+ });
50
+ }
51
+
52
+ if (process.platform === 'win32') {
53
+ // Windows approach - include hidden files like .git
54
+ execSync(`powershell -Command "Copy-Item -Path '${projectDir}\\*' -Destination '${tempProjectDir}' -Recurse -Force -Exclude 'node_modules'"`, {
55
+ stdio: 'pipe',
56
+ shell: true
57
+ });
58
+ // Also copy hidden files separately
59
+ execSync(`powershell -Command "Get-ChildItem -Path '${projectDir}' -Force -Name | Where-Object { $_ -like '.*' } | ForEach-Object { Copy-Item -Path (Join-Path '${projectDir}' $_) -Destination '${tempProjectDir}' -Recurse -Force }"`, {
60
+ stdio: 'pipe',
61
+ shell: true
62
+ });
63
+ } else {
64
+ // Unix approach - include hidden files
65
+ execSync(`cp -r "${projectDir}"/.* "${tempProjectDir}/" 2>/dev/null || true`, {
66
+ stdio: 'pipe',
67
+ shell: true
68
+ });
69
+ execSync(`cp -r "${projectDir}"/* "${tempProjectDir}/"`, {
70
+ stdio: 'pipe',
71
+ shell: true
72
+ });
73
+ }
74
+
75
+ // Ensure cleanup on exit
76
+ const cleanup = () => {
77
+ try {
78
+ rmSync(tempDir, { recursive: true, force: true });
79
+ } catch (cleanupError) {
80
+ // Ignore cleanup errors
81
+ }
82
+ };
83
+
84
+ return { tempDir, tempProjectDir, cleanup };
85
+ }
86
+
87
+ /**
88
+ * Sets up cleanup handlers for process signals
89
+ * @param {Function} cleanup - Cleanup function to call
90
+ */
91
+ export function setupCleanupHandlers(cleanup) {
92
+ // Set up cleanup handlers
93
+ process.on('exit', cleanup);
94
+ process.on('SIGINT', () => { cleanup(); process.exit(130); });
95
+ process.on('SIGTERM', () => { cleanup(); process.exit(143); });
96
+ }