@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.
Files changed (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -0
  3. package/bin/nexuscli.js +117 -0
  4. package/frontend/dist/apple-touch-icon.png +0 -0
  5. package/frontend/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  6. package/frontend/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  7. package/frontend/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  8. package/frontend/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  9. package/frontend/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  10. package/frontend/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  11. package/frontend/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  12. package/frontend/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  13. package/frontend/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  14. package/frontend/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  15. package/frontend/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  16. package/frontend/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  17. package/frontend/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  18. package/frontend/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  19. package/frontend/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  20. package/frontend/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  21. package/frontend/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  22. package/frontend/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  23. package/frontend/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  24. package/frontend/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  25. package/frontend/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  26. package/frontend/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  27. package/frontend/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  28. package/frontend/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  29. package/frontend/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  30. package/frontend/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  31. package/frontend/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  32. package/frontend/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  33. package/frontend/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  34. package/frontend/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  35. package/frontend/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  36. package/frontend/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  37. package/frontend/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  38. package/frontend/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  39. package/frontend/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  40. package/frontend/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  41. package/frontend/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  42. package/frontend/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  43. package/frontend/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  44. package/frontend/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  45. package/frontend/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  46. package/frontend/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  47. package/frontend/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  48. package/frontend/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  49. package/frontend/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  50. package/frontend/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  51. package/frontend/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  52. package/frontend/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  53. package/frontend/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  54. package/frontend/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  55. package/frontend/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  56. package/frontend/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  57. package/frontend/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  58. package/frontend/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  59. package/frontend/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  60. package/frontend/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  61. package/frontend/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  62. package/frontend/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  63. package/frontend/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  64. package/frontend/dist/assets/index-Bn_l1e6e.css +1 -0
  65. package/frontend/dist/assets/index-CikJbUR5.js +8617 -0
  66. package/frontend/dist/browserconfig.xml +12 -0
  67. package/frontend/dist/favicon-16x16.png +0 -0
  68. package/frontend/dist/favicon-32x32.png +0 -0
  69. package/frontend/dist/favicon-48x48.png +0 -0
  70. package/frontend/dist/favicon.ico +0 -0
  71. package/frontend/dist/icon-192.png +0 -0
  72. package/frontend/dist/icon-512.png +0 -0
  73. package/frontend/dist/icon-maskable-192.png +0 -0
  74. package/frontend/dist/icon-maskable-512.png +0 -0
  75. package/frontend/dist/index.html +79 -0
  76. package/frontend/dist/manifest.json +75 -0
  77. package/frontend/dist/sw.js +122 -0
  78. package/frontend/package.json +28 -0
  79. package/lib/cli/api.js +156 -0
  80. package/lib/cli/boot.js +172 -0
  81. package/lib/cli/config.js +185 -0
  82. package/lib/cli/engines.js +257 -0
  83. package/lib/cli/init.js +660 -0
  84. package/lib/cli/logs.js +72 -0
  85. package/lib/cli/start.js +220 -0
  86. package/lib/cli/status.js +187 -0
  87. package/lib/cli/stop.js +64 -0
  88. package/lib/cli/uninstall.js +194 -0
  89. package/lib/cli/users.js +295 -0
  90. package/lib/cli/workspaces.js +337 -0
  91. package/lib/config/manager.js +233 -0
  92. package/lib/server/.env.example +20 -0
  93. package/lib/server/db/adapter.js +314 -0
  94. package/lib/server/db/drivers/better-sqlite3.js +38 -0
  95. package/lib/server/db/drivers/sql-js.js +75 -0
  96. package/lib/server/db/migrate.js +174 -0
  97. package/lib/server/db/migrations/001_ultra_light_schema.sql +96 -0
  98. package/lib/server/db/migrations/002_session_conversation_mapping.sql +19 -0
  99. package/lib/server/db/migrations/003_message_engine_tracking.sql +18 -0
  100. package/lib/server/db/migrations/004_performance_indexes.sql +16 -0
  101. package/lib/server/db.js +2 -0
  102. package/lib/server/lib/cli-wrapper.js +164 -0
  103. package/lib/server/lib/output-parser.js +132 -0
  104. package/lib/server/lib/pty-adapter.js +57 -0
  105. package/lib/server/middleware/auth.js +103 -0
  106. package/lib/server/models/Conversation.js +259 -0
  107. package/lib/server/models/Message.js +228 -0
  108. package/lib/server/models/User.js +115 -0
  109. package/lib/server/package-lock.json +5895 -0
  110. package/lib/server/routes/auth.js +168 -0
  111. package/lib/server/routes/chat.js +206 -0
  112. package/lib/server/routes/codex.js +205 -0
  113. package/lib/server/routes/conversations.js +224 -0
  114. package/lib/server/routes/gemini.js +228 -0
  115. package/lib/server/routes/jobs.js +317 -0
  116. package/lib/server/routes/messages.js +60 -0
  117. package/lib/server/routes/models.js +198 -0
  118. package/lib/server/routes/sessions.js +285 -0
  119. package/lib/server/routes/upload.js +134 -0
  120. package/lib/server/routes/wake-lock.js +95 -0
  121. package/lib/server/routes/workspace.js +80 -0
  122. package/lib/server/routes/workspaces.js +142 -0
  123. package/lib/server/scripts/cleanup-ghost-sessions.js +71 -0
  124. package/lib/server/scripts/seed-users.js +37 -0
  125. package/lib/server/scripts/test-history-access.js +50 -0
  126. package/lib/server/server.js +227 -0
  127. package/lib/server/services/cache.js +85 -0
  128. package/lib/server/services/claude-wrapper.js +312 -0
  129. package/lib/server/services/cli-loader.js +384 -0
  130. package/lib/server/services/codex-output-parser.js +277 -0
  131. package/lib/server/services/codex-wrapper.js +224 -0
  132. package/lib/server/services/context-bridge.js +289 -0
  133. package/lib/server/services/gemini-output-parser.js +398 -0
  134. package/lib/server/services/gemini-wrapper.js +249 -0
  135. package/lib/server/services/history-sync.js +407 -0
  136. package/lib/server/services/output-parser.js +415 -0
  137. package/lib/server/services/session-manager.js +465 -0
  138. package/lib/server/services/summary-generator.js +259 -0
  139. package/lib/server/services/workspace-manager.js +516 -0
  140. package/lib/server/tests/history-sync.test.js +90 -0
  141. package/lib/server/tests/integration-session-sync.test.js +151 -0
  142. package/lib/server/tests/integration.test.js +76 -0
  143. package/lib/server/tests/performance.test.js +118 -0
  144. package/lib/server/tests/services.test.js +160 -0
  145. package/lib/setup/postinstall.js +216 -0
  146. package/lib/utils/paths.js +107 -0
  147. package/lib/utils/termux.js +145 -0
  148. 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;