sandboxbox 3.0.48 → 3.0.49
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/package/package/CLAUDE.md +200 -0
- package/package/package/Dockerfile +95 -0
- package/package/package/README.md +242 -0
- package/package/package/claude-settings.json +85 -0
- package/package/package/cli.js +90 -0
- package/package/package/package.json +39 -0
- package/package/package/sandboxbox-3.0.45.tgz +0 -0
- package/package/package/sandboxbox-settings.json +40 -0
- package/package/package/test.txt +1 -0
- package/package/package/utils/claude-optimizer.js +129 -0
- package/package/package/utils/colors.js +15 -0
- package/package/package/utils/commands/claude.js +501 -0
- package/package/package/utils/commands/container.js +60 -0
- package/package/package/utils/commands/index.js +23 -0
- package/package/package/utils/sandbox.js +341 -0
- package/package/package/utils/system-optimizer.js +231 -0
- package/package/package/utils/ui.js +38 -0
- package/package/package.json +1 -1
- package/package/sandboxbox-3.0.46.tgz +0 -0
- package/package/utils/commands/claude.js +7 -6
- package/package.json +1 -1
- package/sandboxbox-3.0.48.tgz +0 -0
- package/sandboxbox-settings.json +16 -1
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, cpSync, existsSync, mkdirSync, writeFileSync, symlinkSync, realpathSync, readFileSync, appendFileSync } from 'fs';
|
|
2
|
+
import { tmpdir, homedir, platform } from 'os';
|
|
3
|
+
import { join, resolve, dirname } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { spawn, execSync } from 'child_process';
|
|
6
|
+
|
|
7
|
+
export function createSandbox(projectDir, options = {}) {
|
|
8
|
+
const { useHostSettings = false, headlessMode = false } = options;
|
|
9
|
+
const sandboxDir = mkdtempSync(join(tmpdir(), 'sandboxbox-'));
|
|
10
|
+
const workspaceDir = join(sandboxDir, 'workspace');
|
|
11
|
+
const VERBOSE_OUTPUT = process.env.SANDBOX_VERBOSE === 'true' || process.argv.includes('--verbose');
|
|
12
|
+
|
|
13
|
+
// Ensure host directory is a git repository
|
|
14
|
+
if (!existsSync(join(projectDir, '.git'))) {
|
|
15
|
+
// Initialize git repository in host directory if it doesn't exist
|
|
16
|
+
execSync(`git init "${projectDir}"`, {
|
|
17
|
+
stdio: 'pipe',
|
|
18
|
+
shell: true,
|
|
19
|
+
windowsHide: true
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Configure global git safe directories
|
|
24
|
+
execSync(`git config --global --add safe.directory "${projectDir}"`, {
|
|
25
|
+
stdio: 'pipe',
|
|
26
|
+
shell: true,
|
|
27
|
+
windowsHide: true
|
|
28
|
+
});
|
|
29
|
+
execSync(`git config --global --add safe.directory "${projectDir}/.git"`, {
|
|
30
|
+
stdio: 'pipe',
|
|
31
|
+
shell: true,
|
|
32
|
+
windowsHide: true
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Configure host repository to accept pushes to current branch
|
|
36
|
+
try {
|
|
37
|
+
execSync(`cd "${projectDir}" && git config receive.denyCurrentBranch updateInstead`, {
|
|
38
|
+
stdio: 'pipe',
|
|
39
|
+
shell: true,
|
|
40
|
+
windowsHide: true
|
|
41
|
+
});
|
|
42
|
+
} catch (error) {
|
|
43
|
+
// Silently skip repository configuration
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Copy/clone the project to workspace
|
|
47
|
+
if (existsSync(join(projectDir, '.git'))) {
|
|
48
|
+
// If it's a git repo, do a shallow clone
|
|
49
|
+
execSync(`git clone --depth 1 --no-tags "${projectDir}" "${workspaceDir}"`, {
|
|
50
|
+
stdio: 'pipe',
|
|
51
|
+
shell: true,
|
|
52
|
+
windowsHide: true
|
|
53
|
+
});
|
|
54
|
+
} else {
|
|
55
|
+
// If not a git repo, just copy the files
|
|
56
|
+
mkdirSync(workspaceDir, { recursive: true });
|
|
57
|
+
execSync(`cp -r "${projectDir}"/* "${workspaceDir}/"`, {
|
|
58
|
+
stdio: 'pipe',
|
|
59
|
+
shell: true,
|
|
60
|
+
windowsHide: true
|
|
61
|
+
});
|
|
62
|
+
// Initialize git in workspace
|
|
63
|
+
execSync(`git init "${workspaceDir}"`, {
|
|
64
|
+
stdio: 'pipe',
|
|
65
|
+
shell: true,
|
|
66
|
+
windowsHide: true
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Ensure .claude is in .gitignore in the sandbox workspace
|
|
71
|
+
const gitignorePath = join(workspaceDir, '.gitignore');
|
|
72
|
+
if (!existsSync(gitignorePath)) {
|
|
73
|
+
writeFileSync(gitignorePath, `# Claude Code settings (project-specific, not to be committed)
|
|
74
|
+
.claude/
|
|
75
|
+
|
|
76
|
+
# Dependencies
|
|
77
|
+
node_modules/
|
|
78
|
+
*.log
|
|
79
|
+
.DS_Store
|
|
80
|
+
`);
|
|
81
|
+
} else {
|
|
82
|
+
// Add .claude to existing .gitignore if not already present
|
|
83
|
+
const gitignoreContent = readFileSync(gitignorePath, 'utf8');
|
|
84
|
+
if (!gitignoreContent.includes('.claude/')) {
|
|
85
|
+
writeFileSync(gitignorePath, gitignoreContent + '\n# Claude Code settings (project-specific, not to be committed)\n.claude/\n');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Set up host repo as origin in sandbox (pointing to host directory)
|
|
90
|
+
try {
|
|
91
|
+
execSync(`git remote add origin "${projectDir}"`, {
|
|
92
|
+
cwd: workspaceDir,
|
|
93
|
+
stdio: 'pipe',
|
|
94
|
+
shell: true
|
|
95
|
+
});
|
|
96
|
+
} catch (e) {
|
|
97
|
+
// Remote already exists, update it
|
|
98
|
+
execSync(`git remote set-url origin "${projectDir}"`, {
|
|
99
|
+
cwd: workspaceDir,
|
|
100
|
+
stdio: 'pipe',
|
|
101
|
+
shell: true
|
|
102
|
+
});
|
|
103
|
+
// Remote updated silently
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Set up upstream tracking for current branch
|
|
107
|
+
try {
|
|
108
|
+
const currentBranch = execSync(`git branch --show-current`, {
|
|
109
|
+
cwd: workspaceDir,
|
|
110
|
+
encoding: 'utf8',
|
|
111
|
+
stdio: 'pipe'
|
|
112
|
+
}).trim();
|
|
113
|
+
|
|
114
|
+
// Ensure the branch exists on the host side
|
|
115
|
+
try {
|
|
116
|
+
execSync(`cd "${projectDir}" && git checkout ${currentBranch}`, {
|
|
117
|
+
stdio: 'pipe',
|
|
118
|
+
shell: true
|
|
119
|
+
});
|
|
120
|
+
} catch (e) {
|
|
121
|
+
// Branch doesn't exist on host, create it
|
|
122
|
+
execSync(`cd "${projectDir}" && git checkout -b ${currentBranch}`, {
|
|
123
|
+
stdio: 'pipe',
|
|
124
|
+
shell: true
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
execSync(`git branch --set-upstream-to=origin/${currentBranch} ${currentBranch}`, {
|
|
129
|
+
cwd: workspaceDir,
|
|
130
|
+
stdio: 'pipe',
|
|
131
|
+
shell: true
|
|
132
|
+
});
|
|
133
|
+
} catch (e) {
|
|
134
|
+
// Upstream may not exist yet, ignore error
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Copy project's .claude/settings.json if it exists (project-level Claude settings)
|
|
138
|
+
const projectClaudeSettingsPath = join(projectDir, '.claude', 'settings.json');
|
|
139
|
+
if (existsSync(projectClaudeSettingsPath)) {
|
|
140
|
+
const sandboxClaudeSettingsPath = join(workspaceDir, '.claude', 'settings.json');
|
|
141
|
+
mkdirSync(join(workspaceDir, '.claude'), { recursive: true });
|
|
142
|
+
cpSync(projectClaudeSettingsPath, sandboxClaudeSettingsPath);
|
|
143
|
+
|
|
144
|
+
if (VERBOSE_OUTPUT) {
|
|
145
|
+
console.log('✅ Copied project Claude settings to sandbox');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Batch fetch git identity settings for efficiency
|
|
150
|
+
const gitSettings = execSync(`git config --global --get user.name && git config --global --get user.email && git config --global --get color.ui`, {
|
|
151
|
+
stdio: 'pipe',
|
|
152
|
+
shell: true,
|
|
153
|
+
encoding: 'utf8'
|
|
154
|
+
}).trim().split('\n');
|
|
155
|
+
|
|
156
|
+
const [userName, userEmail, colorUi] = gitSettings;
|
|
157
|
+
|
|
158
|
+
// Batch configure git settings in sandbox
|
|
159
|
+
execSync(`git config user.name "${userName}" && git config user.email "${userEmail}" && git config color.ui "${colorUi}"`, {
|
|
160
|
+
cwd: workspaceDir,
|
|
161
|
+
stdio: 'pipe',
|
|
162
|
+
shell: true
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Configure Git remote to host for bidirectional synchronization
|
|
166
|
+
try {
|
|
167
|
+
execSync(`cd "${workspaceDir}" && git remote add host "${projectDir}"`, {
|
|
168
|
+
stdio: 'pipe',
|
|
169
|
+
shell: true
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (VERBOSE_OUTPUT) {
|
|
173
|
+
console.log('✅ Configured Git remote to host repository');
|
|
174
|
+
}
|
|
175
|
+
} catch (error) {
|
|
176
|
+
// Remote might already exist, try to update it
|
|
177
|
+
try {
|
|
178
|
+
execSync(`cd "${workspaceDir}" && git remote set-url host "${projectDir}"`, {
|
|
179
|
+
stdio: 'pipe',
|
|
180
|
+
shell: true
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
if (VERBOSE_OUTPUT) {
|
|
184
|
+
console.log('✅ Updated Git remote to host repository');
|
|
185
|
+
}
|
|
186
|
+
} catch (updateError) {
|
|
187
|
+
if (VERBOSE_OUTPUT) {
|
|
188
|
+
console.log('⚠️ Could not configure Git remote to host');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Setup Claude settings in sandbox
|
|
194
|
+
const hostClaudeDir = join(homedir(), '.claude');
|
|
195
|
+
const sandboxClaudeDir = join(sandboxDir, '.claude');
|
|
196
|
+
|
|
197
|
+
// Always use bundled SandboxBox settings unless host settings are requested
|
|
198
|
+
if (!useHostSettings) {
|
|
199
|
+
// Create sandbox Claude directory and copy bundled settings
|
|
200
|
+
mkdirSync(sandboxClaudeDir, { recursive: true });
|
|
201
|
+
|
|
202
|
+
const bundledSettingsPath = join(resolve(fileURLToPath(import.meta.url), '..', '..'), 'sandboxbox-settings.json');
|
|
203
|
+
const sandboxSettingsPath = join(sandboxClaudeDir, 'settings.json');
|
|
204
|
+
|
|
205
|
+
// Copy bundled settings to sandbox
|
|
206
|
+
if (existsSync(bundledSettingsPath)) {
|
|
207
|
+
if (VERBOSE_OUTPUT) {
|
|
208
|
+
console.log(`🔍 Debug: Found bundled settings at ${bundledSettingsPath}`);
|
|
209
|
+
}
|
|
210
|
+
cpSync(bundledSettingsPath, sandboxSettingsPath);
|
|
211
|
+
|
|
212
|
+
// Also copy credentials from host if available
|
|
213
|
+
const hostCredentialsPath = join(hostClaudeDir, '.credentials.json');
|
|
214
|
+
if (existsSync(hostCredentialsPath)) {
|
|
215
|
+
cpSync(hostCredentialsPath, join(sandboxClaudeDir, '.credentials.json'));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (VERBOSE_OUTPUT) {
|
|
219
|
+
console.log('✅ Using bundled SandboxBox settings with Git integration hooks and MCP servers');
|
|
220
|
+
// Show hook and MCP information
|
|
221
|
+
const settings = JSON.parse(readFileSync(bundledSettingsPath, 'utf8'));
|
|
222
|
+
if (settings.hooks) {
|
|
223
|
+
console.log('📋 Bundled hooks configured:');
|
|
224
|
+
Object.keys(settings.hooks).forEach(hookType => {
|
|
225
|
+
const hookCount = settings.hooks[hookType].length;
|
|
226
|
+
console.log(` ${hookType}: ${hookCount} hook(s)`);
|
|
227
|
+
settings.hooks[hookType].forEach((hook, index) => {
|
|
228
|
+
const commandCount = hook.hooks ? hook.hooks.length : 0;
|
|
229
|
+
console.log(` ${index + 1}. ${hook.matcher || '*'} (${commandCount} commands)`);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
if (settings.mcpServers) {
|
|
234
|
+
console.log('🔧 MCP servers configured:');
|
|
235
|
+
Object.keys(settings.mcpServers).forEach(serverName => {
|
|
236
|
+
const server = settings.mcpServers[serverName];
|
|
237
|
+
console.log(` ${serverName}: ${server.command} ${server.args.join(' ')}`);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
if (VERBOSE_OUTPUT) {
|
|
243
|
+
console.log(`🔍 Debug: Bundled settings not found at ${bundledSettingsPath}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Optimize cache directory handling - use symlinks instead of copying
|
|
249
|
+
const hostCacheDir = join(homedir(), '.cache');
|
|
250
|
+
if (existsSync(hostCacheDir)) {
|
|
251
|
+
const sandboxCacheDir = join(sandboxDir, '.cache');
|
|
252
|
+
mkdirSync(sandboxCacheDir, { recursive: true });
|
|
253
|
+
|
|
254
|
+
// Create symlink to ms-playwright cache instead of copying (major performance improvement)
|
|
255
|
+
const playwrightCacheDir = join(hostCacheDir, 'ms-playwright');
|
|
256
|
+
if (existsSync(playwrightCacheDir)) {
|
|
257
|
+
const sandboxPlaywrightDir = join(sandboxCacheDir, 'ms-playwright');
|
|
258
|
+
try {
|
|
259
|
+
symlinkSync(playwrightCacheDir, sandboxPlaywrightDir, 'dir');
|
|
260
|
+
} catch (error) {
|
|
261
|
+
// Fallback to copying only if symlink fails
|
|
262
|
+
cpSync(playwrightCacheDir, sandboxPlaywrightDir, { recursive: true });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const cleanup = () => {
|
|
268
|
+
// Close any log files that might be open
|
|
269
|
+
if (global.logFileHandle) {
|
|
270
|
+
try {
|
|
271
|
+
appendFileSync(global.logFileHandle, `\n# Session ended: ${new Date().toISOString()}\n`);
|
|
272
|
+
global.logFileHandle = null;
|
|
273
|
+
} catch (error) {
|
|
274
|
+
// Don't fail on log cleanup
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
rmSync(sandboxDir, { recursive: true, force: true });
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
return { sandboxDir, cleanup };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function createSandboxEnv(sandboxDir, options = {}) {
|
|
284
|
+
const sandboxClaudeDir = join(sandboxDir, '.claude');
|
|
285
|
+
const sandboxCacheDir = join(sandboxDir, '.cache');
|
|
286
|
+
|
|
287
|
+
// Start with all process environment variables
|
|
288
|
+
const env = {
|
|
289
|
+
...process.env,
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// IMPORTANT: Set HOME to sandbox directory for Claude to find settings
|
|
293
|
+
// but preserve access to host credentials via symlink
|
|
294
|
+
env.HOME = sandboxDir;
|
|
295
|
+
env.USERPROFILE = sandboxDir;
|
|
296
|
+
|
|
297
|
+
// Set XDG paths to use sandbox Claude directory
|
|
298
|
+
env.XDG_CONFIG_HOME = sandboxClaudeDir;
|
|
299
|
+
env.XDG_DATA_HOME = join(sandboxClaudeDir, '.local', 'share');
|
|
300
|
+
env.XDG_CACHE_HOME = sandboxCacheDir;
|
|
301
|
+
env.TMPDIR = join(sandboxDir, 'tmp');
|
|
302
|
+
env.TEMP = join(sandboxDir, 'tmp');
|
|
303
|
+
env.TMP = join(sandboxDir, 'tmp');
|
|
304
|
+
env.PLAYWRIGHT_BROWSERS_PATH = join(sandboxDir, 'browsers');
|
|
305
|
+
env.PLAYWRIGHT_STORAGE_STATE = join(sandboxDir, '.playwright', 'storage-state.json');
|
|
306
|
+
if (process.env.CLAUDE_CODE_ENTRYPOINT) {
|
|
307
|
+
env.CLAUDE_CODE_ENTRYPOINT = process.env.CLAUDE_CODE_ENTRYPOINT;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Ensure TERM is set with fallback
|
|
311
|
+
env.TERM = process.env.TERM || 'xterm-256color';
|
|
312
|
+
|
|
313
|
+
// Apply any additional options
|
|
314
|
+
Object.assign(env, options);
|
|
315
|
+
|
|
316
|
+
return env;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export function runInSandbox(commandStr, args, sandboxDir, env) {
|
|
320
|
+
return new Promise((resolve, reject) => {
|
|
321
|
+
const fullCommand = args.length > 0 ? `${commandStr} ${args.join(' ')}` : commandStr;
|
|
322
|
+
|
|
323
|
+
const proc = spawn(fullCommand, [], {
|
|
324
|
+
cwd: join(sandboxDir, 'workspace'),
|
|
325
|
+
env,
|
|
326
|
+
stdio: 'inherit',
|
|
327
|
+
shell: true,
|
|
328
|
+
windowsHide: false
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
proc.on('close', (code) => {
|
|
332
|
+
if (code === 0) {
|
|
333
|
+
resolve();
|
|
334
|
+
} else {
|
|
335
|
+
reject(new Error(`Process exited with code ${code}`));
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
proc.on('error', reject);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { existsSync, writeFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
export class SystemOptimizer {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.hasSudo = this.checkSudoAccess();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
checkSudoAccess() {
|
|
11
|
+
try {
|
|
12
|
+
execSync('sudo -n true', { stdio: 'pipe' });
|
|
13
|
+
return true;
|
|
14
|
+
} catch (e) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Optimize system for Claude Code performance
|
|
20
|
+
async optimizeSystem() {
|
|
21
|
+
if (!this.hasSudo) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const optimizations = [
|
|
26
|
+
this.optimizeKernelParameters(),
|
|
27
|
+
this.optimizeNetworkStack(),
|
|
28
|
+
this.preloadCommonLibraries(),
|
|
29
|
+
this.optimizeFilesystem(),
|
|
30
|
+
this.setupClaudeService()
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const results = await Promise.allSettled(optimizations);
|
|
34
|
+
const successCount = results.filter(r => r.status === 'fulfilled').length;
|
|
35
|
+
|
|
36
|
+
return successCount > 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Optimize kernel parameters for faster process spawning
|
|
40
|
+
async optimizeKernelParameters() {
|
|
41
|
+
if (!this.hasSudo) return false;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const kernelTweaks = [
|
|
45
|
+
'sysctl -w vm.overcommit_memory=1', // Allow memory overcommit
|
|
46
|
+
'sysctl -w kernel.sched_min_granularity_ns=1000000', // Faster task scheduling
|
|
47
|
+
'sysctl -w fs.file-max=1048576', // Increase file descriptor limit
|
|
48
|
+
'sysctl -w net.core.somaxconn=65535', // Increase connection backlog
|
|
49
|
+
'sysctl -w net.ipv4.tcp_fin_timeout=15', // Faster TCP connection cleanup
|
|
50
|
+
'sysctl -w net.ipv4.tcp_keepalive_time=600', // Reduce keepalive timeout
|
|
51
|
+
'sysctl -w vm.swappiness=10' // Reduce swapping
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
for (const tweak of kernelTweaks) {
|
|
55
|
+
execSync(`sudo ${tweak}`, { stdio: 'pipe' });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return true;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
// Silently skip optimization if it fails
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Preload Claude and plugin dependencies
|
|
66
|
+
async preloadCommonLibraries() {
|
|
67
|
+
if (!this.hasSudo) return false;
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
// Create a preload script for common Node.js and Claude dependencies
|
|
71
|
+
const preloadScript = `
|
|
72
|
+
# Preload common libraries for faster Claude startup
|
|
73
|
+
echo "Preloading Claude dependencies..."
|
|
74
|
+
|
|
75
|
+
# Common Node.js modules
|
|
76
|
+
export NODE_PATH="/usr/lib/node_modules:$NODE_PATH"
|
|
77
|
+
|
|
78
|
+
# Preload essential binaries
|
|
79
|
+
which npx >/dev/null && npx --version >/dev/null 2>&1 &
|
|
80
|
+
which node >/dev/null && node --version >/dev/null 2>&1 &
|
|
81
|
+
|
|
82
|
+
# Wait for preloads to complete
|
|
83
|
+
wait
|
|
84
|
+
|
|
85
|
+
echo "Preloading complete"
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
const preloadPath = '/tmp/claude-preload.sh';
|
|
89
|
+
writeFileSync(preloadPath, preloadScript);
|
|
90
|
+
execSync(`chmod +x ${preloadPath}`, { stdio: 'pipe' });
|
|
91
|
+
|
|
92
|
+
// Execute preload script with elevated privileges if needed
|
|
93
|
+
execSync(preloadPath, { stdio: 'pipe' });
|
|
94
|
+
|
|
95
|
+
return true;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
// Silently skip optimization if it fails
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Optimize filesystem for faster access
|
|
103
|
+
async optimizeFilesystem() {
|
|
104
|
+
if (!this.hasSudo) return false;
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
// Optimize tmpfs for faster temporary operations
|
|
108
|
+
const tmpfsOptimizations = [
|
|
109
|
+
'mount -t tmpfs -o size=1G tmpfs /tmp/claude-cache 2>/dev/null || true',
|
|
110
|
+
'mkdir -p /tmp/claude-cache /tmp/claude-plugins',
|
|
111
|
+
'chmod 777 /tmp/claude-cache /tmp/claude-plugins'
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
for (const opt of tmpfsOptimizations) {
|
|
115
|
+
execSync(`sudo ${opt}`, { stdio: 'pipe' });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return true;
|
|
119
|
+
} catch (error) {
|
|
120
|
+
// Silently skip optimization if it fails
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Setup Claude as a pre-warmed service
|
|
126
|
+
async setupClaudeService() {
|
|
127
|
+
if (!this.hasSudo) return false;
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const serviceConfig = `[Unit]
|
|
131
|
+
Description=Claude Code Pre-warm Service
|
|
132
|
+
After=network.target
|
|
133
|
+
|
|
134
|
+
[Service]
|
|
135
|
+
Type=forking
|
|
136
|
+
User=root
|
|
137
|
+
ExecStart=/usr/local/bin/claude-prewarm.sh
|
|
138
|
+
Restart=on-failure
|
|
139
|
+
RestartSec=5
|
|
140
|
+
|
|
141
|
+
[Install]
|
|
142
|
+
WantedBy=multi-user.target
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
const prewarmScript = `#!/bin/bash
|
|
146
|
+
# Claude Code pre-warming service
|
|
147
|
+
echo "Starting Claude pre-warm service..."
|
|
148
|
+
|
|
149
|
+
# Create necessary directories
|
|
150
|
+
mkdir -p /tmp/claude-cache /tmp/claude-plugins /tmp/claude-sessions
|
|
151
|
+
|
|
152
|
+
# Pre-warm Node.js runtime
|
|
153
|
+
timeout 10s node --version >/dev/null 2>&1 &
|
|
154
|
+
|
|
155
|
+
# Pre-warm NPX
|
|
156
|
+
timeout 10s npx --version >/dev/null 2>&1 &
|
|
157
|
+
|
|
158
|
+
# Pre-warm common plugins if available
|
|
159
|
+
timeout 5s npx -y glootie-cc@anentrypoint-plugins --help >/dev/null 2>&1 &
|
|
160
|
+
|
|
161
|
+
echo "Claude pre-warm service ready"
|
|
162
|
+
`;
|
|
163
|
+
|
|
164
|
+
// Write service file
|
|
165
|
+
execSync('echo "' + serviceConfig.replace(/"/g, '\\"') + '" | sudo tee /etc/systemd/system/claude-prewarm.service', { stdio: 'pipe' });
|
|
166
|
+
|
|
167
|
+
// Write prewarm script
|
|
168
|
+
execSync('echo "' + prewarmScript.replace(/"/g, '\\"') + '" | sudo tee /usr/local/bin/claude-prewarm.sh', { stdio: 'pipe' });
|
|
169
|
+
execSync('sudo chmod +x /usr/local/bin/claude-prewarm.sh', { stdio: 'pipe' });
|
|
170
|
+
|
|
171
|
+
// Enable and start service
|
|
172
|
+
execSync('sudo systemctl daemon-reload', { stdio: 'pipe' });
|
|
173
|
+
execSync('sudo systemctl enable claude-prewarm.service', { stdio: 'pipe' });
|
|
174
|
+
execSync('sudo systemctl start claude-prewarm.service', { stdio: 'pipe' });
|
|
175
|
+
|
|
176
|
+
return true;
|
|
177
|
+
} catch (error) {
|
|
178
|
+
// Silently skip optimization if it fails
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Optimize network stack for faster plugin communication
|
|
184
|
+
async optimizeNetworkStack() {
|
|
185
|
+
if (!this.hasSudo) return false;
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const networkOptimizations = [
|
|
189
|
+
'sysctl -w net.core.rmem_max=134217728', // Increase receive buffer
|
|
190
|
+
'sysctl -w net.core.wmem_max=134217728', // Increase send buffer
|
|
191
|
+
'sysctl -w net.ipv4.tcp_rmem="4096 65536 134217728"', // TCP receive buffer
|
|
192
|
+
'sysctl -w net.ipv4.tcp_wmem="4096 65536 134217728"', // TCP send buffer
|
|
193
|
+
'sysctl -w net.ipv4.tcp_congestion_control=bbr' // Use BBR congestion control
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
for (const opt of networkOptimizations) {
|
|
197
|
+
execSync(`sudo ${opt}`, { stdio: 'pipe' });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return true;
|
|
201
|
+
} catch (error) {
|
|
202
|
+
// Silently skip optimization if it fails
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Create optimized container environment
|
|
208
|
+
createOptimizedContainerEnv() {
|
|
209
|
+
return {
|
|
210
|
+
// System optimizations
|
|
211
|
+
'NODE_OPTIONS': '--max-old-space-size=4096 --no-experimental-fetch',
|
|
212
|
+
'CLAUDE_OPTIMIZED': '1',
|
|
213
|
+
'CLAUDE_CACHE_DIR': '/tmp/claude-cache',
|
|
214
|
+
'CLAUDE_PLUGIN_DIR': '/tmp/claude-plugins',
|
|
215
|
+
|
|
216
|
+
// Performance tuning
|
|
217
|
+
'UV_THREADPOOL_SIZE': '16',
|
|
218
|
+
'NODEUV_THREADPOOL_SIZE': '16',
|
|
219
|
+
|
|
220
|
+
// Fast startup flags
|
|
221
|
+
'CLAUDE_FAST_INIT': '1',
|
|
222
|
+
'CLAUDE_SKIP_WELCOME': '1',
|
|
223
|
+
'CLAUDE_MINIMAL_PLUGINS': '1',
|
|
224
|
+
|
|
225
|
+
// Timeout optimizations
|
|
226
|
+
'MCP_TIMEOUT': '3000',
|
|
227
|
+
'MCP_CONNECT_TIMEOUT': '2000',
|
|
228
|
+
'CLAUDE_PLUGIN_TIMEOUT': '5000'
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { color } from './colors.js';
|
|
2
|
+
|
|
3
|
+
export function showBanner() {
|
|
4
|
+
const VERBOSE_OUTPUT = process.env.SANDBOX_VERBOSE === 'true' || process.argv.includes('--verbose');
|
|
5
|
+
if (VERBOSE_OUTPUT) {
|
|
6
|
+
console.log(color('cyan', '📦 SandboxBox - Portable Container Runner'));
|
|
7
|
+
console.log(color('cyan', '═════════════════════════════════════════════════'));
|
|
8
|
+
console.log('');
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function showHelp() {
|
|
13
|
+
console.log(color('yellow', 'Usage:'));
|
|
14
|
+
console.log(' npx sandboxbox <command> [options]');
|
|
15
|
+
console.log('');
|
|
16
|
+
console.log(color('yellow', 'Commands:'));
|
|
17
|
+
console.log(' build [dockerfile] Build container from Dockerfile');
|
|
18
|
+
console.log(' run <project-dir> [cmd] Run project in container');
|
|
19
|
+
console.log(' shell <project-dir> Start interactive shell');
|
|
20
|
+
console.log(' claude <project-dir> [prompt] [--host] [--headless] Start Claude Code with Git integration');
|
|
21
|
+
console.log(' version Show version information');
|
|
22
|
+
console.log('');
|
|
23
|
+
console.log(color('yellow', 'Claude Command Options:'));
|
|
24
|
+
console.log(' --host Use host Claude settings instead of bundled');
|
|
25
|
+
console.log(' --headless Configure headless Playwright MCP (runs before Claude)');
|
|
26
|
+
console.log('');
|
|
27
|
+
console.log(color('yellow', 'Examples:'));
|
|
28
|
+
console.log(' npx sandboxbox build');
|
|
29
|
+
console.log(' npx sandboxbox claude ./my-project');
|
|
30
|
+
console.log(' npx sandboxbox run ./my-project "npm test"');
|
|
31
|
+
console.log(' npx sandboxbox shell ./my-project');
|
|
32
|
+
console.log('');
|
|
33
|
+
console.log(color('yellow', 'Requirements:'));
|
|
34
|
+
console.log(' - Docker/Podman runtime');
|
|
35
|
+
console.log(' - Works on Windows, macOS, and Linux!');
|
|
36
|
+
console.log('');
|
|
37
|
+
console.log(color('magenta', '🚀 Fast startup • True isolation • Claude Code integration'));
|
|
38
|
+
}
|
package/package/package.json
CHANGED
|
Binary file
|
|
@@ -3,7 +3,8 @@ import { resolve, join } from 'path';
|
|
|
3
3
|
import { spawn, execSync } from 'child_process';
|
|
4
4
|
import { color } from '../colors.js';
|
|
5
5
|
import { createSandbox, createSandboxEnv } from '../sandbox.js';
|
|
6
|
-
|
|
6
|
+
// ClaudeOptimizer disabled to preserve bundled hooks
|
|
7
|
+
// import { ClaudeOptimizer } from '../claude-optimizer.js';
|
|
7
8
|
import { SystemOptimizer } from '../system-optimizer.js';
|
|
8
9
|
|
|
9
10
|
const ALLOWED_TOOLS = [
|
|
@@ -253,10 +254,10 @@ export async function claudeCommand(projectDir, prompt, flags = {}) {
|
|
|
253
254
|
const envStartTime = Date.now();
|
|
254
255
|
if (VERBOSE_OUTPUT) console.log(color('cyan', '⏱️ Stage 2: Setting up environment...'));
|
|
255
256
|
|
|
256
|
-
// Apply Claude optimizations
|
|
257
|
-
const claudeOptimizer = new ClaudeOptimizer(sandboxDir);
|
|
258
|
-
claudeOptimizer.optimizeSettings();
|
|
259
|
-
await claudeOptimizer.prewarmPlugins();
|
|
257
|
+
// Apply Claude optimizations (disabled to preserve bundled hooks)
|
|
258
|
+
// const claudeOptimizer = new ClaudeOptimizer(sandboxDir);
|
|
259
|
+
// claudeOptimizer.optimizeSettings();
|
|
260
|
+
// await claudeOptimizer.prewarmPlugins();
|
|
260
261
|
|
|
261
262
|
// Apply system optimizations (with sudo access)
|
|
262
263
|
const systemOptimizer = new SystemOptimizer();
|
|
@@ -269,7 +270,7 @@ export async function claudeCommand(projectDir, prompt, flags = {}) {
|
|
|
269
270
|
|
|
270
271
|
const env = systemOptimizationsApplied
|
|
271
272
|
? { ...baseEnv, ...systemOptimizer.createOptimizedContainerEnv() }
|
|
272
|
-
:
|
|
273
|
+
: baseEnv; // Use base environment when optimizer is disabled
|
|
273
274
|
|
|
274
275
|
const envCreateTime = Date.now() - envStartTime;
|
|
275
276
|
if (VERBOSE_OUTPUT) console.log(color('green', `✅ Environment configured in ${envCreateTime}ms`));
|
package/package.json
CHANGED
|
Binary file
|