claude-code-watch 0.0.6 → 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 +96 -9
- package/public/vendor/github-light.min.css +10 -0
- 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
|
@@ -30,6 +30,28 @@
|
|
|
30
30
|
--orange: #fb923c;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
:root[data-theme="light"] {
|
|
34
|
+
--bg: #f8f9fa;
|
|
35
|
+
--bg2: #e9ecef;
|
|
36
|
+
--bg3: #ced4da;
|
|
37
|
+
--border: #adb5bd;
|
|
38
|
+
--text: #495057;
|
|
39
|
+
--dim: #868e96;
|
|
40
|
+
--white: #212529;
|
|
41
|
+
--purple: #6741d9;
|
|
42
|
+
--purple2: #5b21b6;
|
|
43
|
+
--blue: #2563eb;
|
|
44
|
+
--magenta: #9333ea;
|
|
45
|
+
--yellow: #d97706;
|
|
46
|
+
--yellow2: #92400e;
|
|
47
|
+
--green: #059669;
|
|
48
|
+
--cyan: #0891b2;
|
|
49
|
+
--red: #dc2626;
|
|
50
|
+
--red2: #b91c1c;
|
|
51
|
+
--gray: #6b7280;
|
|
52
|
+
--orange: #ea580c;
|
|
53
|
+
}
|
|
54
|
+
|
|
33
55
|
body {
|
|
34
56
|
background: var(--bg);
|
|
35
57
|
color: var(--text);
|
|
@@ -218,6 +240,21 @@ body {
|
|
|
218
240
|
|
|
219
241
|
/* Override highlight.js background to match our theme */
|
|
220
242
|
.hljs { background: #0d1117 !important; }
|
|
243
|
+
|
|
244
|
+
/* Light theme overrides */
|
|
245
|
+
:root[data-theme="light"] .btn.on { background: var(--purple); border-color: var(--purple); color: #fff; }
|
|
246
|
+
:root[data-theme="light"] .btn.on:hover { background: var(--purple2); color: #fff; }
|
|
247
|
+
:root[data-theme="light"] .btn.on:hover::after { background: var(--purple2); color: #fff; }
|
|
248
|
+
:root[data-theme="light"] .hljs { background: #f0f0f0 !important; }
|
|
249
|
+
:root[data-theme="light"] .tree-node:hover { background: rgba(0,0,0,0.06); }
|
|
250
|
+
:root[data-theme="light"] .tree-node.selected { background: rgba(124,58,237,0.2); }
|
|
251
|
+
:root[data-theme="light"] .tree-node .active-dot.off { color: #bbb; }
|
|
252
|
+
:root[data-theme="light"] #tree-resize-handle:hover,
|
|
253
|
+
:root[data-theme="light"] #tree-resize-handle.active { background: var(--purple); }
|
|
254
|
+
:root[data-theme="light"] .stream-line.text { color: var(--text); }
|
|
255
|
+
|
|
256
|
+
/* Theme toggle button */
|
|
257
|
+
#btn-theme { font-size: 14px; }
|
|
221
258
|
</style>
|
|
222
259
|
</head>
|
|
223
260
|
<body>
|
|
@@ -227,12 +264,14 @@ body {
|
|
|
227
264
|
<button class="btn on" id="btn-tool-input" onclick="toggleToolInput()" data-tooltip="Toggle tool input">🔧 Tools</button>
|
|
228
265
|
<button class="btn on" id="btn-tool-output" onclick="toggleToolOutput()" data-tooltip="Toggle tool output">📤 Output</button>
|
|
229
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>
|
|
230
268
|
<span class="sep">│</span>
|
|
231
269
|
<button class="btn on" id="btn-autoscroll" onclick="toggleAutoScroll()" data-tooltip="Auto-scroll">⇣ Auto</button>
|
|
232
270
|
<button class="btn" id="btn-tree-toggle" onclick="toggleTree()" data-tooltip="Toggle tree panel">📂 Tree</button>
|
|
233
271
|
<span class="sep">│</span>
|
|
234
272
|
<span id="session-info">Connecting...</span>
|
|
235
273
|
<div class="auto">
|
|
274
|
+
<button class="btn btn-icon" id="btn-theme" onclick="toggleTheme()" data-tooltip="Toggle theme">🌙</button>
|
|
236
275
|
<button class="btn on" id="btn-autodisco" onclick="toggleAutoDiscovery()" data-tooltip="Auto-discover">🔍 Auto</button>
|
|
237
276
|
<span class="sep">│</span>
|
|
238
277
|
<span id="token-info"></span>
|
|
@@ -342,6 +381,7 @@ let showThinking = true;
|
|
|
342
381
|
let showToolInput = true;
|
|
343
382
|
let showToolOutput = true;
|
|
344
383
|
let showText = true;
|
|
384
|
+
let showHook = true;
|
|
345
385
|
let autoDiscovery = true;
|
|
346
386
|
|
|
347
387
|
let renderPending = false;
|
|
@@ -363,7 +403,7 @@ let collapseAfter = 0;
|
|
|
363
403
|
let collapseTimer = null;
|
|
364
404
|
let activeRefreshTimer = null;
|
|
365
405
|
|
|
366
|
-
const MAX_ITEMS =
|
|
406
|
+
const MAX_ITEMS = 3000;
|
|
367
407
|
const MAX_LINES = 50;
|
|
368
408
|
let renderedItemCount = 0;
|
|
369
409
|
let needsFullRender = true;
|
|
@@ -514,6 +554,7 @@ function handleSnapshot(payload) {
|
|
|
514
554
|
}
|
|
515
555
|
updateFilters();
|
|
516
556
|
rebuildNodes();
|
|
557
|
+
needsFullRender = true;
|
|
517
558
|
scheduleRender();
|
|
518
559
|
}
|
|
519
560
|
|
|
@@ -528,6 +569,7 @@ function handleNewSession(payload) {
|
|
|
528
569
|
});
|
|
529
570
|
updateFilters();
|
|
530
571
|
rebuildNodes();
|
|
572
|
+
needsFullRender = true;
|
|
531
573
|
scheduleRender();
|
|
532
574
|
}
|
|
533
575
|
|
|
@@ -541,6 +583,7 @@ function handleNewAgent(payload) {
|
|
|
541
583
|
});
|
|
542
584
|
updateFilters();
|
|
543
585
|
rebuildNodes();
|
|
586
|
+
needsFullRender = true;
|
|
544
587
|
scheduleRender();
|
|
545
588
|
}
|
|
546
589
|
|
|
@@ -558,9 +601,13 @@ function handleNewBgTask(payload) {
|
|
|
558
601
|
|
|
559
602
|
function handleSessionRemoved(payload) {
|
|
560
603
|
const idx = sessions.findIndex(s => s.id === payload.sessionID);
|
|
561
|
-
if (idx >= 0)
|
|
604
|
+
if (idx >= 0) {
|
|
605
|
+
const session = sessions.splice(idx, 1)[0];
|
|
606
|
+
sessions.push(session);
|
|
607
|
+
}
|
|
562
608
|
updateFilters();
|
|
563
609
|
rebuildNodes();
|
|
610
|
+
needsFullRender = true;
|
|
564
611
|
scheduleRender();
|
|
565
612
|
}
|
|
566
613
|
|
|
@@ -627,6 +674,7 @@ function isItemVisible(item) {
|
|
|
627
674
|
case 'tool_input': return showToolInput;
|
|
628
675
|
case 'tool_output': return showToolOutput;
|
|
629
676
|
case 'text': return showText;
|
|
677
|
+
case 'hook_output': return showHook;
|
|
630
678
|
default: return true;
|
|
631
679
|
}
|
|
632
680
|
}
|
|
@@ -783,6 +831,7 @@ function isSessionActive(session) {
|
|
|
783
831
|
|
|
784
832
|
function renderStream() {
|
|
785
833
|
const visible = streamItems.filter(isItemVisible);
|
|
834
|
+
const wasAutoScroll = autoScroll;
|
|
786
835
|
|
|
787
836
|
if (needsFullRender || renderedItemCount > visible.length) {
|
|
788
837
|
// Full rebuild: filter changed, items trimmed, or initial render
|
|
@@ -806,10 +855,9 @@ function renderStream() {
|
|
|
806
855
|
streamEl.innerHTML = html;
|
|
807
856
|
renderedItemCount = visible.length;
|
|
808
857
|
needsFullRender = false;
|
|
809
|
-
if (
|
|
858
|
+
if (wasAutoScroll) requestAnimationFrame(() => { streamEl.scrollTop = streamEl.scrollHeight; });
|
|
810
859
|
} else {
|
|
811
860
|
// Incremental append: only add new items since last render
|
|
812
|
-
const wasAtBottom = streamEl.scrollHeight - streamEl.scrollTop - streamEl.clientHeight < 50;
|
|
813
861
|
for (let i = renderedItemCount; i < visible.length; i++) {
|
|
814
862
|
for (const l of renderItem(visible[i])) {
|
|
815
863
|
const div = document.createElement('div');
|
|
@@ -819,7 +867,7 @@ function renderStream() {
|
|
|
819
867
|
}
|
|
820
868
|
}
|
|
821
869
|
renderedItemCount = visible.length;
|
|
822
|
-
if (autoScroll
|
|
870
|
+
if (autoScroll) requestAnimationFrame(() => { streamEl.scrollTop = streamEl.scrollHeight; });
|
|
823
871
|
}
|
|
824
872
|
|
|
825
873
|
const maxScroll = streamEl.scrollHeight - streamEl.clientHeight;
|
|
@@ -913,6 +961,7 @@ function refreshButtons() {
|
|
|
913
961
|
document.getElementById('btn-tool-input').classList.toggle('on', showToolInput);
|
|
914
962
|
document.getElementById('btn-tool-output').classList.toggle('on', showToolOutput);
|
|
915
963
|
document.getElementById('btn-text').classList.toggle('on', showText);
|
|
964
|
+
document.getElementById('btn-hook').classList.toggle('on', showHook);
|
|
916
965
|
document.getElementById('btn-autoscroll').classList.toggle('on', autoScroll);
|
|
917
966
|
document.getElementById('btn-tree-toggle').classList.toggle('on', showTree);
|
|
918
967
|
document.getElementById('btn-autodisco').classList.toggle('on', autoDiscovery);
|
|
@@ -1066,8 +1115,12 @@ function removeSelectedSession() {
|
|
|
1066
1115
|
if (node.type === 'session') sid = node.id;
|
|
1067
1116
|
else sid = node.sessionID;
|
|
1068
1117
|
if (!sid) return;
|
|
1069
|
-
if (!confirm(`
|
|
1070
|
-
|
|
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
|
+
}
|
|
1071
1124
|
sendCmd('removeSession', { sessionID: sid });
|
|
1072
1125
|
updateFilters();
|
|
1073
1126
|
rebuildNodes();
|
|
@@ -1082,13 +1135,14 @@ function toggleThinking() { showThinking = !showThinking; needsFullRender = true
|
|
|
1082
1135
|
function toggleToolInput() { showToolInput = !showToolInput; needsFullRender = true; renderStream(); refreshButtons(); }
|
|
1083
1136
|
function toggleToolOutput() { showToolOutput = !showToolOutput; needsFullRender = true; renderStream(); refreshButtons(); }
|
|
1084
1137
|
function toggleText() { showText = !showText; needsFullRender = true; renderStream(); refreshButtons(); }
|
|
1138
|
+
function toggleHook() { showHook = !showHook; needsFullRender = true; renderStream(); refreshButtons(); }
|
|
1085
1139
|
function toggleAutoScroll() { autoScroll = !autoScroll; if (autoScroll) streamEl.scrollTop = streamEl.scrollHeight; renderAll(); }
|
|
1086
1140
|
function toggleTree() { showTree = !showTree; document.getElementById('tree-panel').classList.toggle('hidden', !showTree); }
|
|
1087
1141
|
function toggleAutoDiscovery() { sendCmd('toggleAutoDiscovery'); }
|
|
1088
1142
|
|
|
1089
1143
|
function scrollToTop() { streamEl.scrollTop = 0; autoScroll = false; renderAll(); }
|
|
1090
1144
|
function scrollUp() { streamEl.scrollTop -= 80; autoScroll = false; renderAll(); }
|
|
1091
|
-
function scrollDown() { streamEl.scrollTop += 80; renderAll(); }
|
|
1145
|
+
function scrollDown() { streamEl.scrollTop += 80; if (autoScroll) autoScroll = false; renderAll(); }
|
|
1092
1146
|
function scrollToBottom() { streamEl.scrollTop = streamEl.scrollHeight; autoScroll = true; renderAll(); }
|
|
1093
1147
|
|
|
1094
1148
|
// ══════════════════════════════════════════════════════════════════════════════
|
|
@@ -1232,11 +1286,44 @@ function scheduleRender() {
|
|
|
1232
1286
|
renderPending = true;
|
|
1233
1287
|
requestAnimationFrame(() => {
|
|
1234
1288
|
renderPending = false;
|
|
1235
|
-
|
|
1289
|
+
renderTree();
|
|
1290
|
+
renderStream();
|
|
1291
|
+
refreshButtons();
|
|
1236
1292
|
});
|
|
1237
1293
|
}
|
|
1238
1294
|
}
|
|
1239
1295
|
|
|
1296
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
1297
|
+
// Theme toggle
|
|
1298
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
1299
|
+
|
|
1300
|
+
function applyTheme(theme) {
|
|
1301
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
1302
|
+
const btn = document.getElementById('btn-theme');
|
|
1303
|
+
if (btn) {
|
|
1304
|
+
btn.textContent = theme === 'dark' ? '🌙' : '☀️';
|
|
1305
|
+
btn.setAttribute('data-tooltip', theme === 'dark' ? 'Switch to light' : 'Switch to dark');
|
|
1306
|
+
}
|
|
1307
|
+
// Swap highlight.js stylesheet for theme
|
|
1308
|
+
const hlLink = document.querySelector('link[rel="stylesheet"][href*="github"]');
|
|
1309
|
+
if (hlLink) {
|
|
1310
|
+
hlLink.href = theme === 'dark' ? 'vendor/github-dark.min.css' : 'vendor/github-light.min.css';
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function toggleTheme() {
|
|
1315
|
+
const current = document.documentElement.getAttribute('data-theme') || 'dark';
|
|
1316
|
+
const next = current === 'dark' ? 'light' : 'dark';
|
|
1317
|
+
localStorage.setItem('theme', next);
|
|
1318
|
+
applyTheme(next);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
// Apply saved theme on load (default dark)
|
|
1322
|
+
(function() {
|
|
1323
|
+
const saved = localStorage.getItem('theme');
|
|
1324
|
+
applyTheme(saved || 'dark');
|
|
1325
|
+
})();
|
|
1326
|
+
|
|
1240
1327
|
// ══════════════════════════════════════════════════════════════════════════════
|
|
1241
1328
|
// Init
|
|
1242
1329
|
// ══════════════════════════════════════════════════════════════════════════════
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
|
2
|
+
Theme: GitHub
|
|
3
|
+
Description: Light theme as seen on github.com
|
|
4
|
+
Author: github.com
|
|
5
|
+
Maintainer: @Hirse
|
|
6
|
+
Updated: 2021-05-15
|
|
7
|
+
|
|
8
|
+
Outdated base version: https://github.com/primer/github-syntax-light
|
|
9
|
+
Current colors taken from GitHub's CSS
|
|
10
|
+
*/.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}
|
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();
|