sandboxbox 3.0.49 → 3.0.51

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.
Files changed (41) hide show
  1. package/package.json +1 -1
  2. package/sandboxbox-settings.json +2 -17
  3. package/claude-settings.json +0 -85
  4. package/package/CLAUDE.md +0 -200
  5. package/package/Dockerfile +0 -95
  6. package/package/README.md +0 -242
  7. package/package/claude-settings.json +0 -85
  8. package/package/cli.js +0 -90
  9. package/package/package/CLAUDE.md +0 -200
  10. package/package/package/Dockerfile +0 -95
  11. package/package/package/README.md +0 -242
  12. package/package/package/claude-settings.json +0 -85
  13. package/package/package/cli.js +0 -90
  14. package/package/package/package.json +0 -39
  15. package/package/package/sandboxbox-3.0.45.tgz +0 -0
  16. package/package/package/sandboxbox-settings.json +0 -40
  17. package/package/package/test.txt +0 -1
  18. package/package/package/utils/claude-optimizer.js +0 -129
  19. package/package/package/utils/colors.js +0 -15
  20. package/package/package/utils/commands/claude.js +0 -501
  21. package/package/package/utils/commands/container.js +0 -60
  22. package/package/package/utils/commands/index.js +0 -23
  23. package/package/package/utils/sandbox.js +0 -341
  24. package/package/package/utils/system-optimizer.js +0 -231
  25. package/package/package/utils/ui.js +0 -38
  26. package/package/package.json +0 -39
  27. package/package/sandboxbox-3.0.45.tgz +0 -0
  28. package/package/sandboxbox-3.0.46.tgz +0 -0
  29. package/package/sandboxbox-settings.json +0 -40
  30. package/package/test.txt +0 -1
  31. package/package/utils/claude-optimizer.js +0 -129
  32. package/package/utils/colors.js +0 -15
  33. package/package/utils/commands/claude.js +0 -502
  34. package/package/utils/commands/container.js +0 -60
  35. package/package/utils/commands/index.js +0 -23
  36. package/package/utils/sandbox.js +0 -341
  37. package/package/utils/system-optimizer.js +0 -231
  38. package/package/utils/ui.js +0 -38
  39. package/sandboxbox-3.0.45.tgz +0 -0
  40. package/sandboxbox-3.0.46.tgz +0 -0
  41. package/sandboxbox-3.0.48.tgz +0 -0
@@ -1,129 +0,0 @@
1
- import { existsSync, mkdirSync, writeFileSync } from 'fs';
2
- import { join, dirname } from 'path';
3
-
4
- export class ClaudeOptimizer {
5
- constructor(sandboxDir) {
6
- this.sandboxDir = sandboxDir;
7
- this.claudeDir = join(sandboxDir, '.claude');
8
- }
9
-
10
- // Optimize Claude settings for faster startup
11
- optimizeSettings() {
12
- const settingsPath = join(this.claudeDir, 'settings.json');
13
-
14
- // Load existing settings or create optimized defaults
15
- let settings = {};
16
- if (existsSync(settingsPath)) {
17
- try {
18
- settings = JSON.parse(require('fs').readFileSync(settingsPath, 'utf8'));
19
- } catch (e) {
20
- // Silently create default settings
21
- }
22
- }
23
-
24
- // Apply startup optimizations - preserve bundled hooks!
25
- const optimizedSettings = {
26
- ...settings,
27
- // Reduce plugin timeouts
28
- pluginTimeout: settings.pluginTimeout || 5000,
29
- // Disable unnecessary features for faster startup
30
- alwaysThinkingEnabled: settings.alwaysThinkingEnabled || false,
31
- // Optimize plugin loading - preserve existing plugins
32
- enabledPlugins: {
33
- ...settings.enabledPlugins,
34
- // Only enable essential plugins for performance
35
- 'glootie-cc@anentrypoint-plugins': true
36
- }
37
- // Note: DO NOT override hooks - preserve bundled settings hooks!
38
- };
39
-
40
- // Ensure directory exists
41
- mkdirSync(dirname(settingsPath), { recursive: true });
42
-
43
- // Write optimized settings
44
- writeFileSync(settingsPath, JSON.stringify(optimizedSettings, null, 2));
45
-
46
- return optimizedSettings;
47
- }
48
-
49
- // Pre-warm plugin connections
50
- async prewarmPlugins() {
51
-
52
- const prewarmScript = `
53
- # Pre-warm script for Claude plugins
54
- echo "Pre-warming plugin connections..."
55
-
56
- # Set environment for faster plugin discovery
57
- export NODE_OPTIONS="--max-old-space-size=2048"
58
- export CLAUDE_PLUGIN_TIMEOUT=5000
59
-
60
- # Pre-warm glootie plugin
61
- if command -v npx >/dev/null 2>&1; then
62
- timeout 3s npx -y glootie-cc@anentrypoint-plugins --version >/dev/null 2>&1 &
63
- fi
64
-
65
- echo "Plugin pre-warming complete"
66
- `;
67
-
68
- const scriptPath = join(this.sandboxDir, '.claude', 'prewarm.sh');
69
- writeFileSync(scriptPath, prewarmScript);
70
-
71
- return scriptPath;
72
- }
73
-
74
- // Create optimized environment for Claude
75
- createOptimizedEnv(baseEnv = {}) {
76
- return {
77
- ...baseEnv,
78
- // Performance optimizations
79
- NODE_OPTIONS: "--max-old-space-size=2048",
80
- CLAUDE_PLUGIN_TIMEOUT: "5000",
81
- CLAUDE_CACHE_DIR: join(this.sandboxDir, '.cache', 'claude'),
82
- // Network optimizations
83
- NODE_TLS_REJECT_UNAUTHORIZED: "0",
84
- // Plugin optimizations
85
- MCP_TIMEOUT: "5000",
86
- MCP_CONNECTION_TIMEOUT: "3000",
87
- // Fast startup flags
88
- CLAUDE_FAST_STARTUP: "1",
89
- CLAUDE_MINIMAL_MODE: "1"
90
- };
91
- }
92
-
93
- // Generate startup timing profile
94
- async profileStartup(claudeCommand) {
95
- console.log('šŸ“Š Profiling Claude startup...');
96
-
97
- const profileScript = `
98
- #!/bin/bash
99
- echo "Starting Claude startup profile..."
100
- start_time=$(date +%s%3N)
101
-
102
- # Run Claude with timing
103
- timeout 30s ${claudeCommand} --help >/dev/null 2>&1 &
104
- claude_pid=$!
105
-
106
- # Monitor startup progress
107
- while kill -0 $claude_pid 2>/dev/null; do
108
- current_time=$(date +%s%3N)
109
- elapsed=$((current_time - start_time))
110
-
111
- if [ $elapsed -gt 10000 ]; then
112
- echo "āš ļø Startup taking longer than 10s..."
113
- kill $claude_pid 2>/dev/null
114
- break
115
- fi
116
-
117
- sleep 0.5
118
- done
119
-
120
- wait $claude_pid
121
- end_time=$(date +%s%3N)
122
- total_time=$((end_time - start_time))
123
-
124
- echo "Claude startup completed in ${total_time}ms"
125
- `;
126
-
127
- return profileScript;
128
- }
129
- }
@@ -1,15 +0,0 @@
1
- // Color utilities for CLI output
2
- export const colors = {
3
- red: '\x1b[31m',
4
- green: '\x1b[32m',
5
- yellow: '\x1b[33m',
6
- blue: '\x1b[34m',
7
- magenta: '\x1b[35m',
8
- cyan: '\x1b[36m',
9
- white: '\x1b[37m',
10
- reset: '\x1b[0m'
11
- };
12
-
13
- export function color(colorName, text) {
14
- return `${colors[colorName]}${text}${colors.reset}`;
15
- }
@@ -1,502 +0,0 @@
1
- import { existsSync, writeFileSync, appendFileSync } from 'fs';
2
- import { resolve, join } from 'path';
3
- import { spawn, execSync } from 'child_process';
4
- import { color } from '../colors.js';
5
- import { createSandbox, createSandboxEnv } from '../sandbox.js';
6
- // ClaudeOptimizer disabled to preserve bundled hooks
7
- // import { ClaudeOptimizer } from '../claude-optimizer.js';
8
- import { SystemOptimizer } from '../system-optimizer.js';
9
-
10
- const ALLOWED_TOOLS = [
11
- 'Task', 'Bash', 'Glob', 'Grep', 'Read', 'Edit', 'Write', 'NotebookEdit',
12
- 'WebFetch', 'TodoWrite', 'WebSearch', 'BashOutput', 'KillShell',
13
- 'SlashCommand', 'ExitPlanMode', 'mcp__glootie__execute',
14
- 'mcp__glootie__ast_tool', 'mcp__glootie__caveat',
15
- 'mcp__playwright__browser_navigate', 'mcp__playwright__browser_snapshot',
16
- 'mcp__playwright__browser_click', 'mcp__playwright__browser_type',
17
- 'mcp__playwright__browser_evaluate', 'mcp__playwright__browser_close',
18
- 'mcp__vexify__search_code'
19
- ];
20
-
21
- // Console output configuration
22
- const MAX_CONSOLE_LINES = parseInt(process.env.SANDBOX_MAX_CONSOLE_LINES) || 5;
23
- const MAX_LOG_ENTRY_LENGTH = parseInt(process.env.SANDBOX_MAX_LOG_LENGTH) || 200;
24
- const ENABLE_FILE_LOGGING = process.env.SANDBOX_ENABLE_FILE_LOGGING === 'true';
25
- const VERBOSE_OUTPUT = process.env.SANDBOX_VERBOSE === 'true' || process.argv.includes('--verbose');
26
- global.toolCallLog = [];
27
- global.logFileHandle = null;
28
- global.pendingToolCalls = new Map(); // Track tool calls by ID for result matching
29
- global.conversationalBuffer = ''; // Track conversational text between tool calls
30
-
31
- // Helper function to extract tool metadata without showing actual content
32
- function extractToolMetadata(toolUse) {
33
- const metadata = {
34
- name: toolUse.name || 'unknown',
35
- id: toolUse.id || 'no-id',
36
- inputCount: 0,
37
- inputTypes: {},
38
- inputSizes: {},
39
- totalInputSize: 0
40
- };
41
-
42
- if (toolUse.input && typeof toolUse.input === 'object') {
43
- metadata.inputCount = Object.keys(toolUse.input).length;
44
-
45
- for (const [key, value] of Object.entries(toolUse.input)) {
46
- const type = Array.isArray(value) ? 'array' : typeof value;
47
- metadata.inputTypes[key] = type;
48
-
49
- // Calculate sizes without exposing content
50
- if (type === 'string') {
51
- metadata.inputSizes[key] = `${value.length} chars`;
52
- metadata.totalInputSize += value.length;
53
- } else if (type === 'array') {
54
- metadata.inputSizes[key] = `${value.length} items`;
55
- metadata.totalInputSize += JSON.stringify(value).length;
56
- } else if (type === 'object' && value !== null) {
57
- const objSize = JSON.stringify(value).length;
58
- metadata.inputSizes[key] = `${objSize} chars`;
59
- metadata.totalInputSize += objSize;
60
- } else {
61
- metadata.inputSizes[key] = `${type}`;
62
- }
63
- }
64
- }
65
-
66
- return metadata;
67
- }
68
-
69
- // Helper function to extract tool result metadata
70
- function extractResultMetadata(result) {
71
- const metadata = {
72
- type: 'unknown',
73
- size: 0,
74
- hasContent: false,
75
- isToolResult: false,
76
- isError: false
77
- };
78
-
79
- if (result && typeof result === 'object') {
80
- metadata.isToolResult = result.type === 'tool_result';
81
- metadata.isError = result.is_error || false;
82
-
83
- if (result.content) {
84
- metadata.hasContent = true;
85
-
86
- if (typeof result.content === 'string') {
87
- metadata.type = 'text';
88
- metadata.size = result.content.length;
89
- } else if (Array.isArray(result.content)) {
90
- metadata.type = 'array';
91
- metadata.size = result.content.length;
92
- // Count items by type without showing content
93
- const typeCounts = {};
94
- result.content.forEach(item => {
95
- const itemType = item?.type || 'unknown';
96
- typeCounts[itemType] = (typeCounts[itemType] || 0) + 1;
97
- });
98
- metadata.itemTypes = typeCounts;
99
- } else if (typeof result.content === 'object') {
100
- metadata.type = 'object';
101
- metadata.size = Object.keys(result.content).length;
102
- }
103
- }
104
- }
105
-
106
- return metadata;
107
- }
108
-
109
-
110
- // Helper function to truncate text to a sensible length
111
- function truncateText(text, maxLength = MAX_LOG_ENTRY_LENGTH) {
112
- if (text.length <= maxLength) return text;
113
- return text.substring(0, maxLength - 3) + '...';
114
- }
115
-
116
- // Enhanced tool logging with detailed metadata and length limiting
117
- function logToolCall(toolName, action = 'call', toolUse = null, result = null) {
118
- const shortTime = new Date().toLocaleTimeString();
119
- let logEntry = `Tool: ${toolName}`;
120
-
121
- // Add conversational text if available
122
- if (global.conversationalBuffer.trim()) {
123
- const truncatedText = truncateText(global.conversationalBuffer.trim(), 80);
124
- logEntry += ` - "${truncatedText}"`;
125
- global.conversationalBuffer = ''; // Clear buffer after using
126
- }
127
-
128
- // Add compact metadata
129
- if (toolUse && action === 'call') {
130
- const metadata = extractToolMetadata(toolUse);
131
- const metaInfo = [];
132
-
133
- if (metadata.inputCount > 0) {
134
- metaInfo.push(`${metadata.inputCount} inputs`);
135
- }
136
-
137
- if (metadata.totalInputSize > 0) {
138
- metaInfo.push(`${metadata.totalInputSize} chars`);
139
- }
140
-
141
- if (metaInfo.length > 0) {
142
- logEntry += ` (${metaInfo.join(', ')})`;
143
- }
144
- }
145
-
146
- // Add result metadata
147
- if (result && action === 'result') {
148
- const metadata = extractResultMetadata(result);
149
- const resultInfo = [];
150
-
151
- if (metadata.hasContent) {
152
- if (metadata.size > 0) {
153
- if (metadata.type === 'text') {
154
- resultInfo.push(`${metadata.size} chars`);
155
- } else if (metadata.type === 'array') {
156
- resultInfo.push(`${metadata.size} items`);
157
- }
158
- }
159
-
160
- if (metadata.isError) {
161
- resultInfo.push('ERROR');
162
- }
163
- }
164
-
165
- if (resultInfo.length > 0) {
166
- logEntry += ` → ${resultInfo.join(', ')}`;
167
- }
168
- }
169
-
170
- const finalLogEntry = `[${shortTime}] ${truncateText(logEntry)}`;
171
-
172
- global.toolCallLog.push(finalLogEntry);
173
-
174
- // Keep only the last MAX_CONSOLE_LINES for console display
175
- if (global.toolCallLog.length > MAX_CONSOLE_LINES) {
176
- global.toolCallLog = global.toolCallLog.slice(-MAX_CONSOLE_LINES);
177
- }
178
-
179
- // Optionally log to file with full metadata
180
- if (ENABLE_FILE_LOGGING) {
181
- try {
182
- if (!global.logFileHandle) {
183
- const timestamp = new Date().toISOString();
184
- const logFileName = `sandboxbox-tool-calls-${Date.now()}.log`;
185
- writeFileSync(logFileName, `# SandboxBox Tool Calls Log\n# Started: ${timestamp}\n# Format: [timestamp] Tool: name - "text" (metadata)\n\n`);
186
- global.logFileHandle = logFileName;
187
- }
188
-
189
- const timestamp = new Date().toISOString();
190
- let fileLogEntry = `[${timestamp}] Tool: ${toolName}`;
191
-
192
- if (global.conversationalBuffer.trim()) {
193
- fileLogEntry += ` - "${global.conversationalBuffer.trim()}"`;
194
- }
195
-
196
- if (toolUse && action === 'call') {
197
- const metadata = extractToolMetadata(toolUse);
198
- fileLogEntry += `\n Input details: ${JSON.stringify(metadata.inputSizes, null, 2)}`;
199
- }
200
-
201
- if (result && action === 'result') {
202
- const metadata = extractResultMetadata(result);
203
- fileLogEntry += `\n Result details: ${JSON.stringify(metadata, null, 2)}`;
204
- }
205
-
206
- appendFileSync(global.logFileHandle, fileLogEntry + '\n');
207
- } catch (error) {
208
- // Don't fail if logging fails
209
- console.log(color('yellow', `āš ļø Could not write to log file: ${error.message}`));
210
- }
211
- }
212
- }
213
-
214
- // Function to log conversational text
215
- function logConversationalText(text) {
216
- if (text && text.trim()) {
217
- global.conversationalBuffer += text + ' ';
218
- }
219
- }
220
-
221
- // Helper function to display recent tool calls
222
- function displayRecentToolCalls() {
223
- if (global.toolCallLog.length > 0) {
224
- console.log(color('cyan', `\nšŸ”§ Recent tool calls (showing last ${global.toolCallLog.length}):`));
225
- global.toolCallLog.forEach(log => {
226
- console.log(color('cyan', ` ${log}`));
227
- });
228
- }
229
- }
230
-
231
-
232
- export async function claudeCommand(projectDir, prompt, flags = {}) {
233
- if (!existsSync(projectDir)) {
234
- console.log(color('red', `āŒ Project directory not found: ${projectDir}`));
235
- return false;
236
- }
237
-
238
- // Note: We don't require git repository anymore - the sandbox will initialize it if needed
239
-
240
- // Silent start - only show conversation unless verbose
241
-
242
- const startTime = Date.now();
243
- if (VERBOSE_OUTPUT) console.log(color('cyan', 'ā±ļø Stage 1: Creating sandbox...'));
244
-
245
- const { useHostSettings, headlessMode } = flags;
246
- const { sandboxDir, cleanup } = createSandbox(projectDir, { useHostSettings, headlessMode });
247
- const sandboxCreateTime = Date.now() - startTime;
248
- if (VERBOSE_OUTPUT) console.log(color('green', `āœ… Sandbox created in ${sandboxCreateTime}ms`));
249
-
250
- process.on('SIGINT', cleanup);
251
- process.on('SIGTERM', cleanup);
252
-
253
- try {
254
- const envStartTime = Date.now();
255
- if (VERBOSE_OUTPUT) console.log(color('cyan', 'ā±ļø Stage 2: Setting up environment...'));
256
-
257
- // Apply Claude optimizations (disabled to preserve bundled hooks)
258
- // const claudeOptimizer = new ClaudeOptimizer(sandboxDir);
259
- // claudeOptimizer.optimizeSettings();
260
- // await claudeOptimizer.prewarmPlugins();
261
-
262
- // Apply system optimizations (with sudo access)
263
- const systemOptimizer = new SystemOptimizer();
264
- const systemOptimizationsApplied = await systemOptimizer.optimizeSystem();
265
-
266
- // Create optimized environment
267
- const baseEnv = createSandboxEnv(sandboxDir, {
268
- CLAUDECODE: '1'
269
- });
270
-
271
- const env = systemOptimizationsApplied
272
- ? { ...baseEnv, ...systemOptimizer.createOptimizedContainerEnv() }
273
- : baseEnv; // Use base environment when optimizer is disabled
274
-
275
- const envCreateTime = Date.now() - envStartTime;
276
- if (VERBOSE_OUTPUT) console.log(color('green', `āœ… Environment configured in ${envCreateTime}ms`));
277
-
278
- if (systemOptimizationsApplied && VERBOSE_OUTPUT) {
279
- console.log(color('yellow', `šŸš€ System-level optimizations applied`));
280
- }
281
-
282
- const claudeArgs = [
283
- '--verbose',
284
- '--output-format', 'stream-json',
285
- '--permission-mode', 'bypassPermissions',
286
- '--allowed-tools', ALLOWED_TOOLS.join(',')
287
- ];
288
-
289
- return new Promise((resolve, reject) => {
290
- const claudeStartTime = Date.now();
291
- if (VERBOSE_OUTPUT) console.log(color('cyan', 'ā±ļø Stage 3: Starting Claude Code...'));
292
-
293
- const workspacePath = join(sandboxDir, 'workspace');
294
-
295
- // Handle headless mode Playwright MCP reconfiguration
296
- if (headlessMode) {
297
- if (VERBOSE_OUTPUT) console.log(color('yellow', 'šŸŽ­ Configuring headless Playwright MCP...'));
298
-
299
- try {
300
- // Remove existing Playwright MCP (ignoring failures)
301
- execSync('claude mcp remove playwright', {
302
- cwd: workspacePath,
303
- stdio: 'pipe',
304
- shell: true
305
- });
306
- } catch (e) {
307
- // Ignore removal failures
308
- }
309
-
310
- try {
311
- // Add headless Playwright MCP
312
- execSync('claude mcp add --headless --no-sandbox --scope=user playwright npx u/playwright/mcp@latest', {
313
- cwd: workspacePath,
314
- stdio: VERBOSE_OUTPUT ? 'inherit' : 'pipe',
315
- shell: true
316
- });
317
-
318
- if (VERBOSE_OUTPUT) console.log(color('green', 'āœ… Headless Playwright MCP configured'));
319
- } catch (e) {
320
- console.log(color('red', `āŒ Failed to configure headless Playwright MCP: ${e.message}`));
321
- }
322
- }
323
-
324
- // Modify the prompt to include directory change instruction
325
- const modifiedPrompt = `You are working in a sandboxed environment. Your working directory is "${workspacePath}". All operations should be performed in this directory. ${prompt}`;
326
-
327
- // Add network isolation environment variables for process-level isolation
328
- const networkIsolatedEnv = {
329
- ...env,
330
- // Force network isolation through environment variables
331
- SANDBOX_NETWORK_ISOLATED: 'true',
332
- // Add unique sandbox identifier for port isolation
333
- SANDBOX_ID: Math.random().toString(36).substr(2, 9),
334
- // Restrict network binding to localhost when possible
335
- NODE_OPTIONS: (env.NODE_OPTIONS || '') + ' --no-force-async-hooks-checks'
336
- };
337
-
338
- const proc = spawn('claude', claudeArgs, {
339
- cwd: workspacePath, // Set working directory directly
340
- env: networkIsolatedEnv, // Use network-isolated environment
341
- stdio: ['pipe', 'pipe', 'pipe'],
342
- shell: false, // Don't use shell since we're setting cwd directly
343
- detached: false
344
- });
345
-
346
- let claudeStarted = false;
347
- let jsonBuffer = ''; // Buffer for incomplete JSON lines
348
-
349
- function handleEvent(event) {
350
- if (event.type === 'system' && event.subtype === 'init') {
351
- if (!claudeStarted) {
352
- const claudeCreateTime = Date.now() - claudeStartTime;
353
- if (VERBOSE_OUTPUT) console.log(color('green', `āœ… Claude Code started in ${claudeCreateTime}ms`));
354
- claudeStarted = true;
355
- }
356
- if (VERBOSE_OUTPUT) console.log(color('green', `āœ… Session started (${event.session_id.substring(0, 8)}...)`));
357
- if (VERBOSE_OUTPUT) console.log(color('cyan', `šŸ“¦ Model: ${event.model}`));
358
-
359
- // Simple tool count warning only if less than 15 tools
360
- if (event.tools.length < 15) {
361
- console.log(color('yellow', `āš ļø Only ${event.tools.length} tools available`));
362
- }
363
- } else if (event.type === 'assistant' && event.message) {
364
- const content = event.message.content;
365
- if (Array.isArray(content)) {
366
- for (const block of content) {
367
- if (block.type === 'text') {
368
- // Always show conversational text immediately
369
- process.stdout.write(block.text);
370
- } else if (block.type === 'tool_use') {
371
- // Track the tool call for later result matching
372
- if (block.id) {
373
- global.pendingToolCalls.set(block.id, block.name);
374
- }
375
- logToolCall(block.name, 'call', block);
376
- if (VERBOSE_OUTPUT) {
377
- console.log(color('cyan', `\nšŸ”§ Using tool: ${block.name}`));
378
- }
379
- }
380
- }
381
- }
382
- } else if (event.type === 'user' && event.message) {
383
- const content = event.message.content;
384
- if (Array.isArray(content)) {
385
- for (const block of content) {
386
- if (block.type === 'tool_result' && block.tool_use_id) {
387
- // Match the result with the original tool call
388
- const toolUseId = block.tool_use_id;
389
- const toolName = global.pendingToolCalls.get(toolUseId) || `unknown_tool_${toolUseId}`;
390
-
391
- logToolCall(toolName, 'result', null, block);
392
-
393
- // Remove from pending calls after matching
394
- global.pendingToolCalls.delete(toolUseId);
395
- }
396
- }
397
- }
398
- } else if (event.type === 'result') {
399
- const usage = event.usage || {};
400
- const cost = event.total_cost_usd || 0;
401
- if (VERBOSE_OUTPUT) {
402
- console.log(color('green', `\n\nāœ… Completed in ${event.duration_ms}ms`));
403
- console.log(color('yellow', `šŸ’° Cost: $${cost.toFixed(4)}`));
404
- if (usage.input_tokens) {
405
- console.log(color('cyan', `šŸ“Š Tokens: ${usage.input_tokens} in, ${usage.output_tokens} out`));
406
- }
407
- }
408
- }
409
- }
410
-
411
- function handleStreamingOutput(data) {
412
- jsonBuffer += data.toString();
413
-
414
- // Split by newlines but keep the last incomplete line in buffer
415
- const lines = jsonBuffer.split('\n');
416
- jsonBuffer = lines.pop() || ''; // Keep last line (might be incomplete)
417
-
418
- for (const line of lines) {
419
- if (!line.trim()) continue; // Skip empty lines
420
-
421
- try {
422
- const event = JSON.parse(line);
423
- handleEvent(event);
424
- } catch (jsonError) {
425
- // Log JSON parsing errors for troubleshooting
426
- console.log(color('red', `šŸ” JSON parse error: ${jsonError.message}`));
427
- console.log(color('yellow', `šŸ” Problematic line (${line.length} chars): ${line.substring(0, 200)}${line.length > 200 ? '...' : ''}`));
428
- console.log(color('cyan', `šŸ” Buffer state: ${jsonBuffer.length} chars in buffer`));
429
-
430
- // If we can't parse, put the line back in buffer and try to recover
431
- jsonBuffer = line + '\n' + jsonBuffer;
432
- console.log(color('yellow', `šŸ” Attempting to recover - ${jsonBuffer.length} chars in buffer`));
433
- }
434
- }
435
- }
436
-
437
- // Add error handling
438
- proc.on('error', (error) => {
439
- console.log(color('red', `šŸ” Debug: Process error: ${error.message}`));
440
- reject(error);
441
- });
442
-
443
- // Write modified prompt to stdin
444
- proc.stdin.write(modifiedPrompt);
445
- proc.stdin.end();
446
-
447
- let stdoutOutput = '';
448
- let stderrOutput = '';
449
- let lastError = '';
450
-
451
- proc.stdout.on('data', (data) => {
452
- stdoutOutput += data.toString();
453
-
454
- handleStreamingOutput(data);
455
- });
456
-
457
- proc.stderr.on('data', (data) => {
458
- stderrOutput += data.toString();
459
- process.stderr.write(data);
460
- });
461
-
462
- proc.on('close', (code) => {
463
- const sessionEndTime = Date.now();
464
- const totalTime = sessionEndTime - startTime;
465
- if (VERBOSE_OUTPUT) console.log(color('cyan', `\nā±ļø Stage 4: Session completed in ${totalTime}ms`));
466
-
467
- // Try to parse any remaining data in buffer
468
- if (jsonBuffer.trim()) {
469
- try {
470
- const event = JSON.parse(jsonBuffer);
471
- handleEvent(event);
472
- } catch (error) {
473
- console.log(color('yellow', `āš ļø Could not parse remaining buffer data: ${error.message}`));
474
- console.log(color('yellow', `āš ļø Remaining buffer: ${jsonBuffer.substring(0, 100)}...`));
475
- }
476
- }
477
-
478
- // Display recent tool calls and performance summary only if verbose
479
- if (VERBOSE_OUTPUT) {
480
- displayRecentToolCalls();
481
- console.log(color('cyan', `\nšŸ“Š Performance Summary:`));
482
- console.log(color('cyan', ` • Sandbox creation: ${sandboxCreateTime}ms`));
483
- console.log(color('cyan', ` • Environment setup: ${envCreateTime}ms`));
484
- console.log(color('cyan', ` • Claude Code session: ${totalTime - sandboxCreateTime - envCreateTime}ms`));
485
- console.log(color('cyan', ` • Total time: ${totalTime}ms`));
486
-
487
- // Log file information if enabled
488
- if (ENABLE_FILE_LOGGING && global.logFileHandle) {
489
- console.log(color('yellow', `šŸ“ Tool calls logged to: ${global.logFileHandle}`));
490
- }
491
- }
492
-
493
- cleanup();
494
- resolve(true);
495
- });
496
- });
497
- } catch (error) {
498
- console.log(color('red', `\nāŒ Claude Code failed: ${error.message}`));
499
- cleanup();
500
- return false;
501
- }
502
- }