claude-code-watch 0.0.1

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.
@@ -0,0 +1,528 @@
1
+ 'use strict';
2
+
3
+ // ============================================================================
4
+ // Constants
5
+ // ============================================================================
6
+
7
+ const StreamItemType = {
8
+ THINKING: 'thinking',
9
+ TOOL_INPUT: 'tool_input',
10
+ TOOL_OUTPUT: 'tool_output',
11
+ TEXT: 'text',
12
+ TURN_MARKER: 'turn_marker',
13
+ COMPACT_MARKER: 'compact_marker',
14
+ HOOK_OUTPUT: 'hook_output',
15
+ DIAGNOSTICS: 'diagnostics',
16
+ PR_LINK: 'pr_link',
17
+ DEBUG: 'debug',
18
+ SESSION_TITLE: 'session_title',
19
+ };
20
+
21
+ const AgentIDDisplayLength = 7;
22
+ const debugPreviewLen = 240;
23
+
24
+ let debugAll = false;
25
+
26
+ // ============================================================================
27
+ // Exports
28
+ // ============================================================================
29
+
30
+ function setDebugAll(val) {
31
+ debugAll = val;
32
+ }
33
+
34
+ function agentDisplayName(agentID) {
35
+ if (!agentID) return 'Main';
36
+ return `Agent-${agentID.slice(0, Math.min(AgentIDDisplayLength, agentID.length))}`;
37
+ }
38
+
39
+ // ============================================================================
40
+ // ParseLine
41
+ // ============================================================================
42
+
43
+ function parseLine(line) {
44
+ if (!line || !line.trim()) return [];
45
+
46
+ let raw;
47
+ try {
48
+ raw = JSON.parse(line);
49
+ } catch {
50
+ return []; // gracefully skip malformed lines
51
+ }
52
+
53
+ const timestamp = raw.timestamp ? new Date(raw.timestamp) : new Date();
54
+
55
+ const items = [];
56
+
57
+ switch (raw.type) {
58
+ case 'assistant':
59
+ items.push(...parseAssistantMessage(raw, timestamp));
60
+ break;
61
+ case 'user':
62
+ items.push(...parseUserMessage(raw, timestamp));
63
+ break;
64
+ case 'system':
65
+ items.push(...parseSystemMessage(raw, timestamp));
66
+ if (debugAll && items.length === 0) {
67
+ items.push(debugItem(raw, line, timestamp));
68
+ }
69
+ break;
70
+ case 'agent-name':
71
+ items.push(...parseSessionTitle(raw, timestamp, raw.agentName));
72
+ break;
73
+ case 'custom-title':
74
+ items.push(...parseSessionTitle(raw, timestamp, raw.customTitle));
75
+ break;
76
+ case 'attachment':
77
+ items.push(...parseAttachment(raw, timestamp));
78
+ if (debugAll && items.length === 0) {
79
+ items.push(debugItem(raw, line, timestamp));
80
+ }
81
+ break;
82
+ case 'pr-link':
83
+ items.push(...parsePRLink(raw, timestamp));
84
+ break;
85
+ default:
86
+ if (debugAll) {
87
+ items.push(debugItem(raw, line, timestamp));
88
+ }
89
+ }
90
+
91
+ return items;
92
+ }
93
+
94
+ // ============================================================================
95
+ // Debug item
96
+ // ============================================================================
97
+
98
+ function debugItem(raw, line, timestamp) {
99
+ let label = raw.type;
100
+ if (raw.type === 'system' && raw.subtype) {
101
+ label = `system:${raw.subtype}`;
102
+ } else if (raw.type === 'attachment' && raw.attachment && raw.attachment.type) {
103
+ label = `attachment.${raw.attachment.type}`;
104
+ }
105
+ let preview = line;
106
+ if (preview.length > debugPreviewLen) {
107
+ preview = preview.slice(0, debugPreviewLen) + '\u2026';
108
+ }
109
+ const name = agentDisplayName(raw.agentId);
110
+ return {
111
+ type: StreamItemType.DEBUG,
112
+ sessionID: raw.sessionId,
113
+ agentID: raw.agentId || '',
114
+ agentName: name,
115
+ timestamp,
116
+ toolName: label,
117
+ content: preview,
118
+ toolID: '',
119
+ durationMs: 0,
120
+ inputTokens: 0,
121
+ outputTokens: 0,
122
+ cacheCreationTokens: 0,
123
+ cacheReadTokens: 0,
124
+ model: '',
125
+ };
126
+ }
127
+
128
+ // ============================================================================
129
+ // Session Title
130
+ // ============================================================================
131
+
132
+ function parseSessionTitle(raw, timestamp, title) {
133
+ if (!title) return [];
134
+ return [{
135
+ type: StreamItemType.SESSION_TITLE,
136
+ sessionID: raw.sessionId,
137
+ agentID: '',
138
+ agentName: '',
139
+ timestamp,
140
+ content: title,
141
+ toolName: '',
142
+ toolID: '',
143
+ durationMs: 0,
144
+ inputTokens: 0,
145
+ outputTokens: 0,
146
+ cacheCreationTokens: 0,
147
+ cacheReadTokens: 0,
148
+ model: '',
149
+ }];
150
+ }
151
+
152
+ // ============================================================================
153
+ // System Messages
154
+ // ============================================================================
155
+
156
+ function parseSystemMessage(raw, timestamp) {
157
+ const name = agentDisplayName(raw.agentId);
158
+ switch (raw.subtype) {
159
+ case 'turn_duration':
160
+ return [{
161
+ type: StreamItemType.TURN_MARKER,
162
+ sessionID: raw.sessionId,
163
+ agentID: raw.agentId || '',
164
+ agentName: name,
165
+ timestamp,
166
+ content: '',
167
+ toolName: '',
168
+ toolID: '',
169
+ durationMs: raw.durationMs || 0,
170
+ inputTokens: 0,
171
+ outputTokens: 0,
172
+ cacheCreationTokens: 0,
173
+ cacheReadTokens: 0,
174
+ model: '',
175
+ }];
176
+ case 'compact_boundary':
177
+ return [{
178
+ type: StreamItemType.COMPACT_MARKER,
179
+ sessionID: raw.sessionId,
180
+ agentID: raw.agentId || '',
181
+ agentName: name,
182
+ timestamp,
183
+ content: formatCompactSummary(raw.compactMetadata),
184
+ toolName: '',
185
+ toolID: '',
186
+ durationMs: 0,
187
+ inputTokens: 0,
188
+ outputTokens: 0,
189
+ cacheCreationTokens: 0,
190
+ cacheReadTokens: 0,
191
+ model: '',
192
+ }];
193
+ default:
194
+ return [];
195
+ }
196
+ }
197
+
198
+ function formatCompactSummary(metadata) {
199
+ if (!metadata) return '';
200
+ const parts = [];
201
+ if (metadata.trigger) parts.push(metadata.trigger);
202
+ if (metadata.preTokens > 0) parts.push(`${formatTokenCount(metadata.preTokens)} pre-tokens`);
203
+ return parts.join(', ');
204
+ }
205
+
206
+ function formatTokenCount(n) {
207
+ if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M`;
208
+ if (n >= 1000) return `${Math.floor(n / 1000)}k`;
209
+ return String(n);
210
+ }
211
+
212
+ function contextWindowFor(model) {
213
+ if (!model) return 200000;
214
+ if (model.startsWith('claude-opus-4-7') || model.startsWith('claude-sonnet-4-6')) return 1000000;
215
+ if (model.startsWith('claude-haiku-4-5') || model.startsWith('claude-opus-4-6') ||
216
+ model.startsWith('claude-sonnet-4-5') || model.startsWith('claude-haiku-4')) return 200000;
217
+ return 200000;
218
+ }
219
+
220
+ // ============================================================================
221
+ // Attachment Messages
222
+ // ============================================================================
223
+
224
+ function parseAttachment(raw, timestamp) {
225
+ if (!raw.attachment) return [];
226
+ const name = agentDisplayName(raw.agentId);
227
+ switch (raw.attachment.type) {
228
+ case 'hook_success': {
229
+ const body = raw.attachment.stdout || '';
230
+ return [{
231
+ type: StreamItemType.HOOK_OUTPUT,
232
+ sessionID: raw.sessionId,
233
+ agentID: raw.agentId || '',
234
+ agentName: name,
235
+ timestamp,
236
+ toolName: raw.attachment.hookName || '',
237
+ content: body,
238
+ toolID: '',
239
+ durationMs: raw.attachment.durationMs || 0,
240
+ inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, model: '',
241
+ }];
242
+ }
243
+ case 'diagnostics':
244
+ return diagnosticsItems(raw, timestamp, name);
245
+ default:
246
+ return [];
247
+ }
248
+ }
249
+
250
+ function diagnosticsItems(raw, timestamp, agentName) {
251
+ if (!raw.attachment || !raw.attachment.files) return [];
252
+ const items = [];
253
+ for (const f of raw.attachment.files) {
254
+ if (!f.diagnostics || f.diagnostics.length === 0) continue;
255
+ items.push({
256
+ type: StreamItemType.DIAGNOSTICS,
257
+ sessionID: raw.sessionId,
258
+ agentID: raw.agentId || '',
259
+ agentName,
260
+ timestamp,
261
+ toolName: diagnosticsHeader(f),
262
+ content: diagnosticsBody(f.diagnostics),
263
+ toolID: '',
264
+ durationMs: 0,
265
+ inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, model: '',
266
+ });
267
+ }
268
+ return items;
269
+ }
270
+
271
+ function diagnosticsHeader(f) {
272
+ const counts = {};
273
+ for (const d of f.diagnostics) {
274
+ const sev = (d.severity || '').toLowerCase();
275
+ counts[sev] = (counts[sev] || 0) + 1;
276
+ }
277
+ const parts = [];
278
+ for (const sev of ['error', 'warning', 'info', 'hint']) {
279
+ if (counts[sev]) {
280
+ const label = counts[sev] === 1 ? sev : `${sev}s`;
281
+ parts.push(`${counts[sev]} ${label}`);
282
+ }
283
+ }
284
+ let name = f.uri;
285
+ const idx = name.lastIndexOf('/');
286
+ if (idx >= 0) name = name.slice(idx + 1);
287
+ if (parts.length === 0) return name;
288
+ return `${name} (${parts.join(', ')})`;
289
+ }
290
+
291
+ function diagnosticsBody(diagnostics) {
292
+ return diagnostics.map(d => {
293
+ const sev = d.severity || '?';
294
+ let line = `[${sev}] ${d.message}`;
295
+ if (d.source) line += ` (${d.source})`;
296
+ return line;
297
+ }).join('\n');
298
+ }
299
+
300
+ // ============================================================================
301
+ // PR Link
302
+ // ============================================================================
303
+
304
+ function parsePRLink(raw, timestamp) {
305
+ if (!raw.prNumber && !raw.prUrl) return [];
306
+ let content;
307
+ if (raw.prRepository && raw.prUrl) {
308
+ content = `PR #${raw.prNumber} ${raw.prRepository} \u2192 ${raw.prUrl}`;
309
+ } else if (raw.prUrl) {
310
+ content = `PR #${raw.prNumber} \u2192 ${raw.prUrl}`;
311
+ } else {
312
+ content = `PR #${raw.prNumber}`;
313
+ }
314
+ return [{
315
+ type: StreamItemType.PR_LINK,
316
+ sessionID: raw.sessionId,
317
+ agentID: '',
318
+ agentName: '',
319
+ timestamp,
320
+ content,
321
+ toolName: '',
322
+ toolID: '',
323
+ durationMs: 0,
324
+ inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, model: '',
325
+ }];
326
+ }
327
+
328
+ // ============================================================================
329
+ // Assistant Messages
330
+ // ============================================================================
331
+
332
+ function parseAssistantMessage(raw, timestamp) {
333
+ const msg = raw.message;
334
+ if (!msg || !Array.isArray(msg.content)) return [];
335
+
336
+ const items = [];
337
+ const name = agentDisplayName(raw.agentId);
338
+
339
+ for (const block of msg.content) {
340
+ switch (block.type) {
341
+ case 'thinking':
342
+ if (block.thinking) {
343
+ items.push({
344
+ type: StreamItemType.THINKING,
345
+ agentID: raw.agentId || '',
346
+ agentName: name,
347
+ timestamp,
348
+ content: block.thinking,
349
+ toolName: '',
350
+ toolID: '',
351
+ durationMs: 0,
352
+ inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, model: '',
353
+ });
354
+ }
355
+ break;
356
+ case 'text':
357
+ if (block.text) {
358
+ items.push({
359
+ type: StreamItemType.TEXT,
360
+ agentID: raw.agentId || '',
361
+ agentName: name,
362
+ timestamp,
363
+ content: block.text,
364
+ toolName: '',
365
+ toolID: '',
366
+ durationMs: 0,
367
+ inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, model: '',
368
+ });
369
+ }
370
+ break;
371
+ case 'tool_use':
372
+ items.push({
373
+ type: StreamItemType.TOOL_INPUT,
374
+ agentID: raw.agentId || '',
375
+ agentName: name,
376
+ timestamp,
377
+ content: formatToolInput(block.name, block.input),
378
+ toolName: prettyToolName(block.name),
379
+ toolID: block.id || '',
380
+ durationMs: 0,
381
+ inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, model: '',
382
+ });
383
+ break;
384
+ }
385
+ }
386
+
387
+ // Attach token usage + model to first item only
388
+ if (items.length > 0 && msg.usage) {
389
+ items[0].inputTokens = msg.usage.input_tokens || 0;
390
+ items[0].outputTokens = msg.usage.output_tokens || 0;
391
+ items[0].cacheCreationTokens = msg.usage.cache_creation_input_tokens || 0;
392
+ items[0].cacheReadTokens = msg.usage.cache_read_input_tokens || 0;
393
+ }
394
+ if (items.length > 0 && msg.model && msg.model !== '<synthetic>') {
395
+ items[0].model = msg.model;
396
+ }
397
+
398
+ return items;
399
+ }
400
+
401
+ // ============================================================================
402
+ // User Messages
403
+ // ============================================================================
404
+
405
+ function parseUserMessage(raw, timestamp) {
406
+ const msg = raw.message;
407
+ if (!msg || !Array.isArray(msg.content)) return [];
408
+
409
+ // Parse toolUseResult for duration
410
+ let durationMs = 0;
411
+ if (raw.toolUseResult && typeof raw.toolUseResult.durationMs === 'number') {
412
+ durationMs = raw.toolUseResult.durationMs;
413
+ }
414
+
415
+ const items = [];
416
+ const name = agentDisplayName(raw.agentId);
417
+
418
+ for (const result of msg.content) {
419
+ if (result.type === 'tool_result') {
420
+ items.push({
421
+ type: StreamItemType.TOOL_OUTPUT,
422
+ agentID: raw.agentId || '',
423
+ agentName: name,
424
+ timestamp,
425
+ content: extractToolResultContent(result.content),
426
+ toolName: '',
427
+ toolID: result.tool_use_id || '',
428
+ durationMs,
429
+ inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, model: '',
430
+ });
431
+ }
432
+ }
433
+
434
+ return items;
435
+ }
436
+
437
+ function extractToolResultContent(content) {
438
+ if (!content) return '';
439
+ if (typeof content === 'string') return content;
440
+ if (Array.isArray(content)) {
441
+ const parts = content
442
+ .filter(b => b.text)
443
+ .map(b => b.text);
444
+ return parts.join('\n');
445
+ }
446
+ // Fallback: stringify
447
+ try {
448
+ return JSON.stringify(content);
449
+ } catch {
450
+ return String(content);
451
+ }
452
+ }
453
+
454
+ // ============================================================================
455
+ // Tool Input Formatting
456
+ // ============================================================================
457
+
458
+ function formatToolInput(toolName, input) {
459
+ if (!input) return '';
460
+ const inp = input;
461
+
462
+ switch (toolName) {
463
+ case 'Bash':
464
+ if (inp.description) return `${inp.command}\n # ${inp.description}`;
465
+ return inp.command || '';
466
+ case 'Read':
467
+ return inp.file_path || '';
468
+ case 'Write':
469
+ return `${inp.file_path || ''} (${(inp.content || '').length} bytes)`;
470
+ case 'Edit':
471
+ return inp.file_path || '';
472
+ case 'Glob':
473
+ if (inp.path) return `${inp.pattern} in ${inp.path}`;
474
+ return inp.pattern || '';
475
+ case 'Grep':
476
+ if (inp.path) return `/${inp.pattern}/ in ${inp.path}`;
477
+ return `/${inp.pattern}/`;
478
+ case 'WebFetch':
479
+ return inp.prompt || '';
480
+ case 'WebSearch':
481
+ return inp.query || '';
482
+ case 'Task':
483
+ case 'Agent':
484
+ if (inp.description) return inp.description;
485
+ return inp.prompt || '';
486
+ case 'Skill':
487
+ if (inp.args) return `${inp.skill} \u2014 ${inp.args}`;
488
+ return inp.skill || '';
489
+ case 'ToolSearch':
490
+ return inp.query || '';
491
+ case 'ScheduleWakeup':
492
+ if (inp.reason) return inp.reason;
493
+ if (inp.delaySeconds > 0) return `delay ${inp.delaySeconds}s`;
494
+ return JSON.stringify(input);
495
+ case 'TaskCreate':
496
+ return inp.subject || '';
497
+ case 'TaskUpdate':
498
+ if (inp.taskId) return `task ${inp.taskId}`;
499
+ return JSON.stringify(input);
500
+ case 'TaskStop':
501
+ return inp.task_id || '';
502
+ case 'EnterPlanMode':
503
+ return '(enter plan mode)';
504
+ case 'ExitPlanMode':
505
+ return '(exit plan mode)';
506
+ case 'CronCreate':
507
+ if (inp.cron && inp.prompt) return `${inp.cron}: ${inp.prompt}`;
508
+ return JSON.stringify(input);
509
+ default:
510
+ return JSON.stringify(input);
511
+ }
512
+ }
513
+
514
+ function prettyToolName(name) {
515
+ if (!name.startsWith('mcp__')) return name;
516
+ const idx = name.lastIndexOf('__');
517
+ if (idx <= 'mcp__'.length - 2 || idx === name.length - 2) return name;
518
+ return 'mcp:' + name.slice(idx + 2);
519
+ }
520
+
521
+ module.exports = {
522
+ StreamItemType,
523
+ parseLine,
524
+ setDebugAll,
525
+ contextWindowFor,
526
+ formatTokenCount,
527
+ AgentIDDisplayLength,
528
+ };