sandboxbox 2.3.6 → 2.3.8
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 +4 -3
- package/package.json +1 -1
- package/scripts/podman-extract.js +9 -3
- package/test-npx-run/my-project/test.js +1 -0
- package/utils/commands/claude.js +31 -15
- package/utils/commands/container.js +61 -29
- package/utils/isolation.js +55 -14
- package/utils/podman.js +13 -8
package/cli.js
CHANGED
@@ -34,7 +34,8 @@ function main() {
|
|
34
34
|
console.log(color('yellow', 'Usage: npx sandboxbox run <project-dir> [command]'));
|
35
35
|
process.exit(1);
|
36
36
|
}
|
37
|
-
|
37
|
+
// For NPX usage, resolve relative to current working directory, not script location
|
38
|
+
const projectDir = resolve(process.cwd(), commandArgs[0]);
|
38
39
|
const cmd = commandArgs[1] || 'bash';
|
39
40
|
if (!runCommand(projectDir, cmd)) process.exit(1);
|
40
41
|
break;
|
@@ -45,7 +46,7 @@ function main() {
|
|
45
46
|
console.log(color('yellow', 'Usage: npx sandboxbox shell <project-dir>'));
|
46
47
|
process.exit(1);
|
47
48
|
}
|
48
|
-
const shellProjectDir = resolve(commandArgs[0]);
|
49
|
+
const shellProjectDir = resolve(process.cwd(), commandArgs[0]);
|
49
50
|
if (!shellCommand(shellProjectDir)) process.exit(1);
|
50
51
|
break;
|
51
52
|
|
@@ -55,7 +56,7 @@ function main() {
|
|
55
56
|
console.log(color('yellow', 'Usage: npx sandboxbox claude <project-dir>'));
|
56
57
|
process.exit(1);
|
57
58
|
}
|
58
|
-
const claudeProjectDir = resolve(commandArgs[0]);
|
59
|
+
const claudeProjectDir = resolve(process.cwd(), commandArgs[0]);
|
59
60
|
const claudeCmd = commandArgs.slice(1).join(' ') || 'claude';
|
60
61
|
if (!claudeCommand(claudeProjectDir, claudeCmd)) process.exit(1);
|
61
62
|
break;
|
package/package.json
CHANGED
@@ -10,7 +10,9 @@ export async function extractZip(zipPath, extractTo) {
|
|
10
10
|
|
11
11
|
execSync(`powershell -Command "${psCommand}"`, {
|
12
12
|
stdio: 'pipe',
|
13
|
-
shell: true
|
13
|
+
shell: true,
|
14
|
+
windowsHide: true,
|
15
|
+
timeout: 120000 // ZIP extraction can take time
|
14
16
|
});
|
15
17
|
|
16
18
|
resolve();
|
@@ -33,14 +35,18 @@ export async function extractTarGz(tarPath, extractTo, stripComponents = 0) {
|
|
33
35
|
try {
|
34
36
|
execSync(`tar -xf "${tarWithoutGz}" -C "${extractTo}"${stripComponents ? ` --strip-components=${stripComponents}` : ''}`, {
|
35
37
|
stdio: 'pipe',
|
36
|
-
shell: process.platform === 'win32'
|
38
|
+
shell: process.platform === 'win32',
|
39
|
+
windowsHide: process.platform === 'win32',
|
40
|
+
timeout: 120000
|
37
41
|
});
|
38
42
|
} catch (tarError) {
|
39
43
|
if (process.platform === 'win32') {
|
40
44
|
try {
|
41
45
|
execSync(`bsdtar -xf "${tarWithoutGz}" -C "${extractTo}"${stripComponents ? ` --strip-components=${stripComponents}` : ''}`, {
|
42
46
|
stdio: 'pipe',
|
43
|
-
shell: true
|
47
|
+
shell: true,
|
48
|
+
windowsHide: true,
|
49
|
+
timeout: 120000
|
44
50
|
});
|
45
51
|
} catch (bsdtarError) {
|
46
52
|
throw new Error(`Failed to extract tar archive. Please install tar or bsdtar: ${tarError.message}`);
|
@@ -0,0 +1 @@
|
|
1
|
+
console.log('Hello from test project');
|
package/utils/commands/claude.js
CHANGED
@@ -44,23 +44,39 @@ export function claudeCommand(projectDir, command = 'claude') {
|
|
44
44
|
if (!buildPodman) return false;
|
45
45
|
if (!setupBackendNonBlocking(buildPodman)) return false;
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
const mounts = buildContainerMounts(tempProjectDir, projectDir);
|
51
|
-
const containerCommand = buildClaudeContainerCommand(tempProjectDir, buildPodman, command, mounts);
|
47
|
+
// Retry container operation with backend readiness check
|
48
|
+
let retries = 0;
|
49
|
+
const maxRetries = process.platform === 'linux' ? 3 : 12; // More retries for Windows/macOS
|
52
50
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
51
|
+
while (retries < maxRetries) {
|
52
|
+
try {
|
53
|
+
const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
|
54
|
+
setupCleanupHandlers(cleanup);
|
55
|
+
const mounts = buildContainerMounts(tempProjectDir, projectDir);
|
56
|
+
const containerCommand = buildClaudeContainerCommand(tempProjectDir, buildPodman, command, mounts);
|
57
57
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
58
|
+
execSync(containerCommand, {
|
59
|
+
stdio: 'inherit',
|
60
|
+
shell: process.platform === 'win32',
|
61
|
+
timeout: 30000 // 30 second timeout
|
62
|
+
});
|
63
|
+
|
64
|
+
cleanup();
|
65
|
+
console.log(color('green', '\n✅ Claude Code session completed! (Isolated - no host changes)'));
|
66
|
+
return true;
|
67
|
+
} catch (error) {
|
68
|
+
retries++;
|
69
|
+
if (retries < maxRetries && error.message.includes('Cannot connect to Podman')) {
|
70
|
+
console.log(color('yellow', ` Backend not ready yet (${retries}/${maxRetries}), waiting 15 seconds...`));
|
71
|
+
const start = Date.now();
|
72
|
+
while (Date.now() - start < 15000) {
|
73
|
+
// Wait 15 seconds
|
74
|
+
}
|
75
|
+
continue;
|
76
|
+
}
|
77
|
+
console.log(color('red', `\n❌ Claude Code failed: ${error.message}`));
|
78
|
+
return false;
|
79
|
+
}
|
64
80
|
}
|
65
81
|
}
|
66
82
|
|
@@ -73,22 +73,38 @@ export function runCommand(projectDir, cmd = 'bash') {
|
|
73
73
|
return false; // Only block on Linux for rootless service
|
74
74
|
}
|
75
75
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
76
|
+
// Retry container operation with backend readiness check
|
77
|
+
let retries = 0;
|
78
|
+
const maxRetries = process.platform === 'linux' ? 3 : 12; // More retries for Windows/macOS
|
79
|
+
|
80
|
+
while (retries < maxRetries) {
|
81
|
+
try {
|
82
|
+
const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
|
83
|
+
setupCleanupHandlers(cleanup);
|
84
|
+
const mounts = buildContainerMounts(tempProjectDir, projectDir);
|
85
|
+
|
86
|
+
execSync(`"${podmanPath}" run --rm -it ${mounts.join(' ')} -w /workspace sandboxbox:latest ${cmd}`, {
|
87
|
+
stdio: 'inherit',
|
88
|
+
shell: process.platform === 'win32',
|
89
|
+
timeout: 30000 // 30 second timeout
|
90
|
+
});
|
91
|
+
|
92
|
+
cleanup();
|
93
|
+
console.log(color('green', '\n✅ Container execution completed! (Isolated - no host changes)'));
|
94
|
+
return true;
|
95
|
+
} catch (error) {
|
96
|
+
retries++;
|
97
|
+
if (retries < maxRetries && error.message.includes('Cannot connect to Podman')) {
|
98
|
+
console.log(color('yellow', ` Backend not ready yet (${retries}/${maxRetries}), waiting 15 seconds...`));
|
99
|
+
const start = Date.now();
|
100
|
+
while (Date.now() - start < 15000) {
|
101
|
+
// Wait 15 seconds
|
102
|
+
}
|
103
|
+
continue;
|
104
|
+
}
|
105
|
+
console.log(color('red', `\n❌ Run failed: ${error.message}`));
|
106
|
+
return false;
|
107
|
+
}
|
92
108
|
}
|
93
109
|
}
|
94
110
|
|
@@ -111,20 +127,36 @@ export function shellCommand(projectDir) {
|
|
111
127
|
return false; // Only block on Linux for rootless service
|
112
128
|
}
|
113
129
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
const mounts = buildContainerMounts(tempProjectDir, projectDir);
|
130
|
+
// Retry container operation with backend readiness check
|
131
|
+
let retries = 0;
|
132
|
+
const maxRetries = process.platform === 'linux' ? 3 : 12; // More retries for Windows/macOS
|
118
133
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
134
|
+
while (retries < maxRetries) {
|
135
|
+
try {
|
136
|
+
const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
|
137
|
+
setupCleanupHandlers(cleanup);
|
138
|
+
const mounts = buildContainerMounts(tempProjectDir, projectDir);
|
123
139
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
140
|
+
execSync(`"${podmanPath}" run --rm -it ${mounts.join(' ')} -w /workspace sandboxbox:latest /bin/bash`, {
|
141
|
+
stdio: 'inherit',
|
142
|
+
shell: process.platform === 'win32',
|
143
|
+
timeout: 30000 // 30 second timeout
|
144
|
+
});
|
145
|
+
|
146
|
+
cleanup();
|
147
|
+
return true;
|
148
|
+
} catch (error) {
|
149
|
+
retries++;
|
150
|
+
if (retries < maxRetries && error.message.includes('Cannot connect to Podman')) {
|
151
|
+
console.log(color('yellow', ` Backend not ready yet (${retries}/${maxRetries}), waiting 15 seconds...`));
|
152
|
+
const start = Date.now();
|
153
|
+
while (Date.now() - start < 15000) {
|
154
|
+
// Wait 15 seconds
|
155
|
+
}
|
156
|
+
continue;
|
157
|
+
}
|
158
|
+
console.log(color('red', `\n❌ Shell failed: ${error.message}`));
|
159
|
+
return false;
|
160
|
+
}
|
129
161
|
}
|
130
162
|
}
|
package/utils/isolation.js
CHANGED
@@ -44,12 +44,15 @@ export function createIsolatedEnvironment(projectDir) {
|
|
44
44
|
if (process.platform === 'win32') {
|
45
45
|
execSync(`powershell -Command "New-Item -ItemType Directory -Path '${tempProjectDir}' -Force"`, {
|
46
46
|
stdio: 'pipe',
|
47
|
-
shell: true
|
47
|
+
shell: true,
|
48
|
+
windowsHide: true,
|
49
|
+
timeout: 30000
|
48
50
|
});
|
49
51
|
} else {
|
50
52
|
execSync(`mkdir -p "${tempProjectDir}"`, {
|
51
53
|
stdio: 'pipe',
|
52
|
-
shell: true
|
54
|
+
shell: true,
|
55
|
+
timeout: 30000
|
53
56
|
});
|
54
57
|
}
|
55
58
|
|
@@ -57,27 +60,53 @@ export function createIsolatedEnvironment(projectDir) {
|
|
57
60
|
// Windows approach - include hidden files like .git
|
58
61
|
execSync(`powershell -Command "Copy-Item -Path '${projectDir}\\*' -Destination '${tempProjectDir}' -Recurse -Force -Exclude 'node_modules'"`, {
|
59
62
|
stdio: 'pipe',
|
60
|
-
shell: true
|
63
|
+
shell: true,
|
64
|
+
windowsHide: true,
|
65
|
+
timeout: 60000 // Copy operations can take longer
|
61
66
|
});
|
62
67
|
// Also copy hidden files separately
|
63
68
|
execSync(`powershell -Command "Get-ChildItem -Path '${projectDir}' -Force -Name | Where-Object { $_ -like '.*' } | ForEach-Object { Copy-Item -Path (Join-Path '${projectDir}' $_) -Destination '${tempProjectDir}' -Recurse -Force }"`, {
|
64
69
|
stdio: 'pipe',
|
65
|
-
shell: true
|
70
|
+
shell: true,
|
71
|
+
windowsHide: true,
|
72
|
+
timeout: 60000
|
66
73
|
});
|
67
74
|
} else {
|
68
75
|
// Unix approach - include hidden files
|
69
76
|
execSync(`cp -r "${projectDir}"/.* "${tempProjectDir}/" 2>/dev/null || true`, {
|
70
77
|
stdio: 'pipe',
|
71
|
-
shell: true
|
78
|
+
shell: true,
|
79
|
+
timeout: 60000
|
72
80
|
});
|
73
81
|
execSync(`cp -r "${projectDir}"/* "${tempProjectDir}/"`, {
|
74
82
|
stdio: 'pipe',
|
75
|
-
shell: true
|
83
|
+
shell: true,
|
84
|
+
timeout: 60000
|
76
85
|
});
|
77
86
|
}
|
78
87
|
|
79
|
-
// Configure git remote to point to mounted host repository
|
88
|
+
// Configure git remote to point to mounted host repository (only if git repo)
|
80
89
|
try {
|
90
|
+
// Check if the project directory is a git repository
|
91
|
+
const gitDirPath = process.platform === 'win32'
|
92
|
+
? `${projectDir}\\.git`
|
93
|
+
: `${projectDir}/.git`;
|
94
|
+
|
95
|
+
if (!existsSync(gitDirPath)) {
|
96
|
+
// Not a git repository, skip git setup
|
97
|
+
return { tempDir, tempProjectDir, cleanup };
|
98
|
+
}
|
99
|
+
|
100
|
+
// Also check if the temp directory has .git after copy
|
101
|
+
const tempGitDirPath = process.platform === 'win32'
|
102
|
+
? `${tempProjectDir}\\.git`
|
103
|
+
: `${tempProjectDir}/.git`;
|
104
|
+
|
105
|
+
if (!existsSync(tempGitDirPath)) {
|
106
|
+
// Copy didn't preserve git, skip git setup
|
107
|
+
return { tempDir, tempProjectDir, cleanup };
|
108
|
+
}
|
109
|
+
|
81
110
|
// Normalize paths for cross-platform compatibility
|
82
111
|
const normalizedTempDir = tempProjectDir.replace(/\\/g, '/');
|
83
112
|
const normalizedOriginalDir = projectDir.replace(/\\/g, '/');
|
@@ -85,7 +114,9 @@ export function createIsolatedEnvironment(projectDir) {
|
|
85
114
|
// Configure git to allow operations in mounted directories
|
86
115
|
execSync(`git config --global --add safe.directory /workspace`, {
|
87
116
|
stdio: 'pipe',
|
88
|
-
shell: true
|
117
|
+
shell: true,
|
118
|
+
windowsHide: process.platform === 'win32',
|
119
|
+
timeout: 10000
|
89
120
|
});
|
90
121
|
|
91
122
|
// Configure host repository to accept pushes to checked-out branch
|
@@ -93,7 +124,9 @@ export function createIsolatedEnvironment(projectDir) {
|
|
93
124
|
try {
|
94
125
|
execSync(`cd "${normalizedOriginalDir}" && git config receive.denyCurrentBranch ignore`, {
|
95
126
|
stdio: 'pipe',
|
96
|
-
shell: true
|
127
|
+
shell: true,
|
128
|
+
windowsHide: true,
|
129
|
+
timeout: 10000
|
97
130
|
});
|
98
131
|
} catch (e) {
|
99
132
|
// Ignore if git config fails
|
@@ -101,7 +134,8 @@ export function createIsolatedEnvironment(projectDir) {
|
|
101
134
|
} else {
|
102
135
|
execSync(`cd "${normalizedOriginalDir}" && git config receive.denyCurrentBranch ignore`, {
|
103
136
|
stdio: 'pipe',
|
104
|
-
shell: true
|
137
|
+
shell: true,
|
138
|
+
timeout: 10000
|
105
139
|
});
|
106
140
|
}
|
107
141
|
|
@@ -110,7 +144,9 @@ export function createIsolatedEnvironment(projectDir) {
|
|
110
144
|
try {
|
111
145
|
execSync(`cd "${normalizedTempDir}" && git remote remove origin`, {
|
112
146
|
stdio: 'pipe',
|
113
|
-
shell: true
|
147
|
+
shell: true,
|
148
|
+
windowsHide: true,
|
149
|
+
timeout: 10000
|
114
150
|
});
|
115
151
|
} catch (e) {
|
116
152
|
// Ignore if origin doesn't exist
|
@@ -118,20 +154,25 @@ export function createIsolatedEnvironment(projectDir) {
|
|
118
154
|
} else {
|
119
155
|
execSync(`cd "${normalizedTempDir}" && git remote remove origin 2>/dev/null || true`, {
|
120
156
|
stdio: 'pipe',
|
121
|
-
shell: true
|
157
|
+
shell: true,
|
158
|
+
timeout: 10000
|
122
159
|
});
|
123
160
|
}
|
124
161
|
|
125
162
|
// Add origin pointing to mounted host repository (accessible from container)
|
126
163
|
execSync(`cd "${normalizedTempDir}" && git remote add origin /host-repo`, {
|
127
164
|
stdio: 'pipe',
|
128
|
-
shell: true
|
165
|
+
shell: true,
|
166
|
+
windowsHide: process.platform === 'win32',
|
167
|
+
timeout: 10000
|
129
168
|
});
|
130
169
|
|
131
170
|
// Set up upstream tracking for current branch (use push -u to set upstream)
|
132
171
|
const currentBranch = execSync(`cd "${normalizedTempDir}" && git branch --show-current`, {
|
133
172
|
encoding: 'utf8',
|
134
|
-
stdio: 'pipe'
|
173
|
+
stdio: 'pipe',
|
174
|
+
windowsHide: process.platform === 'win32',
|
175
|
+
timeout: 10000
|
135
176
|
}).trim();
|
136
177
|
|
137
178
|
// Note: Upstream will be set automatically on first push with -u flag
|
package/utils/podman.js
CHANGED
@@ -94,21 +94,26 @@ function setupMachineBackground(podmanPath) {
|
|
94
94
|
? `"${podmanPath}" machine init --rootful=false`
|
95
95
|
: `"${podmanPath}" machine init`;
|
96
96
|
|
97
|
-
|
98
|
-
|
97
|
+
// Windows-specific: Use completely hidden process execution
|
98
|
+
const spawnOptions = process.platform === 'win32' ? {
|
99
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
100
|
+
shell: true,
|
101
|
+
detached: true,
|
102
|
+
windowsHide: true, // Hide the console window on Windows
|
103
|
+
cwd: process.cwd() // Ensure working directory is set
|
104
|
+
} : {
|
105
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
99
106
|
shell: true,
|
100
107
|
detached: true
|
101
|
-
}
|
108
|
+
};
|
102
109
|
|
110
|
+
const initProcess = spawn(initCmd, spawnOptions);
|
103
111
|
initProcess.unref();
|
104
112
|
|
105
113
|
// Start machine after init completes (with delay)
|
106
114
|
setTimeout(() => {
|
107
|
-
const
|
108
|
-
|
109
|
-
shell: true,
|
110
|
-
detached: true
|
111
|
-
});
|
115
|
+
const startCmd = `"${podmanPath}" machine start`;
|
116
|
+
const startProcess = spawn(startCmd, spawnOptions);
|
112
117
|
startProcess.unref();
|
113
118
|
}, 30000); // Wait 30 seconds for init to complete
|
114
119
|
|