claude-code-watch 0.0.11 → 0.0.12

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.
@@ -113,9 +113,8 @@ async function runUpdate() {
113
113
  console.log(` Latest version: v${latest}`);
114
114
  console.log(' Running npm install -g claude-code-watch@latest...\n');
115
115
 
116
- const { execSync } = require('child_process');
117
116
  try {
118
- execSync('npm install -g claude-code-watch@latest', { stdio: 'inherit' });
117
+ cp.execSync('npm install -g claude-code-watch@latest', { stdio: 'inherit' });
119
118
  console.log(`\n Updated to v${latest}. Restart to use the new version.`);
120
119
  } catch {
121
120
  console.error('\n Update failed. Try manually: npm install -g claude-code-watch@latest');
@@ -139,9 +138,16 @@ async function main() {
139
138
  openBrowser: true,
140
139
  };
141
140
 
142
- // First pass: collect all option values
141
+ // Action flags
142
+ let listSessionsLimit = 0; // 0 = no list, >0 = limit
143
+ let listActiveLimit = 0; // 0 = no list, >0 = limit, -1 = all
144
+ let showVersion = false;
145
+ let showHelp = false;
146
+ let doUpdate = false;
147
+
143
148
  for (let i = 0; i < args.length; i++) {
144
- switch (args[i]) {
149
+ const arg = args[i];
150
+ switch (arg) {
145
151
  case '-s':
146
152
  options.sessionID = args[++i] || '';
147
153
  break;
@@ -151,7 +157,7 @@ async function main() {
151
157
  case '-p':
152
158
  case '--port': {
153
159
  if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
154
- console.error(`Error: ${args[i]} requires a port number`);
160
+ console.error(`Error: ${arg} requires a port number`);
155
161
  process.exit(1);
156
162
  }
157
163
  const pv = parseInt(args[++i], 10);
@@ -165,7 +171,7 @@ async function main() {
165
171
  case '-h':
166
172
  case '--host':
167
173
  if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
168
- console.error(`Error: ${args[i]} requires a host address`);
174
+ console.error(`Error: ${arg} requires a host address`);
169
175
  process.exit(1);
170
176
  }
171
177
  options.host = args[++i];
@@ -196,75 +202,85 @@ async function main() {
196
202
  case '--no-open':
197
203
  options.openBrowser = false;
198
204
  break;
199
- default:
200
- break;
201
- }
202
- }
203
-
204
- // Second pass: execute action flags with fully resolved options
205
- for (let i = 0; i < args.length; i++) {
206
- switch (args[i]) {
207
205
  case '-l': {
208
- const v = parseInt(args[i + 1]);
209
- const limit = !isNaN(v) ? v : 10;
206
+ const next = args[i + 1];
207
+ const v = parseInt(next);
208
+ listSessionsLimit = !isNaN(v) ? v : 10;
210
209
  if (!isNaN(v)) i++;
211
- const sessions = await listSessions(limit);
212
- if (sessions.length === 0) {
213
- console.log('No sessions found.');
214
- } else {
215
- const now = Date.now();
216
- for (const s of sessions) {
217
- const age = Math.round((now - new Date(s.modified).getTime()) / 1000);
218
- const ageStr = age < 60 ? `${age}s ago` : age < 3600 ? `${Math.floor(age / 60)}m ago` : `${Math.floor(age / 3600)}h ago`;
219
- const active = s.isActive ? '●' : '○';
220
- const id = s.id.length > 40 ? s.id.slice(0, 37) + '...' : s.id;
221
- console.log(`${active} ${id} ${s.projectPath || '?'} ${ageStr}`);
222
- }
223
- }
224
- return;
210
+ break;
225
211
  }
226
212
  case '-a': {
227
- const v = parseInt(args[i + 1]);
228
- const limit = !isNaN(v) ? v : 0;
229
- if (!isNaN(v)) i++;
230
- const sessions = await listActiveSessions(options.activeWindow);
231
- const result = limit > 0 ? sessions.slice(0, limit) : sessions;
232
- if (result.length === 0) {
233
- console.log('No active sessions found.');
234
- } else {
235
- const now = Date.now();
236
- for (const s of result) {
237
- const age = Math.round((now - new Date(s.modified).getTime()) / 1000);
238
- const ageStr = age < 60 ? `${age}s ago` : age < 3600 ? `${Math.floor(age / 60)}m ago` : `${Math.floor(age / 3600)}h ago`;
239
- const id = s.id.length > 40 ? s.id.slice(0, 37) + '...' : s.id;
240
- console.log(`● ${id} ${s.projectPath || '?'} ${ageStr}`);
241
- }
242
- }
243
- return;
213
+ const next = args[i + 1];
214
+ const v = parseInt(next);
215
+ if (!isNaN(v)) { listActiveLimit = v; i++; }
216
+ else { listActiveLimit = -1; }
217
+ break;
244
218
  }
245
219
  case '-v':
246
- console.log(`claude-watch v${VERSION}`);
247
- return;
220
+ showVersion = true;
221
+ break;
248
222
  case '--help':
249
- printHelp();
250
- return;
223
+ showHelp = true;
224
+ break;
251
225
  case 'update':
252
- await runUpdate();
253
- return;
254
- // Skip option flags already handled in first pass
255
- case '-s': case '-n': case '-p': case '--port':
256
- case '-h': case '--host': case '-w': case '-c':
257
- case '-m': case '-D': case '--poll': case '--no-open':
226
+ doUpdate = true;
258
227
  break;
259
228
  default:
260
- if (args[i].startsWith('-')) {
261
- console.error(`Unknown option: ${args[i]}`);
229
+ if (arg.startsWith('-')) {
230
+ console.error(`Unknown option: ${arg}`);
262
231
  printHelp();
263
232
  process.exit(1);
264
233
  }
265
234
  }
266
235
  }
267
236
 
237
+ // Execute action flags
238
+ if (showVersion) {
239
+ printVersion();
240
+ return;
241
+ }
242
+ if (showHelp) {
243
+ printHelp();
244
+ return;
245
+ }
246
+ if (doUpdate) {
247
+ await runUpdate();
248
+ return;
249
+ }
250
+ if (listSessionsLimit > 0) {
251
+ const sessions = await listSessions(listSessionsLimit);
252
+ if (sessions.length === 0) {
253
+ console.log('No sessions found.');
254
+ } else {
255
+ const now = Date.now();
256
+ for (const s of sessions) {
257
+ const age = Math.round((now - new Date(s.modified).getTime()) / 1000);
258
+ const ageStr = age < 60 ? `${age}s ago` : age < 3600 ? `${Math.floor(age / 60)}m ago` : `${Math.floor(age / 3600)}h ago`;
259
+ const active = s.isActive ? '●' : '○';
260
+ const id = s.id.length > 40 ? s.id.slice(0, 37) + '...' : s.id;
261
+ console.log(`${active} ${id} ${s.projectPath || '?'} ${ageStr}`);
262
+ }
263
+ }
264
+ return;
265
+ }
266
+ if (listActiveLimit !== 0) {
267
+ const limit = listActiveLimit > 0 ? listActiveLimit : 0;
268
+ const sessions = await listActiveSessions(options.activeWindow);
269
+ const result = limit > 0 ? sessions.slice(0, limit) : sessions;
270
+ if (result.length === 0) {
271
+ console.log('No active sessions found.');
272
+ } else {
273
+ const now = Date.now();
274
+ for (const s of result) {
275
+ const age = Math.round((now - new Date(s.modified).getTime()) / 1000);
276
+ const ageStr = age < 60 ? `${age}s ago` : age < 3600 ? `${Math.floor(age / 60)}m ago` : `${Math.floor(age / 3600)}h ago`;
277
+ const id = s.id.length > 40 ? s.id.slice(0, 37) + '...' : s.id;
278
+ console.log(`● ${id} ${s.projectPath || '?'} ${ageStr}`);
279
+ }
280
+ }
281
+ return;
282
+ }
283
+
268
284
  checkForUpdate();
269
285
  startServer(options);
270
286
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-watch",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "Web-based real-time monitor for Claude Code.",
5
5
  "main": "./src/server/server.js",
6
6
  "bin": {
package/public/index.html CHANGED
@@ -393,16 +393,19 @@ let needsFullRender = true;
393
393
  // ══════════════════════════════════════════════════════════════════════════════
394
394
 
395
395
  const mdRenderer = new marked.Renderer();
396
- mdRenderer.code = function (code, lang) {
396
+ mdRenderer.code = function (codeOrObj, langOrEsc) {
397
+ // marked v4: code(text, lang, escaped) — marked v5+: code({ text, lang })
398
+ const text = typeof codeOrObj === 'object' ? codeOrObj.text : codeOrObj;
399
+ const lang = typeof codeOrObj === 'object' ? codeOrObj.lang : langOrEsc;
397
400
  let highlighted;
398
401
  if (lang && hljs.getLanguage(lang)) {
399
402
  try {
400
- highlighted = hljs.highlight(code, { language: lang }).value;
403
+ highlighted = hljs.highlight(text, { language: lang }).value;
401
404
  } catch {
402
- highlighted = hljs.highlightAuto(code).value;
405
+ highlighted = hljs.highlightAuto(text).value;
403
406
  }
404
407
  } else {
405
- highlighted = hljs.highlightAuto(code).value;
408
+ highlighted = hljs.highlightAuto(text).value;
406
409
  }
407
410
  const langTag = lang ? `<span class="lang-tag">${esc(lang)}</span>` : '';
408
411
  return `<div class="code-block-wrapper">
@@ -55,6 +55,8 @@ class DashboardServer {
55
55
  ctx = { inputTokens: 0, outputTokens: 0, cacheCreation: 0, cacheRead: 0, model: '', contextWindow: 200000, lastActivity: Date.now() };
56
56
  this.contextMap.set(key, ctx);
57
57
  }
58
+ // inputTokens: Claude API returns cumulative total per call, not incremental — use Math.max
59
+ // outputTokens/cache tokens: API returns incremental values — use +=
58
60
  if (item.inputTokens) ctx.inputTokens = Math.max(ctx.inputTokens, item.inputTokens);
59
61
  if (item.outputTokens) ctx.outputTokens += item.outputTokens;
60
62
  if (item.cacheCreationTokens) ctx.cacheCreation += item.cacheCreationTokens;
@@ -101,7 +103,9 @@ class DashboardServer {
101
103
  }
102
104
  for (const ws of toRemove) {
103
105
  this.clients.delete(ws);
104
- try { ws.terminate(); } catch {}
106
+ try { ws.terminate(); } catch (err) {
107
+ if (this.debugAll) console.error('[server] terminate error:', err.message);
108
+ }
105
109
  }
106
110
  }
107
111
 
@@ -224,7 +228,9 @@ class DashboardServer {
224
228
  try {
225
229
  const cmd = JSON.parse(data.toString('utf-8'));
226
230
  this.handleCommand(ws, cmd);
227
- } catch {}
231
+ } catch (err) {
232
+ if (this.debugAll) console.error('[server] WS message error:', err.message);
233
+ }
228
234
  });
229
235
 
230
236
  ws.on('close', () => {
@@ -373,7 +379,9 @@ class DashboardServer {
373
379
  } else {
374
380
  process.kill(parsedPid, 'SIGTERM');
375
381
  }
376
- } catch {}
382
+ } catch (err) {
383
+ console.error(`[server] Failed to SIGTERM pid ${parsedPid}: ${err.message}`);
384
+ }
377
385
  }
378
386
  }
379
387
 
@@ -383,7 +391,9 @@ class DashboardServer {
383
391
  for (const pid of pids) {
384
392
  const parsedPid = parseInt(pid, 10);
385
393
  if (Number.isInteger(parsedPid) && parsedPid > 1 && parsedPid !== myPid) {
386
- try { process.kill(parsedPid, 0); process.kill(parsedPid, 'SIGKILL'); } catch {}
394
+ try { process.kill(parsedPid, 0); process.kill(parsedPid, 'SIGKILL'); } catch {
395
+ // Process already gone — nothing to do
396
+ }
387
397
  }
388
398
  }
389
399
  }
@@ -424,15 +424,21 @@ class Watcher extends EventEmitter {
424
424
 
425
425
  if (p.endsWith('.jsonl')) {
426
426
  if (p.includes('/subagents/')) {
427
- this._handleNewSubagentFile(p);
427
+ this._handleNewSubagentFile(p).catch(err => {
428
+ if (this.debug) console.error('[watcher] _handleNewSubagentFile error:', err.message);
429
+ });
428
430
  } else if (this.watchActive) {
429
- this._handleNewSessionFile(p); // fire-and-forget, session will be discovered on next poll
431
+ this._handleNewSessionFile(p).catch(err => {
432
+ if (this.debug) console.error('[watcher] _handleNewSessionFile error:', err.message);
433
+ });
430
434
  }
431
435
  return;
432
436
  }
433
437
 
434
438
  if (p.endsWith('.txt') && p.includes('/tool-results/')) {
435
- this._handleNewToolResultFile(p);
439
+ this._handleNewToolResultFile(p).catch(err => {
440
+ if (this.debug) console.error('[watcher] _handleNewToolResultFile error:', err.message);
441
+ });
436
442
  }
437
443
  }
438
444
 
@@ -448,9 +454,13 @@ class Watcher extends EventEmitter {
448
454
  continue;
449
455
  }
450
456
  if (base === 'subagents' && entry.name.endsWith('.jsonl')) {
451
- this._handleNewSubagentFile(fullPath);
457
+ this._handleNewSubagentFile(fullPath).catch(err => {
458
+ if (this.debug) console.error('[watcher] _handleNewSubagentFile error:', err.message);
459
+ });
452
460
  } else if (base === 'tool-results' && entry.name.endsWith('.txt')) {
453
- this._handleNewToolResultFile(fullPath);
461
+ this._handleNewToolResultFile(fullPath).catch(err => {
462
+ if (this.debug) console.error('[watcher] _handleNewToolResultFile error:', err.message);
463
+ });
454
464
  }
455
465
  }
456
466
  }
@@ -542,7 +552,9 @@ class Watcher extends EventEmitter {
542
552
  this.pendingSubagents.delete(session.id);
543
553
  for (const sp of pending) {
544
554
  const agentID = path.basename(sp).replace(/^agent-/, '').replace(/\.jsonl$/, '');
545
- this._registerSubagent(session, session.id, agentID, sp);
555
+ this._registerSubagent(session, session.id, agentID, sp).catch(err => {
556
+ if (this.debug) console.error('[watcher] _registerSubagent error (pending):', err.message);
557
+ });
546
558
  }
547
559
  }
548
560
  }
@@ -561,7 +573,9 @@ class Watcher extends EventEmitter {
561
573
  return;
562
574
  }
563
575
 
564
- this._registerSubagent(session, sessionID, agentID, p); // fire-and-forget, event handler context
576
+ this._registerSubagent(session, sessionID, agentID, p).catch(err => {
577
+ if (this.debug) console.error('[watcher] _registerSubagent error:', err.message);
578
+ });
565
579
  }
566
580
 
567
581
  async _registerSubagent(session, sessionID, agentID, p) {