sandboxbox 2.3.7 → 2.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
- const projectDir = resolve(commandArgs[0]);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sandboxbox",
3
- "version": "2.3.7",
3
+ "version": "2.3.9",
4
4
  "description": "Portable container runner with Podman - Claude Code & Playwright support. Works on Windows, macOS, and Linux.",
5
5
  "type": "module",
6
6
  "main": "cli.js",
@@ -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');
@@ -26,7 +26,8 @@ export function claudeCommand(projectDir, command = 'claude') {
26
26
  try {
27
27
  execSync(`"${podmanPath}" image inspect sandboxbox-local:latest`, {
28
28
  stdio: 'pipe',
29
- shell: process.platform === 'win32'
29
+ shell: process.platform === 'win32',
30
+ windowsHide: process.platform === 'win32'
30
31
  });
31
32
  } catch {
32
33
  console.log(color('yellow', 'šŸ“¦ Building Claude Code container...'));
@@ -44,23 +45,40 @@ export function claudeCommand(projectDir, command = 'claude') {
44
45
  if (!buildPodman) return false;
45
46
  if (!setupBackendNonBlocking(buildPodman)) return false;
46
47
 
47
- try {
48
- const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
49
- setupCleanupHandlers(cleanup);
50
- const mounts = buildContainerMounts(tempProjectDir, projectDir);
51
- const containerCommand = buildClaudeContainerCommand(tempProjectDir, buildPodman, command, mounts);
48
+ // Retry container operation with backend readiness check
49
+ let retries = 0;
50
+ const maxRetries = process.platform === 'linux' ? 3 : 12; // More retries for Windows/macOS
52
51
 
53
- execSync(containerCommand, {
54
- stdio: 'inherit',
55
- shell: process.platform === 'win32'
56
- });
52
+ while (retries < maxRetries) {
53
+ try {
54
+ const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
55
+ setupCleanupHandlers(cleanup);
56
+ const mounts = buildContainerMounts(tempProjectDir, projectDir);
57
+ const containerCommand = buildClaudeContainerCommand(tempProjectDir, buildPodman, command, mounts);
57
58
 
58
- cleanup();
59
- console.log(color('green', '\nāœ… Claude Code session completed! (Isolated - no host changes)'));
60
- return true;
61
- } catch (error) {
62
- console.log(color('red', `\nāŒ Claude Code failed: ${error.message}`));
63
- return false;
59
+ execSync(containerCommand, {
60
+ stdio: 'inherit',
61
+ shell: process.platform === 'win32',
62
+ windowsHide: process.platform === 'win32',
63
+ timeout: 30000 // 30 second timeout
64
+ });
65
+
66
+ cleanup();
67
+ console.log(color('green', '\nāœ… Claude Code session completed! (Isolated - no host changes)'));
68
+ return true;
69
+ } catch (error) {
70
+ retries++;
71
+ if (retries < maxRetries && error.message.includes('Cannot connect to Podman')) {
72
+ console.log(color('yellow', ` Backend not ready yet (${retries}/${maxRetries}), waiting 15 seconds...`));
73
+ const start = Date.now();
74
+ while (Date.now() - start < 15000) {
75
+ // Wait 15 seconds
76
+ }
77
+ continue;
78
+ }
79
+ console.log(color('red', `\nāŒ Claude Code failed: ${error.message}`));
80
+ return false;
81
+ }
64
82
  }
65
83
  }
66
84
 
@@ -79,7 +97,8 @@ function buildClaudeContainer() {
79
97
  execSync(`"${podmanPath}" build -f "${dockerfilePath}" -t sandboxbox-local:latest .`, {
80
98
  stdio: 'inherit',
81
99
  cwd: resolve(__dirname, '..', '..'),
82
- shell: process.platform === 'win32'
100
+ shell: process.platform === 'win32',
101
+ windowsHide: process.platform === 'win32'
83
102
  });
84
103
  console.log(color('green', '\nāœ… Claude Code container built successfully!'));
85
104
  return true;
@@ -33,6 +33,7 @@ export function buildCommand(dockerfilePath) {
33
33
  stdio: 'inherit',
34
34
  cwd: dirname(dockerfilePath),
35
35
  shell: process.platform === 'win32',
36
+ windowsHide: process.platform === 'win32',
36
37
  timeout: 30000 // 30 second timeout
37
38
  });
38
39
  console.log(color('green', '\nāœ… Container built successfully!'));
@@ -73,22 +74,39 @@ export function runCommand(projectDir, cmd = 'bash') {
73
74
  return false; // Only block on Linux for rootless service
74
75
  }
75
76
 
76
- try {
77
- const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
78
- setupCleanupHandlers(cleanup);
79
- const mounts = buildContainerMounts(tempProjectDir, projectDir);
80
-
81
- execSync(`"${podmanPath}" run --rm -it ${mounts.join(' ')} -w /workspace sandboxbox:latest ${cmd}`, {
82
- stdio: 'inherit',
83
- shell: process.platform === 'win32'
84
- });
85
-
86
- cleanup();
87
- console.log(color('green', '\nāœ… Container execution completed! (Isolated - no host changes)'));
88
- return true;
89
- } catch (error) {
90
- console.log(color('red', `\nāŒ Run failed: ${error.message}`));
91
- return false;
77
+ // Retry container operation with backend readiness check
78
+ let retries = 0;
79
+ const maxRetries = process.platform === 'linux' ? 3 : 12; // More retries for Windows/macOS
80
+
81
+ while (retries < maxRetries) {
82
+ try {
83
+ const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
84
+ setupCleanupHandlers(cleanup);
85
+ const mounts = buildContainerMounts(tempProjectDir, projectDir);
86
+
87
+ execSync(`"${podmanPath}" run --rm -it ${mounts.join(' ')} -w /workspace sandboxbox:latest ${cmd}`, {
88
+ stdio: 'inherit',
89
+ shell: process.platform === 'win32',
90
+ windowsHide: process.platform === 'win32',
91
+ timeout: 30000 // 30 second timeout
92
+ });
93
+
94
+ cleanup();
95
+ console.log(color('green', '\nāœ… Container execution completed! (Isolated - no host changes)'));
96
+ return true;
97
+ } catch (error) {
98
+ retries++;
99
+ if (retries < maxRetries && error.message.includes('Cannot connect to Podman')) {
100
+ console.log(color('yellow', ` Backend not ready yet (${retries}/${maxRetries}), waiting 15 seconds...`));
101
+ const start = Date.now();
102
+ while (Date.now() - start < 15000) {
103
+ // Wait 15 seconds
104
+ }
105
+ continue;
106
+ }
107
+ console.log(color('red', `\nāŒ Run failed: ${error.message}`));
108
+ return false;
109
+ }
92
110
  }
93
111
  }
94
112
 
@@ -111,20 +129,37 @@ export function shellCommand(projectDir) {
111
129
  return false; // Only block on Linux for rootless service
112
130
  }
113
131
 
114
- try {
115
- const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
116
- setupCleanupHandlers(cleanup);
117
- const mounts = buildContainerMounts(tempProjectDir, projectDir);
132
+ // Retry container operation with backend readiness check
133
+ let retries = 0;
134
+ const maxRetries = process.platform === 'linux' ? 3 : 12; // More retries for Windows/macOS
118
135
 
119
- execSync(`"${podmanPath}" run --rm -it ${mounts.join(' ')} -w /workspace sandboxbox:latest /bin/bash`, {
120
- stdio: 'inherit',
121
- shell: process.platform === 'win32'
122
- });
136
+ while (retries < maxRetries) {
137
+ try {
138
+ const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
139
+ setupCleanupHandlers(cleanup);
140
+ const mounts = buildContainerMounts(tempProjectDir, projectDir);
123
141
 
124
- cleanup();
125
- return true;
126
- } catch (error) {
127
- console.log(color('red', `\nāŒ Shell failed: ${error.message}`));
128
- return false;
142
+ execSync(`"${podmanPath}" run --rm -it ${mounts.join(' ')} -w /workspace sandboxbox:latest /bin/bash`, {
143
+ stdio: 'inherit',
144
+ shell: process.platform === 'win32',
145
+ windowsHide: process.platform === 'win32',
146
+ timeout: 30000 // 30 second timeout
147
+ });
148
+
149
+ cleanup();
150
+ return true;
151
+ } catch (error) {
152
+ retries++;
153
+ if (retries < maxRetries && error.message.includes('Cannot connect to Podman')) {
154
+ console.log(color('yellow', ` Backend not ready yet (${retries}/${maxRetries}), waiting 15 seconds...`));
155
+ const start = Date.now();
156
+ while (Date.now() - start < 15000) {
157
+ // Wait 15 seconds
158
+ }
159
+ continue;
160
+ }
161
+ console.log(color('red', `\nāŒ Shell failed: ${error.message}`));
162
+ return false;
163
+ }
129
164
  }
130
165
  }
@@ -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
- const initProcess = spawn(initCmd, {
98
- stdio: ['pipe', 'pipe', 'pipe'],
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 startProcess = spawn(`"${podmanPath}" machine start`, {
108
- stdio: ['pipe', 'pipe', 'pipe'],
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