sandboxbox 3.0.74 → 3.0.76

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.
@@ -48,6 +48,6 @@
48
48
  "dependencies": {
49
49
  "@playwright/mcp": "^0.0.45",
50
50
  "mcp-glootie": "^3.4.67",
51
- "vexify": "^0.16.28"
51
+ "vexify": "^0.19.1"
52
52
  }
53
53
  }
package/.mcp.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://schemas.modelcontextprotocol.io/0.1.0/mcp.json",
3
+ "mcpServers": {
4
+ "sandboxbox": {
5
+ "command": "npx",
6
+ "args": ["sandboxbox", "mcp"]
7
+ }
8
+ }
9
+ }
package/CHANGELOG.md CHANGED
@@ -1,23 +1,31 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [3.0.75] - 2025-11-13
4
4
 
5
5
  ### Added
6
- - `.claude-sandbox/` directory structure for bundled Claude settings and plugins
7
- - Automatic plugin path rewriting during sandbox creation to point to sandbox locations
8
- - Bundled glootie-cc MCP plugin in `.claude-sandbox/plugins/marketplaces/`
9
- - MCP servers configuration in plugin.json and settings.json (glootie, playwright, vexify)
10
- - Automatic MCP server path rewriting to use absolute sandbox directory paths
6
+ - MCP server mode: Run sandboxbox as an MCP server with `npx sandboxbox mcp`
7
+ - `sandboxbox_run` MCP tool: Execute prompts in isolated sandboxbox environments via MCP protocol
8
+ - `.mcp.json` configuration file for easy Claude Code integration
9
+ - Fetch polyfill in container: undici package installed globally with automatic polyfill via NODE_OPTIONS
10
+ - Build command implementation: `npx sandboxbox build` to rebuild container images
11
+ - Plugin registry files: installed_plugins.json and known_marketplaces.json in .claude-sandbox/
12
+ - Automatic path rewriting for all plugin configuration files during sandbox creation
11
13
 
12
14
  ### Changed
13
- - Sandbox creation now uses `.claude-sandbox/` configuration instead of `sandboxbox-settings.json`
14
- - Plugin paths in `config.json` are automatically updated to sandbox-relative paths
15
- - MCP server paths are rewritten from ${HOME} placeholders to actual sandbox paths
16
- - Simplified Claude settings management with repository-based configuration
17
-
18
- ### Known Issues
19
- - MCP tools not loading in Claude Code CLI - configuration is correct but servers not starting
20
- - Requires investigation into Claude Code CLI MCP server loading mechanism
15
+ - Container claude wrapper uses NODE_OPTIONS to inject fetch polyfill automatically
16
+ - Removed enabledPlugins from .claude-sandbox/settings.json (controlled by config.json)
17
+ - Updated cli.js to support 'mcp' command
18
+ - Added @modelcontextprotocol/sdk and undici dependencies
19
+ - Dockerfile creates fetch-init.mjs for global fetch availability
20
+
21
+ ### Fixed
22
+ - Fetch API not available in container: Added undici polyfill loaded via NODE_OPTIONS
23
+ - Plugin paths now correctly rewritten in installed_plugins.json and known_marketplaces.json
24
+ - Build command now functional with proper podman integration
25
+
26
+ ### Removed
27
+ - Manual MCP server configurations from settings.json (delegated to plugin's .mcp.json)
28
+ - Firewall script from Dockerfile (not essential for core functionality)
21
29
 
22
30
  ## [3.0.64] - 2025-10-27
23
31
 
package/CLAUDE.md CHANGED
@@ -78,8 +78,15 @@ if (process.platform === 'win32') {
78
78
  1. Copy project to temporary directory (including .git)
79
79
  2. Mount temporary directory as /workspace in container
80
80
  3. Run commands in isolated environment
81
- 4. Clean up temporary directory on exit
82
- 5. Changes persist via git push to host repository
81
+ 4. Auto-commit and push changes to host repository
82
+ 5. Clean up temporary directory on exit
83
+
84
+ ### Auto-Commit & Push (v3.0.76+)
85
+ - Cleanup function automatically detects uncommitted changes
86
+ - Auto-commits with timestamped message: "sandboxbox auto-commit: {ISO timestamp}"
87
+ - Auto-pushes to host repository before sandbox cleanup
88
+ - Changes persist automatically without manual git operations
89
+ - Graceful error handling - cleanup continues even if git operations fail
83
90
 
84
91
  ### Pattern
85
92
  ```javascript
package/Dockerfile CHANGED
@@ -78,18 +78,27 @@ RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/
78
78
  -a "export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
79
79
  -x
80
80
 
81
- # Install Claude
82
- RUN npm install -g @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}
83
-
84
- # Install playwright deps (commented out due to build issues)
85
- # RUN npx --yes playwright install-deps
81
+ # Install Claude and dependencies
82
+ RUN npm install -g @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION} undici
86
83
 
87
84
  RUN npm i -g @playwright/mcp
88
85
 
89
- # Copy and set up firewall script
90
- COPY init-firewall.sh /usr/local/bin/
86
+ # Switch to root to create wrapper scripts
91
87
  USER root
92
- RUN chmod +x /usr/local/bin/init-firewall.sh && \
93
- echo "node ALL=(root) NOPASSWD: /usr/local/bin/init-firewall.sh" > /etc/sudoers.d/node-firewall && \
94
- chmod 0440 /etc/sudoers.d/node-firewall
88
+
89
+ # Create fetch polyfill init script
90
+ RUN echo 'import { fetch, Headers, Request, Response } from "undici";\n\
91
+ globalThis.fetch = fetch;\n\
92
+ globalThis.Headers = Headers;\n\
93
+ globalThis.Request = Request;\n\
94
+ globalThis.Response = Response;\n\
95
+ ' > /usr/local/lib/fetch-init.mjs
96
+
97
+ # Create wrapper script that uses the init
98
+ RUN echo '#!/bin/bash\n\
99
+ NODE_OPTIONS="--import=/usr/local/lib/fetch-init.mjs" exec /home/node/.local/bin/claude "$@"\n\
100
+ ' > /usr/local/bin/claude-with-fetch && \
101
+ chmod +x /usr/local/bin/claude-with-fetch && \
102
+ ln -sf /usr/local/bin/claude-with-fetch /usr/local/share/npm-global/bin/claude
103
+
95
104
  USER node
package/cli.js CHANGED
@@ -1,10 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- /**
4
- * SandboxBox CLI - Process Containment Sandbox
5
- * Lightweight process isolation for CLI tools
6
- */
7
-
8
3
  import { resolve } from 'path';
9
4
  import { color } from './utils/colors.js';
10
5
  import { showBanner, showHelp } from './utils/ui.js';
@@ -76,6 +71,23 @@ async function main() {
76
71
  if (!versionCommand()) process.exit(1);
77
72
  break;
78
73
 
74
+ case 'mcp':
75
+ const { spawn } = await import('child_process');
76
+ const { fileURLToPath } = await import('url');
77
+ const { dirname } = await import('path');
78
+ const __filename = fileURLToPath(import.meta.url);
79
+ const __dirname = dirname(__filename);
80
+ const mcpServerPath = resolve(__dirname, 'utils', 'mcp-server.js');
81
+
82
+ const mcpProcess = spawn('node', [mcpServerPath], {
83
+ stdio: 'inherit'
84
+ });
85
+
86
+ mcpProcess.on('exit', (code) => {
87
+ process.exit(code || 0);
88
+ });
89
+ break;
90
+
79
91
  default:
80
92
  console.log(color('red', `❌ Unknown command: ${command}`));
81
93
  console.log(color('yellow', 'Use --help for usage information'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sandboxbox",
3
- "version": "3.0.74",
3
+ "version": "3.0.76",
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",
@@ -29,7 +29,10 @@
29
29
  ],
30
30
  "author": "",
31
31
  "license": "MIT",
32
- "dependencies": {},
32
+ "dependencies": {
33
+ "@modelcontextprotocol/sdk": "^1.21.1",
34
+ "undici": "^7.16.0"
35
+ },
33
36
  "repository": {
34
37
  "type": "git",
35
38
  "url": "git+https://github.com/AnEntrypoint/sandboxbox.git"
@@ -1,23 +1,23 @@
1
+ import { fetch } from 'undici';
2
+ if (!globalThis.fetch) {
3
+ globalThis.fetch = fetch;
4
+ }
5
+
1
6
  import { existsSync, writeFileSync, appendFileSync } from 'fs';
2
7
  import { resolve, join } from 'path';
3
8
  import { spawn, execSync } from 'child_process';
4
9
  import { color } from '../colors.js';
5
10
  import { createSandbox, createSandboxEnv } from '../sandbox.js';
6
- // ClaudeOptimizer disabled to preserve bundled hooks
7
- // import { ClaudeOptimizer } from '../claude-optimizer.js';
8
11
  import { SystemOptimizer } from '../system-optimizer.js';
9
12
 
10
- // Console output configuration
11
13
  const MAX_CONSOLE_LINES = parseInt(process.env.SANDBOX_MAX_CONSOLE_LINES) || 5;
12
14
  const MAX_LOG_ENTRY_LENGTH = parseInt(process.env.SANDBOX_MAX_LOG_LENGTH) || 200;
13
15
  const ENABLE_FILE_LOGGING = process.env.SANDBOX_ENABLE_FILE_LOGGING === 'true';
14
16
  const VERBOSE_OUTPUT = process.env.SANDBOX_VERBOSE === 'true' || process.argv.includes('--verbose');
15
17
  global.toolCallLog = [];
16
18
  global.logFileHandle = null;
17
- global.pendingToolCalls = new Map(); // Track tool calls by ID for result matching
18
- global.conversationalBuffer = ''; // Track conversational text between tool calls
19
-
20
- // Helper function to extract tool metadata without showing actual content
19
+ global.pendingToolCalls = new Map();
20
+ global.conversationalBuffer = '';
21
21
  function extractToolMetadata(toolUse) {
22
22
  const metadata = {
23
23
  name: toolUse.name || 'unknown',
@@ -316,10 +316,13 @@ ${prompt}`;
316
316
  // Environment is now properly configured with same permissions as run command
317
317
 
318
318
  const proc = spawn('claude', claudeArgs, {
319
- cwd: workspacePath, // Set working directory directly
320
- env: env, // Use the same environment as run command for Git permissions
319
+ cwd: workspacePath,
320
+ env: {
321
+ ...env,
322
+ NODE_OPTIONS: '--import=/usr/local/lib/fetch-init.mjs'
323
+ },
321
324
  stdio: ['pipe', 'pipe', 'pipe'],
322
- shell: false, // Don't use shell since we're setting cwd directly
325
+ shell: false,
323
326
  detached: false
324
327
  });
325
328
 
@@ -1,11 +1,38 @@
1
1
  import { existsSync } from 'fs';
2
- import { resolve, join } from 'path';
2
+ import { resolve, join, dirname } from 'path';
3
+ import { execSync } from 'child_process';
3
4
  import { color } from '../colors.js';
4
5
  import { createSandbox, createSandboxEnv, runInSandbox } from '../sandbox.js';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
5
10
 
6
11
  export function buildCommand(dockerfilePath) {
7
- console.log(color('yellow', '⚠️ Build command not yet implemented'));
8
- return false;
12
+ const repoRoot = resolve(__dirname, '..', '..');
13
+ const dockerfile = dockerfilePath || join(repoRoot, 'Dockerfile');
14
+
15
+ if (!existsSync(dockerfile)) {
16
+ console.log(color('red', `❌ Dockerfile not found: ${dockerfile}`));
17
+ return false;
18
+ }
19
+
20
+ console.log(color('cyan', `📦 Building sandboxbox container from ${dockerfile}...`));
21
+
22
+ try {
23
+ const buildContext = dirname(dockerfile);
24
+
25
+ execSync(`podman build -t sandboxbox:latest -f "${dockerfile}" "${buildContext}"`, {
26
+ stdio: 'inherit',
27
+ shell: process.platform === 'win32'
28
+ });
29
+
30
+ console.log(color('green', '✅ Container built successfully!'));
31
+ return true;
32
+ } catch (error) {
33
+ console.log(color('red', `❌ Build failed: ${error.message}`));
34
+ return false;
35
+ }
9
36
  }
10
37
 
11
38
  export async function runCommand(projectDir, cmd) {
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
6
+ import { spawn } from 'child_process';
7
+ import { fileURLToPath } from 'url';
8
+ import { dirname, resolve } from 'path';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+
13
+ const server = new Server(
14
+ {
15
+ name: 'sandboxbox',
16
+ version: '1.0.0',
17
+ },
18
+ {
19
+ capabilities: {
20
+ tools: {},
21
+ },
22
+ }
23
+ );
24
+
25
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
26
+ return {
27
+ tools: [
28
+ {
29
+ name: 'sandboxbox_run',
30
+ description: 'Execute a prompt in an isolated sandboxbox environment. Runs Claude Code with the given prompt in a temporary containerized workspace.',
31
+ inputSchema: {
32
+ type: 'object',
33
+ properties: {
34
+ prompt: {
35
+ type: 'string',
36
+ description: 'The prompt to execute in the sandboxbox environment',
37
+ },
38
+ directory: {
39
+ type: 'string',
40
+ description: 'The directory to run in (defaults to current directory)',
41
+ },
42
+ },
43
+ required: ['prompt'],
44
+ },
45
+ },
46
+ ],
47
+ };
48
+ });
49
+
50
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
51
+ if (request.params.name !== 'sandboxbox_run') {
52
+ throw new Error(`Unknown tool: ${request.params.name}`);
53
+ }
54
+
55
+ const { prompt, directory } = request.params.arguments;
56
+
57
+ if (!prompt || typeof prompt !== 'string') {
58
+ throw new Error('prompt is required and must be a string');
59
+ }
60
+
61
+ const targetDir = directory || process.cwd();
62
+ const cliPath = resolve(__dirname, '..', 'cli.js');
63
+
64
+ return new Promise((resolve, reject) => {
65
+ const args = ['claude', targetDir, prompt];
66
+ const child = spawn('node', [cliPath, ...args], {
67
+ stdio: ['ignore', 'pipe', 'pipe'],
68
+ env: { ...process.env, SANDBOX_VERBOSE: 'false' }
69
+ });
70
+
71
+ let stdout = '';
72
+ let stderr = '';
73
+
74
+ child.stdout.on('data', (data) => {
75
+ stdout += data.toString();
76
+ });
77
+
78
+ child.stderr.on('data', (data) => {
79
+ stderr += data.toString();
80
+ });
81
+
82
+ child.on('close', (code) => {
83
+ if (code !== 0) {
84
+ resolve({
85
+ content: [{
86
+ type: 'text',
87
+ text: `Error (exit code ${code}):\n${stderr || stdout}`,
88
+ }],
89
+ isError: true,
90
+ });
91
+ } else {
92
+ resolve({
93
+ content: [{
94
+ type: 'text',
95
+ text: stdout || 'Command completed successfully',
96
+ }],
97
+ });
98
+ }
99
+ });
100
+
101
+ child.on('error', (error) => {
102
+ resolve({
103
+ content: [{
104
+ type: 'text',
105
+ text: `Error: ${error.message}`,
106
+ }],
107
+ isError: true,
108
+ });
109
+ });
110
+ });
111
+ });
112
+
113
+ async function main() {
114
+ const transport = new StdioServerTransport();
115
+ await server.connect(transport);
116
+ }
117
+
118
+ main().catch((error) => {
119
+ console.error('Server error:', error);
120
+ process.exit(1);
121
+ });
package/utils/sandbox.js CHANGED
@@ -325,6 +325,66 @@ node_modules/
325
325
  }
326
326
 
327
327
  const cleanup = () => {
328
+ const VERBOSE_OUTPUT = process.env.SANDBOX_VERBOSE === 'true' || process.argv.includes('--verbose');
329
+
330
+ // Push any committed changes back to host before cleanup
331
+ try {
332
+ // Check if there are any uncommitted changes
333
+ const status = execSync(`git status --porcelain`, {
334
+ cwd: workspaceDir,
335
+ encoding: 'utf8',
336
+ stdio: 'pipe'
337
+ }).trim();
338
+
339
+ if (status) {
340
+ // Add all changes
341
+ execSync(`git add -A`, {
342
+ cwd: workspaceDir,
343
+ stdio: 'pipe',
344
+ shell: true
345
+ });
346
+
347
+ // Commit with timestamp
348
+ const commitMessage = `sandboxbox auto-commit: ${new Date().toISOString()}
349
+
350
+ 🤖 Generated with SandboxBox
351
+ Changes made during sandboxbox session`;
352
+
353
+ execSync(`git commit -m "${commitMessage}"`, {
354
+ cwd: workspaceDir,
355
+ stdio: 'pipe',
356
+ shell: true
357
+ });
358
+
359
+ if (VERBOSE_OUTPUT) {
360
+ console.log('✅ Committed sandbox changes');
361
+ }
362
+ }
363
+
364
+ // Push to host repository (origin points to host)
365
+ try {
366
+ execSync(`git push origin HEAD`, {
367
+ cwd: workspaceDir,
368
+ stdio: 'pipe',
369
+ shell: true
370
+ });
371
+
372
+ if (VERBOSE_OUTPUT) {
373
+ console.log('✅ Pushed changes to host repository');
374
+ }
375
+ } catch (pushError) {
376
+ // Push might fail if there are no changes or network issues
377
+ if (VERBOSE_OUTPUT) {
378
+ console.log('⚠️ Could not push to host repository');
379
+ }
380
+ }
381
+ } catch (error) {
382
+ // Don't fail cleanup if git operations fail
383
+ if (VERBOSE_OUTPUT) {
384
+ console.log(`⚠️ Git sync failed: ${error.message}`);
385
+ }
386
+ }
387
+
328
388
  // Close any log files that might be open
329
389
  if (global.logFileHandle) {
330
390
  try {
@@ -334,6 +394,7 @@ node_modules/
334
394
  // Don't fail on log cleanup
335
395
  }
336
396
  }
397
+
337
398
  rmSync(sandboxDir, { recursive: true, force: true });
338
399
  };
339
400
 
package/utils/ui.js CHANGED
@@ -18,6 +18,7 @@ export function showHelp() {
18
18
  console.log(' run <project-dir> [cmd] Run project in container');
19
19
  console.log(' shell <project-dir> Start interactive shell');
20
20
  console.log(' claude <project-dir> [prompt] [--host] [--headless] Start Claude Code with Git integration');
21
+ console.log(' mcp Start as MCP server (stdio transport)');
21
22
  console.log(' version Show version information');
22
23
  console.log('');
23
24
  console.log(color('yellow', 'Claude Command Options:'));