sandboxbox 3.0.15 → 3.0.16

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.16",
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,50 @@ 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 ENABLE_FILE_LOGGING = process.env.SANDBOX_ENABLE_FILE_LOGGING === 'true';
23
+ global.toolCallLog = [];
24
+ global.logFileHandle = null;
25
+
26
+ // Helper function to log tool calls
27
+ function logToolCall(toolName, action = 'call') {
28
+ const timestamp = new Date().toISOString();
29
+ const logEntry = `[${timestamp}] Tool ${action}: ${toolName}`;
30
+
31
+ global.toolCallLog.push(logEntry);
32
+
33
+ // Keep only the last MAX_CONSOLE_LINES for console display
34
+ if (global.toolCallLog.length > MAX_CONSOLE_LINES) {
35
+ global.toolCallLog = global.toolCallLog.slice(-MAX_CONSOLE_LINES);
36
+ }
37
+
38
+ // Optionally log to file
39
+ if (ENABLE_FILE_LOGGING) {
40
+ try {
41
+ if (!global.logFileHandle) {
42
+ const logFileName = `sandboxbox-tool-calls-${Date.now()}.log`;
43
+ writeFileSync(logFileName, `# SandboxBox Tool Calls Log\n# Started: ${timestamp}\n\n`);
44
+ global.logFileHandle = logFileName;
45
+ }
46
+ appendFileSync(global.logFileHandle, logEntry + '\n');
47
+ } catch (error) {
48
+ // Don't fail if logging fails
49
+ console.log(color('yellow', `⚠️ Could not write to log file: ${error.message}`));
50
+ }
51
+ }
52
+ }
53
+
54
+ // Helper function to display recent tool calls
55
+ function displayRecentToolCalls() {
56
+ if (global.toolCallLog.length > 0) {
57
+ console.log(color('cyan', `\n🔧 Recent tool calls (showing last ${global.toolCallLog.length}):`));
58
+ global.toolCallLog.forEach(log => {
59
+ console.log(color('cyan', ` ${log}`));
60
+ });
61
+ }
62
+ }
63
+
20
64
 
21
65
  export async function claudeCommand(projectDir, prompt) {
22
66
  if (!existsSync(projectDir)) {
@@ -99,56 +143,71 @@ export async function claudeCommand(projectDir, prompt) {
99
143
  });
100
144
 
101
145
  let claudeStarted = false;
146
+ let jsonBuffer = ''; // Buffer for incomplete JSON lines
147
+
148
+ function handleEvent(event) {
149
+ if (event.type === 'system' && event.subtype === 'init') {
150
+ if (!claudeStarted) {
151
+ const claudeCreateTime = Date.now() - claudeStartTime;
152
+ console.log(color('green', `✅ Claude Code started in ${claudeCreateTime}ms`));
153
+ claudeStarted = true;
154
+ }
155
+ console.log(color('green', `✅ Session started (${event.session_id.substring(0, 8)}...)`));
156
+ console.log(color('cyan', `📦 Model: ${event.model}`));
157
+ console.log(color('cyan', `🔧 Tools: ${event.tools.length} available`));
158
+
159
+ // List available tools
160
+ if (event.tools && event.tools.length > 0) {
161
+ const toolNames = event.tools.map(tool => tool.name || tool).sort();
162
+ console.log(color('yellow', ` Available: ${toolNames.join(', ')}\n`));
163
+ } else {
164
+ console.log('');
165
+ }
166
+ } else if (event.type === 'assistant' && event.message) {
167
+ const content = event.message.content;
168
+ if (Array.isArray(content)) {
169
+ for (const block of content) {
170
+ if (block.type === 'text') {
171
+ process.stdout.write(block.text);
172
+ } else if (block.type === 'tool_use') {
173
+ logToolCall(block.name);
174
+ console.log(color('cyan', `\n🔧 Using tool: ${block.name}`));
175
+ }
176
+ }
177
+ }
178
+ } else if (event.type === 'result') {
179
+ const usage = event.usage || {};
180
+ const cost = event.total_cost_usd || 0;
181
+ console.log(color('green', `\n\n✅ Completed in ${event.duration_ms}ms`));
182
+ console.log(color('yellow', `💰 Cost: $${cost.toFixed(4)}`));
183
+ if (usage.input_tokens) {
184
+ console.log(color('cyan', `📊 Tokens: ${usage.input_tokens} in, ${usage.output_tokens} out`));
185
+ }
186
+ }
187
+ }
102
188
 
103
189
  function handleStreamingOutput(data) {
104
- const lines = data.toString().split('\n').filter(line => line.trim());
190
+ jsonBuffer += data.toString();
191
+
192
+ // Split by newlines but keep the last incomplete line in buffer
193
+ const lines = jsonBuffer.split('\n');
194
+ jsonBuffer = lines.pop() || ''; // Keep last line (might be incomplete)
105
195
 
106
196
  for (const line of lines) {
197
+ if (!line.trim()) continue; // Skip empty lines
198
+
107
199
  try {
108
200
  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
- }
201
+ handleEvent(event);
147
202
  } catch (jsonError) {
148
203
  // Log JSON parsing errors for troubleshooting
149
204
  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'));
205
+ console.log(color('yellow', `🔍 Problematic line (${line.length} chars): ${line.substring(0, 200)}${line.length > 200 ? '...' : ''}`));
206
+ console.log(color('cyan', `🔍 Buffer state: ${jsonBuffer.length} chars in buffer`));
207
+
208
+ // If we can't parse, put the line back in buffer and try to recover
209
+ jsonBuffer = line + '\n' + jsonBuffer;
210
+ console.log(color('yellow', `🔍 Attempting to recover - ${jsonBuffer.length} chars in buffer`));
152
211
  }
153
212
  }
154
213
  }
@@ -183,6 +242,20 @@ export async function claudeCommand(projectDir, prompt) {
183
242
  const totalTime = sessionEndTime - startTime;
184
243
  console.log(color('cyan', `\n⏱️ Stage 4: Session completed in ${totalTime}ms`));
185
244
 
245
+ // Try to parse any remaining data in buffer
246
+ if (jsonBuffer.trim()) {
247
+ try {
248
+ const event = JSON.parse(jsonBuffer);
249
+ handleEvent(event);
250
+ } catch (error) {
251
+ console.log(color('yellow', `⚠️ Could not parse remaining buffer data: ${error.message}`));
252
+ console.log(color('yellow', `⚠️ Remaining buffer: ${jsonBuffer.substring(0, 100)}...`));
253
+ }
254
+ }
255
+
256
+ // Display recent tool calls
257
+ displayRecentToolCalls();
258
+
186
259
  // Performance summary
187
260
  console.log(color('cyan', `\n📊 Performance Summary:`));
188
261
  console.log(color('cyan', ` • Sandbox creation: ${sandboxCreateTime}ms`));
@@ -190,6 +263,11 @@ export async function claudeCommand(projectDir, prompt) {
190
263
  console.log(color('cyan', ` • Claude Code session: ${totalTime - sandboxCreateTime - envCreateTime}ms`));
191
264
  console.log(color('cyan', ` • Total time: ${totalTime}ms`));
192
265
 
266
+ // Log file information if enabled
267
+ if (ENABLE_FILE_LOGGING && global.logFileHandle) {
268
+ console.log(color('yellow', `📝 Tool calls logged to: ${global.logFileHandle}`));
269
+ }
270
+
193
271
  cleanup();
194
272
  resolve(true);
195
273
  });
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