@mmmbuto/nexuscli 0.5.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/LICENSE +21 -0
- package/README.md +172 -0
- package/bin/nexuscli.js +117 -0
- package/frontend/dist/apple-touch-icon.png +0 -0
- package/frontend/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/frontend/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/frontend/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/frontend/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/frontend/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/frontend/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/frontend/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/frontend/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/frontend/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/frontend/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/frontend/dist/assets/index-Bn_l1e6e.css +1 -0
- package/frontend/dist/assets/index-CikJbUR5.js +8617 -0
- package/frontend/dist/browserconfig.xml +12 -0
- package/frontend/dist/favicon-16x16.png +0 -0
- package/frontend/dist/favicon-32x32.png +0 -0
- package/frontend/dist/favicon-48x48.png +0 -0
- package/frontend/dist/favicon.ico +0 -0
- package/frontend/dist/icon-192.png +0 -0
- package/frontend/dist/icon-512.png +0 -0
- package/frontend/dist/icon-maskable-192.png +0 -0
- package/frontend/dist/icon-maskable-512.png +0 -0
- package/frontend/dist/index.html +79 -0
- package/frontend/dist/manifest.json +75 -0
- package/frontend/dist/sw.js +122 -0
- package/frontend/package.json +28 -0
- package/lib/cli/api.js +156 -0
- package/lib/cli/boot.js +172 -0
- package/lib/cli/config.js +185 -0
- package/lib/cli/engines.js +257 -0
- package/lib/cli/init.js +660 -0
- package/lib/cli/logs.js +72 -0
- package/lib/cli/start.js +220 -0
- package/lib/cli/status.js +187 -0
- package/lib/cli/stop.js +64 -0
- package/lib/cli/uninstall.js +194 -0
- package/lib/cli/users.js +295 -0
- package/lib/cli/workspaces.js +337 -0
- package/lib/config/manager.js +233 -0
- package/lib/server/.env.example +20 -0
- package/lib/server/db/adapter.js +314 -0
- package/lib/server/db/drivers/better-sqlite3.js +38 -0
- package/lib/server/db/drivers/sql-js.js +75 -0
- package/lib/server/db/migrate.js +174 -0
- package/lib/server/db/migrations/001_ultra_light_schema.sql +96 -0
- package/lib/server/db/migrations/002_session_conversation_mapping.sql +19 -0
- package/lib/server/db/migrations/003_message_engine_tracking.sql +18 -0
- package/lib/server/db/migrations/004_performance_indexes.sql +16 -0
- package/lib/server/db.js +2 -0
- package/lib/server/lib/cli-wrapper.js +164 -0
- package/lib/server/lib/output-parser.js +132 -0
- package/lib/server/lib/pty-adapter.js +57 -0
- package/lib/server/middleware/auth.js +103 -0
- package/lib/server/models/Conversation.js +259 -0
- package/lib/server/models/Message.js +228 -0
- package/lib/server/models/User.js +115 -0
- package/lib/server/package-lock.json +5895 -0
- package/lib/server/routes/auth.js +168 -0
- package/lib/server/routes/chat.js +206 -0
- package/lib/server/routes/codex.js +205 -0
- package/lib/server/routes/conversations.js +224 -0
- package/lib/server/routes/gemini.js +228 -0
- package/lib/server/routes/jobs.js +317 -0
- package/lib/server/routes/messages.js +60 -0
- package/lib/server/routes/models.js +198 -0
- package/lib/server/routes/sessions.js +285 -0
- package/lib/server/routes/upload.js +134 -0
- package/lib/server/routes/wake-lock.js +95 -0
- package/lib/server/routes/workspace.js +80 -0
- package/lib/server/routes/workspaces.js +142 -0
- package/lib/server/scripts/cleanup-ghost-sessions.js +71 -0
- package/lib/server/scripts/seed-users.js +37 -0
- package/lib/server/scripts/test-history-access.js +50 -0
- package/lib/server/server.js +227 -0
- package/lib/server/services/cache.js +85 -0
- package/lib/server/services/claude-wrapper.js +312 -0
- package/lib/server/services/cli-loader.js +384 -0
- package/lib/server/services/codex-output-parser.js +277 -0
- package/lib/server/services/codex-wrapper.js +224 -0
- package/lib/server/services/context-bridge.js +289 -0
- package/lib/server/services/gemini-output-parser.js +398 -0
- package/lib/server/services/gemini-wrapper.js +249 -0
- package/lib/server/services/history-sync.js +407 -0
- package/lib/server/services/output-parser.js +415 -0
- package/lib/server/services/session-manager.js +465 -0
- package/lib/server/services/summary-generator.js +259 -0
- package/lib/server/services/workspace-manager.js +516 -0
- package/lib/server/tests/history-sync.test.js +90 -0
- package/lib/server/tests/integration-session-sync.test.js +151 -0
- package/lib/server/tests/integration.test.js +76 -0
- package/lib/server/tests/performance.test.js +118 -0
- package/lib/server/tests/services.test.js +160 -0
- package/lib/setup/postinstall.js +216 -0
- package/lib/utils/paths.js +107 -0
- package/lib/utils/termux.js +145 -0
- package/package.json +82 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output Parser for Claude CLI JSON Stream
|
|
3
|
+
* Parses Claude Code CLI --output-format stream-json output
|
|
4
|
+
*
|
|
5
|
+
* JSON Event Types from Claude:
|
|
6
|
+
* - system/init: Session initialization
|
|
7
|
+
* - assistant: Message with content (text or tool_use)
|
|
8
|
+
* - user: Tool results
|
|
9
|
+
* - result: Final result with usage stats
|
|
10
|
+
*
|
|
11
|
+
* Emits:
|
|
12
|
+
* - status events (tool execution, thinking)
|
|
13
|
+
* - response_chunk (text content for chat)
|
|
14
|
+
* - response_done (final text)
|
|
15
|
+
* - done (completion with usage)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
class OutputParser {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.buffer = '';
|
|
21
|
+
this.finalResponse = '';
|
|
22
|
+
this.usage = null;
|
|
23
|
+
this.sessionId = null;
|
|
24
|
+
this.currentToolUses = new Map(); // Track pending tool uses by ID
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parse a chunk of stdout (may contain multiple JSON lines)
|
|
29
|
+
* @param {string} chunk - Raw stdout chunk from node-pty
|
|
30
|
+
* @returns {Array} Array of event objects for SSE
|
|
31
|
+
*/
|
|
32
|
+
parse(chunk) {
|
|
33
|
+
const events = [];
|
|
34
|
+
|
|
35
|
+
// Add chunk to buffer
|
|
36
|
+
this.buffer += chunk;
|
|
37
|
+
|
|
38
|
+
// Process complete lines
|
|
39
|
+
const lines = this.buffer.split('\n');
|
|
40
|
+
this.buffer = lines.pop() || ''; // Keep incomplete last line
|
|
41
|
+
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
const trimmed = line.trim();
|
|
44
|
+
if (!trimmed) continue;
|
|
45
|
+
|
|
46
|
+
// Try to parse as JSON
|
|
47
|
+
try {
|
|
48
|
+
const json = JSON.parse(trimmed);
|
|
49
|
+
const lineEvents = this.parseJsonEvent(json);
|
|
50
|
+
events.push(...lineEvents);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
// Not valid JSON - might be raw text or ANSI codes
|
|
53
|
+
console.log('[OutputParser] Non-JSON line (ignored):', trimmed.substring(0, 100));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return events;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Parse a single JSON event from Claude
|
|
62
|
+
* @param {Object} event - Parsed JSON object
|
|
63
|
+
* @returns {Array} Events to emit
|
|
64
|
+
*/
|
|
65
|
+
parseJsonEvent(event) {
|
|
66
|
+
const events = [];
|
|
67
|
+
|
|
68
|
+
// Debug: log all event types
|
|
69
|
+
console.log(`[OutputParser] Event type: ${event.type}, subtype: ${event.subtype || 'none'}`);
|
|
70
|
+
if (event.message?.content) {
|
|
71
|
+
console.log(`[OutputParser] Message content types:`,
|
|
72
|
+
Array.isArray(event.message.content)
|
|
73
|
+
? event.message.content.map(c => c.type).join(', ')
|
|
74
|
+
: typeof event.message.content
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
switch (event.type) {
|
|
79
|
+
case 'system':
|
|
80
|
+
// Init event - extract session info
|
|
81
|
+
if (event.subtype === 'init') {
|
|
82
|
+
this.sessionId = event.session_id;
|
|
83
|
+
console.log('[OutputParser] Session initialized:', this.sessionId);
|
|
84
|
+
events.push({
|
|
85
|
+
type: 'status',
|
|
86
|
+
category: 'system',
|
|
87
|
+
message: 'Session initialized',
|
|
88
|
+
icon: '🚀',
|
|
89
|
+
timestamp: new Date().toISOString(),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
|
|
94
|
+
case 'assistant':
|
|
95
|
+
// Assistant message - may contain text and/or tool_use
|
|
96
|
+
if (event.message?.content) {
|
|
97
|
+
// Handle content as string (some Claude versions)
|
|
98
|
+
if (typeof event.message.content === 'string') {
|
|
99
|
+
const text = event.message.content;
|
|
100
|
+
if (text.trim()) {
|
|
101
|
+
this.finalResponse = text;
|
|
102
|
+
console.log(`[OutputParser] Extracted text (string format): ${text.substring(0, 100)}...`);
|
|
103
|
+
events.push({
|
|
104
|
+
type: 'response_chunk',
|
|
105
|
+
text: text,
|
|
106
|
+
isIncremental: false,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
} else if (Array.isArray(event.message.content)) {
|
|
110
|
+
// Handle content as array of blocks
|
|
111
|
+
for (const item of event.message.content) {
|
|
112
|
+
if (item.type === 'tool_use') {
|
|
113
|
+
// Tool being invoked
|
|
114
|
+
const toolEvent = this.formatToolUseEvent(item);
|
|
115
|
+
events.push(toolEvent);
|
|
116
|
+
|
|
117
|
+
// Track this tool use for later result matching
|
|
118
|
+
this.currentToolUses.set(item.id, item);
|
|
119
|
+
} else if (item.type === 'text') {
|
|
120
|
+
// Text response - this is the actual chat content
|
|
121
|
+
const text = item.text || '';
|
|
122
|
+
if (text.trim()) {
|
|
123
|
+
this.finalResponse = text;
|
|
124
|
+
events.push({
|
|
125
|
+
type: 'response_chunk',
|
|
126
|
+
text: text,
|
|
127
|
+
isIncremental: false, // Complete text block
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
|
|
136
|
+
case 'user':
|
|
137
|
+
// Tool result - match with pending tool use
|
|
138
|
+
if (event.message?.content) {
|
|
139
|
+
for (const item of event.message.content) {
|
|
140
|
+
if (item.type === 'tool_result') {
|
|
141
|
+
const toolUse = this.currentToolUses.get(item.tool_use_id);
|
|
142
|
+
const toolName = toolUse?.name || 'Tool';
|
|
143
|
+
|
|
144
|
+
// Check if tool result indicates an error
|
|
145
|
+
const isError = item.is_error === true ||
|
|
146
|
+
(typeof item.content === 'string' && (
|
|
147
|
+
item.content.includes('Error:') ||
|
|
148
|
+
item.content.includes('error:') ||
|
|
149
|
+
item.content.startsWith('Failed')
|
|
150
|
+
));
|
|
151
|
+
|
|
152
|
+
// Special handling for TodoWrite - preserve todoData
|
|
153
|
+
if (toolName === 'TodoWrite' && toolUse?.input?.todos) {
|
|
154
|
+
const todos = toolUse.input.todos;
|
|
155
|
+
const inProgress = todos.filter(t => t.status === 'in_progress').length;
|
|
156
|
+
const completed = todos.filter(t => t.status === 'completed').length;
|
|
157
|
+
const pending = todos.filter(t => t.status === 'pending').length;
|
|
158
|
+
|
|
159
|
+
events.push({
|
|
160
|
+
type: 'status',
|
|
161
|
+
category: 'tool',
|
|
162
|
+
message: `Todos updated (${todos.length}: ${completed}✓ ${inProgress}⚡ ${pending}○)`,
|
|
163
|
+
icon: this.getToolIcon(toolName),
|
|
164
|
+
todoData: todos,
|
|
165
|
+
timestamp: new Date().toISOString(),
|
|
166
|
+
});
|
|
167
|
+
} else {
|
|
168
|
+
// Emit tool completion status (distinguish success vs error)
|
|
169
|
+
const statusMessage = isError
|
|
170
|
+
? `${toolName}: error`
|
|
171
|
+
: `${toolName}: completed`;
|
|
172
|
+
|
|
173
|
+
events.push({
|
|
174
|
+
type: 'status',
|
|
175
|
+
category: 'tool',
|
|
176
|
+
message: statusMessage,
|
|
177
|
+
icon: isError ? '❌' : this.getToolIcon(toolName),
|
|
178
|
+
toolOutput: this.truncateOutput(item.content),
|
|
179
|
+
timestamp: new Date().toISOString(),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Clean up
|
|
184
|
+
this.currentToolUses.delete(item.tool_use_id);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
break;
|
|
189
|
+
|
|
190
|
+
case 'result':
|
|
191
|
+
// Final result - extract usage and final response
|
|
192
|
+
this.usage = event.usage || null;
|
|
193
|
+
if (event.result && !this.finalResponse) {
|
|
194
|
+
this.finalResponse = event.result;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Emit response_done with final text
|
|
198
|
+
events.push({
|
|
199
|
+
type: 'response_done',
|
|
200
|
+
fullText: this.finalResponse,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Emit done event with usage stats
|
|
204
|
+
events.push({
|
|
205
|
+
type: 'done',
|
|
206
|
+
usage: {
|
|
207
|
+
prompt_tokens: this.usage?.input_tokens || 0,
|
|
208
|
+
completion_tokens: this.usage?.output_tokens || 0,
|
|
209
|
+
total_tokens: (this.usage?.input_tokens || 0) + (this.usage?.output_tokens || 0),
|
|
210
|
+
cache_creation_tokens: this.usage?.cache_creation_input_tokens || 0,
|
|
211
|
+
cache_read_tokens: this.usage?.cache_read_input_tokens || 0,
|
|
212
|
+
cost_usd: event.total_cost_usd || 0,
|
|
213
|
+
},
|
|
214
|
+
duration_ms: event.duration_ms,
|
|
215
|
+
sessionId: event.session_id,
|
|
216
|
+
});
|
|
217
|
+
break;
|
|
218
|
+
|
|
219
|
+
default:
|
|
220
|
+
// Unknown event type - log for debugging
|
|
221
|
+
console.log('[OutputParser] Unknown event type:', event.type);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return events;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Format a tool_use event into a status event
|
|
229
|
+
*/
|
|
230
|
+
formatToolUseEvent(toolUse) {
|
|
231
|
+
const { name, input } = toolUse;
|
|
232
|
+
let message = name || 'Tool';
|
|
233
|
+
|
|
234
|
+
// Format message based on tool type
|
|
235
|
+
switch (name) {
|
|
236
|
+
case 'Bash':
|
|
237
|
+
message = `Bash: ${this.truncate(input?.command || '', 60)}`;
|
|
238
|
+
break;
|
|
239
|
+
case 'BashOutput':
|
|
240
|
+
message = `Reading output: ${this.truncate(input?.bash_id || input?.shell_id || '', 20)}`;
|
|
241
|
+
break;
|
|
242
|
+
case 'KillShell':
|
|
243
|
+
message = `Killing shell: ${this.truncate(input?.shell_id || '', 20)}`;
|
|
244
|
+
break;
|
|
245
|
+
case 'Read':
|
|
246
|
+
message = `Reading: ${this.truncate(input?.file_path || '', 50)}`;
|
|
247
|
+
break;
|
|
248
|
+
case 'Write':
|
|
249
|
+
message = `Writing: ${this.truncate(input?.file_path || '', 50)}`;
|
|
250
|
+
break;
|
|
251
|
+
case 'Edit':
|
|
252
|
+
message = `Editing: ${this.truncate(input?.file_path || '', 50)}`;
|
|
253
|
+
break;
|
|
254
|
+
case 'Grep':
|
|
255
|
+
message = `Searching: ${this.truncate(input?.pattern || '', 40)}`;
|
|
256
|
+
break;
|
|
257
|
+
case 'Glob':
|
|
258
|
+
message = `Finding: ${this.truncate(input?.pattern || '', 40)}`;
|
|
259
|
+
break;
|
|
260
|
+
case 'Task':
|
|
261
|
+
message = `Task: ${this.truncate(input?.description || '', 50)}`;
|
|
262
|
+
break;
|
|
263
|
+
case 'WebFetch':
|
|
264
|
+
message = `Fetching: ${this.truncate(input?.url || '', 50)}`;
|
|
265
|
+
break;
|
|
266
|
+
case 'WebSearch':
|
|
267
|
+
message = `Searching web: ${this.truncate(input?.query || '', 40)}`;
|
|
268
|
+
break;
|
|
269
|
+
case 'NotebookEdit':
|
|
270
|
+
message = `Editing notebook: ${this.truncate(input?.notebook_path || '', 40)}`;
|
|
271
|
+
break;
|
|
272
|
+
case 'EnterPlanMode':
|
|
273
|
+
message = 'Entering plan mode...';
|
|
274
|
+
break;
|
|
275
|
+
case 'ExitPlanMode':
|
|
276
|
+
message = 'Exiting plan mode...';
|
|
277
|
+
break;
|
|
278
|
+
case 'Skill':
|
|
279
|
+
message = `Using skill: ${this.truncate(input?.skill || '', 30)}`;
|
|
280
|
+
break;
|
|
281
|
+
case 'SlashCommand':
|
|
282
|
+
message = `Running: ${this.truncate(input?.command || '', 40)}`;
|
|
283
|
+
break;
|
|
284
|
+
case 'TodoWrite':
|
|
285
|
+
const todos = input?.todos || [];
|
|
286
|
+
const inProgress = todos.filter(t => t.status === 'in_progress').length;
|
|
287
|
+
const completed = todos.filter(t => t.status === 'completed').length;
|
|
288
|
+
const pending = todos.filter(t => t.status === 'pending').length;
|
|
289
|
+
message = `Updating todos (${todos.length}: ${completed}✓ ${inProgress}⚡ ${pending}○)`;
|
|
290
|
+
// Return early with todoData included
|
|
291
|
+
return {
|
|
292
|
+
type: 'status',
|
|
293
|
+
category: 'tool',
|
|
294
|
+
message,
|
|
295
|
+
icon: this.getToolIcon(name),
|
|
296
|
+
todoData: todos,
|
|
297
|
+
timestamp: new Date().toISOString(),
|
|
298
|
+
};
|
|
299
|
+
case 'AskUserQuestion':
|
|
300
|
+
message = `Asking: ${this.truncate(input?.question || '', 40)}`;
|
|
301
|
+
break;
|
|
302
|
+
default:
|
|
303
|
+
// Handle MCP tools (mcp__*) and other unknown tools gracefully
|
|
304
|
+
if (name?.startsWith('mcp__')) {
|
|
305
|
+
// Extract readable name from MCP tool: mcp__server__tool -> tool
|
|
306
|
+
const parts = name.split('__');
|
|
307
|
+
const toolName = parts[parts.length - 1] || 'mcp';
|
|
308
|
+
message = `MCP: ${toolName}`;
|
|
309
|
+
} else {
|
|
310
|
+
// For any other tool, show the name
|
|
311
|
+
message = `${name || 'Tool'}: running...`;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
type: 'status',
|
|
317
|
+
category: 'tool',
|
|
318
|
+
message,
|
|
319
|
+
icon: this.getToolIcon(name),
|
|
320
|
+
timestamp: new Date().toISOString(),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Get emoji icon for tool type
|
|
326
|
+
*/
|
|
327
|
+
getToolIcon(tool) {
|
|
328
|
+
const icons = {
|
|
329
|
+
'Bash': '🔧',
|
|
330
|
+
'BashOutput': '📤',
|
|
331
|
+
'KillShell': '🛑',
|
|
332
|
+
'Read': '📖',
|
|
333
|
+
'Write': '✍️',
|
|
334
|
+
'Edit': '📝',
|
|
335
|
+
'Grep': '🔍',
|
|
336
|
+
'Glob': '🗂️',
|
|
337
|
+
'Task': '📋',
|
|
338
|
+
'WebFetch': '🌐',
|
|
339
|
+
'WebSearch': '🔎',
|
|
340
|
+
'TodoWrite': '✅',
|
|
341
|
+
'NotebookEdit': '📓',
|
|
342
|
+
'AskUserQuestion': '❓',
|
|
343
|
+
'Skill': '🎯',
|
|
344
|
+
'SlashCommand': '⌨️',
|
|
345
|
+
'EnterPlanMode': '📐',
|
|
346
|
+
'ExitPlanMode': '🚪',
|
|
347
|
+
// MCP tools
|
|
348
|
+
'mcp__dag-memory__memory_read': '🧠',
|
|
349
|
+
'mcp__dag-memory__memory_write': '💾',
|
|
350
|
+
'mcp__dag-memory__memory_search': '🔍',
|
|
351
|
+
'mcp__dag-memory__memory_stats': '📊',
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
// For MCP tools not in the list, use a generic MCP icon
|
|
355
|
+
if (tool?.startsWith('mcp__')) {
|
|
356
|
+
return icons[tool] || '🔌';
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return icons[tool] || '⚙️';
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Truncate string to max length
|
|
364
|
+
*/
|
|
365
|
+
truncate(str, maxLen) {
|
|
366
|
+
if (!str) return '';
|
|
367
|
+
if (str.length <= maxLen) return str;
|
|
368
|
+
return str.substring(0, maxLen) + '...';
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Truncate tool output (can be very long)
|
|
373
|
+
*/
|
|
374
|
+
truncateOutput(content) {
|
|
375
|
+
if (!content) return null;
|
|
376
|
+
const str = typeof content === 'string' ? content : JSON.stringify(content);
|
|
377
|
+
if (str.length > 500) {
|
|
378
|
+
return str.substring(0, 500) + '\n... (truncated)';
|
|
379
|
+
}
|
|
380
|
+
return str;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Get accumulated final response
|
|
385
|
+
*/
|
|
386
|
+
getFinalResponse() {
|
|
387
|
+
return this.finalResponse;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Alias for getFinalResponse
|
|
392
|
+
*/
|
|
393
|
+
extractFinalResponse() {
|
|
394
|
+
return this.finalResponse.trim();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Get usage statistics
|
|
399
|
+
*/
|
|
400
|
+
getUsage() {
|
|
401
|
+
return this.usage;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Reset parser state for new request
|
|
406
|
+
*/
|
|
407
|
+
reset() {
|
|
408
|
+
this.buffer = '';
|
|
409
|
+
this.finalResponse = '';
|
|
410
|
+
this.usage = null;
|
|
411
|
+
this.currentToolUses.clear();
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
module.exports = OutputParser;
|