fleetlens 0.1.3 → 0.2.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.
Files changed (34) hide show
  1. package/app/apps/web/.next/BUILD_ID +1 -1
  2. package/app/apps/web/.next/build-manifest.json +3 -3
  3. package/app/apps/web/.next/prerender-manifest.json +3 -3
  4. package/app/apps/web/.next/server/app/_global-error.html +1 -1
  5. package/app/apps/web/.next/server/app/_global-error.rsc +1 -1
  6. package/app/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/app/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/app/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  9. package/app/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  10. package/app/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  13. package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
  14. package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
  15. package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  16. package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  17. package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  18. package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
  19. package/app/apps/web/.next/server/chunks/ssr/_0yyvgac._.js +1 -1
  20. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_06j3c.0._.js +1 -1
  21. package/app/apps/web/.next/server/middleware-build-manifest.js +3 -3
  22. package/app/apps/web/.next/server/pages/500.html +1 -1
  23. package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
  24. package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
  25. package/app/apps/web/.next/static/chunks/{0ipd1ph4bgubq.js → 0dtwmo5wu8b96.js} +1 -1
  26. package/app/apps/web/app/layout.tsx +2 -2
  27. package/app/apps/web/components/sidebar.tsx +4 -2
  28. package/app/apps/web/package.json +1 -1
  29. package/app/apps/web/tsconfig.tsbuildinfo +1 -1
  30. package/dist/index.js +398 -198
  31. package/package.json +1 -1
  32. /package/app/apps/web/.next/static/{xYJud5MeHVY9ZFuYH3d2a → Ha1-RttFUdwakgsbK-ky-}/_buildManifest.js +0 -0
  33. /package/app/apps/web/.next/static/{xYJud5MeHVY9ZFuYH3d2a → Ha1-RttFUdwakgsbK-ky-}/_clientMiddlewareManifest.js +0 -0
  34. /package/app/apps/web/.next/static/{xYJud5MeHVY9ZFuYH3d2a → Ha1-RttFUdwakgsbK-ky-}/_ssgManifest.js +0 -0
package/dist/index.js CHANGED
@@ -158,6 +158,8 @@ var init_server = __esm({
158
158
 
159
159
  // src/updater.ts
160
160
  import { execSync, spawnSync } from "node:child_process";
161
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
162
+ import { join as join2 } from "node:path";
161
163
  function shouldUpdate(local, remote) {
162
164
  const l = local.split(".").map(Number);
163
165
  const r = remote.split(".").map(Number);
@@ -193,8 +195,92 @@ function runNpmInstall() {
193
195
  return false;
194
196
  }
195
197
  }
198
+ function verifyInstalledVersion() {
199
+ try {
200
+ const globalRoot = execSync("npm root -g", {
201
+ encoding: "utf8",
202
+ timeout: 5e3
203
+ }).trim();
204
+ const pkgPath = join2(globalRoot, PACKAGE_NAME, "package.json");
205
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
206
+ if (!pkg.version) {
207
+ return { ok: false, reason: `no version field in ${pkgPath}` };
208
+ }
209
+ return {
210
+ ok: true,
211
+ installedVersion: pkg.version,
212
+ installedPath: join2(globalRoot, PACKAGE_NAME)
213
+ };
214
+ } catch (err) {
215
+ return { ok: false, reason: err.message };
216
+ }
217
+ }
218
+ function currentBinaryPath() {
219
+ try {
220
+ const cmd = process.platform === "win32" ? "where" : "command -v";
221
+ return execSync(`${cmd} ${PACKAGE_NAME}`, {
222
+ encoding: "utf8",
223
+ timeout: 3e3
224
+ }).trim().split("\n")[0] || null;
225
+ } catch {
226
+ return null;
227
+ }
228
+ }
229
+ function reportInstallOutcome(expectedLatest) {
230
+ const verify = verifyInstalledVersion();
231
+ if (!verify.ok) {
232
+ console.log(
233
+ `
234
+ npm install succeeded, but I couldn't verify the installed version`
235
+ );
236
+ console.log(` (${verify.reason})`);
237
+ console.log(
238
+ ` \u2192 Run 'fleetlens --version' in a new shell to confirm.`
239
+ );
240
+ return;
241
+ }
242
+ console.log(
243
+ `
244
+ Installed: ${PACKAGE_NAME}@${verify.installedVersion} at ${verify.installedPath}`
245
+ );
246
+ if (verify.installedVersion !== expectedLatest) {
247
+ console.warn(
248
+ ` \u26A0 Expected ${expectedLatest} but got ${verify.installedVersion} \u2014 npm may have cached an older tarball.`
249
+ );
250
+ }
251
+ const runningPath = process.argv[1] ?? "";
252
+ const pathResolved = currentBinaryPath();
253
+ if (pathResolved && !pathResolved.startsWith(verify.installedPath)) {
254
+ console.warn(
255
+ `
256
+ \u26A0 Your shell resolves '${PACKAGE_NAME}' to a DIFFERENT location:`
257
+ );
258
+ console.warn(` PATH target: ${pathResolved}`);
259
+ console.warn(` Just installed: ${verify.installedPath}`);
260
+ console.warn(
261
+ ` This usually means you have multiple Node installs (nvm, homebrew,`
262
+ );
263
+ console.warn(` system). Start a new shell from the matching Node env, or:`);
264
+ console.warn(` \u2022 run 'hash -r' (zsh/bash) to clear the command cache`);
265
+ console.warn(` \u2022 check 'which -a ${PACKAGE_NAME}' to see all copies`);
266
+ console.warn(
267
+ ` \u2022 reinstall in the correct Node env: 'npm install -g ${PACKAGE_NAME}@latest'`
268
+ );
269
+ } else {
270
+ console.log(
271
+ ` \u2192 This process is still running ${"0.2.1"}. Next invocation will use ${verify.installedVersion}.`
272
+ );
273
+ }
274
+ void runningPath;
275
+ }
196
276
  function reExec() {
197
- const result = spawnSync(process.argv[0], process.argv.slice(1), {
277
+ const verify = verifyInstalledVersion();
278
+ let script = process.argv[1] ?? "";
279
+ if (verify.ok) {
280
+ const candidate = join2(verify.installedPath, "dist", "index.js");
281
+ if (existsSync2(candidate)) script = candidate;
282
+ }
283
+ const result = spawnSync(process.argv[0], [script, ...process.argv.slice(2)], {
198
284
  stdio: "inherit",
199
285
  env: { ...process.env, __FLEETLENS_UPDATED: "1" }
200
286
  });
@@ -209,12 +295,13 @@ async function checkForUpdate() {
209
295
  if (process.env.__FLEETLENS_UPDATED === "1" || process.env.__CCLENS_UPDATED === "1") return;
210
296
  const latest = await fetchLatestVersion();
211
297
  if (latest === null) return;
212
- const current = "0.1.3";
298
+ const current = "0.2.1";
213
299
  if (!shouldUpdate(current, latest)) return;
214
300
  console.log(`Updating ${PACKAGE_NAME} ${current} \u2192 ${latest}...`);
215
301
  const ok = runNpmInstall();
216
302
  if (ok) {
217
- console.log("Updated successfully. Restarting...");
303
+ reportInstallOutcome(latest);
304
+ console.log("\nRestarting with the new version...");
218
305
  reExec();
219
306
  } else {
220
307
  console.warn(
@@ -226,7 +313,7 @@ async function checkForUpdate() {
226
313
  }
227
314
  async function forceUpdate() {
228
315
  const latest = await fetchLatestVersion();
229
- const current = "0.1.3";
316
+ const current = "0.2.1";
230
317
  if (latest === null) {
231
318
  console.error("Could not reach npm registry. Check your network.");
232
319
  process.exit(1);
@@ -238,9 +325,11 @@ async function forceUpdate() {
238
325
  }
239
326
  const ok = runNpmInstall();
240
327
  if (ok) {
241
- console.log(shouldUpdate(current, latest) ? `Updated to ${latest}.` : "Reinstall complete.");
328
+ reportInstallOutcome(latest);
242
329
  } else {
243
330
  console.error("Update failed.");
331
+ console.error(` \u2192 Try manually: npm install -g ${PACKAGE_NAME}@latest`);
332
+ console.error(` \u2192 Or with sudo if your global npm prefix needs it.`);
244
333
  process.exit(1);
245
334
  }
246
335
  }
@@ -253,6 +342,187 @@ var init_updater = __esm({
253
342
  }
254
343
  });
255
344
 
345
+ // src/usage/storage.ts
346
+ import { appendFileSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
347
+ import { dirname as dirname3 } from "node:path";
348
+ function appendSnapshot(filePath, snapshot) {
349
+ mkdirSync2(dirname3(filePath), { recursive: true });
350
+ appendFileSync(filePath, JSON.stringify(snapshot) + "\n", "utf8");
351
+ }
352
+ function readSnapshots(filePath) {
353
+ if (!existsSync3(filePath)) return [];
354
+ const content = readFileSync3(filePath, "utf8");
355
+ const snapshots = [];
356
+ for (const line of content.split("\n")) {
357
+ if (!line.trim()) continue;
358
+ try {
359
+ snapshots.push(JSON.parse(line));
360
+ } catch {
361
+ }
362
+ }
363
+ return snapshots;
364
+ }
365
+ function latestSnapshot(filePath) {
366
+ const all = readSnapshots(filePath);
367
+ return all.length > 0 ? all[all.length - 1] : null;
368
+ }
369
+ var init_storage = __esm({
370
+ "src/usage/storage.ts"() {
371
+ "use strict";
372
+ }
373
+ });
374
+
375
+ // src/commands/daemon.ts
376
+ var daemon_exports = {};
377
+ __export(daemon_exports, {
378
+ daemon: () => daemon,
379
+ getDaemonStatusInfo: () => getDaemonStatusInfo,
380
+ startDaemonSilent: () => startDaemonSilent,
381
+ stopDaemonSilent: () => stopDaemonSilent
382
+ });
383
+ import { spawn as spawn2 } from "node:child_process";
384
+ import { join as join3, dirname as dirname4 } from "node:path";
385
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
386
+ import { existsSync as existsSync4, statSync, readFileSync as readFileSync4 } from "node:fs";
387
+ import { homedir as homedir2 } from "node:os";
388
+ function startDaemonSilent() {
389
+ cleanStalePid(DAEMON_PID);
390
+ const existing = readPid(DAEMON_PID);
391
+ if (existing !== null && isProcessAlive(existing.pid)) {
392
+ return { started: false, pid: existing.pid, alreadyRunning: true };
393
+ }
394
+ const script = workerPath();
395
+ if (!existsSync4(script)) {
396
+ return {
397
+ started: false,
398
+ pid: null,
399
+ alreadyRunning: false,
400
+ error: `Daemon worker not found at ${script}. Rebuild with: pnpm -F fleetlens build`
401
+ };
402
+ }
403
+ const child = spawn2(process.execPath, [script], {
404
+ detached: true,
405
+ stdio: "ignore"
406
+ });
407
+ child.unref();
408
+ const pid = child.pid;
409
+ writePid(DAEMON_PID, pid);
410
+ return { started: true, pid, alreadyRunning: false };
411
+ }
412
+ function stopDaemonSilent() {
413
+ cleanStalePid(DAEMON_PID);
414
+ const entry = readPid(DAEMON_PID);
415
+ if (entry === null) return { stopped: false, pid: null };
416
+ try {
417
+ process.kill(entry.pid, "SIGTERM");
418
+ } catch {
419
+ }
420
+ removePid(DAEMON_PID);
421
+ return { stopped: true, pid: entry.pid };
422
+ }
423
+ function getDaemonStatusInfo() {
424
+ cleanStalePid(DAEMON_PID);
425
+ const entry = readPid(DAEMON_PID);
426
+ const running = entry !== null && isProcessAlive(entry.pid);
427
+ const latest = latestSnapshot(USAGE_LOG);
428
+ const lastSnapshotAgeMs = latest ? Date.now() - new Date(latest.captured_at).getTime() : null;
429
+ const usageLogSize = existsSync4(USAGE_LOG) ? statSync(USAGE_LOG).size : null;
430
+ return {
431
+ running,
432
+ pid: running ? entry.pid : null,
433
+ lastSnapshotAgeMs,
434
+ lastSnapshot: latest,
435
+ usageLogSize
436
+ };
437
+ }
438
+ function workerPath() {
439
+ const here = dirname4(fileURLToPath2(import.meta.url));
440
+ return join3(here, "daemon-worker.js");
441
+ }
442
+ async function daemon(args2) {
443
+ const sub = args2[0] ?? "status";
444
+ switch (sub) {
445
+ case "start":
446
+ await daemonStart();
447
+ break;
448
+ case "stop":
449
+ daemonStop();
450
+ break;
451
+ case "status":
452
+ daemonStatus();
453
+ break;
454
+ case "logs":
455
+ daemonLogs();
456
+ break;
457
+ default:
458
+ console.error(`Unknown daemon subcommand: ${sub}`);
459
+ console.error("Usage: fleetlens daemon <start|stop|status|logs>");
460
+ process.exit(1);
461
+ }
462
+ }
463
+ async function daemonStart() {
464
+ const result = startDaemonSilent();
465
+ if (result.alreadyRunning) {
466
+ console.log(`Daemon is already running (PID ${result.pid})`);
467
+ return;
468
+ }
469
+ if (!result.started) {
470
+ console.error(result.error);
471
+ process.exit(1);
472
+ }
473
+ console.log(`Daemon started (PID ${result.pid})`);
474
+ console.log(`Polling every 5 minutes. Logs: ${DAEMON_LOG}`);
475
+ }
476
+ function daemonStop() {
477
+ const result = stopDaemonSilent();
478
+ if (!result.stopped) {
479
+ console.log("Daemon is not running.");
480
+ return;
481
+ }
482
+ console.log(`Stopped daemon (PID ${result.pid})`);
483
+ }
484
+ function daemonStatus() {
485
+ const info = getDaemonStatusInfo();
486
+ if (info.running) {
487
+ console.log(`Daemon: running (PID ${info.pid})`);
488
+ } else {
489
+ console.log("Daemon: not running");
490
+ }
491
+ if (info.lastSnapshot && info.lastSnapshotAgeMs !== null) {
492
+ const age = Math.round(info.lastSnapshotAgeMs / 1e3);
493
+ const ageStr = age < 60 ? `${age}s` : age < 3600 ? `${Math.round(age / 60)}m` : `${Math.round(age / 3600)}h`;
494
+ console.log(`Last snapshot: ${ageStr} ago`);
495
+ console.log(` 5h: ${info.lastSnapshot.five_hour.utilization?.toFixed(1) ?? "\u2014"}%`);
496
+ console.log(` 7d: ${info.lastSnapshot.seven_day.utilization?.toFixed(1) ?? "\u2014"}%`);
497
+ } else {
498
+ console.log("Last snapshot: none yet");
499
+ }
500
+ if (info.usageLogSize !== null) {
501
+ console.log(`Usage log: ${USAGE_LOG} (${info.usageLogSize} bytes)`);
502
+ }
503
+ }
504
+ function daemonLogs() {
505
+ if (!existsSync4(DAEMON_LOG)) {
506
+ console.log("No daemon logs yet.");
507
+ return;
508
+ }
509
+ const content = readFileSync4(DAEMON_LOG, "utf8");
510
+ const lines = content.trim().split("\n").slice(-20);
511
+ for (const line of lines) process.stdout.write(line + "\n");
512
+ }
513
+ var STATE_DIR2, DAEMON_PID, USAGE_LOG, DAEMON_LOG;
514
+ var init_daemon = __esm({
515
+ "src/commands/daemon.ts"() {
516
+ "use strict";
517
+ init_pid();
518
+ init_storage();
519
+ STATE_DIR2 = join3(homedir2(), ".cclens");
520
+ DAEMON_PID = join3(STATE_DIR2, "daemon.pid");
521
+ USAGE_LOG = join3(STATE_DIR2, "usage.jsonl");
522
+ DAEMON_LOG = join3(STATE_DIR2, "daemon.log");
523
+ }
524
+ });
525
+
256
526
  // src/commands/start.ts
257
527
  var start_exports = {};
258
528
  __export(start_exports, {
@@ -262,33 +532,48 @@ async function start(args2) {
262
532
  const portFlag = args2.indexOf("--port");
263
533
  const port = portFlag !== -1 ? parseInt(args2[portFlag + 1], 10) : void 0;
264
534
  const noOpen = args2.includes("--no-open");
535
+ const noDaemon = args2.includes("--no-daemon");
265
536
  try {
266
537
  await checkForUpdate();
267
538
  } catch {
268
539
  }
269
- const status = getServerStatus();
270
- if (status.running) {
271
- const url = `http://localhost:${status.port}`;
272
- console.log(`fleetlens is already running on ${url} (PID ${status.pid})`);
273
- if (!noOpen) openBrowser(url);
274
- return;
540
+ const status2 = getServerStatus();
541
+ let serverUrl;
542
+ let serverPid;
543
+ if (status2.running) {
544
+ serverUrl = `http://localhost:${status2.port}`;
545
+ serverPid = status2.pid;
546
+ console.log(`Server: already running on ${serverUrl} (PID ${serverPid})`);
547
+ } else {
548
+ console.log("Starting Fleetlens...");
549
+ try {
550
+ const result = await startServer({ port });
551
+ serverUrl = `http://localhost:${result.port}`;
552
+ serverPid = result.pid;
553
+ console.log(`Server: started on ${serverUrl} (PID ${serverPid})`);
554
+ } catch (err) {
555
+ console.error(`Failed to start server: ${err.message}`);
556
+ process.exit(1);
557
+ }
275
558
  }
276
- console.log("Starting fleetlens...");
277
- try {
278
- const result = await startServer({ port });
279
- const url = `http://localhost:${result.port}`;
280
- console.log(`fleetlens running on ${url} (PID ${result.pid})`);
281
- if (!noOpen) openBrowser(url);
282
- } catch (err) {
283
- console.error(`Failed to start: ${err.message}`);
284
- process.exit(1);
559
+ if (!noDaemon) {
560
+ const daemon2 = startDaemonSilent();
561
+ if (daemon2.alreadyRunning) {
562
+ console.log(`Daemon: already running (PID ${daemon2.pid})`);
563
+ } else if (daemon2.started) {
564
+ console.log(`Daemon: started (PID ${daemon2.pid})`);
565
+ } else {
566
+ console.warn(`Daemon: failed to start \u2014 ${daemon2.error}`);
567
+ }
285
568
  }
569
+ if (!noOpen) openBrowser(serverUrl);
286
570
  }
287
571
  var init_start = __esm({
288
572
  "src/commands/start.ts"() {
289
573
  "use strict";
290
574
  init_server();
291
575
  init_updater();
576
+ init_daemon();
292
577
  }
293
578
  });
294
579
 
@@ -298,20 +583,70 @@ __export(stop_exports, {
298
583
  stop: () => stop
299
584
  });
300
585
  async function stop() {
301
- const status = getServerStatus();
302
- if (!status.running) {
303
- console.log("fleetlens is not running.");
304
- return;
586
+ let didSomething = false;
587
+ const status2 = getServerStatus();
588
+ if (status2.running) {
589
+ const result = stopServer();
590
+ if (result.stopped) {
591
+ console.log(`Server: stopped (PID ${result.pid})`);
592
+ didSomething = true;
593
+ }
594
+ } else {
595
+ console.log("Server: not running");
305
596
  }
306
- const result = stopServer();
307
- if (result.stopped) {
308
- console.log(`Stopped fleetlens (PID ${result.pid})`);
597
+ const daemon2 = stopDaemonSilent();
598
+ if (daemon2.stopped) {
599
+ console.log(`Daemon: stopped (PID ${daemon2.pid})`);
600
+ didSomething = true;
601
+ } else {
602
+ console.log("Daemon: not running");
603
+ }
604
+ if (!didSomething) {
605
+ console.log("Fleetlens is not running.");
309
606
  }
310
607
  }
311
608
  var init_stop = __esm({
312
609
  "src/commands/stop.ts"() {
313
610
  "use strict";
314
611
  init_server();
612
+ init_daemon();
613
+ }
614
+ });
615
+
616
+ // src/commands/status.ts
617
+ var status_exports = {};
618
+ __export(status_exports, {
619
+ status: () => status
620
+ });
621
+ async function status() {
622
+ const server = getServerStatus();
623
+ if (server.running) {
624
+ const url = `http://localhost:${server.port}`;
625
+ console.log(`Server: running on ${url} (PID ${server.pid})`);
626
+ } else {
627
+ console.log("Server: not running");
628
+ }
629
+ const daemon2 = getDaemonStatusInfo();
630
+ if (daemon2.running) {
631
+ console.log(`Daemon: running (PID ${daemon2.pid})`);
632
+ } else {
633
+ console.log("Daemon: not running");
634
+ }
635
+ if (daemon2.lastSnapshot && daemon2.lastSnapshotAgeMs !== null) {
636
+ const age = Math.round(daemon2.lastSnapshotAgeMs / 1e3);
637
+ const ageStr = age < 60 ? `${age}s` : age < 3600 ? `${Math.round(age / 60)}m` : `${Math.round(age / 3600)}h`;
638
+ console.log(`Latest: ${ageStr} ago`);
639
+ console.log(` 5h: ${daemon2.lastSnapshot.five_hour.utilization?.toFixed(1) ?? "\u2014"}%`);
640
+ console.log(` 7d: ${daemon2.lastSnapshot.seven_day.utilization?.toFixed(1) ?? "\u2014"}%`);
641
+ } else {
642
+ console.log("Latest: no usage snapshot yet");
643
+ }
644
+ }
645
+ var init_status = __esm({
646
+ "src/commands/status.ts"() {
647
+ "use strict";
648
+ init_server();
649
+ init_daemon();
315
650
  }
316
651
  });
317
652
 
@@ -325,9 +660,9 @@ async function web(args2) {
325
660
  const positional = args2.filter((a) => !a.startsWith("--"));
326
661
  const rawPath = positional[0] ?? "";
327
662
  const path2 = rawPath.startsWith("/") ? rawPath : rawPath ? `/${rawPath}` : "";
328
- const status = getServerStatus();
329
- if (status.running) {
330
- const url = `http://localhost:${status.port}${path2}`;
663
+ const status2 = getServerStatus();
664
+ if (status2.running) {
665
+ const url = `http://localhost:${status2.port}${path2}`;
331
666
  console.log(noOpen ? `Server running at ${url}` : `Opening ${url}`);
332
667
  if (!noOpen) openBrowser(url);
333
668
  return;
@@ -998,8 +1333,8 @@ async function printStats(sinceDate) {
998
1333
  const output = renderTable(rows, "Claude Code Token Usage Report \u2014 Daily");
999
1334
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1000
1335
  const todaySessions = filtered.filter((s) => s.firstTimestamp?.startsWith(today)).length;
1001
- const status = getServerStatus();
1002
- const serverLine = status.running ? `Server running on http://localhost:${status.port}` : "Server not running";
1336
+ const status2 = getServerStatus();
1337
+ const serverLine = status2.running ? `Server running on http://localhost:${status2.port}` : "Server not running";
1003
1338
  console.log(output);
1004
1339
  console.log(` ${todaySessions} sessions today \xB7 ${serverLine}`);
1005
1340
  console.log("");
@@ -1087,9 +1422,9 @@ var init_stats = __esm({
1087
1422
 
1088
1423
  // src/usage/token.ts
1089
1424
  import { execFileSync } from "node:child_process";
1090
- import { readFileSync as readFileSync2 } from "node:fs";
1091
- import { homedir as homedir2, platform } from "node:os";
1092
- import { join as join2 } from "node:path";
1425
+ import { readFileSync as readFileSync5 } from "node:fs";
1426
+ import { homedir as homedir3, platform } from "node:os";
1427
+ import { join as join4 } from "node:path";
1093
1428
  function readOAuthToken() {
1094
1429
  if (platform() === "darwin") {
1095
1430
  return readFromMacKeychain() ?? readFromCredentialsFile();
@@ -1110,12 +1445,12 @@ function readFromMacKeychain() {
1110
1445
  }
1111
1446
  function readFromCredentialsFile() {
1112
1447
  const candidates = [
1113
- join2(homedir2(), ".claude", ".credentials.json"),
1114
- join2(homedir2(), ".config", "claude", "credentials.json")
1448
+ join4(homedir3(), ".claude", ".credentials.json"),
1449
+ join4(homedir3(), ".config", "claude", "credentials.json")
1115
1450
  ];
1116
1451
  for (const path2 of candidates) {
1117
1452
  try {
1118
- const blob = readFileSync2(path2, "utf8");
1453
+ const blob = readFileSync5(path2, "utf8");
1119
1454
  const token = extractToken(blob);
1120
1455
  if (token) return token;
1121
1456
  } catch {
@@ -1309,48 +1644,18 @@ var init_format = __esm({
1309
1644
  }
1310
1645
  });
1311
1646
 
1312
- // src/usage/storage.ts
1313
- import { appendFileSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, existsSync as existsSync2 } from "node:fs";
1314
- import { dirname as dirname3 } from "node:path";
1315
- function appendSnapshot(filePath, snapshot) {
1316
- mkdirSync2(dirname3(filePath), { recursive: true });
1317
- appendFileSync(filePath, JSON.stringify(snapshot) + "\n", "utf8");
1318
- }
1319
- function readSnapshots(filePath) {
1320
- if (!existsSync2(filePath)) return [];
1321
- const content = readFileSync3(filePath, "utf8");
1322
- const snapshots = [];
1323
- for (const line of content.split("\n")) {
1324
- if (!line.trim()) continue;
1325
- try {
1326
- snapshots.push(JSON.parse(line));
1327
- } catch {
1328
- }
1329
- }
1330
- return snapshots;
1331
- }
1332
- function latestSnapshot(filePath) {
1333
- const all = readSnapshots(filePath);
1334
- return all.length > 0 ? all[all.length - 1] : null;
1335
- }
1336
- var init_storage = __esm({
1337
- "src/usage/storage.ts"() {
1338
- "use strict";
1339
- }
1340
- });
1341
-
1342
1647
  // src/commands/usage.ts
1343
1648
  var usage_exports = {};
1344
1649
  __export(usage_exports, {
1345
1650
  usage: () => usage
1346
1651
  });
1347
- import { join as join3 } from "node:path";
1348
- import { homedir as homedir3 } from "node:os";
1652
+ import { join as join5 } from "node:path";
1653
+ import { homedir as homedir4 } from "node:os";
1349
1654
  async function usage(args2) {
1350
1655
  const save = args2.includes("--save");
1351
1656
  try {
1352
1657
  const snapshot = await fetchUsage();
1353
- if (save) appendSnapshot(USAGE_LOG, snapshot);
1658
+ if (save) appendSnapshot(USAGE_LOG2, snapshot);
1354
1659
  process.stdout.write(formatUsage(snapshot));
1355
1660
  } catch (err) {
1356
1661
  if (err instanceof UsageApiError) {
@@ -1360,131 +1665,14 @@ async function usage(args2) {
1360
1665
  throw err;
1361
1666
  }
1362
1667
  }
1363
- var USAGE_LOG;
1668
+ var USAGE_LOG2;
1364
1669
  var init_usage = __esm({
1365
1670
  "src/commands/usage.ts"() {
1366
1671
  "use strict";
1367
1672
  init_api();
1368
1673
  init_format();
1369
1674
  init_storage();
1370
- USAGE_LOG = join3(homedir3(), ".cclens", "usage.jsonl");
1371
- }
1372
- });
1373
-
1374
- // src/commands/daemon.ts
1375
- var daemon_exports = {};
1376
- __export(daemon_exports, {
1377
- daemon: () => daemon
1378
- });
1379
- import { spawn as spawn2 } from "node:child_process";
1380
- import { join as join4, dirname as dirname4 } from "node:path";
1381
- import { fileURLToPath as fileURLToPath2 } from "node:url";
1382
- import { existsSync as existsSync3, statSync, readFileSync as readFileSync4 } from "node:fs";
1383
- import { homedir as homedir4 } from "node:os";
1384
- function workerPath() {
1385
- const here = dirname4(fileURLToPath2(import.meta.url));
1386
- return join4(here, "daemon-worker.js");
1387
- }
1388
- async function daemon(args2) {
1389
- const sub = args2[0] ?? "status";
1390
- switch (sub) {
1391
- case "start":
1392
- await daemonStart();
1393
- break;
1394
- case "stop":
1395
- daemonStop();
1396
- break;
1397
- case "status":
1398
- daemonStatus();
1399
- break;
1400
- case "logs":
1401
- daemonLogs();
1402
- break;
1403
- default:
1404
- console.error(`Unknown daemon subcommand: ${sub}`);
1405
- console.error("Usage: fleetlens daemon <start|stop|status|logs>");
1406
- process.exit(1);
1407
- }
1408
- }
1409
- async function daemonStart() {
1410
- cleanStalePid(DAEMON_PID);
1411
- const existing = readPid(DAEMON_PID);
1412
- if (existing !== null && isProcessAlive(existing.pid)) {
1413
- console.log(`Daemon is already running (PID ${existing.pid})`);
1414
- return;
1415
- }
1416
- const script = workerPath();
1417
- if (!existsSync3(script)) {
1418
- console.error(`Daemon worker not found at ${script}. Rebuild with: pnpm -F fleetlens build`);
1419
- process.exit(1);
1420
- }
1421
- const child = spawn2(process.execPath, [script], {
1422
- detached: true,
1423
- stdio: "ignore"
1424
- });
1425
- child.unref();
1426
- const pid = child.pid;
1427
- writePid(DAEMON_PID, pid);
1428
- console.log(`Daemon started (PID ${pid})`);
1429
- console.log(`Polling every 5 minutes. Logs: ${DAEMON_LOG}`);
1430
- }
1431
- function daemonStop() {
1432
- cleanStalePid(DAEMON_PID);
1433
- const entry = readPid(DAEMON_PID);
1434
- if (entry === null) {
1435
- console.log("Daemon is not running.");
1436
- return;
1437
- }
1438
- try {
1439
- process.kill(entry.pid, "SIGTERM");
1440
- } catch {
1441
- }
1442
- removePid(DAEMON_PID);
1443
- console.log(`Stopped daemon (PID ${entry.pid})`);
1444
- }
1445
- function daemonStatus() {
1446
- cleanStalePid(DAEMON_PID);
1447
- const entry = readPid(DAEMON_PID);
1448
- const running = entry !== null && isProcessAlive(entry.pid);
1449
- if (running) {
1450
- console.log(`Daemon: running (PID ${entry.pid})`);
1451
- } else {
1452
- console.log("Daemon: not running");
1453
- }
1454
- const latest = latestSnapshot(USAGE_LOG2);
1455
- if (latest) {
1456
- const age = Math.round((Date.now() - new Date(latest.captured_at).getTime()) / 1e3);
1457
- const ageStr = age < 60 ? `${age}s` : age < 3600 ? `${Math.round(age / 60)}m` : `${Math.round(age / 3600)}h`;
1458
- console.log(`Last snapshot: ${ageStr} ago`);
1459
- console.log(` 5h: ${latest.five_hour.utilization?.toFixed(1) ?? "\u2014"}%`);
1460
- console.log(` 7d: ${latest.seven_day.utilization?.toFixed(1) ?? "\u2014"}%`);
1461
- } else {
1462
- console.log("Last snapshot: none yet");
1463
- }
1464
- if (existsSync3(USAGE_LOG2)) {
1465
- const size = statSync(USAGE_LOG2).size;
1466
- console.log(`Usage log: ${USAGE_LOG2} (${size} bytes)`);
1467
- }
1468
- }
1469
- function daemonLogs() {
1470
- if (!existsSync3(DAEMON_LOG)) {
1471
- console.log("No daemon logs yet.");
1472
- return;
1473
- }
1474
- const content = readFileSync4(DAEMON_LOG, "utf8");
1475
- const lines = content.trim().split("\n").slice(-20);
1476
- for (const line of lines) process.stdout.write(line + "\n");
1477
- }
1478
- var STATE_DIR2, DAEMON_PID, USAGE_LOG2, DAEMON_LOG;
1479
- var init_daemon = __esm({
1480
- "src/commands/daemon.ts"() {
1481
- "use strict";
1482
- init_pid();
1483
- init_storage();
1484
- STATE_DIR2 = join4(homedir4(), ".cclens");
1485
- DAEMON_PID = join4(STATE_DIR2, "daemon.pid");
1486
- USAGE_LOG2 = join4(STATE_DIR2, "usage.jsonl");
1487
- DAEMON_LOG = join4(STATE_DIR2, "daemon.log");
1675
+ USAGE_LOG2 = join5(homedir4(), ".cclens", "usage.jsonl");
1488
1676
  }
1489
1677
  });
1490
1678
 
@@ -1503,6 +1691,11 @@ async function main() {
1503
1691
  await stop2();
1504
1692
  break;
1505
1693
  }
1694
+ case "status": {
1695
+ const { status: status2 } = await Promise.resolve().then(() => (init_status(), status_exports));
1696
+ await status2();
1697
+ break;
1698
+ }
1506
1699
  case "web": {
1507
1700
  const { web: web2 } = await Promise.resolve().then(() => (init_web(), web_exports));
1508
1701
  await web2(args.slice(1));
@@ -1531,21 +1724,28 @@ async function main() {
1531
1724
  case "version":
1532
1725
  case "--version":
1533
1726
  case "-v":
1534
- console.log(`fleetlens ${"0.1.3"}`);
1727
+ console.log(`fleetlens ${"0.2.1"}`);
1535
1728
  break;
1536
1729
  case "help":
1537
1730
  case "--help":
1538
1731
  case "-h":
1539
1732
  console.log(`Usage: fleetlens <command>
1540
1733
 
1541
- Commands:
1542
- start [--port N] [--no-open] Start the dashboard server
1543
- stop Stop the dashboard server
1544
- web [page] [--no-open] Open dashboard in browser (e.g. 'web usage')
1734
+ Common:
1735
+ start [--port N] [--no-open] Start dashboard + usage daemon
1736
+ stop Stop dashboard + usage daemon
1737
+ status Show server + daemon + latest snapshot
1545
1738
  update Update to the latest version
1546
- stats [--live] [-s D] [--days N] Show token usage statistics
1547
- usage [--save] Show Claude Code plan utilization (5h/7d)
1548
- daemon <start|stop|status|logs> Background poller for usage metrics
1739
+
1740
+ Terminal:
1741
+ stats [--live] [-s D] [--days N] Daily token usage table
1742
+ usage [--save] Plan utilization (5h/7d) in one shot
1743
+
1744
+ Advanced:
1745
+ web [page] [--no-open] Open dashboard in browser without auto-starting daemon
1746
+ start --no-daemon Start only the web server (no daemon)
1747
+ daemon <start|stop|status|logs> Manage the usage daemon by itself
1748
+
1549
1749
  version Print version`);
1550
1750
  break;
1551
1751
  default: