claude-code-watch 0.0.7 → 0.0.8
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 +2 -2
- package/README.zh-CN.md +1 -1
- package/bin/claude-watch.js +2 -2
- package/package.json +1 -1
- package/public/index.html +27 -9
- package/src/server/server.js +27 -23
- package/src/watcher/watcher.js +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Claude Code writes detailed JSONL logs under `~/.claude/projects/` as it works
|
|
|
12
12
|
- **Multi-session** — watch all active Claude Code sessions simultaneously in a tree view
|
|
13
13
|
- **Subagent tracking** — see subagent activity nested under their parent session
|
|
14
14
|
- **Token & cost visibility** — tracks input/output/cache tokens per agent, with context window utilization
|
|
15
|
-
- **Filter controls** — toggle thinking, tool input, tool output, and text visibility independently
|
|
15
|
+
- **Filter controls** — toggle thinking, tool input, tool output, hook output, and text visibility independently
|
|
16
16
|
- **Auto-discovery** — automatically picks up new sessions as they start (toggleable)
|
|
17
17
|
|
|
18
18
|
## Quick Start
|
|
@@ -51,7 +51,7 @@ OPTIONS:
|
|
|
51
51
|
-n Start from newest (skip history, live only)
|
|
52
52
|
-l [N] List recent sessions (default 10) and exit
|
|
53
53
|
-a [N] List active sessions (default all) and exit
|
|
54
|
-
-w <dur> Active window duration (default
|
|
54
|
+
-w <dur> Active window duration (default 30m, e.g. 30s, 2m, 10m)
|
|
55
55
|
-m <N> Max sessions to show in tree (default 0=unlimited)
|
|
56
56
|
-c <dur> Auto-collapse sessions inactive for this duration (e.g. 2m)
|
|
57
57
|
-D Debug: show raw type:subtype for every JSONL line we'd drop
|
package/README.zh-CN.md
CHANGED
|
@@ -22,7 +22,7 @@ Claude Code 在运行时会将详细的 JSONL 日志写入 `~/.claude/projects/`
|
|
|
22
22
|
- **多会话监视** — 同时查看所有活跃的 Claude Code 会话
|
|
23
23
|
- **子代理追踪** — 在父会话下嵌套显示子代理活动
|
|
24
24
|
- **Token/成本追踪** — 每个代理的输入/输出/缓存 token 及上下文窗口利用率
|
|
25
|
-
- **过滤控制** — 独立切换 thinking
|
|
25
|
+
- **过滤控制** — 独立切换 thinking、工具输入/输出、hook 输出、文本的可见性
|
|
26
26
|
- **自动发现** — 新会话启动时自动纳入监控
|
|
27
27
|
|
|
28
28
|
## 致谢
|
package/bin/claude-watch.js
CHANGED
|
@@ -150,7 +150,7 @@ async function main() {
|
|
|
150
150
|
sessionID: '',
|
|
151
151
|
skipHistory: false,
|
|
152
152
|
pollMs: 500,
|
|
153
|
-
activeWindow:
|
|
153
|
+
activeWindow: 30 * 60 * 1000,
|
|
154
154
|
maxSessions: 0,
|
|
155
155
|
collapseAfter: 0,
|
|
156
156
|
debugAll: false,
|
|
@@ -190,7 +190,7 @@ async function main() {
|
|
|
190
190
|
break;
|
|
191
191
|
case '-w':
|
|
192
192
|
try {
|
|
193
|
-
options.activeWindow = parseDuration(args[++i] || '
|
|
193
|
+
options.activeWindow = parseDuration(args[++i] || '30m');
|
|
194
194
|
} catch {
|
|
195
195
|
options.activeWindow = 5 * 60 * 1000;
|
|
196
196
|
}
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -264,6 +264,7 @@ body {
|
|
|
264
264
|
<button class="btn on" id="btn-tool-input" onclick="toggleToolInput()" data-tooltip="Toggle tool input">🔧 Tools</button>
|
|
265
265
|
<button class="btn on" id="btn-tool-output" onclick="toggleToolOutput()" data-tooltip="Toggle tool output">📤 Output</button>
|
|
266
266
|
<button class="btn on" id="btn-text" onclick="toggleText()" data-tooltip="Toggle text responses">💬 Text</button>
|
|
267
|
+
<button class="btn on" id="btn-hook" onclick="toggleHook()" data-tooltip="Toggle hook output">🪝 Hook</button>
|
|
267
268
|
<span class="sep">│</span>
|
|
268
269
|
<button class="btn on" id="btn-autoscroll" onclick="toggleAutoScroll()" data-tooltip="Auto-scroll">⇣ Auto</button>
|
|
269
270
|
<button class="btn" id="btn-tree-toggle" onclick="toggleTree()" data-tooltip="Toggle tree panel">📂 Tree</button>
|
|
@@ -380,6 +381,7 @@ let showThinking = true;
|
|
|
380
381
|
let showToolInput = true;
|
|
381
382
|
let showToolOutput = true;
|
|
382
383
|
let showText = true;
|
|
384
|
+
let showHook = true;
|
|
383
385
|
let autoDiscovery = true;
|
|
384
386
|
|
|
385
387
|
let renderPending = false;
|
|
@@ -401,7 +403,7 @@ let collapseAfter = 0;
|
|
|
401
403
|
let collapseTimer = null;
|
|
402
404
|
let activeRefreshTimer = null;
|
|
403
405
|
|
|
404
|
-
const MAX_ITEMS =
|
|
406
|
+
const MAX_ITEMS = 3000;
|
|
405
407
|
const MAX_LINES = 50;
|
|
406
408
|
let renderedItemCount = 0;
|
|
407
409
|
let needsFullRender = true;
|
|
@@ -552,6 +554,7 @@ function handleSnapshot(payload) {
|
|
|
552
554
|
}
|
|
553
555
|
updateFilters();
|
|
554
556
|
rebuildNodes();
|
|
557
|
+
needsFullRender = true;
|
|
555
558
|
scheduleRender();
|
|
556
559
|
}
|
|
557
560
|
|
|
@@ -566,6 +569,7 @@ function handleNewSession(payload) {
|
|
|
566
569
|
});
|
|
567
570
|
updateFilters();
|
|
568
571
|
rebuildNodes();
|
|
572
|
+
needsFullRender = true;
|
|
569
573
|
scheduleRender();
|
|
570
574
|
}
|
|
571
575
|
|
|
@@ -579,6 +583,7 @@ function handleNewAgent(payload) {
|
|
|
579
583
|
});
|
|
580
584
|
updateFilters();
|
|
581
585
|
rebuildNodes();
|
|
586
|
+
needsFullRender = true;
|
|
582
587
|
scheduleRender();
|
|
583
588
|
}
|
|
584
589
|
|
|
@@ -596,9 +601,13 @@ function handleNewBgTask(payload) {
|
|
|
596
601
|
|
|
597
602
|
function handleSessionRemoved(payload) {
|
|
598
603
|
const idx = sessions.findIndex(s => s.id === payload.sessionID);
|
|
599
|
-
if (idx >= 0)
|
|
604
|
+
if (idx >= 0) {
|
|
605
|
+
const session = sessions.splice(idx, 1)[0];
|
|
606
|
+
sessions.push(session);
|
|
607
|
+
}
|
|
600
608
|
updateFilters();
|
|
601
609
|
rebuildNodes();
|
|
610
|
+
needsFullRender = true;
|
|
602
611
|
scheduleRender();
|
|
603
612
|
}
|
|
604
613
|
|
|
@@ -665,6 +674,7 @@ function isItemVisible(item) {
|
|
|
665
674
|
case 'tool_input': return showToolInput;
|
|
666
675
|
case 'tool_output': return showToolOutput;
|
|
667
676
|
case 'text': return showText;
|
|
677
|
+
case 'hook_output': return showHook;
|
|
668
678
|
default: return true;
|
|
669
679
|
}
|
|
670
680
|
}
|
|
@@ -821,6 +831,7 @@ function isSessionActive(session) {
|
|
|
821
831
|
|
|
822
832
|
function renderStream() {
|
|
823
833
|
const visible = streamItems.filter(isItemVisible);
|
|
834
|
+
const wasAutoScroll = autoScroll;
|
|
824
835
|
|
|
825
836
|
if (needsFullRender || renderedItemCount > visible.length) {
|
|
826
837
|
// Full rebuild: filter changed, items trimmed, or initial render
|
|
@@ -844,10 +855,9 @@ function renderStream() {
|
|
|
844
855
|
streamEl.innerHTML = html;
|
|
845
856
|
renderedItemCount = visible.length;
|
|
846
857
|
needsFullRender = false;
|
|
847
|
-
if (
|
|
858
|
+
if (wasAutoScroll) requestAnimationFrame(() => { streamEl.scrollTop = streamEl.scrollHeight; });
|
|
848
859
|
} else {
|
|
849
860
|
// Incremental append: only add new items since last render
|
|
850
|
-
const wasAtBottom = streamEl.scrollHeight - streamEl.scrollTop - streamEl.clientHeight < 50;
|
|
851
861
|
for (let i = renderedItemCount; i < visible.length; i++) {
|
|
852
862
|
for (const l of renderItem(visible[i])) {
|
|
853
863
|
const div = document.createElement('div');
|
|
@@ -857,7 +867,7 @@ function renderStream() {
|
|
|
857
867
|
}
|
|
858
868
|
}
|
|
859
869
|
renderedItemCount = visible.length;
|
|
860
|
-
if (autoScroll
|
|
870
|
+
if (autoScroll) requestAnimationFrame(() => { streamEl.scrollTop = streamEl.scrollHeight; });
|
|
861
871
|
}
|
|
862
872
|
|
|
863
873
|
const maxScroll = streamEl.scrollHeight - streamEl.clientHeight;
|
|
@@ -951,6 +961,7 @@ function refreshButtons() {
|
|
|
951
961
|
document.getElementById('btn-tool-input').classList.toggle('on', showToolInput);
|
|
952
962
|
document.getElementById('btn-tool-output').classList.toggle('on', showToolOutput);
|
|
953
963
|
document.getElementById('btn-text').classList.toggle('on', showText);
|
|
964
|
+
document.getElementById('btn-hook').classList.toggle('on', showHook);
|
|
954
965
|
document.getElementById('btn-autoscroll').classList.toggle('on', autoScroll);
|
|
955
966
|
document.getElementById('btn-tree-toggle').classList.toggle('on', showTree);
|
|
956
967
|
document.getElementById('btn-autodisco').classList.toggle('on', autoDiscovery);
|
|
@@ -1104,8 +1115,12 @@ function removeSelectedSession() {
|
|
|
1104
1115
|
if (node.type === 'session') sid = node.id;
|
|
1105
1116
|
else sid = node.sessionID;
|
|
1106
1117
|
if (!sid) return;
|
|
1107
|
-
if (!confirm(`
|
|
1108
|
-
|
|
1118
|
+
if (!confirm(`Move session ${sid.slice(0, 12)}... to bottom?`)) return;
|
|
1119
|
+
const idx = sessions.findIndex(s => s.id === sid);
|
|
1120
|
+
if (idx >= 0) {
|
|
1121
|
+
const session = sessions.splice(idx, 1)[0];
|
|
1122
|
+
sessions.push(session);
|
|
1123
|
+
}
|
|
1109
1124
|
sendCmd('removeSession', { sessionID: sid });
|
|
1110
1125
|
updateFilters();
|
|
1111
1126
|
rebuildNodes();
|
|
@@ -1120,13 +1135,14 @@ function toggleThinking() { showThinking = !showThinking; needsFullRender = true
|
|
|
1120
1135
|
function toggleToolInput() { showToolInput = !showToolInput; needsFullRender = true; renderStream(); refreshButtons(); }
|
|
1121
1136
|
function toggleToolOutput() { showToolOutput = !showToolOutput; needsFullRender = true; renderStream(); refreshButtons(); }
|
|
1122
1137
|
function toggleText() { showText = !showText; needsFullRender = true; renderStream(); refreshButtons(); }
|
|
1138
|
+
function toggleHook() { showHook = !showHook; needsFullRender = true; renderStream(); refreshButtons(); }
|
|
1123
1139
|
function toggleAutoScroll() { autoScroll = !autoScroll; if (autoScroll) streamEl.scrollTop = streamEl.scrollHeight; renderAll(); }
|
|
1124
1140
|
function toggleTree() { showTree = !showTree; document.getElementById('tree-panel').classList.toggle('hidden', !showTree); }
|
|
1125
1141
|
function toggleAutoDiscovery() { sendCmd('toggleAutoDiscovery'); }
|
|
1126
1142
|
|
|
1127
1143
|
function scrollToTop() { streamEl.scrollTop = 0; autoScroll = false; renderAll(); }
|
|
1128
1144
|
function scrollUp() { streamEl.scrollTop -= 80; autoScroll = false; renderAll(); }
|
|
1129
|
-
function scrollDown() { streamEl.scrollTop += 80; renderAll(); }
|
|
1145
|
+
function scrollDown() { streamEl.scrollTop += 80; if (autoScroll) autoScroll = false; renderAll(); }
|
|
1130
1146
|
function scrollToBottom() { streamEl.scrollTop = streamEl.scrollHeight; autoScroll = true; renderAll(); }
|
|
1131
1147
|
|
|
1132
1148
|
// ══════════════════════════════════════════════════════════════════════════════
|
|
@@ -1270,7 +1286,9 @@ function scheduleRender() {
|
|
|
1270
1286
|
renderPending = true;
|
|
1271
1287
|
requestAnimationFrame(() => {
|
|
1272
1288
|
renderPending = false;
|
|
1273
|
-
|
|
1289
|
+
renderTree();
|
|
1290
|
+
renderStream();
|
|
1291
|
+
refreshButtons();
|
|
1274
1292
|
});
|
|
1275
1293
|
}
|
|
1276
1294
|
}
|
package/src/server/server.js
CHANGED
|
@@ -21,7 +21,7 @@ var MIME = {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
var MAX_ITEM_BUFFER = 2000;
|
|
24
|
-
var CONTEXT_STALE_MS =
|
|
24
|
+
var CONTEXT_STALE_MS = 60 * 60 * 1000; // 60 minutes
|
|
25
25
|
|
|
26
26
|
class DashboardServer {
|
|
27
27
|
constructor(options = {}) {
|
|
@@ -52,7 +52,7 @@ class DashboardServer {
|
|
|
52
52
|
ctx = { inputTokens: 0, outputTokens: 0, cacheCreation: 0, cacheRead: 0, model: '', contextWindow: 200000, lastActivity: Date.now() };
|
|
53
53
|
this.contextMap.set(key, ctx);
|
|
54
54
|
}
|
|
55
|
-
if (item.inputTokens) ctx.inputTokens
|
|
55
|
+
if (item.inputTokens) ctx.inputTokens = Math.max(ctx.inputTokens, item.inputTokens);
|
|
56
56
|
if (item.outputTokens) ctx.outputTokens += item.outputTokens;
|
|
57
57
|
if (item.cacheCreationTokens) ctx.cacheCreation += item.cacheCreationTokens;
|
|
58
58
|
if (item.cacheReadTokens) ctx.cacheRead += item.cacheReadTokens;
|
|
@@ -353,7 +353,7 @@ class DashboardServer {
|
|
|
353
353
|
}
|
|
354
354
|
const skipHistory = options.skipHistory || false;
|
|
355
355
|
const pollMs = options.pollMs || 500;
|
|
356
|
-
const activeWindow = options.activeWindow ||
|
|
356
|
+
const activeWindow = options.activeWindow || 100 * 60 * 1000;
|
|
357
357
|
const maxSessions = options.maxSessions || 0;
|
|
358
358
|
const openBrowser = options.openBrowser !== false;
|
|
359
359
|
|
|
@@ -400,19 +400,6 @@ class DashboardServer {
|
|
|
400
400
|
await w.init();
|
|
401
401
|
if (skipHistory) w.setSkipHistory(true);
|
|
402
402
|
await w.start();
|
|
403
|
-
|
|
404
|
-
// Open browser AFTER sessions are discovered, so new clients get a full snapshot
|
|
405
|
-
if (openBrowser) {
|
|
406
|
-
const url = `http://localhost:${this.port}`;
|
|
407
|
-
const platform = process.platform;
|
|
408
|
-
if (platform === 'darwin') {
|
|
409
|
-
cp.spawn('open', [url]);
|
|
410
|
-
} else if (platform === 'win32') {
|
|
411
|
-
cp.spawn('cmd', ['/c', 'start', '', url]);
|
|
412
|
-
} else {
|
|
413
|
-
cp.spawn('xdg-open', [url]);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
403
|
} catch (err) {
|
|
417
404
|
console.error('Watcher init error:', err.message);
|
|
418
405
|
process.exit(1);
|
|
@@ -420,15 +407,32 @@ class DashboardServer {
|
|
|
420
407
|
|
|
421
408
|
this._contextCleanupTimer = setInterval(() => this.cleanupContextMap(), CONTEXT_STALE_MS);
|
|
422
409
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
410
|
+
// Start listening and wait for server to be ready before opening browser
|
|
411
|
+
await new Promise((resolve) => {
|
|
412
|
+
this.server.listen(this.port, this.host, () => {
|
|
413
|
+
const url = `http://localhost:${this.port}`;
|
|
414
|
+
console.log(`\n claude-watch web server`);
|
|
415
|
+
console.log(` ───────────────────────────`);
|
|
416
|
+
console.log(` Local: ${url}`);
|
|
417
|
+
console.log(` Network: http://${this.host}:${this.port}`);
|
|
418
|
+
console.log(` Quit: Ctrl+C\n`);
|
|
419
|
+
resolve();
|
|
420
|
+
});
|
|
430
421
|
});
|
|
431
422
|
|
|
423
|
+
// Open browser AFTER server is confirmed listening and watcher is ready
|
|
424
|
+
if (openBrowser) {
|
|
425
|
+
const url = `http://localhost:${this.port}`;
|
|
426
|
+
const platform = process.platform;
|
|
427
|
+
if (platform === 'darwin') {
|
|
428
|
+
cp.spawn('open', [url]);
|
|
429
|
+
} else if (platform === 'win32') {
|
|
430
|
+
cp.spawn('cmd', ['/c', 'start', '', url]);
|
|
431
|
+
} else {
|
|
432
|
+
cp.spawn('xdg-open', [url]);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
432
436
|
return { server: this.server, watcher: w };
|
|
433
437
|
}
|
|
434
438
|
|
package/src/watcher/watcher.js
CHANGED
|
@@ -111,7 +111,7 @@ class Watcher extends EventEmitter {
|
|
|
111
111
|
super();
|
|
112
112
|
this.claudeDir = getClaudeProjectsDir();
|
|
113
113
|
this.pollInterval = pollInterval || 500;
|
|
114
|
-
this.activeWindow = activeWindow ||
|
|
114
|
+
this.activeWindow = activeWindow || 100 * 60 * 1000;
|
|
115
115
|
this.maxSessions = maxSessions || 0;
|
|
116
116
|
this.sessions = new Map();
|
|
117
117
|
this.filePositions = new Map();
|