sandboxbox 3.0.15 → 3.0.17

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.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sandboxbox",
3
- "version": "3.0.15",
3
+ "version": "3.0.17",
4
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",
@@ -1,4 +1,4 @@
1
- import { existsSync } from 'fs';
1
+ import { existsSync, writeFileSync, appendFileSync } from 'fs';
2
2
  import { resolve, join } from 'path';
3
3
  import { spawn, execSync } from 'child_process';
4
4
  import { color } from '../colors.js';
@@ -17,6 +17,216 @@ const ALLOWED_TOOLS = [
17
17
  'mcp__vexify__search_code'
18
18
  ];
19
19
 
20
+ // Console output configuration
21
+ const MAX_CONSOLE_LINES = parseInt(process.env.SANDBOX_MAX_CONSOLE_LINES) || 5;
22
+ const MAX_LOG_ENTRY_LENGTH = parseInt(process.env.SANDBOX_MAX_LOG_LENGTH) || 200;
23
+ const ENABLE_FILE_LOGGING = process.env.SANDBOX_ENABLE_FILE_LOGGING === 'true';
24
+ const VERBOSE_OUTPUT = process.env.SANDBOX_VERBOSE === 'true' || process.argv.includes('--verbose');
25
+ global.toolCallLog = [];
26
+ global.logFileHandle = null;
27
+ global.pendingToolCalls = new Map(); // Track tool calls by ID for result matching
28
+ global.conversationalBuffer = ''; // Track conversational text between tool calls
29
+
30
+ // Helper function to extract tool metadata without showing actual content
31
+ function extractToolMetadata(toolUse) {
32
+ const metadata = {
33
+ name: toolUse.name || 'unknown',
34
+ id: toolUse.id || 'no-id',
35
+ inputCount: 0,
36
+ inputTypes: {},
37
+ inputSizes: {},
38
+ totalInputSize: 0
39
+ };
40
+
41
+ if (toolUse.input && typeof toolUse.input === 'object') {
42
+ metadata.inputCount = Object.keys(toolUse.input).length;
43
+
44
+ for (const [key, value] of Object.entries(toolUse.input)) {
45
+ const type = Array.isArray(value) ? 'array' : typeof value;
46
+ metadata.inputTypes[key] = type;
47
+
48
+ // Calculate sizes without exposing content
49
+ if (type === 'string') {
50
+ metadata.inputSizes[key] = `${value.length} chars`;
51
+ metadata.totalInputSize += value.length;
52
+ } else if (type === 'array') {
53
+ metadata.inputSizes[key] = `${value.length} items`;
54
+ metadata.totalInputSize += JSON.stringify(value).length;
55
+ } else if (type === 'object' && value !== null) {
56
+ const objSize = JSON.stringify(value).length;
57
+ metadata.inputSizes[key] = `${objSize} chars`;
58
+ metadata.totalInputSize += objSize;
59
+ } else {
60
+ metadata.inputSizes[key] = `${type}`;
61
+ }
62
+ }
63
+ }
64
+
65
+ return metadata;
66
+ }
67
+
68
+ // Helper function to extract tool result metadata
69
+ function extractResultMetadata(result) {
70
+ const metadata = {
71
+ type: 'unknown',
72
+ size: 0,
73
+ hasContent: false,
74
+ isToolResult: false,
75
+ isError: false
76
+ };
77
+
78
+ if (result && typeof result === 'object') {
79
+ metadata.isToolResult = result.type === 'tool_result';
80
+ metadata.isError = result.is_error || false;
81
+
82
+ if (result.content) {
83
+ metadata.hasContent = true;
84
+
85
+ if (typeof result.content === 'string') {
86
+ metadata.type = 'text';
87
+ metadata.size = result.content.length;
88
+ } else if (Array.isArray(result.content)) {
89
+ metadata.type = 'array';
90
+ metadata.size = result.content.length;
91
+ // Count items by type without showing content
92
+ const typeCounts = {};
93
+ result.content.forEach(item => {
94
+ const itemType = item?.type || 'unknown';
95
+ typeCounts[itemType] = (typeCounts[itemType] || 0) + 1;
96
+ });
97
+ metadata.itemTypes = typeCounts;
98
+ } else if (typeof result.content === 'object') {
99
+ metadata.type = 'object';
100
+ metadata.size = Object.keys(result.content).length;
101
+ }
102
+ }
103
+ }
104
+
105
+ return metadata;
106
+ }
107
+
108
+
109
+ // Helper function to truncate text to a sensible length
110
+ function truncateText(text, maxLength = MAX_LOG_ENTRY_LENGTH) {
111
+ if (text.length <= maxLength) return text;
112
+ return text.substring(0, maxLength - 3) + '...';
113
+ }
114
+
115
+ // Enhanced tool logging with detailed metadata and length limiting
116
+ function logToolCall(toolName, action = 'call', toolUse = null, result = null) {
117
+ const shortTime = new Date().toLocaleTimeString();
118
+ let logEntry = `Tool: ${toolName}`;
119
+
120
+ // Add conversational text if available
121
+ if (global.conversationalBuffer.trim()) {
122
+ const truncatedText = truncateText(global.conversationalBuffer.trim(), 80);
123
+ logEntry += ` - "${truncatedText}"`;
124
+ global.conversationalBuffer = ''; // Clear buffer after using
125
+ }
126
+
127
+ // Add compact metadata
128
+ if (toolUse && action === 'call') {
129
+ const metadata = extractToolMetadata(toolUse);
130
+ const metaInfo = [];
131
+
132
+ if (metadata.inputCount > 0) {
133
+ metaInfo.push(`${metadata.inputCount} inputs`);
134
+ }
135
+
136
+ if (metadata.totalInputSize > 0) {
137
+ metaInfo.push(`${metadata.totalInputSize} chars`);
138
+ }
139
+
140
+ if (metaInfo.length > 0) {
141
+ logEntry += ` (${metaInfo.join(', ')})`;
142
+ }
143
+ }
144
+
145
+ // Add result metadata
146
+ if (result && action === 'result') {
147
+ const metadata = extractResultMetadata(result);
148
+ const resultInfo = [];
149
+
150
+ if (metadata.hasContent) {
151
+ if (metadata.size > 0) {
152
+ if (metadata.type === 'text') {
153
+ resultInfo.push(`${metadata.size} chars`);
154
+ } else if (metadata.type === 'array') {
155
+ resultInfo.push(`${metadata.size} items`);
156
+ }
157
+ }
158
+
159
+ if (metadata.isError) {
160
+ resultInfo.push('ERROR');
161
+ }
162
+ }
163
+
164
+ if (resultInfo.length > 0) {
165
+ logEntry += ` → ${resultInfo.join(', ')}`;
166
+ }
167
+ }
168
+
169
+ const finalLogEntry = `[${shortTime}] ${truncateText(logEntry)}`;
170
+
171
+ global.toolCallLog.push(finalLogEntry);
172
+
173
+ // Keep only the last MAX_CONSOLE_LINES for console display
174
+ if (global.toolCallLog.length > MAX_CONSOLE_LINES) {
175
+ global.toolCallLog = global.toolCallLog.slice(-MAX_CONSOLE_LINES);
176
+ }
177
+
178
+ // Optionally log to file with full metadata
179
+ if (ENABLE_FILE_LOGGING) {
180
+ try {
181
+ if (!global.logFileHandle) {
182
+ const timestamp = new Date().toISOString();
183
+ const logFileName = `sandboxbox-tool-calls-${Date.now()}.log`;
184
+ writeFileSync(logFileName, `# SandboxBox Tool Calls Log\n# Started: ${timestamp}\n# Format: [timestamp] Tool: name - "text" (metadata)\n\n`);
185
+ global.logFileHandle = logFileName;
186
+ }
187
+
188
+ const timestamp = new Date().toISOString();
189
+ let fileLogEntry = `[${timestamp}] Tool: ${toolName}`;
190
+
191
+ if (global.conversationalBuffer.trim()) {
192
+ fileLogEntry += ` - "${global.conversationalBuffer.trim()}"`;
193
+ }
194
+
195
+ if (toolUse && action === 'call') {
196
+ const metadata = extractToolMetadata(toolUse);
197
+ fileLogEntry += `\n Input details: ${JSON.stringify(metadata.inputSizes, null, 2)}`;
198
+ }
199
+
200
+ if (result && action === 'result') {
201
+ const metadata = extractResultMetadata(result);
202
+ fileLogEntry += `\n Result details: ${JSON.stringify(metadata, null, 2)}`;
203
+ }
204
+
205
+ appendFileSync(global.logFileHandle, fileLogEntry + '\n');
206
+ } catch (error) {
207
+ // Don't fail if logging fails
208
+ console.log(color('yellow', `āš ļø Could not write to log file: ${error.message}`));
209
+ }
210
+ }
211
+ }
212
+
213
+ // Function to log conversational text
214
+ function logConversationalText(text) {
215
+ if (text && text.trim()) {
216
+ global.conversationalBuffer += text + ' ';
217
+ }
218
+ }
219
+
220
+ // Helper function to display recent tool calls
221
+ function displayRecentToolCalls() {
222
+ if (global.toolCallLog.length > 0) {
223
+ console.log(color('cyan', `\nšŸ”§ Recent tool calls (showing last ${global.toolCallLog.length}):`));
224
+ global.toolCallLog.forEach(log => {
225
+ console.log(color('cyan', ` ${log}`));
226
+ });
227
+ }
228
+ }
229
+
20
230
 
21
231
  export async function claudeCommand(projectDir, prompt) {
22
232
  if (!existsSync(projectDir)) {
@@ -32,18 +242,18 @@ export async function claudeCommand(projectDir, prompt) {
32
242
  console.log('');
33
243
 
34
244
  const startTime = Date.now();
35
- console.log(color('cyan', 'ā±ļø Stage 1: Creating sandbox...'));
245
+ if (VERBOSE_OUTPUT) console.log(color('cyan', 'ā±ļø Stage 1: Creating sandbox...'));
36
246
 
37
247
  const { sandboxDir, cleanup } = createSandbox(projectDir);
38
248
  const sandboxCreateTime = Date.now() - startTime;
39
- console.log(color('green', `āœ… Sandbox created in ${sandboxCreateTime}ms`));
249
+ if (VERBOSE_OUTPUT) console.log(color('green', `āœ… Sandbox created in ${sandboxCreateTime}ms`));
40
250
 
41
251
  process.on('SIGINT', cleanup);
42
252
  process.on('SIGTERM', cleanup);
43
253
 
44
254
  try {
45
255
  const envStartTime = Date.now();
46
- console.log(color('cyan', 'ā±ļø Stage 2: Setting up environment...'));
256
+ if (VERBOSE_OUTPUT) console.log(color('cyan', 'ā±ļø Stage 2: Setting up environment...'));
47
257
 
48
258
  // Apply Claude optimizations
49
259
  const claudeOptimizer = new ClaudeOptimizer(sandboxDir);
@@ -64,13 +274,13 @@ export async function claudeCommand(projectDir, prompt) {
64
274
  : claudeOptimizer.createOptimizedEnv(baseEnv);
65
275
 
66
276
  const envCreateTime = Date.now() - envStartTime;
67
- console.log(color('green', `āœ… Environment configured in ${envCreateTime}ms`));
277
+ if (VERBOSE_OUTPUT) console.log(color('green', `āœ… Environment configured in ${envCreateTime}ms`));
68
278
 
69
- if (systemOptimizationsApplied) {
279
+ if (systemOptimizationsApplied && VERBOSE_OUTPUT) {
70
280
  console.log(color('yellow', `šŸš€ System-level optimizations applied`));
71
281
  }
72
282
 
73
- console.log(color('cyan', `šŸ“¦ Using host Claude settings with all available tools\n`));
283
+ if (VERBOSE_OUTPUT) console.log(color('cyan', `šŸ“¦ Using host Claude settings with all available tools\n`));
74
284
 
75
285
  const claudeArgs = [
76
286
  '--verbose',
@@ -79,7 +289,7 @@ export async function claudeCommand(projectDir, prompt) {
79
289
  '--allowed-tools', ALLOWED_TOOLS.join(',')
80
290
  ];
81
291
 
82
- console.log(color('blue', `šŸ“ Running Claude Code with host settings\n`));
292
+ if (VERBOSE_OUTPUT) console.log(color('blue', `šŸ“ Running Claude Code with host settings\n`));
83
293
 
84
294
  return new Promise((resolve, reject) => {
85
295
  const claudeStartTime = Date.now();
@@ -99,56 +309,98 @@ export async function claudeCommand(projectDir, prompt) {
99
309
  });
100
310
 
101
311
  let claudeStarted = false;
312
+ let jsonBuffer = ''; // Buffer for incomplete JSON lines
313
+
314
+ function handleEvent(event) {
315
+ if (event.type === 'system' && event.subtype === 'init') {
316
+ if (!claudeStarted) {
317
+ const claudeCreateTime = Date.now() - claudeStartTime;
318
+ console.log(color('green', `āœ… Claude Code started in ${claudeCreateTime}ms`));
319
+ claudeStarted = true;
320
+ }
321
+ console.log(color('green', `āœ… Session started (${event.session_id.substring(0, 8)}...)`));
322
+ console.log(color('cyan', `šŸ“¦ Model: ${event.model}`));
323
+ console.log(color('cyan', `šŸ”§ Tools: ${event.tools.length} available`));
324
+
325
+ // List available tools
326
+ if (event.tools && event.tools.length > 0) {
327
+ const toolNames = event.tools.map(tool => tool.name || tool).sort();
328
+ console.log(color('yellow', ` Available: ${toolNames.join(', ')}\n`));
329
+ } else {
330
+ console.log('');
331
+ }
332
+ } else if (event.type === 'assistant' && event.message) {
333
+ const content = event.message.content;
334
+ if (Array.isArray(content)) {
335
+ for (const block of content) {
336
+ if (block.type === 'text') {
337
+ // Capture conversational text and display if verbose
338
+ if (VERBOSE_OUTPUT) {
339
+ process.stdout.write(block.text);
340
+ } else {
341
+ logConversationalText(block.text);
342
+ }
343
+ } else if (block.type === 'tool_use') {
344
+ // Track the tool call for later result matching
345
+ if (block.id) {
346
+ global.pendingToolCalls.set(block.id, block.name);
347
+ }
348
+ logToolCall(block.name, 'call', block);
349
+ if (VERBOSE_OUTPUT) {
350
+ console.log(color('cyan', `\nšŸ”§ Using tool: ${block.name}`));
351
+ }
352
+ }
353
+ }
354
+ }
355
+ } else if (event.type === 'user' && event.message) {
356
+ const content = event.message.content;
357
+ if (Array.isArray(content)) {
358
+ for (const block of content) {
359
+ if (block.type === 'tool_result' && block.tool_use_id) {
360
+ // Match the result with the original tool call
361
+ const toolUseId = block.tool_use_id;
362
+ const toolName = global.pendingToolCalls.get(toolUseId) || `unknown_tool_${toolUseId}`;
363
+
364
+ logToolCall(toolName, 'result', null, block);
365
+
366
+ // Remove from pending calls after matching
367
+ global.pendingToolCalls.delete(toolUseId);
368
+ }
369
+ }
370
+ }
371
+ } else if (event.type === 'result') {
372
+ const usage = event.usage || {};
373
+ const cost = event.total_cost_usd || 0;
374
+ console.log(color('green', `\n\nāœ… Completed in ${event.duration_ms}ms`));
375
+ console.log(color('yellow', `šŸ’° Cost: $${cost.toFixed(4)}`));
376
+ if (usage.input_tokens) {
377
+ console.log(color('cyan', `šŸ“Š Tokens: ${usage.input_tokens} in, ${usage.output_tokens} out`));
378
+ }
379
+ }
380
+ }
102
381
 
103
382
  function handleStreamingOutput(data) {
104
- const lines = data.toString().split('\n').filter(line => line.trim());
383
+ jsonBuffer += data.toString();
384
+
385
+ // Split by newlines but keep the last incomplete line in buffer
386
+ const lines = jsonBuffer.split('\n');
387
+ jsonBuffer = lines.pop() || ''; // Keep last line (might be incomplete)
105
388
 
106
389
  for (const line of lines) {
390
+ if (!line.trim()) continue; // Skip empty lines
391
+
107
392
  try {
108
393
  const event = JSON.parse(line);
109
-
110
- if (event.type === 'system' && event.subtype === 'init') {
111
- if (!claudeStarted) {
112
- const claudeCreateTime = Date.now() - claudeStartTime;
113
- console.log(color('green', `āœ… Claude Code started in ${claudeCreateTime}ms`));
114
- claudeStarted = true;
115
- }
116
- console.log(color('green', `āœ… Session started (${event.session_id.substring(0, 8)}...)`));
117
- console.log(color('cyan', `šŸ“¦ Model: ${event.model}`));
118
- console.log(color('cyan', `šŸ”§ Tools: ${event.tools.length} available`));
119
-
120
- // List available tools
121
- if (event.tools && event.tools.length > 0) {
122
- const toolNames = event.tools.map(tool => tool.name || tool).sort();
123
- console.log(color('yellow', ` Available: ${toolNames.join(', ')}\n`));
124
- } else {
125
- console.log('');
126
- }
127
- } else if (event.type === 'assistant' && event.message) {
128
- const content = event.message.content;
129
- if (Array.isArray(content)) {
130
- for (const block of content) {
131
- if (block.type === 'text') {
132
- process.stdout.write(block.text);
133
- } else if (block.type === 'tool_use') {
134
- console.log(color('cyan', `\nšŸ”§ Using tool: ${block.name}`));
135
- }
136
- }
137
- }
138
- } else if (event.type === 'result') {
139
- const usage = event.usage || {};
140
- const cost = event.total_cost_usd || 0;
141
- console.log(color('green', `\n\nāœ… Completed in ${event.duration_ms}ms`));
142
- console.log(color('yellow', `šŸ’° Cost: $${cost.toFixed(4)}`));
143
- if (usage.input_tokens) {
144
- console.log(color('cyan', `šŸ“Š Tokens: ${usage.input_tokens} in, ${usage.output_tokens} out`));
145
- }
146
- }
394
+ handleEvent(event);
147
395
  } catch (jsonError) {
148
396
  // Log JSON parsing errors for troubleshooting
149
397
  console.log(color('red', `šŸ” JSON parse error: ${jsonError.message}`));
150
- console.log(color('yellow', `šŸ” Problematic line: ${line}`));
151
- console.log(color('cyan', 'šŸ” This suggests an issue with the output stream formatting'));
398
+ console.log(color('yellow', `šŸ” Problematic line (${line.length} chars): ${line.substring(0, 200)}${line.length > 200 ? '...' : ''}`));
399
+ console.log(color('cyan', `šŸ” Buffer state: ${jsonBuffer.length} chars in buffer`));
400
+
401
+ // If we can't parse, put the line back in buffer and try to recover
402
+ jsonBuffer = line + '\n' + jsonBuffer;
403
+ console.log(color('yellow', `šŸ” Attempting to recover - ${jsonBuffer.length} chars in buffer`));
152
404
  }
153
405
  }
154
406
  }
@@ -183,6 +435,20 @@ export async function claudeCommand(projectDir, prompt) {
183
435
  const totalTime = sessionEndTime - startTime;
184
436
  console.log(color('cyan', `\nā±ļø Stage 4: Session completed in ${totalTime}ms`));
185
437
 
438
+ // Try to parse any remaining data in buffer
439
+ if (jsonBuffer.trim()) {
440
+ try {
441
+ const event = JSON.parse(jsonBuffer);
442
+ handleEvent(event);
443
+ } catch (error) {
444
+ console.log(color('yellow', `āš ļø Could not parse remaining buffer data: ${error.message}`));
445
+ console.log(color('yellow', `āš ļø Remaining buffer: ${jsonBuffer.substring(0, 100)}...`));
446
+ }
447
+ }
448
+
449
+ // Display recent tool calls
450
+ displayRecentToolCalls();
451
+
186
452
  // Performance summary
187
453
  console.log(color('cyan', `\nšŸ“Š Performance Summary:`));
188
454
  console.log(color('cyan', ` • Sandbox creation: ${sandboxCreateTime}ms`));
@@ -190,6 +456,11 @@ export async function claudeCommand(projectDir, prompt) {
190
456
  console.log(color('cyan', ` • Claude Code session: ${totalTime - sandboxCreateTime - envCreateTime}ms`));
191
457
  console.log(color('cyan', ` • Total time: ${totalTime}ms`));
192
458
 
459
+ // Log file information if enabled
460
+ if (ENABLE_FILE_LOGGING && global.logFileHandle) {
461
+ console.log(color('yellow', `šŸ“ Tool calls logged to: ${global.logFileHandle}`));
462
+ }
463
+
193
464
  cleanup();
194
465
  resolve(true);
195
466
  });
package/utils/sandbox.js CHANGED
@@ -190,6 +190,15 @@ export function createSandbox(projectDir) {
190
190
  }
191
191
 
192
192
  const cleanup = () => {
193
+ // Close any log files that might be open
194
+ if (global.logFileHandle) {
195
+ try {
196
+ appendFileSync(global.logFileHandle, `\n# Session ended: ${new Date().toISOString()}\n`);
197
+ global.logFileHandle = null;
198
+ } catch (error) {
199
+ // Don't fail on log cleanup
200
+ }
201
+ }
193
202
  rmSync(sandboxDir, { recursive: true, force: true });
194
203
  };
195
204
 
package/utils/ui.js CHANGED
@@ -1,9 +1,12 @@
1
1
  import { color } from './colors.js';
2
2
 
3
3
  export function showBanner() {
4
- console.log(color('cyan', 'šŸ“¦ SandboxBox - Portable Container Runner'));
5
- console.log(color('cyan', '═════════════════════════════════════════════════'));
6
- console.log('');
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
+ }
7
10
  }
8
11
 
9
12
  export function showHelp() {