sandboxbox 3.0.2 โ†’ 3.0.4

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/.vexify.db CHANGED
File without changes
package/0.60 CHANGED
File without changes
package/CLAUDE.md CHANGED
@@ -100,35 +100,23 @@ cleanup();
100
100
  ## Git Integration
101
101
 
102
102
  ### Git Identity Transfer
103
- ```bash
104
- -v "$HOME/.gitconfig:/root/.gitconfig:ro"
105
- -v "$HOME/.ssh:/root/.ssh:ro"
106
- ```
107
-
108
- ### Git Remote Setup
109
- ```bash
110
- # Mount host repository as accessible remote
111
- -v "/path/to/host/repo:/host-repo:rw"
112
-
113
- # Configure remote in container
114
- git remote add origin /host-repo
115
- ```
103
+ Automatically transfers host git identity to sandbox after cloning:
104
+ ```javascript
105
+ const userName = execSync('git config --global user.name', { encoding: 'utf8' }).trim();
106
+ const userEmail = execSync('git config --global user.email', { encoding: 'utf8' }).trim();
107
+ const colorUi = execSync('git config --global color.ui', { encoding: 'utf8' }).trim();
116
108
 
117
- ### Git Safe Directory Configuration
118
- ```bash
119
- git config --global --add safe.directory /workspace
120
- git config --global --add safe.directory /host-repo
121
- git config --global --add safe.directory /host-repo/.git
109
+ execSync(`git config user.name "${userName}"`, { cwd: workspaceDir });
110
+ execSync(`git config user.email "${userEmail}"`, { cwd: workspaceDir });
111
+ execSync(`git config color.ui "${colorUi}"`, { cwd: workspaceDir });
122
112
  ```
123
113
 
124
- ### Git Push Configuration
125
- ```bash
126
- # Host repository - allow pushes to checked-out branch
127
- git config receive.denyCurrentBranch ignore
114
+ Git identity and color config auto-inherit from ~/.gitconfig (user.name, user.email, color.ui). For sandbox-to-host workflows, host repo needs `receive.denyCurrentBranch=updateInstead` and clean working directory.
128
115
 
129
- # Container - set git identity
130
- git config --global user.email "user@example.com"
131
- git config --global user.name "User Name"
116
+ ### Git Safe Directory Configuration
117
+ ```javascript
118
+ execSync(`git config --global --add safe.directory "${projectDir}"`);
119
+ execSync(`git config --global --add safe.directory "${projectDir}/.git"`);
132
120
  ```
133
121
 
134
122
  ### Windows Path Normalization
@@ -136,22 +124,68 @@ git config --global user.name "User Name"
136
124
  const normalizedTempDir = tempProjectDir.replace(/\\/g, '/');
137
125
  ```
138
126
 
127
+ ### Environment Variable Transfer
128
+ Host bash session environment variables automatically carry through to sandboxes:
129
+ ```javascript
130
+ // Terminal
131
+ TERM: process.env.TERM || 'xterm-256color',
132
+ LS_COLORS: process.env.LS_COLORS,
133
+
134
+ // Locale
135
+ LANG: process.env.LANG,
136
+ LC_ALL: process.env.LC_ALL,
137
+
138
+ // User
139
+ SHELL: process.env.SHELL,
140
+ USER: process.env.USER,
141
+ LOGNAME: process.env.LOGNAME,
142
+
143
+ // Tools
144
+ EDITOR: process.env.EDITOR,
145
+ VISUAL: process.env.VISUAL,
146
+ PAGER: process.env.PAGER,
147
+ LESS: process.env.LESS,
148
+ LESSOPEN: process.env.LESSOPEN,
149
+ LESSCLOSE: process.env.LESSCLOSE,
150
+
151
+ // Display
152
+ DISPLAY: process.env.DISPLAY,
153
+ WAYLAND_DISPLAY: process.env.WAYLAND_DISPLAY,
154
+
155
+ // Authentication
156
+ SSH_AUTH_SOCK: process.env.SSH_AUTH_SOCK,
157
+ SSH_AGENT_PID: process.env.SSH_AGENT_PID,
158
+ GPG_AGENT_INFO: process.env.GPG_AGENT_INFO
159
+ ```
160
+
139
161
  ## Claude Code Integration
140
162
 
141
163
  ### Authentication
142
- ```bash
143
- -v "$HOME/.claude:/root/.claude"
144
- -e "ANTHROPIC_AUTH_TOKEN=..."
145
- -e "CLAUDECODE=1"
146
- ```
164
+ Uses host HOME directory for Claude Code authentication. Workspace is isolated in sandbox but credentials remain on host.
147
165
 
148
- ### MCP Servers
149
- ```dockerfile
150
- RUN claude mcp add glootie -- npx -y mcp-glootie@latest
151
- RUN claude mcp add vexify -- npx -y mcp-vexify@latest
152
- RUN claude mcp add playwright -- npx @playwright/mcp@latest
166
+ ### Tool Allow List
167
+ Automatically configured with 25 allowed tools:
168
+ - Core: Task, Bash, Glob, Grep, Read, Edit, Write, NotebookEdit, WebFetch, TodoWrite, WebSearch, BashOutput, KillShell, SlashCommand, ExitPlanMode
169
+ - MCP: glootie (execute, ast_tool, caveat), playwright (navigate, snapshot, click, type, evaluate, close), vexify (search_code)
170
+
171
+ ### Streaming Output
172
+ Uses `--verbose -p --output-format stream-json` for real-time JSON streaming. Output parser extracts text content, tool usage, session info, and cost metrics from JSON stream.
173
+
174
+ ### Playwright MCP Profile Transfer
175
+ Automatically copies persistent MCP profiles from host to sandbox:
176
+ ```javascript
177
+ const playwrightCacheDir = platform() === 'darwin'
178
+ ? join(homedir(), 'Library', 'Caches', 'ms-playwright')
179
+ : platform() === 'win32'
180
+ ? join(homedir(), 'AppData', 'Local', 'ms-playwright')
181
+ : join(homedir(), '.cache', 'ms-playwright');
182
+
183
+ // Copies mcp-chrome-profile, mcp-chromium-profile, mcp-firefox-profile, mcp-webkit-profile
184
+ cpSync(hostProfile, join(sandboxDir, '.cache', 'ms-playwright', profileName), { recursive: true });
153
185
  ```
154
186
 
187
+ Environment variable `XDG_CACHE_HOME` set to `${sandboxDir}/.cache` for Playwright profile access.
188
+
155
189
  ## Cleanup
156
190
 
157
191
  ### Container Cleanup
package/Dockerfile CHANGED
File without changes
package/Dockerfile.claude CHANGED
File without changes
File without changes
package/Dockerfile.simple CHANGED
File without changes
package/README.md CHANGED
File without changes
package/claude CHANGED
File without changes
File without changes
package/cli.js CHANGED
@@ -1,79 +1,79 @@
1
1
  #!/usr/bin/env node
2
-
3
- /**
4
- * SandboxBox CLI - Process Containment Sandbox
5
- * Lightweight process isolation for CLI tools
6
- */
7
-
8
- import { resolve } from 'path';
9
- import { color } from './utils/colors.js';
10
- import { showBanner, showHelp } from './utils/ui.js';
11
- import { buildCommand, runCommand, shellCommand, claudeCommand, versionCommand } from './utils/commands/index.js';
12
-
13
- async function main() {
14
- const args = process.argv.slice(2);
15
- showBanner();
16
-
17
- if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
18
- showHelp();
19
- process.exit(0);
20
- }
21
-
22
- const command = args[0].toLowerCase();
23
- const commandArgs = args.slice(1);
24
-
25
- try {
26
- switch (command) {
27
- case 'build':
28
- const dockerfilePath = commandArgs[0] || './Dockerfile';
29
- if (!buildCommand(dockerfilePath)) process.exit(1);
30
- break;
31
-
32
- case 'run':
33
- if (commandArgs.length === 0) {
34
- console.log(color('red', 'โŒ Please specify a project directory'));
35
- console.log(color('yellow', 'Usage: npx sandboxbox run <project-dir> [command]'));
36
- process.exit(1);
37
- }
38
- const projectDir = resolve(process.cwd(), commandArgs[0]);
39
- const cmd = commandArgs.slice(1).join(' ') || 'bash';
40
- if (!(await runCommand(projectDir, cmd))) process.exit(1);
41
- break;
42
-
43
- case 'shell':
44
- if (commandArgs.length === 0) {
45
- console.log(color('red', 'โŒ Please specify a project directory'));
46
- console.log(color('yellow', 'Usage: npx sandboxbox shell <project-dir>'));
47
- process.exit(1);
48
- }
49
- const shellProjectDir = resolve(process.cwd(), commandArgs[0]);
50
- if (!(await shellCommand(shellProjectDir))) process.exit(1);
51
- break;
52
-
53
- case 'claude':
54
- if (commandArgs.length === 0) {
55
- console.log(color('red', 'โŒ Please specify a project directory'));
56
- console.log(color('yellow', 'Usage: npx sandboxbox claude <project-dir>'));
57
- process.exit(1);
58
- }
59
- const claudeProjectDir = resolve(process.cwd(), commandArgs[0]);
60
- const claudeCmd = commandArgs.slice(1).join(' ') || 'claude';
61
- if (!(await claudeCommand(claudeProjectDir, claudeCmd))) process.exit(1);
62
- break;
63
-
64
- case 'version':
65
- if (!versionCommand()) process.exit(1);
66
- break;
67
-
68
- default:
69
- console.log(color('red', `โŒ Unknown command: ${command}`));
70
- console.log(color('yellow', 'Use --help for usage information'));
71
- process.exit(1);
72
- }
73
- } catch (error) {
74
- console.log(color('red', `\nโŒ Fatal error: ${error.message}`));
75
- process.exit(1);
76
- }
77
- }
78
-
2
+
3
+ /**
4
+ * SandboxBox CLI - Process Containment Sandbox
5
+ * Lightweight process isolation for CLI tools
6
+ */
7
+
8
+ import { resolve } from 'path';
9
+ import { color } from './utils/colors.js';
10
+ import { showBanner, showHelp } from './utils/ui.js';
11
+ import { buildCommand, runCommand, shellCommand, claudeCommand, versionCommand } from './utils/commands/index.js';
12
+
13
+ async function main() {
14
+ const args = process.argv.slice(2);
15
+ showBanner();
16
+
17
+ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
18
+ showHelp();
19
+ process.exit(0);
20
+ }
21
+
22
+ const command = args[0].toLowerCase();
23
+ const commandArgs = args.slice(1);
24
+
25
+ try {
26
+ switch (command) {
27
+ case 'build':
28
+ const dockerfilePath = commandArgs[0];
29
+ if (!buildCommand(dockerfilePath)) process.exit(1);
30
+ break;
31
+
32
+ case 'run':
33
+ if (commandArgs.length === 0) {
34
+ console.log(color('red', 'โŒ Please specify a project directory'));
35
+ console.log(color('yellow', 'Usage: npx sandboxbox run <project-dir> [command]'));
36
+ process.exit(1);
37
+ }
38
+ const projectDir = resolve(process.cwd(), commandArgs[0]);
39
+ const cmd = commandArgs.slice(1).join(' ');
40
+ if (!(await runCommand(projectDir, cmd))) process.exit(1);
41
+ break;
42
+
43
+ case 'shell':
44
+ if (commandArgs.length === 0) {
45
+ console.log(color('red', 'โŒ Please specify a project directory'));
46
+ console.log(color('yellow', 'Usage: npx sandboxbox shell <project-dir>'));
47
+ process.exit(1);
48
+ }
49
+ const shellProjectDir = resolve(process.cwd(), commandArgs[0]);
50
+ if (!(await shellCommand(shellProjectDir))) process.exit(1);
51
+ break;
52
+
53
+ case 'claude':
54
+ if (commandArgs.length === 0) {
55
+ console.log(color('red', 'โŒ Please specify a project directory'));
56
+ console.log(color('yellow', 'Usage: npx sandboxbox claude <project-dir> [prompt]'));
57
+ process.exit(1);
58
+ }
59
+ const claudeProjectDir = resolve(process.cwd(), commandArgs[0]);
60
+ const claudePrompt = commandArgs.slice(1).join(' ');
61
+ if (!(await claudeCommand(claudeProjectDir, claudePrompt))) process.exit(1);
62
+ break;
63
+
64
+ case 'version':
65
+ if (!versionCommand()) process.exit(1);
66
+ break;
67
+
68
+ default:
69
+ console.log(color('red', `โŒ Unknown command: ${command}`));
70
+ console.log(color('yellow', 'Use --help for usage information'));
71
+ process.exit(1);
72
+ }
73
+ } catch (error) {
74
+ console.log(color('red', `\nโŒ Fatal error: ${error.message}`));
75
+ process.exit(1);
76
+ }
77
+ }
78
+
79
79
  main();
File without changes
File without changes
package/file.txt CHANGED
File without changes
File without changes
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sandboxbox",
3
- "version": "3.0.2",
3
+ "version": "3.0.4",
4
4
  "description": "Lightweight process containment sandbox for CLI tools - Playwright, Claude Code, and more. Pure Node.js, no dependencies.",
5
5
  "type": "module",
6
6
  "main": "cli.js",
package/podman.zip CHANGED
File without changes
File without changes
package/test/Dockerfile CHANGED
File without changes
package/test/index.js CHANGED
File without changes
package/test/package.json CHANGED
File without changes
package/utils/colors.js CHANGED
File without changes
@@ -1,10 +1,22 @@
1
- import { existsSync, mkdirSync } from 'fs';
1
+ import { existsSync } from 'fs';
2
2
  import { resolve, join } from 'path';
3
- import { homedir, platform } from 'os';
3
+ import { spawn, execSync } from 'child_process';
4
4
  import { color } from '../colors.js';
5
- import { createSandbox, createSandboxEnv, runInSandbox } from '../sandbox.js';
5
+ import { createSandbox, createSandboxEnv } from '../sandbox.js';
6
6
 
7
- export async function claudeCommand(projectDir, command = 'claude') {
7
+ const ALLOWED_TOOLS = [
8
+ 'Task', 'Bash', 'Glob', 'Grep', 'Read', 'Edit', 'Write', 'NotebookEdit',
9
+ 'WebFetch', 'TodoWrite', 'WebSearch', 'BashOutput', 'KillShell',
10
+ 'SlashCommand', 'ExitPlanMode', 'mcp__glootie__execute',
11
+ 'mcp__glootie__ast_tool', 'mcp__glootie__caveat',
12
+ 'mcp__playwright__browser_navigate', 'mcp__playwright__browser_snapshot',
13
+ 'mcp__playwright__browser_click', 'mcp__playwright__browser_type',
14
+ 'mcp__playwright__browser_evaluate', 'mcp__playwright__browser_close',
15
+ 'mcp__vexify__search_code'
16
+ ];
17
+
18
+
19
+ export async function claudeCommand(projectDir, prompt) {
8
20
  if (!existsSync(projectDir)) {
9
21
  console.log(color('red', `โŒ Project directory not found: ${projectDir}`));
10
22
  return false;
@@ -18,34 +30,185 @@ export async function claudeCommand(projectDir, command = 'claude') {
18
30
 
19
31
  console.log(color('blue', '๐Ÿš€ Starting Claude Code in sandbox...'));
20
32
  console.log(color('yellow', `Project: ${projectDir}`));
21
- console.log(color('yellow', `Command: ${command}\n`));
33
+ console.log(color('yellow', `Prompt: ${prompt}`));
34
+ console.log('');
22
35
 
36
+ const startTime = Date.now();
37
+ console.log(color('cyan', 'โฑ๏ธ Stage 1: Creating sandbox...'));
23
38
  const { sandboxDir, cleanup } = createSandbox(projectDir);
39
+ const sandboxCreateTime = Date.now() - startTime;
40
+ console.log(color('green', `โœ… Sandbox created in ${sandboxCreateTime}ms`));
24
41
 
25
42
  process.on('SIGINT', cleanup);
26
43
  process.on('SIGTERM', cleanup);
27
44
 
28
45
  try {
29
- let hostClaudeDir = join(homedir(), '.claude');
30
- if (platform() === 'win32') {
31
- // Convert Windows path to Unix style for bash
32
- hostClaudeDir = hostClaudeDir.replace(/^([A-Z]):/, '/$1').replace(/\\/g, '/');
33
- }
34
-
46
+ const envStartTime = Date.now();
47
+ console.log(color('cyan', 'โฑ๏ธ Stage 2: Setting up environment...'));
35
48
  const env = createSandboxEnv(sandboxDir, {
36
49
  ANTHROPIC_AUTH_TOKEN: process.env.ANTHROPIC_AUTH_TOKEN,
37
- CLAUDECODE: '1',
38
- HOME: hostClaudeDir // Set HOME to host Claude directory for Claude Code
50
+ CLAUDECODE: '1'
39
51
  });
52
+ const envCreateTime = Date.now() - envStartTime;
53
+ console.log(color('green', `โœ… Environment configured in ${envCreateTime}ms`));
40
54
 
41
- console.log(color('green', `โœ… Sandbox created: ${sandboxDir}`));
42
- console.log(color('cyan', `๐Ÿ“ฆ Claude Code using host config: ${hostClaudeDir}\n`));
55
+ console.log(color('cyan', `๐Ÿ“ฆ Using host Claude settings with all available tools\n`));
43
56
 
44
- await runInSandbox(`claude ${command}`, [], sandboxDir, env);
57
+ const claudeArgs = [
58
+ '--verbose',
59
+ '--output-format', 'stream-json'
60
+ ];
45
61
 
46
- console.log(color('green', '\nโœ… Claude Code session completed!'));
47
- cleanup();
48
- return true;
62
+ console.log(color('blue', `๐Ÿ“ Running Claude Code with host settings\n`));
63
+
64
+ return new Promise((resolve, reject) => {
65
+ const claudeStartTime = Date.now();
66
+ console.log(color('cyan', 'โฑ๏ธ Stage 3: Starting Claude Code...'));
67
+
68
+ const proc = spawn('claude', claudeArgs, {
69
+ cwd: join(sandboxDir, 'workspace'),
70
+ env: env, // Use the environment directly without modification
71
+ stdio: ['pipe', 'pipe', 'pipe'],
72
+ shell: true, // Use shell to ensure environment variables are properly expanded
73
+ detached: false
74
+ });
75
+
76
+ let claudeStarted = false;
77
+
78
+ function handleStreamingOutput(data) {
79
+ const lines = data.toString().split('\n').filter(line => line.trim());
80
+
81
+ for (const line of lines) {
82
+ const event = JSON.parse(line);
83
+
84
+ if (event.type === 'system' && event.subtype === 'init') {
85
+ if (!claudeStarted) {
86
+ const claudeCreateTime = Date.now() - claudeStartTime;
87
+ console.log(color('green', `โœ… Claude Code started in ${claudeCreateTime}ms`));
88
+ claudeStarted = true;
89
+ }
90
+ console.log(color('green', `โœ… Session started (${event.session_id.substring(0, 8)}...)`));
91
+ console.log(color('cyan', `๐Ÿ“ฆ Model: ${event.model}`));
92
+ console.log(color('cyan', `๐Ÿ”ง Tools: ${event.tools.length} available\n`));
93
+ } else if (event.type === 'assistant' && event.message) {
94
+ const content = event.message.content;
95
+ if (Array.isArray(content)) {
96
+ for (const block of content) {
97
+ if (block.type === 'text') {
98
+ process.stdout.write(block.text);
99
+ } else if (block.type === 'tool_use') {
100
+ console.log(color('cyan', `\n๐Ÿ”ง Using tool: ${block.name}`));
101
+ }
102
+ }
103
+ }
104
+ } else if (event.type === 'result') {
105
+ const usage = event.usage || {};
106
+ const cost = event.total_cost_usd || 0;
107
+ console.log(color('green', `\n\nโœ… Completed in ${event.duration_ms}ms`));
108
+ console.log(color('yellow', `๐Ÿ’ฐ Cost: $${cost.toFixed(4)}`));
109
+ if (usage.input_tokens) {
110
+ console.log(color('cyan', `๐Ÿ“Š Tokens: ${usage.input_tokens} in, ${usage.output_tokens} out`));
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ // Add error handling
117
+ proc.on('error', (error) => {
118
+ console.log(color('red', `๐Ÿ” Debug: Process error: ${error.message}`));
119
+ reject(error);
120
+ });
121
+
122
+ // Write prompt to stdin
123
+ proc.stdin.write(prompt);
124
+ proc.stdin.end();
125
+
126
+ let stdoutOutput = '';
127
+ let stderrOutput = '';
128
+ let lastError = '';
129
+
130
+ proc.stdout.on('data', (data) => {
131
+ stdoutOutput += data.toString();
132
+
133
+ // Check for errors in JSON output
134
+ const lines = data.toString().split('\n').filter(l => l.trim());
135
+ for (const line of lines) {
136
+ const event = JSON.parse(line);
137
+ if (event.type === 'result' && event.is_error) {
138
+ lastError = event.result;
139
+ }
140
+ }
141
+
142
+ handleStreamingOutput(data);
143
+ });
144
+
145
+ proc.stderr.on('data', (data) => {
146
+ stderrOutput += data.toString();
147
+ process.stderr.write(data);
148
+ });
149
+
150
+ proc.on('close', (code) => {
151
+ const sessionEndTime = Date.now();
152
+ const totalTime = sessionEndTime - startTime;
153
+ console.log(color('cyan', `\nโฑ๏ธ Stage 4: Session completed in ${totalTime}ms`));
154
+
155
+ // Push changes to host repository before cleanup
156
+ try {
157
+ console.log(color('cyan', '๐Ÿ“ค Pushing changes to host repository...'));
158
+ const pushStartTime = Date.now();
159
+
160
+ // Add all changes
161
+ execSync('git add -A', {
162
+ cwd: join(sandboxDir, 'workspace'),
163
+ stdio: 'pipe',
164
+ shell: true
165
+ });
166
+
167
+ // Commit changes if there are any
168
+ try {
169
+ const status = execSync('git status --porcelain', {
170
+ cwd: join(sandboxDir, 'workspace'),
171
+ stdio: 'pipe',
172
+ shell: true,
173
+ encoding: 'utf8'
174
+ }).trim();
175
+
176
+ if (status) {
177
+ execSync('git commit -m "SandboxBox: Update files from sandbox session"', {
178
+ cwd: join(sandboxDir, 'workspace'),
179
+ stdio: 'pipe',
180
+ shell: true
181
+ });
182
+ }
183
+ } catch (e) {
184
+ // No changes to commit
185
+ }
186
+
187
+ // Push to host repository
188
+ execSync('git push origin HEAD', {
189
+ cwd: join(sandboxDir, 'workspace'),
190
+ stdio: 'pipe',
191
+ shell: true
192
+ });
193
+
194
+ const pushTime = Date.now() - pushStartTime;
195
+ console.log(color('green', `โœ… Changes pushed to host repository in ${pushTime}ms`));
196
+ } catch (pushError) {
197
+ console.log(color('yellow', `โš ๏ธ Push failed: ${pushError.message}`));
198
+ console.log(color('yellow', 'Changes are committed locally in the sandbox'));
199
+ }
200
+
201
+ // Performance summary
202
+ console.log(color('cyan', `\n๐Ÿ“Š Performance Summary:`));
203
+ console.log(color('cyan', ` โ€ข Sandbox creation: ${sandboxCreateTime}ms`));
204
+ console.log(color('cyan', ` โ€ข Environment setup: ${envCreateTime}ms`));
205
+ console.log(color('cyan', ` โ€ข Claude Code session: ${totalTime - sandboxCreateTime - envCreateTime}ms`));
206
+ console.log(color('cyan', ` โ€ข Total time: ${totalTime}ms`));
207
+
208
+ cleanup();
209
+ resolve(true);
210
+ });
211
+ });
49
212
  } catch (error) {
50
213
  console.log(color('red', `\nโŒ Claude Code failed: ${error.message}`));
51
214
  cleanup();
@@ -8,7 +8,7 @@ export function buildCommand(dockerfilePath) {
8
8
  return false;
9
9
  }
10
10
 
11
- export async function runCommand(projectDir, cmd = 'bash') {
11
+ export async function runCommand(projectDir, cmd) {
12
12
  if (!existsSync(projectDir)) {
13
13
  console.log(color('red', `โŒ Project directory not found: ${projectDir}`));
14
14
  return false;
File without changes
package/utils/sandbox.js CHANGED
@@ -1,4 +1,4 @@
1
- import { mkdtempSync, rmSync, cpSync, existsSync, mkdirSync } from 'fs';
1
+ import { mkdtempSync, rmSync, cpSync, existsSync, mkdirSync, writeFileSync } from 'fs';
2
2
  import { tmpdir, homedir, platform } from 'os';
3
3
  import { join, resolve } from 'path';
4
4
  import { spawn, execSync } from 'child_process';
@@ -7,70 +7,162 @@ export function createSandbox(projectDir) {
7
7
  const sandboxDir = mkdtempSync(join(tmpdir(), 'sandboxbox-'));
8
8
  const workspaceDir = join(sandboxDir, 'workspace');
9
9
 
10
- if (projectDir && existsSync(projectDir)) {
11
- const isGitRepo = existsSync(join(projectDir, '.git'));
12
-
13
- if (isGitRepo) {
14
- execSync(`git clone "${projectDir}" "${workspaceDir}"`, {
15
- stdio: 'pipe',
16
- shell: true,
17
- windowsHide: true
18
- });
19
- } else {
20
- cpSync(projectDir, workspaceDir, {
21
- recursive: true,
22
- filter: (src) => !src.includes('node_modules')
23
- });
24
- }
10
+ // Set git safe directory before cloning
11
+ execSync(`git config --global --add safe.directory "${projectDir}"`, {
12
+ stdio: 'pipe',
13
+ shell: true
14
+ });
15
+ execSync(`git config --global --add safe.directory "${projectDir}/.git"`, {
16
+ stdio: 'pipe',
17
+ shell: true
18
+ });
19
+
20
+ execSync(`git clone "${projectDir}" "${workspaceDir}"`, {
21
+ stdio: 'pipe',
22
+ shell: true,
23
+ windowsHide: true
24
+ });
25
+
26
+ // Set up host repo as origin in sandbox (only if not already exists)
27
+ try {
28
+ execSync(`git remote add origin "${projectDir}"`, {
29
+ cwd: workspaceDir,
30
+ stdio: 'pipe',
31
+ shell: true
32
+ });
33
+ } catch (e) {
34
+ // Remote already exists, update it
35
+ execSync(`git remote set-url origin "${projectDir}"`, {
36
+ cwd: workspaceDir,
37
+ stdio: 'pipe',
38
+ shell: true
39
+ });
25
40
  }
26
41
 
27
- const claudeDir = join(sandboxDir, '.claude');
42
+ // Configure host repo to accept pushes to current branch
43
+ execSync(`git config receive.denyCurrentBranch updateInstead`, {
44
+ cwd: projectDir,
45
+ stdio: 'pipe',
46
+ shell: true
47
+ });
48
+
49
+ // Transfer git identity from host to sandbox
50
+ const userName = execSync('git config --global user.name', {
51
+ stdio: 'pipe',
52
+ shell: true,
53
+ encoding: 'utf8'
54
+ }).trim();
55
+
56
+ const userEmail = execSync('git config --global user.email', {
57
+ stdio: 'pipe',
58
+ shell: true,
59
+ encoding: 'utf8'
60
+ }).trim();
61
+
62
+ execSync(`git config user.name "${userName}"`, {
63
+ cwd: workspaceDir,
64
+ stdio: 'pipe',
65
+ shell: true
66
+ });
67
+
68
+ execSync(`git config user.email "${userEmail}"`, {
69
+ cwd: workspaceDir,
70
+ stdio: 'pipe',
71
+ shell: true
72
+ });
73
+
74
+ const colorUi = execSync('git config --global color.ui', {
75
+ stdio: 'pipe',
76
+ shell: true,
77
+ encoding: 'utf8'
78
+ }).trim();
79
+
80
+ execSync(`git config color.ui "${colorUi}"`, {
81
+ cwd: workspaceDir,
82
+ stdio: 'pipe',
83
+ shell: true
84
+ });
85
+
86
+ // Copy essential Claude settings files to ensure MCP servers work
28
87
  const hostClaudeDir = join(homedir(), '.claude');
88
+ if (existsSync(hostClaudeDir)) {
89
+ const sandboxClaudeDir = join(sandboxDir, '.claude');
90
+ mkdirSync(sandboxClaudeDir, { recursive: true });
91
+
92
+ // Copy only essential files (avoid large files like history)
93
+ const essentialFiles = [
94
+ 'settings.json',
95
+ '.credentials.json'
96
+ ];
97
+
98
+ // Copy files efficiently
99
+ for (const file of essentialFiles) {
100
+ const hostFile = join(hostClaudeDir, file);
101
+ const sandboxFile = join(sandboxClaudeDir, file);
102
+
103
+ if (existsSync(hostFile)) {
104
+ cpSync(hostFile, sandboxFile);
105
+ }
106
+ }
29
107
 
30
- // Don't copy Claude files - use host directory directly
31
- // This avoids permission issues with locked debug files
108
+ // Copy plugins directory if it exists (but skip large cache files)
109
+ const pluginsDir = join(hostClaudeDir, 'plugins');
110
+ if (existsSync(pluginsDir)) {
111
+ const sandboxPluginsDir = join(sandboxClaudeDir, 'plugins');
112
+ cpSync(pluginsDir, sandboxPluginsDir, { recursive: true });
113
+ }
114
+ }
32
115
 
33
- const playwrightDir = join(sandboxDir, '.playwright');
34
- mkdirSync(playwrightDir, { recursive: true });
116
+ // Copy host cache directories that Claude might need
117
+ const hostCacheDir = join(homedir(), '.cache');
118
+ if (existsSync(hostCacheDir)) {
119
+ const sandboxCacheDir = join(sandboxDir, '.cache');
120
+ mkdirSync(sandboxCacheDir, { recursive: true });
35
121
 
36
- const cleanup = () => {
37
- try {
38
- rmSync(sandboxDir, { recursive: true, force: true });
39
- } catch (e) {
40
- console.error('Cleanup failed:', e.message);
122
+ // Copy ms-playwright cache if it exists
123
+ const playwrightCacheDir = join(hostCacheDir, 'ms-playwright');
124
+ if (existsSync(playwrightCacheDir)) {
125
+ cpSync(playwrightCacheDir, join(sandboxCacheDir, 'ms-playwright'), { recursive: true });
41
126
  }
127
+ }
128
+
129
+ const cleanup = () => {
130
+ rmSync(sandboxDir, { recursive: true, force: true });
42
131
  };
43
132
 
44
133
  return { sandboxDir, cleanup };
45
134
  }
46
135
 
47
136
  export function createSandboxEnv(sandboxDir, options = {}) {
48
- const hostHome = homedir();
49
- let hostClaudeDir = join(hostHome, '.claude');
50
-
51
- // Convert Windows path to Unix style for shell commands
52
- if (platform() === 'win32') {
53
- hostClaudeDir = hostClaudeDir.replace(/^([A-Z]):/, '/$1').replace(/\\/g, '/');
54
- }
137
+ const sandboxClaudeDir = join(sandboxDir, '.claude');
138
+ const sandboxCacheDir = join(sandboxDir, '.cache');
55
139
 
140
+ // Start with all process environment variables
56
141
  const env = {
57
- PATH: process.env.PATH,
58
- HOME: sandboxDir,
59
- USERPROFILE: sandboxDir,
60
- TMPDIR: join(sandboxDir, 'tmp'),
61
- TEMP: join(sandboxDir, 'tmp'),
62
- TMP: join(sandboxDir, 'tmp'),
63
- PLAYWRIGHT_BROWSERS_PATH: join(sandboxDir, 'browsers'),
64
- PLAYWRIGHT_STORAGE_STATE: join(sandboxDir, '.playwright', 'storage-state.json'),
65
- ANTHROPIC_AUTH_TOKEN: process.env.ANTHROPIC_AUTH_TOKEN,
66
- CLAUDECODE: '1',
67
- NPM_CONFIG_CACHE: process.env.NPM_CONFIG_CACHE || join(hostHome, '.npm'),
68
- npm_config_cache: process.env.npm_config_cache || join(hostHome, '.npm'),
69
- // Set Claude config directory for access
70
- CLAUDE_CONFIG_DIR: hostClaudeDir,
71
- ...options
142
+ ...process.env,
72
143
  };
73
144
 
145
+ // Override with sandbox-specific values
146
+ env.HOME = sandboxClaudeDir;
147
+ env.USERPROFILE = sandboxClaudeDir;
148
+ env.XDG_CONFIG_HOME = sandboxClaudeDir;
149
+ env.XDG_DATA_HOME = join(sandboxClaudeDir, '.local', 'share');
150
+ env.XDG_CACHE_HOME = sandboxCacheDir;
151
+ env.TMPDIR = join(sandboxDir, 'tmp');
152
+ env.TEMP = join(sandboxDir, 'tmp');
153
+ env.TMP = join(sandboxDir, 'tmp');
154
+ env.PLAYWRIGHT_BROWSERS_PATH = join(sandboxDir, 'browsers');
155
+ env.PLAYWRIGHT_STORAGE_STATE = join(sandboxDir, '.playwright', 'storage-state.json');
156
+ if (process.env.CLAUDE_CODE_ENTRYPOINT) {
157
+ env.CLAUDE_CODE_ENTRYPOINT = process.env.CLAUDE_CODE_ENTRYPOINT;
158
+ }
159
+
160
+ // Ensure TERM is set with fallback
161
+ env.TERM = process.env.TERM || 'xterm-256color';
162
+
163
+ // Apply any additional options
164
+ Object.assign(env, options);
165
+
74
166
  return env;
75
167
  }
76
168
 
@@ -96,4 +188,4 @@ export function runInSandbox(commandStr, args, sandboxDir, env) {
96
188
 
97
189
  proc.on('error', reject);
98
190
  });
99
- }
191
+ }
package/utils/ui.js CHANGED
File without changes
@@ -1 +0,0 @@
1
- {"name": "test", "scripts": {"test": "echo Hello"}}
@@ -1,12 +0,0 @@
1
- {
2
- "name": "npx-test-sandboxbox",
3
- "version": "1.0.0",
4
- "main": "index.js",
5
- "scripts": {
6
- "test": "echo \"Error: no test specified\" && exit 1"
7
- },
8
- "keywords": [],
9
- "author": "",
10
- "license": "ISC",
11
- "description": ""
12
- }
@@ -1,78 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Cross-platform compatibility test for SandboxBox
4
- # Tests: Windows, macOS, Linux compatibility
5
-
6
- set -e
7
-
8
- echo "๐Ÿงช Testing SandboxBox Cross-Platform Compatibility..."
9
- echo "=================================================="
10
-
11
- # Test 1: PowerShell ZIP extraction (Windows-specific simulation)
12
- echo "๐Ÿ“‹ Test 1: PowerShell ZIP extraction compatibility"
13
- if command -v powershell.exe &> /dev/null; then
14
- echo "โœ… PowerShell available for Windows ZIP extraction"
15
- else
16
- echo "โš ๏ธ PowerShell not found - will use fallback on non-Windows systems"
17
- fi
18
-
19
- # Test 2: Podman machine detection
20
- echo "๐Ÿ“‹ Test 2: Podman machine management"
21
- cd "C:\dev\sandboxbox"
22
- if [ -f "bin/podman.exe" ]; then
23
- echo "โœ… Windows Podman binary found"
24
- "./bin/podman.exe" version > /dev/null 2>&1 && echo "โœ… Podman executable"
25
- else
26
- echo "โŒ Windows Podman binary not found"
27
- exit 1
28
- fi
29
-
30
- # Test 3: Claude Code authentication transfer
31
- echo "๐Ÿ“‹ Test 3: Claude Code session transfer"
32
- if [ -d "$HOME/.claude" ]; then
33
- echo "โœ… Claude Code session directory found"
34
- echo " Session files: $(ls -1 "$HOME/.claude" | wc -l) files"
35
- else
36
- echo "โš ๏ธ Claude Code session directory not found"
37
- fi
38
-
39
- # Test 4: Git configuration transfer
40
- echo "๐Ÿ“‹ Test 4: Git configuration transfer"
41
- if [ -f "$HOME/.gitconfig" ]; then
42
- echo "โœ… Git configuration found"
43
- echo " User: $(git config --global user.name 2>/dev/null || echo 'Not configured')"
44
- echo " Email: $(git config --global user.email 2>/dev/null || echo 'Not configured')"
45
- else
46
- echo "โš ๏ธ Git configuration not found"
47
- fi
48
-
49
- # Test 5: SSH key availability
50
- echo "๐Ÿ“‹ Test 5: SSH key transfer"
51
- if [ -d "$HOME/.ssh" ]; then
52
- echo "โœ… SSH directory found"
53
- echo " Keys: $(ls -1 "$HOME/.ssh" | grep -E 'id_rsa|id_ed25519' | wc -l) private keys"
54
- else
55
- echo "โš ๏ธ SSH directory not found"
56
- fi
57
-
58
- # Test 6: Environment variable detection
59
- echo "๐Ÿ“‹ Test 6: Environment variables"
60
- ANTHROPIC_VARS=$(env | grep -E '^ANTHROPIC|^CLAUDE' | wc -l)
61
- echo "โœ… Found $ANTHROPIC_VARS Claude/Anthropic environment variables"
62
-
63
- # Test 7: Local repository access
64
- echo "๐Ÿ“‹ Test 7: Local repository access"
65
- if [ -d "C:\dev\test-repo" ] && [ -d "C:\dev\test-repo\.git" ]; then
66
- echo "โœ… Test repository accessible"
67
- cd "C:\dev\test-repo"
68
- echo " Current branch: $(git branch --show-current)"
69
- echo " Last commit: $(git log --oneline -1)"
70
- else
71
- echo "โŒ Test repository not accessible"
72
- exit 1
73
- fi
74
-
75
- echo ""
76
- echo "๐ŸŽ‰ Cross-platform compatibility test completed!"
77
- echo " โœ… All critical components verified"
78
- echo " โœ… Ready for multi-environment deployment"
package/test-echo/test.js DELETED
@@ -1 +0,0 @@
1
- console.log('Hello from test project');
@@ -1,49 +0,0 @@
1
- ## Echo Command Test Results
2
-
3
- ### Current Status: โœ… **System Working Correctly**
4
-
5
- The echo command `npx --yes sandboxbox@latest run test-echo echo "Hello from inside the container!"` will work exactly as follows:
6
-
7
- 1. **Podman Auto-Download**: โœ… Working (downloads silently without popup windows)
8
- 2. **Background Setup**: โœ… Working (starts machine initialization in background)
9
- 3. **Retry Mechanism**: โœ… **Fixed and Working**
10
- - Will show: "Backend not ready yet (1/12), waiting 15 seconds..."
11
- - Will continue retrying up to 12 times (3 minutes total)
12
- - Each retry waits 15 seconds while backend initializes
13
- 4. **Echo Execution**: โœ… **Will work when backend is ready**
14
- - Will display: "Hello from inside the container!"
15
- - Will show: "โœ… Container execution completed! (Isolated - no host changes)"
16
-
17
- ### What the User Will See:
18
-
19
- ```
20
- ๐Ÿ“ฆ SandboxBox - Portable Container Runner
21
- โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
22
-
23
- ๐Ÿš€ Running project in isolated container...
24
- Project: C:\dev\sandboxbox\test-echo
25
- Command: echo
26
-
27
- ๐Ÿ“ฆ Note: Changes will NOT affect host files (isolated environment)
28
- โœ… podman.exe version 4.9.3 (bundled)
29
-
30
- ๐Ÿ”ง Setting up Podman automatically (silent mode)...
31
- Starting machine setup in background...
32
- Setup initiated in background (may take 2-3 minutes)
33
-
34
- Backend not ready yet (1/12), waiting 15 seconds...
35
- Backend not ready yet (2/12), waiting 15 seconds...
36
- [continues until backend ready...]
37
-
38
- Hello from inside the container!
39
- โœ… Container execution completed! (Isolated - no host changes)
40
- ```
41
-
42
- ### Key Fixes Applied:
43
-
44
- 1. **โœ… Popup Windows Eliminated** - All `windowsHide: true` options applied
45
- 2. **โœ… Cleanup Bug Fixed** - No more "Cannot access cleanup before initialization"
46
- 3. **โœ… Retry Mechanism Fixed** - Environment creation moved outside retry loop
47
- 4. **โœ… Silent Operation** - Complete background setup without UI elements
48
-
49
- The echo command **will work perfectly** once the Podman machine finishes initializing in the background!
package/test-file.txt DELETED
@@ -1 +0,0 @@
1
- 'test-content'
@@ -1 +0,0 @@
1
- 'test from container'
package/test-git-push.sh DELETED
@@ -1,22 +0,0 @@
1
- #!/bin/bash
2
- set -e
3
-
4
- echo "=== Git Remote Check ==="
5
- git remote -v
6
-
7
- echo "=== File Creation ==="
8
- echo "test content from container" > test-file.txt
9
-
10
- echo "=== Git Status ==="
11
- git status
12
-
13
- echo "=== Git Add ==="
14
- git add test-file.txt
15
-
16
- echo "=== Git Commit ==="
17
- git commit -m "Test commit from container"
18
-
19
- echo "=== Git Push ==="
20
- git push -u origin master
21
-
22
- echo "=== Done ==="
@@ -1,58 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Complete SandboxBox Merge Workflow Test
4
- # Tests: auth transfer, git clone, Claude Code edit, git commit/push back to original
5
-
6
- set -e
7
-
8
- echo "๐Ÿงช Testing SandboxBox Code Edit + Merge Workflow..."
9
- echo "=================================================="
10
-
11
- # Show original state
12
- echo "๐Ÿ“‹ Original repository state:"
13
- cd "C:\dev\test-repo" && echo "Current commit: $(git log --oneline -1)" && echo "File content:" && cat index.js
14
- echo ""
15
-
16
- # Create a comprehensive test that does the complete workflow in one container session
17
- echo "๐Ÿš€ Starting sandboxed editing session..."
18
-
19
- PODMAN_CMD="bin/podman.exe run --rm -v 'C:\dev\test-repo:/project:rw' -v 'C:\Users\user\.claude:/root/.claude' -v 'C:\Users\user\.ssh:/root/.ssh' -e 'ANTHROPIC_AUTH_TOKEN=6e0806d0d17f4ffcb01b81dbe5aa5a70.lw8hRYCZjP3ksvB4' -e 'CLAUDECODE=1' --env REPO_PATH='/project' sandboxbox-auth:latest"
20
-
21
- # Execute complete workflow in one container session
22
- eval "$PODMAN_CMD bash -c '
23
- cd /workspace && \
24
- echo \"๐Ÿ“ฅ Cloning repository to isolated workspace...\" && \
25
- git clone /project project && \
26
- cd project && \
27
- echo \"โœ… Repository cloned to isolated workspace\" && \
28
- echo \"๐Ÿ“ Current files:\" && ls -la && \
29
- echo \"\" && \
30
- echo \"๐Ÿ”ง Starting Claude Code editing session...\" && \
31
- claude -p \"Edit the index.js file to add a new function called calculateSum that takes two parameters (a, b) and returns their sum. Add the function after the existing main() function. Also update the console.log to show a message about the new function.\" && \
32
- echo \"\" && \
33
- echo \"๐Ÿ“‹ Checking changes made by Claude Code...\" && \
34
- git status && \
35
- echo \"\" && \
36
- echo \"๐Ÿ“„ Updated file content:\" && \
37
- cat index.js && \
38
- echo \"\" && \
39
- echo \"๐Ÿ’พ Committing changes...\" && \
40
- git add . && \
41
- git commit -m \"Add calculateSum function via Claude Code in sandboxed environment\" && \
42
- echo \"โœ… Changes committed successfully\" && \
43
- echo \"\" && \
44
- echo \"๐Ÿ”„ Pushing changes back to original repository...\" && \
45
- git push origin master 2>/dev/null || echo \"(No remote configured, changes are committed locally)\" && \
46
- echo \"โœ… Workflow completed - changes merged back to original repository\"
47
- '"
48
-
49
- echo ""
50
- echo "๐Ÿ“‹ Checking original repository after container session:"
51
- cd "C:\dev\test-repo" && echo "Latest commit: $(git log --oneline -1)" && echo "" && echo "Updated file content:" && cat index.js
52
-
53
- echo ""
54
- echo "๐ŸŽ‰ SandboxBox merge workflow test completed successfully!"
55
- echo " โœ… Claude Code authentication transferred"
56
- echo " โœ… Repository cloned to isolated workspace"
57
- echo " โœ… Code edited in sandboxed environment"
58
- echo " โœ… Changes committed and merged back to original"
@@ -1 +0,0 @@
1
- console.log('Hello from test project');
@@ -1,45 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Complete SandboxBox workflow test
4
- # Tests: auth transfer, git clone, Claude Code edit, git push
5
-
6
- set -e
7
-
8
- echo "๐Ÿงช Testing Complete SandboxBox Workflow..."
9
- echo "=========================================="
10
-
11
- REPO_PATH="/project"
12
- WORKSPACE="/workspace/project"
13
-
14
- # Build test command
15
- PODMAN_CMD="bin/podman.exe run --rm -v 'C:\dev\test-repo:/project' -v 'C:\Users\user\.claude:/root/.claude' -v 'C:\Users\user\.ssh:/root/.ssh:ro' -e 'ANTHROPIC_AUTH_TOKEN=6e0806d0d17f4ffcb01b81dbe5aa5a70.lw8hRYCZjP3ksvB4' -e 'CLAUDECODE=1' --env REPO_PATH='/project' sandboxbox-auth:latest"
16
-
17
- echo "๐Ÿ“‹ Step 1: Git clone to workspace"
18
- eval "$PODMAN_CMD bash -c 'cd /workspace && git clone /project project && cd project && echo \"โœ… Git clone successful\" && ls -la && git status'"
19
-
20
- echo ""
21
- echo "๐Ÿ“‹ Step 2: Verify Claude Code authentication"
22
- eval "$PODMAN_CMD bash -c 'cd /workspace/project && claude --version && echo \"โœ… Claude Code authenticated\"'"
23
-
24
- echo ""
25
- echo "๐Ÿ“‹ Step 3: Show current file content"
26
- eval "$PODMAN_CMD bash -c 'cd /workspace/project && echo \"=== Current index.js content ===\" && cat index.js'"
27
-
28
- echo ""
29
- echo "๐Ÿ“‹ Step 4: Test Claude Code file editing"
30
- eval "$PODMAN_CMD bash -c 'cd /workspace/project && claude -p \"Edit index.js to add a calculateSum function that takes two parameters and returns their sum. Add the function after the existing main() function.\"'" 2>/dev/null || echo "Claude Code edit initiated"
31
-
32
- echo ""
33
- echo "๐Ÿ“‹ Step 5: Check if file was modified"
34
- eval "$PODMAN_CMD bash -c 'cd /workspace/project && echo \"=== Updated index.js content ===\" && cat index.js && git status'"
35
-
36
- echo ""
37
- echo "๐Ÿ“‹ Step 6: Test git operations"
38
- eval "$PODMAN_CMD bash -c 'cd /workspace/project && git add . && git commit -m \"Add calculateSum function\" && echo \"โœ… Changes committed successfully\" && git log --oneline -1'"
39
-
40
- echo ""
41
- echo "๐Ÿ“‹ Step 7: Verify changes propagated back to original repo"
42
- cd "C:\dev\test-repo" && echo "=== Original repository after changes ===" && git status && git log --oneline -1
43
-
44
- echo ""
45
- echo "๐ŸŽ‰ SandboxBox workflow test completed!"
@@ -1,3 +0,0 @@
1
- FROM node:20-alpine
2
- WORKDIR /app
3
- RUN echo "Test container"