sandboxbox 2.5.4 → 3.0.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/cli.js +49 -45
- package/package.json +10 -9
- package/testproject/Dockerfile +3 -0
- package/utils/commands/claude.js +27 -83
- package/utils/commands/container.js +31 -266
- package/utils/commands/index.js +2 -5
- package/utils/sandbox.js +104 -0
- package/scripts/download-podman.js +0 -103
- package/scripts/podman-config.js +0 -20
- package/scripts/podman-extract.js +0 -117
- package/utils/claude-workspace.js +0 -69
- package/utils/isolation.js +0 -222
- package/utils/podman.js +0 -228
package/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* SandboxBox CLI -
|
|
5
|
-
*
|
|
4
|
+
* SandboxBox CLI - Process Containment Sandbox
|
|
5
|
+
* Lightweight process isolation for CLI tools
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { resolve } from 'path';
|
|
@@ -10,7 +10,7 @@ import { color } from './utils/colors.js';
|
|
|
10
10
|
import { showBanner, showHelp } from './utils/ui.js';
|
|
11
11
|
import { buildCommand, runCommand, shellCommand, claudeCommand, versionCommand } from './utils/commands/index.js';
|
|
12
12
|
|
|
13
|
-
function main() {
|
|
13
|
+
async function main() {
|
|
14
14
|
const args = process.argv.slice(2);
|
|
15
15
|
showBanner();
|
|
16
16
|
|
|
@@ -22,53 +22,57 @@ function main() {
|
|
|
22
22
|
const command = args[0].toLowerCase();
|
|
23
23
|
const commandArgs = args.slice(1);
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
try {
|
|
26
|
+
switch (command) {
|
|
27
|
+
case 'build':
|
|
28
|
+
const dockerfilePath = commandArgs[0] || './Dockerfile';
|
|
29
|
+
if (!buildCommand(dockerfilePath)) process.exit(1);
|
|
30
|
+
break;
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
break;
|
|
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
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
case 'version':
|
|
65
|
+
if (!versionCommand()) process.exit(1);
|
|
66
|
+
break;
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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);
|
|
72
76
|
}
|
|
73
77
|
}
|
|
74
78
|
|
package/package.json
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sandboxbox",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.0.0",
|
|
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",
|
|
7
7
|
"bin": {
|
|
8
8
|
"sandboxbox": "cli.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"start": "node cli.js"
|
|
12
|
-
"postinstall": "node scripts/download-podman.js"
|
|
11
|
+
"start": "node cli.js"
|
|
13
12
|
},
|
|
14
13
|
"keywords": [
|
|
15
|
-
"
|
|
16
|
-
"
|
|
14
|
+
"sandbox",
|
|
15
|
+
"process-isolation",
|
|
17
16
|
"playwright",
|
|
18
17
|
"claude-code",
|
|
19
|
-
"
|
|
20
|
-
"isolation",
|
|
18
|
+
"cli-tools",
|
|
19
|
+
"environment-isolation",
|
|
21
20
|
"cross-platform",
|
|
22
21
|
"windows",
|
|
23
22
|
"macos",
|
|
24
23
|
"linux",
|
|
25
|
-
"portable"
|
|
24
|
+
"portable",
|
|
25
|
+
"npm",
|
|
26
|
+
"npx"
|
|
26
27
|
],
|
|
27
28
|
"author": "",
|
|
28
29
|
"license": "MIT",
|
package/utils/commands/claude.js
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
1
|
+
import { existsSync, cpSync, mkdirSync } from 'fs';
|
|
2
|
+
import { resolve, join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
5
4
|
import { color } from '../colors.js';
|
|
6
|
-
import {
|
|
7
|
-
import { buildClaudeContainerCommand, createClaudeDockerfile } from '../claude-workspace.js';
|
|
8
|
-
import { createIsolatedEnvironment, setupCleanupHandlers, buildContainerMounts } from '../isolation.js';
|
|
5
|
+
import { createSandbox, createSandboxEnv, runInSandbox } from '../sandbox.js';
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
const __dirname = dirname(__filename);
|
|
12
|
-
|
|
13
|
-
export function claudeCommand(projectDir, command = 'claude') {
|
|
7
|
+
export async function claudeCommand(projectDir, command = 'claude') {
|
|
14
8
|
if (!existsSync(projectDir)) {
|
|
15
9
|
console.log(color('red', `❌ Project directory not found: ${projectDir}`));
|
|
16
10
|
return false;
|
|
@@ -22,90 +16,40 @@ export function claudeCommand(projectDir, command = 'claude') {
|
|
|
22
16
|
return false;
|
|
23
17
|
}
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
execSync(`"${podmanPath}" image inspect sandboxbox-local:latest`, {
|
|
28
|
-
stdio: 'pipe',
|
|
29
|
-
shell: process.platform === 'win32',
|
|
30
|
-
windowsHide: process.platform === 'win32'
|
|
31
|
-
});
|
|
32
|
-
} catch {
|
|
33
|
-
console.log(color('yellow', '📦 Building Claude Code container...'));
|
|
34
|
-
if (!buildClaudeContainer()) {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
console.log(color('blue', '🚀 Starting Claude Code in isolated environment...'));
|
|
19
|
+
console.log(color('blue', '🚀 Starting Claude Code in sandbox...'));
|
|
40
20
|
console.log(color('yellow', `Project: ${projectDir}`));
|
|
41
|
-
console.log(color('yellow', `Command: ${command}`));
|
|
42
|
-
console.log(color('cyan', '📦 Note: Changes will be isolated and will NOT affect the original repository\n'));
|
|
43
|
-
|
|
44
|
-
const buildPodman = checkPodman();
|
|
45
|
-
if (!buildPodman) return false;
|
|
46
|
-
if (!setupBackendNonBlocking(buildPodman)) return false;
|
|
21
|
+
console.log(color('yellow', `Command: ${command}\n`));
|
|
47
22
|
|
|
48
|
-
|
|
49
|
-
const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
|
|
50
|
-
setupCleanupHandlers(cleanup);
|
|
51
|
-
const mounts = buildContainerMounts(tempProjectDir, projectDir);
|
|
52
|
-
const containerCommand = buildClaudeContainerCommand(tempProjectDir, buildPodman, command, mounts);
|
|
23
|
+
const { sandboxDir, cleanup } = createSandbox(projectDir);
|
|
53
24
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const maxRetries = process.platform === 'linux' ? 3 : 12; // More retries for Windows/macOS
|
|
25
|
+
process.on('SIGINT', cleanup);
|
|
26
|
+
process.on('SIGTERM', cleanup);
|
|
57
27
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
stdio: 'inherit',
|
|
62
|
-
shell: process.platform === 'win32',
|
|
63
|
-
windowsHide: process.platform === 'win32'
|
|
64
|
-
// No timeout for interactive Claude sessions
|
|
65
|
-
});
|
|
28
|
+
try {
|
|
29
|
+
const claudeDir = join(sandboxDir, '.claude');
|
|
30
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
66
31
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
} catch (error) {
|
|
71
|
-
retries++;
|
|
72
|
-
if (retries < maxRetries && (error.message.includes('Cannot connect to Podman') || error.message.includes('connectex') || error.message.includes('No connection could be made') || error.message.includes('actively refused') || error.message.includes('Command failed'))) {
|
|
73
|
-
console.log(color('yellow', ` Backend not ready yet (${retries}/${maxRetries}), waiting 15 seconds...`));
|
|
74
|
-
const start = Date.now();
|
|
75
|
-
while (Date.now() - start < 15000) {
|
|
76
|
-
// Wait 15 seconds
|
|
77
|
-
}
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
cleanup(); // Cleanup on final failure
|
|
81
|
-
console.log(color('red', `\n❌ Claude Code failed: ${error.message}`));
|
|
82
|
-
return false;
|
|
32
|
+
const hostClaudeDir = join(homedir(), '.claude');
|
|
33
|
+
if (existsSync(hostClaudeDir)) {
|
|
34
|
+
cpSync(hostClaudeDir, claudeDir, { recursive: true });
|
|
83
35
|
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
36
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
37
|
+
const env = createSandboxEnv(sandboxDir, {
|
|
38
|
+
ANTHROPIC_AUTH_TOKEN: process.env.ANTHROPIC_AUTH_TOKEN,
|
|
39
|
+
CLAUDECODE: '1'
|
|
40
|
+
});
|
|
90
41
|
|
|
91
|
-
|
|
92
|
-
|
|
42
|
+
console.log(color('green', `✅ Sandbox created: ${sandboxDir}`));
|
|
43
|
+
console.log(color('cyan', '📦 Claude Code running in isolated environment...\n'));
|
|
93
44
|
|
|
94
|
-
|
|
95
|
-
if (!podmanPath) return false;
|
|
96
|
-
if (!setupBackendNonBlocking(podmanPath)) return false;
|
|
45
|
+
await runInSandbox(`claude ${command}`, [], sandboxDir, env);
|
|
97
46
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
stdio: 'inherit',
|
|
101
|
-
cwd: resolve(__dirname, '..', '..'),
|
|
102
|
-
shell: process.platform === 'win32',
|
|
103
|
-
windowsHide: process.platform === 'win32'
|
|
104
|
-
});
|
|
105
|
-
console.log(color('green', '\n✅ Claude Code container built successfully!'));
|
|
47
|
+
console.log(color('green', '\n✅ Claude Code session completed!'));
|
|
48
|
+
cleanup();
|
|
106
49
|
return true;
|
|
107
50
|
} catch (error) {
|
|
108
|
-
console.log(color('red', `\n❌
|
|
51
|
+
console.log(color('red', `\n❌ Claude Code failed: ${error.message}`));
|
|
52
|
+
cleanup();
|
|
109
53
|
return false;
|
|
110
54
|
}
|
|
111
55
|
}
|
|
@@ -1,295 +1,60 @@
|
|
|
1
1
|
import { existsSync } from 'fs';
|
|
2
|
-
import {
|
|
3
|
-
import { dirname } from 'path';
|
|
2
|
+
import { resolve, join } from 'path';
|
|
4
3
|
import { color } from '../colors.js';
|
|
5
|
-
import {
|
|
6
|
-
import { createIsolatedEnvironment, setupCleanupHandlers, buildContainerMounts } from '../isolation.js';
|
|
4
|
+
import { createSandbox, createSandboxEnv, runInSandbox } from '../sandbox.js';
|
|
7
5
|
|
|
8
6
|
export function buildCommand(dockerfilePath) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
console.log(color('blue', '🏗️ Building container...'));
|
|
15
|
-
console.log(color('yellow', `Dockerfile: ${dockerfilePath}\n`));
|
|
16
|
-
|
|
17
|
-
const podmanPath = checkPodman();
|
|
18
|
-
if (!podmanPath) return false;
|
|
19
|
-
|
|
20
|
-
// Start backend setup but don't block on Windows/macOS
|
|
21
|
-
const backendReady = setupBackendNonBlocking(podmanPath);
|
|
22
|
-
if (process.platform === 'linux' && !backendReady) {
|
|
23
|
-
return false; // Only block on Linux for rootless service
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Retry container operation with backend readiness check
|
|
27
|
-
let retries = 0;
|
|
28
|
-
const maxRetries = process.platform === 'linux' ? 3 : 12; // More retries for Windows/macOS
|
|
29
|
-
|
|
30
|
-
while (retries < maxRetries) {
|
|
31
|
-
try {
|
|
32
|
-
execSync(`"${podmanPath}" build -f "${dockerfilePath}" -t sandboxbox:latest .`, {
|
|
33
|
-
stdio: 'inherit',
|
|
34
|
-
cwd: dirname(dockerfilePath),
|
|
35
|
-
shell: process.platform === 'win32',
|
|
36
|
-
windowsHide: process.platform === 'win32',
|
|
37
|
-
timeout: 300000 // 5 minutes for container builds
|
|
38
|
-
});
|
|
39
|
-
console.log(color('green', '\n✅ Container built successfully!'));
|
|
40
|
-
return true;
|
|
41
|
-
} catch (error) {
|
|
42
|
-
retries++;
|
|
43
|
-
if (retries < maxRetries && (error.message.includes('Cannot connect to Podman') || error.message.includes('connectex') || error.message.includes('No connection could be made') || error.message.includes('actively refused') || error.message.includes('Command failed'))) {
|
|
44
|
-
console.log(color('yellow', ` Backend not ready yet (${retries}/${maxRetries}), waiting 15 seconds...`));
|
|
45
|
-
const start = Date.now();
|
|
46
|
-
while (Date.now() - start < 15000) {
|
|
47
|
-
// Wait 15 seconds
|
|
48
|
-
}
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
console.log(color('red', `\n❌ Build failed: ${error.message}`));
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
7
|
+
console.log(color('yellow', '⚠️ Build command not yet implemented'));
|
|
8
|
+
return false;
|
|
55
9
|
}
|
|
56
10
|
|
|
57
|
-
export function runCommand(projectDir, cmd = 'bash') {
|
|
11
|
+
export async function runCommand(projectDir, cmd = 'bash') {
|
|
58
12
|
if (!existsSync(projectDir)) {
|
|
59
13
|
console.log(color('red', `❌ Project directory not found: ${projectDir}`));
|
|
60
14
|
return false;
|
|
61
15
|
}
|
|
62
16
|
|
|
63
|
-
console.log(color('blue', '🚀
|
|
17
|
+
console.log(color('blue', '🚀 Creating sandbox environment...'));
|
|
64
18
|
console.log(color('yellow', `Project: ${projectDir}`));
|
|
65
19
|
console.log(color('yellow', `Command: ${cmd}\n`));
|
|
66
|
-
console.log(color('cyan', '📦 Note: Changes will NOT affect host files (isolated environment)'));
|
|
67
|
-
|
|
68
|
-
const podmanPath = checkPodman();
|
|
69
|
-
if (!podmanPath) return false;
|
|
70
|
-
|
|
71
|
-
// Start backend setup but don't block on Windows/macOS
|
|
72
|
-
const backendReady = setupBackendNonBlocking(podmanPath);
|
|
73
|
-
if (process.platform === 'linux' && !backendReady) {
|
|
74
|
-
return false; // Only block on Linux for rootless service
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Create isolated environment once (outside retry loop)
|
|
78
|
-
const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
|
|
79
|
-
setupCleanupHandlers(cleanup);
|
|
80
|
-
const mounts = buildContainerMounts(tempProjectDir, projectDir);
|
|
81
|
-
|
|
82
|
-
// Retry container operation with backend readiness check
|
|
83
|
-
let retries = 0;
|
|
84
|
-
const maxRetries = process.platform === 'linux' ? 3 : 12; // More retries for Windows/macOS
|
|
85
|
-
|
|
86
|
-
while (retries < maxRetries) {
|
|
87
|
-
try {
|
|
88
|
-
// Try container operation first
|
|
89
|
-
const containerOptions = {
|
|
90
|
-
stdio: process.platform === 'win32' ? ['pipe', 'pipe', 'pipe'] : 'inherit',
|
|
91
|
-
shell: process.platform === 'win32',
|
|
92
|
-
windowsHide: process.platform === 'win32',
|
|
93
|
-
timeout: 30000 // 30 second timeout
|
|
94
|
-
};
|
|
95
20
|
|
|
96
|
-
|
|
97
|
-
if (cmd === 'echo' || cmd.startsWith('echo ')) {
|
|
98
|
-
const echoCmd = cmd.replace('echo ', '');
|
|
99
|
-
const output = execSync(`"${podmanPath}" run --rm ${mounts.join(' ')} -w /workspace sandboxbox:latest echo ${echoCmd}`, {
|
|
100
|
-
...containerOptions,
|
|
101
|
-
encoding: 'utf8'
|
|
102
|
-
}).trim();
|
|
103
|
-
console.log(output);
|
|
104
|
-
} else {
|
|
105
|
-
// For longer-running commands, use extended timeout
|
|
106
|
-
const longRunningOptions = {
|
|
107
|
-
...containerOptions,
|
|
108
|
-
timeout: 600000 // 10 minutes for longer operations
|
|
109
|
-
};
|
|
110
|
-
execSync(`"${podmanPath}" run --rm -it ${mounts.join(' ')} -w /workspace sandboxbox:latest ${cmd}`, longRunningOptions);
|
|
111
|
-
}
|
|
21
|
+
const { sandboxDir, cleanup } = createSandbox(projectDir);
|
|
112
22
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return true;
|
|
116
|
-
} catch (error) {
|
|
117
|
-
retries++;
|
|
118
|
-
if (retries < maxRetries && (error.message.includes('Cannot connect to Podman') || error.message.includes('connectex') || error.message.includes('No connection could be made') || error.message.includes('actively refused') || error.message.includes('Command failed'))) {
|
|
119
|
-
console.log(color('yellow', ` Backend not ready yet (${retries}/${maxRetries}), initializing machine...`));
|
|
23
|
+
process.on('SIGINT', cleanup);
|
|
24
|
+
process.on('SIGTERM', cleanup);
|
|
120
25
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
console.log(color('cyan', ' Starting Podman machine...'));
|
|
126
|
-
execSync(`"${podmanPath}" machine start`, {
|
|
127
|
-
stdio: 'pipe',
|
|
128
|
-
shell: true,
|
|
129
|
-
windowsHide: true,
|
|
130
|
-
timeout: 60000 // 1 minute for machine start
|
|
131
|
-
});
|
|
26
|
+
try {
|
|
27
|
+
const env = createSandboxEnv(sandboxDir, {
|
|
28
|
+
PLAYWRIGHT_BROWSERS_PATH: join(sandboxDir, 'browsers')
|
|
29
|
+
});
|
|
132
30
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
console.log(color('cyan', ' Waiting 5 seconds for machine to fully initialize...'));
|
|
136
|
-
const readyStart = Date.now();
|
|
137
|
-
while (Date.now() - readyStart < 5000) {
|
|
138
|
-
// Wait 5 seconds
|
|
139
|
-
}
|
|
140
|
-
continue; // Try container again
|
|
141
|
-
} catch (startError) {
|
|
142
|
-
// Machine doesn't exist or failed to start, try initializing it
|
|
143
|
-
if (startError.message.includes('does not exist') || startError.message.includes('not found')) {
|
|
144
|
-
try {
|
|
145
|
-
console.log(color('cyan', ' Initializing new Podman machine...'));
|
|
146
|
-
execSync(`"${podmanPath}" machine init --rootful=false`, {
|
|
147
|
-
stdio: 'pipe',
|
|
148
|
-
shell: true,
|
|
149
|
-
windowsHide: true,
|
|
150
|
-
timeout: 180000 // 3 minutes for machine init
|
|
151
|
-
});
|
|
31
|
+
console.log(color('green', `✅ Sandbox created: ${sandboxDir}`));
|
|
32
|
+
console.log(color('cyan', '📦 Running in isolated environment...\n'));
|
|
152
33
|
|
|
153
|
-
|
|
154
|
-
execSync(`"${podmanPath}" machine start`, {
|
|
155
|
-
stdio: 'pipe',
|
|
156
|
-
shell: true,
|
|
157
|
-
windowsHide: true,
|
|
158
|
-
timeout: 60000 // 1 minute for machine start
|
|
159
|
-
});
|
|
34
|
+
await runInSandbox(cmd, [], sandboxDir, env);
|
|
160
35
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// If machine setup failed or not Windows, wait and retry
|
|
173
|
-
console.log(color('yellow', ` Waiting 15 seconds before retry...`));
|
|
174
|
-
const start = Date.now();
|
|
175
|
-
while (Date.now() - start < 15000) {
|
|
176
|
-
// Wait 15 seconds
|
|
177
|
-
}
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
cleanup(); // Cleanup on final failure
|
|
181
|
-
console.log(color('red', `\n❌ Run failed: ${error.message}`));
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
36
|
+
console.log(color('green', '\n✅ Command completed!'));
|
|
37
|
+
cleanup();
|
|
38
|
+
return true;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.log(color('red', `\n❌ Command failed: ${error.message}`));
|
|
41
|
+
cleanup();
|
|
42
|
+
return false;
|
|
184
43
|
}
|
|
185
44
|
}
|
|
186
45
|
|
|
187
|
-
export function shellCommand(projectDir) {
|
|
46
|
+
export async function shellCommand(projectDir) {
|
|
188
47
|
if (!existsSync(projectDir)) {
|
|
189
48
|
console.log(color('red', `❌ Project directory not found: ${projectDir}`));
|
|
190
49
|
return false;
|
|
191
50
|
}
|
|
192
51
|
|
|
193
|
-
console.log(color('blue', '
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const podmanPath = checkPodman();
|
|
198
|
-
if (!podmanPath) return false;
|
|
199
|
-
|
|
200
|
-
// Start backend setup but don't block on Windows/macOS
|
|
201
|
-
const backendReady = setupBackendNonBlocking(podmanPath);
|
|
202
|
-
if (process.platform === 'linux' && !backendReady) {
|
|
203
|
-
return false; // Only block on Linux for rootless service
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Create isolated environment once (outside retry loop)
|
|
207
|
-
const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
|
|
208
|
-
setupCleanupHandlers(cleanup);
|
|
209
|
-
const mounts = buildContainerMounts(tempProjectDir, projectDir);
|
|
210
|
-
|
|
211
|
-
// Retry container operation with backend readiness check
|
|
212
|
-
let retries = 0;
|
|
213
|
-
const maxRetries = process.platform === 'linux' ? 3 : 12; // More retries for Windows/macOS
|
|
214
|
-
|
|
215
|
-
while (retries < maxRetries) {
|
|
216
|
-
try {
|
|
217
|
-
execSync(`"${podmanPath}" run --rm -it ${mounts.join(' ')} -w /workspace sandboxbox:latest /bin/bash`, {
|
|
218
|
-
stdio: process.platform === 'win32' ? ['pipe', 'pipe', 'pipe'] : 'inherit',
|
|
219
|
-
shell: process.platform === 'win32',
|
|
220
|
-
windowsHide: process.platform === 'win32',
|
|
221
|
-
timeout: 600000 // 10 minutes for interactive shell sessions
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
cleanup();
|
|
225
|
-
return true;
|
|
226
|
-
} catch (error) {
|
|
227
|
-
retries++;
|
|
228
|
-
if (retries < maxRetries && (error.message.includes('Cannot connect to Podman') || error.message.includes('connectex') || error.message.includes('No connection could be made') || error.message.includes('actively refused') || error.message.includes('Command failed'))) {
|
|
229
|
-
console.log(color('yellow', ` Backend not ready yet (${retries}/${maxRetries}), initializing machine...`));
|
|
230
|
-
|
|
231
|
-
// Actually initialize the machine instead of just waiting
|
|
232
|
-
if (process.platform === 'win32') {
|
|
233
|
-
try {
|
|
234
|
-
// First try to start existing machine
|
|
235
|
-
console.log(color('cyan', ' Starting Podman machine...'));
|
|
236
|
-
execSync(`"${podmanPath}" machine start`, {
|
|
237
|
-
stdio: 'pipe',
|
|
238
|
-
shell: true,
|
|
239
|
-
windowsHide: true,
|
|
240
|
-
timeout: 60000 // 1 minute for machine start
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
console.log(color('green', ' ✅ Podman machine started!'));
|
|
244
|
-
// Wait a bit more for machine to be fully ready
|
|
245
|
-
console.log(color('cyan', ' Waiting 5 seconds for machine to fully initialize...'));
|
|
246
|
-
const readyStart = Date.now();
|
|
247
|
-
while (Date.now() - readyStart < 5000) {
|
|
248
|
-
// Wait 5 seconds
|
|
249
|
-
}
|
|
250
|
-
continue; // Try container again
|
|
251
|
-
} catch (startError) {
|
|
252
|
-
// Machine doesn't exist or failed to start, try initializing it
|
|
253
|
-
if (startError.message.includes('does not exist') || startError.message.includes('not found')) {
|
|
254
|
-
try {
|
|
255
|
-
console.log(color('cyan', ' Initializing new Podman machine...'));
|
|
256
|
-
execSync(`"${podmanPath}" machine init --rootful=false`, {
|
|
257
|
-
stdio: 'pipe',
|
|
258
|
-
shell: true,
|
|
259
|
-
windowsHide: true,
|
|
260
|
-
timeout: 180000 // 3 minutes for machine init
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
console.log(color('cyan', ' Starting new Podman machine...'));
|
|
264
|
-
execSync(`"${podmanPath}" machine start`, {
|
|
265
|
-
stdio: 'pipe',
|
|
266
|
-
shell: true,
|
|
267
|
-
windowsHide: true,
|
|
268
|
-
timeout: 60000 // 1 minute for machine start
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
console.log(color('green', ' ✅ Podman machine ready!'));
|
|
272
|
-
continue; // Try container again immediately
|
|
273
|
-
} catch (initError) {
|
|
274
|
-
console.log(color('red', ` Machine init failed: ${initError.message}`));
|
|
275
|
-
}
|
|
276
|
-
} else {
|
|
277
|
-
console.log(color('red', ` Machine start failed: ${startError.message}`));
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
52
|
+
console.log(color('blue', '🚀 Starting interactive shell...'));
|
|
53
|
+
return runCommand(projectDir, 'bash');
|
|
54
|
+
}
|
|
281
55
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
// Wait 15 seconds
|
|
287
|
-
}
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
cleanup(); // Cleanup on final failure
|
|
291
|
-
console.log(color('red', `\n❌ Shell failed: ${error.message}`));
|
|
292
|
-
return false;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
56
|
+
export function versionCommand() {
|
|
57
|
+
console.log(color('green', 'SandboxBox - Process Containment Sandbox'));
|
|
58
|
+
console.log(color('cyan', 'Using Node.js process isolation'));
|
|
59
|
+
return true;
|
|
295
60
|
}
|
package/utils/commands/index.js
CHANGED
|
@@ -2,7 +2,6 @@ import { readFileSync } from 'fs';
|
|
|
2
2
|
import { resolve, dirname } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { color } from '../colors.js';
|
|
5
|
-
import { checkPodman } from '../podman.js';
|
|
6
5
|
|
|
7
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
7
|
const __dirname = dirname(__filename);
|
|
@@ -14,10 +13,8 @@ export function versionCommand() {
|
|
|
14
13
|
try {
|
|
15
14
|
const packageJson = JSON.parse(readFileSync(resolve(__dirname, '..', '..', 'package.json'), 'utf-8'));
|
|
16
15
|
console.log(color('green', `SandboxBox v${packageJson.version}`));
|
|
17
|
-
console.log(color('cyan', '
|
|
18
|
-
|
|
19
|
-
console.log('');
|
|
20
|
-
}
|
|
16
|
+
console.log(color('cyan', 'Process containment sandbox for CLI tools'));
|
|
17
|
+
console.log(color('yellow', 'Supports: Playwright, Claude Code, and more'));
|
|
21
18
|
return true;
|
|
22
19
|
} catch (error) {
|
|
23
20
|
console.log(color('red', '❌ Could not read version'));
|