sandboxbox 2.0.8 → 2.0.9

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,37 @@ 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 } 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
+ // Run command with isolated directory
161
+ execSync(`podman run --rm -it -v "${tempProjectDir}:/workspace:rw" -w /workspace sandboxbox:latest ${cmd}`, {
162
+ stdio: 'inherit',
163
+ shell: process.platform === 'win32'
164
+ });
178
165
 
179
- process.on('exit', cleanup);
180
- process.on('SIGINT', () => { cleanup(); process.exit(130); });
181
- process.on('SIGTERM', () => { cleanup(); process.exit(143); });
166
+ // Clean up on completion
167
+ cleanup();
182
168
  ```
183
169
 
184
170
  ### Container Naming and Cleanup
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 } 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,54 +183,11 @@ 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
192
  // Run the command in isolated container with temporary directory
224
193
  execSync(`"${runPodman}" run --rm -it -v "${tempProjectDir}:/workspace:rw" -w /workspace sandboxbox:latest ${cmd}`, {
@@ -258,46 +227,19 @@ async function main() {
258
227
  if (!shellPodman) process.exit(1);
259
228
 
260
229
  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
- };
230
+ // Create isolated environment
231
+ const { tempProjectDir, cleanup } = createIsolatedEnvironment(shellProjectDir);
275
232
 
276
233
  // 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); });
234
+ setupCleanupHandlers(cleanup);
280
235
 
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
- });
286
-
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}"`, {
236
+ // Start interactive shell in isolated container with temporary directory
237
+ execSync(`"${shellPodman}" run --rm -it -v "${tempProjectDir}:/workspace:rw" -w /workspace sandboxbox:latest /bin/bash`, {
296
238
  stdio: 'inherit',
297
239
  shell: process.platform === 'win32'
298
240
  });
299
241
 
300
- // Clean up the temporary container
242
+ // Clean up the temporary directory
301
243
  cleanup();
302
244
  } catch (error) {
303
245
  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.0.9",
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",
@@ -22,12 +22,11 @@ export function buildClaudeContainerCommand(projectPath, podmanPath, command = '
22
22
  const homeDir = process.platform === 'win32' ? process.env.USERPROFILE : process.env.HOME;
23
23
 
24
24
  return `${podmanPath} run --rm -it \
25
- -v "${projectPath}:/project:rw" \
25
+ -v "${projectPath}:/workspace:rw" \
26
26
  -v "${homeDir}/.claude:/root/.claude" \
27
27
  -v "${homeDir}/.ssh:/root/.ssh:ro" \
28
28
  -v "${homeDir}/.gitconfig:/root/.gitconfig:ro" \
29
29
  ${envArgs} \
30
- --env REPO_PATH=/project \
31
30
  --env HOME=/root \
32
31
  sandboxbox-local:latest \
33
32
  ${command}`;
@@ -46,32 +45,27 @@ WORKDIR /workspace
46
45
  # Install Claude Code
47
46
  RUN npm install -g @anthropic-ai/claude-code@latest
48
47
 
49
- # Create local workspace script
48
+ # Create isolated workspace script
50
49
  RUN echo '#!/bin/bash
51
50
  set -e
52
51
 
53
- REPO_PATH=\${REPO_PATH:-"/project"}
54
- WORKSPACE_DIR="/workspace/project"
52
+ echo "šŸš€ Starting SandboxBox with Claude Code in isolated environment..."
53
+ echo "šŸ“ Working directory: /workspace"
54
+ echo "šŸŽÆ This is an isolated copy of your repository"
55
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!"
56
+ if [ -d "/workspace/.git" ]; then
57
+ echo "āœ… Git repository detected in workspace"
65
58
  echo "šŸ“‹ Current status:"
66
59
  git status
67
60
  echo ""
68
61
  echo "šŸ”§ Starting Claude Code..."
69
- echo "šŸ’” Changes will be saved directly to the local repository"
62
+ echo "šŸ’” Changes will be isolated and will NOT affect the original repository"
63
+ echo "šŸ“ To save changes, use git commands to commit and push before exiting"
70
64
  exec claude
71
65
  else
72
- echo "āŒ Error: \$REPO_PATH is not a valid git repository"
66
+ echo "āŒ Error: /workspace is not a valid git repository"
73
67
  exit 1
74
- fi' > /usr/local/bin/start-local-sandbox.sh && chmod +x /usr/local/bin/start-local-sandbox.sh
68
+ fi' > /usr/local/bin/start-isolated-sandbox.sh && chmod +x /usr/local/bin/start-isolated-sandbox.sh
75
69
 
76
- CMD ["/usr/local/bin/start-local-sandbox.sh"]`;
70
+ CMD ["/usr/local/bin/start-isolated-sandbox.sh"]`;
77
71
  }
@@ -0,0 +1,74 @@
1
+ import { mkdtempSync, rmSync } from 'fs';
2
+ import { tmpdir } from 'os';
3
+ import { join } from 'path';
4
+ import { execSync } from 'child_process';
5
+
6
+ /**
7
+ * Creates an isolated temporary environment for running commands
8
+ * @param {string} projectDir - Source project directory
9
+ * @returns {Object} - Contains tempDir, tempProjectDir, and cleanup function
10
+ */
11
+ export function createIsolatedEnvironment(projectDir) {
12
+ const tempDir = mkdtempSync(join(tmpdir(), 'sandboxbox-'));
13
+ const projectName = projectDir.split(/[\\\/]/).pop() || 'project';
14
+ const tempProjectDir = join(tempDir, projectName);
15
+
16
+ // Copy project to temporary directory (creates isolation)
17
+ // First create the directory (cross-platform)
18
+ if (process.platform === 'win32') {
19
+ execSync(`powershell -Command "New-Item -ItemType Directory -Path '${tempProjectDir}' -Force"`, {
20
+ stdio: 'pipe',
21
+ shell: true
22
+ });
23
+ } else {
24
+ execSync(`mkdir -p "${tempProjectDir}"`, {
25
+ stdio: 'pipe',
26
+ shell: true
27
+ });
28
+ }
29
+
30
+ if (process.platform === 'win32') {
31
+ // Windows approach - include hidden files like .git
32
+ execSync(`powershell -Command "Copy-Item -Path '${projectDir}\\*' -Destination '${tempProjectDir}' -Recurse -Force -Exclude 'node_modules'"`, {
33
+ stdio: 'pipe',
34
+ shell: true
35
+ });
36
+ // Also copy hidden files separately
37
+ execSync(`powershell -Command "Get-ChildItem -Path '${projectDir}' -Force -Name | Where-Object { $_ -like '.*' } | ForEach-Object { Copy-Item -Path (Join-Path '${projectDir}' $_) -Destination '${tempProjectDir}' -Recurse -Force }"`, {
38
+ stdio: 'pipe',
39
+ shell: true
40
+ });
41
+ } else {
42
+ // Unix approach - include hidden files
43
+ execSync(`cp -r "${projectDir}"/.* "${tempProjectDir}/" 2>/dev/null || true`, {
44
+ stdio: 'pipe',
45
+ shell: true
46
+ });
47
+ execSync(`cp -r "${projectDir}"/* "${tempProjectDir}/"`, {
48
+ stdio: 'pipe',
49
+ shell: true
50
+ });
51
+ }
52
+
53
+ // Ensure cleanup on exit
54
+ const cleanup = () => {
55
+ try {
56
+ rmSync(tempDir, { recursive: true, force: true });
57
+ } catch (cleanupError) {
58
+ // Ignore cleanup errors
59
+ }
60
+ };
61
+
62
+ return { tempDir, tempProjectDir, cleanup };
63
+ }
64
+
65
+ /**
66
+ * Sets up cleanup handlers for process signals
67
+ * @param {Function} cleanup - Cleanup function to call
68
+ */
69
+ export function setupCleanupHandlers(cleanup) {
70
+ // Set up cleanup handlers
71
+ process.on('exit', cleanup);
72
+ process.on('SIGINT', () => { cleanup(); process.exit(130); });
73
+ process.on('SIGTERM', () => { cleanup(); process.exit(143); });
74
+ }