claude-code-templates 1.10.1 → 1.12.0
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/README.md +6 -0
- package/bin/create-claude-config.js +1 -0
- package/package.json +1 -2
- package/src/analytics/core/ConversationAnalyzer.js +159 -43
- package/src/analytics/core/FileWatcher.js +146 -11
- package/src/analytics/data/DataCache.js +124 -19
- package/src/analytics/notifications/NotificationManager.js +37 -0
- package/src/analytics/notifications/WebSocketServer.js +1 -1
- package/src/analytics-web/FRONT_ARCHITECTURE.md +46 -0
- package/src/analytics-web/assets/js/{main.js → main.js.deprecated} +32 -3
- package/src/analytics-web/components/AgentsPage.js +4744 -0
- package/src/analytics-web/components/App.js +441 -0
- package/src/analytics-web/components/{Dashboard.js → Dashboard.js.deprecated} +23 -7
- package/src/analytics-web/components/DashboardPage.js +1531 -0
- package/src/analytics-web/components/Sidebar.js +197 -0
- package/src/analytics-web/components/ToolDisplay.js +554 -0
- package/src/analytics-web/index.html +5189 -1760
- package/src/analytics-web/services/DataService.js +89 -16
- package/src/analytics-web/services/StateService.js +9 -0
- package/src/analytics-web/services/WebSocketService.js +17 -5
- package/src/analytics.js +665 -38
- package/src/console-bridge.js +610 -0
- package/src/file-operations.js +143 -23
- package/src/index.js +24 -1
- package/src/templates.js +4 -0
- package/src/test-console-bridge.js +67 -0
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolDisplay - Dedicated component for displaying tool uses and results safely
|
|
3
|
+
* Handles proper formatting, truncation, and escaping of tool content
|
|
4
|
+
*/
|
|
5
|
+
class ToolDisplay {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.maxContentLength = 500;
|
|
8
|
+
this.maxParamLength = 100;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Render a tool use block
|
|
13
|
+
* @param {Object} toolBlock - Tool use block
|
|
14
|
+
* @param {Array} toolResults - Associated tool results (optional)
|
|
15
|
+
* @returns {string} Safe HTML string
|
|
16
|
+
*/
|
|
17
|
+
renderToolUse(toolBlock, toolResults = null) {
|
|
18
|
+
const originalToolName = toolBlock.name || 'Unknown';
|
|
19
|
+
const toolName = this.escapeHtml(originalToolName);
|
|
20
|
+
const toolId = toolBlock.id ? toolBlock.id.slice(-8) : 'unknown';
|
|
21
|
+
|
|
22
|
+
// Generate compact command representation
|
|
23
|
+
const commandSummary = this.generateCompactCommand(toolName, toolBlock.input);
|
|
24
|
+
|
|
25
|
+
// For ALL tools, ALWAYS add a "Show details" button
|
|
26
|
+
const contentId = originalToolName.toLowerCase() + '_' + toolId + '_' + Date.now();
|
|
27
|
+
|
|
28
|
+
// Try to find corresponding tool result in toolResults first
|
|
29
|
+
let matchingResult = null;
|
|
30
|
+
if (toolResults && Array.isArray(toolResults)) {
|
|
31
|
+
matchingResult = toolResults.find(result => result.tool_use_id === toolBlock.id);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Prepare comprehensive modal content for all tools (without tool results, as they're shown inline now)
|
|
35
|
+
let modalContent = this.generateComprehensiveToolContent(toolName, toolBlock, null);
|
|
36
|
+
|
|
37
|
+
// Store the tool content for modal display
|
|
38
|
+
if (typeof window !== 'undefined') {
|
|
39
|
+
window.storedContent = window.storedContent || {};
|
|
40
|
+
window.storedContent[contentId] = modalContent;
|
|
41
|
+
|
|
42
|
+
// Store tool data for all supported tools for custom modals
|
|
43
|
+
const supportedTools = ['Read', 'Edit', 'Write', 'Bash', 'Glob', 'Grep', 'TodoWrite'];
|
|
44
|
+
if (supportedTools.includes(originalToolName)) {
|
|
45
|
+
console.log(`🔧 ToolDisplay: Storing ${originalToolName} tool data for contentId:`, contentId);
|
|
46
|
+
window.storedToolData = window.storedToolData || {};
|
|
47
|
+
window.storedToolData[contentId] = {
|
|
48
|
+
name: originalToolName,
|
|
49
|
+
input: toolBlock.input || {},
|
|
50
|
+
id: toolBlock.id,
|
|
51
|
+
isToolDetails: true
|
|
52
|
+
};
|
|
53
|
+
console.log(`🔧 ToolDisplay: Stored tool data:`, window.storedToolData[contentId]);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Always show "Show details" for ALL tools
|
|
58
|
+
const buttonClass = toolName === 'Bash' ? 'bash-cmd-btn' : 'tool-detail-btn';
|
|
59
|
+
let showResultsButton = ` <button class="show-results-btn ${buttonClass}" data-content-id="${contentId}">Show details</button>`;
|
|
60
|
+
|
|
61
|
+
let toolUseHtml = `
|
|
62
|
+
<div class="terminal-tool tool-use compact">
|
|
63
|
+
<span class="tool-command">${commandSummary}${showResultsButton}</span>
|
|
64
|
+
</div>
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
// Render associated tool results if they exist, with proper truncation
|
|
68
|
+
if (matchingResult) {
|
|
69
|
+
toolUseHtml += this.renderToolResultWithTruncation(matchingResult);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return toolUseHtml;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Render a tool result block with truncation support
|
|
77
|
+
* @param {Object} toolResultBlock - Tool result block
|
|
78
|
+
* @returns {string} Safe HTML string
|
|
79
|
+
*/
|
|
80
|
+
renderToolResultWithTruncation(toolResultBlock) {
|
|
81
|
+
const toolId = toolResultBlock.tool_use_id ? toolResultBlock.tool_use_id.slice(-8) : 'unknown';
|
|
82
|
+
const isError = toolResultBlock.is_error || false;
|
|
83
|
+
|
|
84
|
+
// Generate enhanced result content with metadata
|
|
85
|
+
const resultContent = this.generateEnhancedResultContent(toolResultBlock);
|
|
86
|
+
const compactOutput = this.generateCompactOutput(resultContent, isError);
|
|
87
|
+
|
|
88
|
+
return `
|
|
89
|
+
<div class="terminal-tool tool-result compact ${isError ? 'error' : 'success'}" data-tool-use-id="${toolResultBlock.tool_use_id}">
|
|
90
|
+
<span class="tool-prompt">⎿</span>
|
|
91
|
+
<span class="tool-output-compact">${compactOutput}</span>
|
|
92
|
+
</div>
|
|
93
|
+
`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Render a tool result block (legacy method, kept for compatibility)
|
|
98
|
+
* @param {Object} toolResultBlock - Tool result block
|
|
99
|
+
* @returns {string} Safe HTML string
|
|
100
|
+
*/
|
|
101
|
+
renderToolResult(toolResultBlock) {
|
|
102
|
+
return this.renderToolResultWithTruncation(toolResultBlock);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Generate enhanced result content including metadata
|
|
107
|
+
* @param {Object} toolResultBlock - Tool result block
|
|
108
|
+
* @returns {string} Enhanced result content
|
|
109
|
+
*/
|
|
110
|
+
generateEnhancedResultContent(toolResultBlock) {
|
|
111
|
+
let content = '';
|
|
112
|
+
|
|
113
|
+
// Add return code interpretation if available
|
|
114
|
+
if (toolResultBlock.returnCodeInterpretation && toolResultBlock.returnCodeInterpretation !== 'none') {
|
|
115
|
+
content += `${toolResultBlock.returnCodeInterpretation}\n`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Add main content
|
|
119
|
+
if (toolResultBlock.content) {
|
|
120
|
+
if (content) content += '\n';
|
|
121
|
+
content += toolResultBlock.content;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Add stdout if different from content
|
|
125
|
+
if (toolResultBlock.stdout && toolResultBlock.stdout !== toolResultBlock.content) {
|
|
126
|
+
if (content) content += '\n';
|
|
127
|
+
content += toolResultBlock.stdout;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Add stderr if present
|
|
131
|
+
if (toolResultBlock.stderr && toolResultBlock.stderr.trim()) {
|
|
132
|
+
if (content) content += '\n';
|
|
133
|
+
content += `stderr: ${toolResultBlock.stderr}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return content || '[Empty result]';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Generate compact command representation for tool use
|
|
141
|
+
* @param {string} toolName - Tool name
|
|
142
|
+
* @param {Object} input - Tool input parameters
|
|
143
|
+
* @returns {string} Compact command
|
|
144
|
+
*/
|
|
145
|
+
generateCompactCommand(toolName, input) {
|
|
146
|
+
if (!input || typeof input !== 'object') {
|
|
147
|
+
return `${toolName}()`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
switch (toolName) {
|
|
151
|
+
case 'Bash':
|
|
152
|
+
if (input.command) {
|
|
153
|
+
const command = this.escapeHtml(input.command);
|
|
154
|
+
return `<span class="tool-name-bold">Bash </span>(${command})`;
|
|
155
|
+
}
|
|
156
|
+
break;
|
|
157
|
+
|
|
158
|
+
case 'Read':
|
|
159
|
+
if (input.file_path) {
|
|
160
|
+
const fileName = input.file_path.split('/').pop();
|
|
161
|
+
return `<span class="tool-name-bold">Read </span>(${this.escapeHtml(fileName)})`;
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
|
|
165
|
+
case 'Edit':
|
|
166
|
+
if (input.file_path) {
|
|
167
|
+
const fileName = input.file_path.split('/').pop();
|
|
168
|
+
return `<span class="tool-name-bold">Edit </span>(${this.escapeHtml(fileName)})`;
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
|
|
172
|
+
case 'Write':
|
|
173
|
+
if (input.file_path) {
|
|
174
|
+
const fileName = input.file_path.split('/').pop();
|
|
175
|
+
return `<span class="tool-name-bold">Write </span>(${this.escapeHtml(fileName)})`;
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
178
|
+
|
|
179
|
+
case 'Glob':
|
|
180
|
+
if (input.pattern) {
|
|
181
|
+
return `<span class="tool-name-bold">Glob </span>("${this.escapeHtml(input.pattern)}")`;
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
|
|
185
|
+
case 'Grep':
|
|
186
|
+
if (input.pattern) {
|
|
187
|
+
return `<span class="tool-name-bold">Grep </span>("${this.escapeHtml(input.pattern)}")`;
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
|
|
191
|
+
case 'TodoWrite':
|
|
192
|
+
const todoCount = Array.isArray(input.todos) ? input.todos.length : 0;
|
|
193
|
+
return `<span class="tool-name-bold">TodoWrite </span>(${todoCount} todos)`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return `${toolName}()`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Generate compact output representation for tool results
|
|
201
|
+
* @param {*} content - Tool result content
|
|
202
|
+
* @param {boolean} _isError - Whether this is an error result (unused)
|
|
203
|
+
* @returns {string} Compact output
|
|
204
|
+
*/
|
|
205
|
+
generateCompactOutput(content, _isError) {
|
|
206
|
+
if (typeof content === 'string') {
|
|
207
|
+
// For JSON content, try to format it nicely
|
|
208
|
+
if (content.trim().startsWith('{') && content.trim().endsWith('}')) {
|
|
209
|
+
try {
|
|
210
|
+
const parsed = JSON.parse(content);
|
|
211
|
+
const formatted = JSON.stringify(parsed, null, 2);
|
|
212
|
+
const lines = formatted.split('\n');
|
|
213
|
+
if (lines.length > 5) {
|
|
214
|
+
const preview = lines.slice(0, 5).join('\n');
|
|
215
|
+
const remaining = lines.length - 5;
|
|
216
|
+
const contentId = 'json_' + Date.now() + '_' + Math.random().toString(36).substring(2, 11);
|
|
217
|
+
|
|
218
|
+
if (typeof window !== 'undefined') {
|
|
219
|
+
window.storedContent = window.storedContent || {};
|
|
220
|
+
window.storedContent[contentId] = formatted;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return `<pre class="json-output">${this.escapeHtml(preview)}\n<span class="continuation">… +${remaining} lines hidden <button class="show-results-btn text-expand-btn" data-content-id="${contentId}">Show +${remaining} lines</button></span></pre>`;
|
|
224
|
+
} else {
|
|
225
|
+
return `<pre class="json-output">${this.escapeHtml(formatted)}</pre>`;
|
|
226
|
+
}
|
|
227
|
+
} catch (e) {
|
|
228
|
+
// Fall through to regular text handling
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// For multi-line content, show first few lines with continuation
|
|
233
|
+
const lines = content.split('\n');
|
|
234
|
+
if (lines.length > 5) {
|
|
235
|
+
const preview = lines.slice(0, 5).join('\n');
|
|
236
|
+
const remaining = lines.length - 5;
|
|
237
|
+
const contentId = 'content_' + Date.now() + '_' + Math.random().toString(36).substring(2, 11);
|
|
238
|
+
|
|
239
|
+
// Store content in global storage without inline scripts
|
|
240
|
+
if (typeof window !== 'undefined') {
|
|
241
|
+
window.storedContent = window.storedContent || {};
|
|
242
|
+
window.storedContent[contentId] = content;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return `<pre class="text-output">${this.escapeHtml(preview)}\n<span class="continuation">… +${remaining} lines hidden <button class="show-results-btn text-expand-btn" data-content-id="${contentId}">Show +${remaining} lines</button></span></pre>`;
|
|
246
|
+
} else {
|
|
247
|
+
return `<pre class="text-output">${this.escapeHtml(content)}</pre>`;
|
|
248
|
+
}
|
|
249
|
+
} else if (Array.isArray(content)) {
|
|
250
|
+
return `<span class="array-output">[${content.length} items]</span>`;
|
|
251
|
+
} else if (content && typeof content === 'object') {
|
|
252
|
+
const keys = Object.keys(content);
|
|
253
|
+
return `<span class="object-output">{${keys.length} properties}</span>`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return '<span class="empty-output">[empty]</span>';
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Generate tool summary based on tool type
|
|
261
|
+
* @param {string} toolName - Tool name
|
|
262
|
+
* @param {Object} input - Tool input parameters
|
|
263
|
+
* @returns {string} Tool summary
|
|
264
|
+
*/
|
|
265
|
+
generateToolSummary(toolName, input) {
|
|
266
|
+
if (!input || typeof input !== 'object') return '';
|
|
267
|
+
|
|
268
|
+
switch (toolName) {
|
|
269
|
+
case 'TodoWrite':
|
|
270
|
+
const todoCount = Array.isArray(input.todos) ? input.todos.length : 0;
|
|
271
|
+
return `${todoCount} todo${todoCount !== 1 ? 's' : ''}`;
|
|
272
|
+
|
|
273
|
+
case 'Read':
|
|
274
|
+
if (input.file_path) {
|
|
275
|
+
const fileName = input.file_path.split('/').pop();
|
|
276
|
+
return this.escapeHtml(fileName);
|
|
277
|
+
}
|
|
278
|
+
break;
|
|
279
|
+
|
|
280
|
+
case 'Edit':
|
|
281
|
+
if (input.file_path) {
|
|
282
|
+
const fileName = input.file_path.split('/').pop();
|
|
283
|
+
const changeSize = input.old_string ? input.old_string.length : 0;
|
|
284
|
+
return `${this.escapeHtml(fileName)} (${changeSize}b)`;
|
|
285
|
+
}
|
|
286
|
+
break;
|
|
287
|
+
|
|
288
|
+
case 'Bash':
|
|
289
|
+
if (input.command) {
|
|
290
|
+
const command = this.truncateText(input.command, 40);
|
|
291
|
+
return `<span class="bash-command">${this.escapeHtml(command)}</span>`;
|
|
292
|
+
}
|
|
293
|
+
break;
|
|
294
|
+
|
|
295
|
+
case 'Write':
|
|
296
|
+
if (input.file_path) {
|
|
297
|
+
const fileName = input.file_path.split('/').pop();
|
|
298
|
+
const contentSize = input.content ? input.content.length : 0;
|
|
299
|
+
return `${this.escapeHtml(fileName)} (${contentSize}b)`;
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
302
|
+
|
|
303
|
+
case 'Glob':
|
|
304
|
+
if (input.pattern) {
|
|
305
|
+
return `"${this.escapeHtml(input.pattern)}"`;
|
|
306
|
+
}
|
|
307
|
+
break;
|
|
308
|
+
|
|
309
|
+
case 'Grep':
|
|
310
|
+
if (input.pattern) {
|
|
311
|
+
return `"${this.escapeHtml(input.pattern)}"`;
|
|
312
|
+
}
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return '';
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Format Bash command output with proper console styling
|
|
322
|
+
* @param {string} content - Bash output content
|
|
323
|
+
* @returns {string} Formatted HTML
|
|
324
|
+
*/
|
|
325
|
+
formatBashOutput(content) {
|
|
326
|
+
if (!content) return '';
|
|
327
|
+
|
|
328
|
+
const lines = content.split('\n');
|
|
329
|
+
const formattedLines = lines.map(line => {
|
|
330
|
+
// Escape HTML first
|
|
331
|
+
line = this.escapeHtml(line);
|
|
332
|
+
|
|
333
|
+
// Highlight different types of output
|
|
334
|
+
if (line.includes('Error:') || line.includes('ERROR') || line.includes('❌')) {
|
|
335
|
+
return `<span class="console-error">${line}</span>`;
|
|
336
|
+
} else if (line.includes('Warning:') || line.includes('WARN') || line.includes('⚠️')) {
|
|
337
|
+
return `<span class="console-warning">${line}</span>`;
|
|
338
|
+
} else if (line.includes('✅') || line.includes('SUCCESS')) {
|
|
339
|
+
return `<span class="console-success">${line}</span>`;
|
|
340
|
+
} else if (line.startsWith('>')) {
|
|
341
|
+
return `<span class="console-command">${line}</span>`;
|
|
342
|
+
} else if (line.includes('📊') || line.includes('🔧') || line.includes('⚡')) {
|
|
343
|
+
return `<span class="console-info">${line}</span>`;
|
|
344
|
+
} else {
|
|
345
|
+
return `<span class="console-output">${line}</span>`;
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
return formattedLines.join('<br>');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Generate result preview
|
|
354
|
+
* @param {*} content - Tool result content
|
|
355
|
+
* @returns {string} Result preview
|
|
356
|
+
*/
|
|
357
|
+
generateResultPreview(content) {
|
|
358
|
+
if (typeof content === 'string') {
|
|
359
|
+
if (content.length > 50) {
|
|
360
|
+
const preview = this.truncateText(content, 50);
|
|
361
|
+
return this.escapeHtml(preview);
|
|
362
|
+
}
|
|
363
|
+
return this.escapeHtml(content);
|
|
364
|
+
} else if (Array.isArray(content)) {
|
|
365
|
+
return `${content.length} items`;
|
|
366
|
+
} else if (content && typeof content === 'object') {
|
|
367
|
+
const keys = Object.keys(content);
|
|
368
|
+
return `${keys.length} props`;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return '';
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Truncate text safely
|
|
376
|
+
* @param {string} text - Text to truncate
|
|
377
|
+
* @param {number} maxLength - Maximum length
|
|
378
|
+
* @returns {string} Truncated text
|
|
379
|
+
*/
|
|
380
|
+
truncateText(text, maxLength) {
|
|
381
|
+
if (!text || text.length <= maxLength) return text;
|
|
382
|
+
return text.substring(0, maxLength - 3) + '...';
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Escape HTML to prevent XSS
|
|
387
|
+
* @param {string} text - Text to escape
|
|
388
|
+
* @returns {string} Escaped text
|
|
389
|
+
*/
|
|
390
|
+
escapeHtml(text) {
|
|
391
|
+
if (typeof text !== 'string') return String(text);
|
|
392
|
+
|
|
393
|
+
const div = document.createElement('div');
|
|
394
|
+
div.textContent = text;
|
|
395
|
+
return div.innerHTML;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Find tool result in globally stored messages
|
|
400
|
+
* @param {string} toolUseId - Tool use ID to find result for
|
|
401
|
+
* @returns {string|null} Tool result content if found
|
|
402
|
+
*/
|
|
403
|
+
findToolResultInGlobalMessages(toolUseId) {
|
|
404
|
+
// Try to find tool result in cached messages
|
|
405
|
+
try {
|
|
406
|
+
if (typeof window !== 'undefined' && window.currentMessages) {
|
|
407
|
+
|
|
408
|
+
// First pass: Look for direct tool_result matches in any message
|
|
409
|
+
for (let i = 0; i < window.currentMessages.length; i++) {
|
|
410
|
+
const message = window.currentMessages[i];
|
|
411
|
+
|
|
412
|
+
if (Array.isArray(message.content)) {
|
|
413
|
+
for (let j = 0; j < message.content.length; j++) {
|
|
414
|
+
const block = message.content[j];
|
|
415
|
+
|
|
416
|
+
if (block.type === 'tool_result' && block.tool_use_id === toolUseId) {
|
|
417
|
+
return block.content;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Second pass: Sequential search - look for tool_use then find matching tool_result
|
|
424
|
+
let foundToolUse = false;
|
|
425
|
+
let toolUseIndex = -1;
|
|
426
|
+
|
|
427
|
+
for (let i = 0; i < window.currentMessages.length; i++) {
|
|
428
|
+
const message = window.currentMessages[i];
|
|
429
|
+
|
|
430
|
+
if (Array.isArray(message.content)) {
|
|
431
|
+
for (const block of message.content) {
|
|
432
|
+
if (block.type === 'tool_use' && block.id === toolUseId) {
|
|
433
|
+
foundToolUse = true;
|
|
434
|
+
toolUseIndex = i;
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// If we found the tool_use, look for the result in subsequent messages
|
|
441
|
+
if (foundToolUse) {
|
|
442
|
+
for (let j = toolUseIndex; j < window.currentMessages.length; j++) {
|
|
443
|
+
const laterMessage = window.currentMessages[j];
|
|
444
|
+
if (Array.isArray(laterMessage.content)) {
|
|
445
|
+
for (const laterBlock of laterMessage.content) {
|
|
446
|
+
if (laterBlock.type === 'tool_result' && laterBlock.tool_use_id === toolUseId) {
|
|
447
|
+
return laterBlock.content;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
break; // Stop after finding tool_use and searching subsequent messages
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
} catch (error) {
|
|
457
|
+
// Silently handle errors in tool result search
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Generate comprehensive tool content for modal display
|
|
465
|
+
* @param {string} toolName - Name of the tool
|
|
466
|
+
* @param {Object} toolBlock - Tool use block with input parameters
|
|
467
|
+
* @param {string|null} resultContent - Tool result content if available
|
|
468
|
+
* @returns {string} Comprehensive tool information for modal
|
|
469
|
+
*/
|
|
470
|
+
generateComprehensiveToolContent(toolName, toolBlock, resultContent) {
|
|
471
|
+
let content = `=== TOOL: ${toolName} ===\n\n`;
|
|
472
|
+
|
|
473
|
+
// Tool ID and basic info
|
|
474
|
+
content += `Tool ID: ${toolBlock.id || 'Unknown'}\n`;
|
|
475
|
+
content += `Short ID: ${toolBlock.id ? toolBlock.id.slice(-8) : 'Unknown'}\n\n`;
|
|
476
|
+
|
|
477
|
+
// Tool Input Parameters
|
|
478
|
+
content += `--- INPUT PARAMETERS ---\n`;
|
|
479
|
+
if (toolBlock.input && typeof toolBlock.input === 'object') {
|
|
480
|
+
Object.entries(toolBlock.input).forEach(([key, value]) => {
|
|
481
|
+
if (typeof value === 'string') {
|
|
482
|
+
// For long strings, show preview + length
|
|
483
|
+
if (value.length > 200) {
|
|
484
|
+
content += `${key}: "${value.substring(0, 200)}..." [${value.length} characters total]\n`;
|
|
485
|
+
} else {
|
|
486
|
+
content += `${key}: "${value}"\n`;
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
content += `${key}: ${JSON.stringify(value, null, 2)}\n`;
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
} else {
|
|
493
|
+
content += `No input parameters provided.\n`;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Tool-specific details
|
|
497
|
+
content += `\n--- TOOL DETAILS ---\n`;
|
|
498
|
+
switch (toolName) {
|
|
499
|
+
case 'Bash':
|
|
500
|
+
content += `Command executed: ${toolBlock.input?.command || 'Unknown'}\n`;
|
|
501
|
+
content += `Description: ${toolBlock.input?.description || 'No description provided'}\n`;
|
|
502
|
+
content += `Timeout: ${toolBlock.input?.timeout || 'Default (120s)'}\n`;
|
|
503
|
+
break;
|
|
504
|
+
case 'Read':
|
|
505
|
+
content += `File path: ${toolBlock.input?.file_path || 'Unknown'}\n`;
|
|
506
|
+
content += `Offset: ${toolBlock.input?.offset || 'Start of file'}\n`;
|
|
507
|
+
content += `Limit: ${toolBlock.input?.limit || 'Entire file'}\n`;
|
|
508
|
+
break;
|
|
509
|
+
case 'Write':
|
|
510
|
+
content += `File path: ${toolBlock.input?.file_path || 'Unknown'}\n`;
|
|
511
|
+
const contentLength = toolBlock.input?.content ? toolBlock.input.content.length : 0;
|
|
512
|
+
content += `Content length: ${contentLength} characters\n`;
|
|
513
|
+
break;
|
|
514
|
+
case 'Edit':
|
|
515
|
+
content += `File path: ${toolBlock.input?.file_path || 'Unknown'}\n`;
|
|
516
|
+
content += `Replace all: ${toolBlock.input?.replace_all ? 'Yes' : 'No'}\n`;
|
|
517
|
+
const oldLength = toolBlock.input?.old_string ? toolBlock.input.old_string.length : 0;
|
|
518
|
+
const newLength = toolBlock.input?.new_string ? toolBlock.input.new_string.length : 0;
|
|
519
|
+
content += `Old string length: ${oldLength} characters\n`;
|
|
520
|
+
content += `New string length: ${newLength} characters\n`;
|
|
521
|
+
break;
|
|
522
|
+
case 'Glob':
|
|
523
|
+
content += `Pattern: ${toolBlock.input?.pattern || 'Unknown'}\n`;
|
|
524
|
+
content += `Search path: ${toolBlock.input?.path || 'Current directory'}\n`;
|
|
525
|
+
break;
|
|
526
|
+
case 'Grep':
|
|
527
|
+
content += `Pattern: ${toolBlock.input?.pattern || 'Unknown'}\n`;
|
|
528
|
+
content += `Include filter: ${toolBlock.input?.include || 'All files'}\n`;
|
|
529
|
+
content += `Search path: ${toolBlock.input?.path || 'Current directory'}\n`;
|
|
530
|
+
break;
|
|
531
|
+
case 'TodoWrite':
|
|
532
|
+
const todoCount = Array.isArray(toolBlock.input?.todos) ? toolBlock.input.todos.length : 0;
|
|
533
|
+
content += `Number of todos: ${todoCount}\n`;
|
|
534
|
+
break;
|
|
535
|
+
default:
|
|
536
|
+
content += `Tool-specific details not available for ${toolName}\n`;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return content;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Bind events for tool displays (simplified for terminal style)
|
|
544
|
+
* @param {Element} _container - Container element (unused in terminal style)
|
|
545
|
+
*/
|
|
546
|
+
bindEvents(_container) {
|
|
547
|
+
// No expand/collapse needed for terminal style - everything is compact
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Export for module use
|
|
552
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
553
|
+
module.exports = ToolDisplay;
|
|
554
|
+
}
|