fleetlens 0.1.3 → 0.2.0

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 +306 -195
  31. package/package.json +1 -1
  32. /package/app/apps/web/.next/static/{xYJud5MeHVY9ZFuYH3d2a → yIPyeob0Uqi3suT_D6qZD}/_buildManifest.js +0 -0
  33. /package/app/apps/web/.next/static/{xYJud5MeHVY9ZFuYH3d2a → yIPyeob0Uqi3suT_D6qZD}/_clientMiddlewareManifest.js +0 -0
  34. /package/app/apps/web/.next/static/{xYJud5MeHVY9ZFuYH3d2a → yIPyeob0Uqi3suT_D6qZD}/_ssgManifest.js +0 -0
package/dist/index.js CHANGED
@@ -209,7 +209,7 @@ async function checkForUpdate() {
209
209
  if (process.env.__FLEETLENS_UPDATED === "1" || process.env.__CCLENS_UPDATED === "1") return;
210
210
  const latest = await fetchLatestVersion();
211
211
  if (latest === null) return;
212
- const current = "0.1.3";
212
+ const current = "0.2.0";
213
213
  if (!shouldUpdate(current, latest)) return;
214
214
  console.log(`Updating ${PACKAGE_NAME} ${current} \u2192 ${latest}...`);
215
215
  const ok = runNpmInstall();
@@ -226,7 +226,7 @@ async function checkForUpdate() {
226
226
  }
227
227
  async function forceUpdate() {
228
228
  const latest = await fetchLatestVersion();
229
- const current = "0.1.3";
229
+ const current = "0.2.0";
230
230
  if (latest === null) {
231
231
  console.error("Could not reach npm registry. Check your network.");
232
232
  process.exit(1);
@@ -253,6 +253,187 @@ var init_updater = __esm({
253
253
  }
254
254
  });
255
255
 
256
+ // src/usage/storage.ts
257
+ import { appendFileSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
258
+ import { dirname as dirname3 } from "node:path";
259
+ function appendSnapshot(filePath, snapshot) {
260
+ mkdirSync2(dirname3(filePath), { recursive: true });
261
+ appendFileSync(filePath, JSON.stringify(snapshot) + "\n", "utf8");
262
+ }
263
+ function readSnapshots(filePath) {
264
+ if (!existsSync2(filePath)) return [];
265
+ const content = readFileSync2(filePath, "utf8");
266
+ const snapshots = [];
267
+ for (const line of content.split("\n")) {
268
+ if (!line.trim()) continue;
269
+ try {
270
+ snapshots.push(JSON.parse(line));
271
+ } catch {
272
+ }
273
+ }
274
+ return snapshots;
275
+ }
276
+ function latestSnapshot(filePath) {
277
+ const all = readSnapshots(filePath);
278
+ return all.length > 0 ? all[all.length - 1] : null;
279
+ }
280
+ var init_storage = __esm({
281
+ "src/usage/storage.ts"() {
282
+ "use strict";
283
+ }
284
+ });
285
+
286
+ // src/commands/daemon.ts
287
+ var daemon_exports = {};
288
+ __export(daemon_exports, {
289
+ daemon: () => daemon,
290
+ getDaemonStatusInfo: () => getDaemonStatusInfo,
291
+ startDaemonSilent: () => startDaemonSilent,
292
+ stopDaemonSilent: () => stopDaemonSilent
293
+ });
294
+ import { spawn as spawn2 } from "node:child_process";
295
+ import { join as join2, dirname as dirname4 } from "node:path";
296
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
297
+ import { existsSync as existsSync3, statSync, readFileSync as readFileSync3 } from "node:fs";
298
+ import { homedir as homedir2 } from "node:os";
299
+ function startDaemonSilent() {
300
+ cleanStalePid(DAEMON_PID);
301
+ const existing = readPid(DAEMON_PID);
302
+ if (existing !== null && isProcessAlive(existing.pid)) {
303
+ return { started: false, pid: existing.pid, alreadyRunning: true };
304
+ }
305
+ const script = workerPath();
306
+ if (!existsSync3(script)) {
307
+ return {
308
+ started: false,
309
+ pid: null,
310
+ alreadyRunning: false,
311
+ error: `Daemon worker not found at ${script}. Rebuild with: pnpm -F fleetlens build`
312
+ };
313
+ }
314
+ const child = spawn2(process.execPath, [script], {
315
+ detached: true,
316
+ stdio: "ignore"
317
+ });
318
+ child.unref();
319
+ const pid = child.pid;
320
+ writePid(DAEMON_PID, pid);
321
+ return { started: true, pid, alreadyRunning: false };
322
+ }
323
+ function stopDaemonSilent() {
324
+ cleanStalePid(DAEMON_PID);
325
+ const entry = readPid(DAEMON_PID);
326
+ if (entry === null) return { stopped: false, pid: null };
327
+ try {
328
+ process.kill(entry.pid, "SIGTERM");
329
+ } catch {
330
+ }
331
+ removePid(DAEMON_PID);
332
+ return { stopped: true, pid: entry.pid };
333
+ }
334
+ function getDaemonStatusInfo() {
335
+ cleanStalePid(DAEMON_PID);
336
+ const entry = readPid(DAEMON_PID);
337
+ const running = entry !== null && isProcessAlive(entry.pid);
338
+ const latest = latestSnapshot(USAGE_LOG);
339
+ const lastSnapshotAgeMs = latest ? Date.now() - new Date(latest.captured_at).getTime() : null;
340
+ const usageLogSize = existsSync3(USAGE_LOG) ? statSync(USAGE_LOG).size : null;
341
+ return {
342
+ running,
343
+ pid: running ? entry.pid : null,
344
+ lastSnapshotAgeMs,
345
+ lastSnapshot: latest,
346
+ usageLogSize
347
+ };
348
+ }
349
+ function workerPath() {
350
+ const here = dirname4(fileURLToPath2(import.meta.url));
351
+ return join2(here, "daemon-worker.js");
352
+ }
353
+ async function daemon(args2) {
354
+ const sub = args2[0] ?? "status";
355
+ switch (sub) {
356
+ case "start":
357
+ await daemonStart();
358
+ break;
359
+ case "stop":
360
+ daemonStop();
361
+ break;
362
+ case "status":
363
+ daemonStatus();
364
+ break;
365
+ case "logs":
366
+ daemonLogs();
367
+ break;
368
+ default:
369
+ console.error(`Unknown daemon subcommand: ${sub}`);
370
+ console.error("Usage: fleetlens daemon <start|stop|status|logs>");
371
+ process.exit(1);
372
+ }
373
+ }
374
+ async function daemonStart() {
375
+ const result = startDaemonSilent();
376
+ if (result.alreadyRunning) {
377
+ console.log(`Daemon is already running (PID ${result.pid})`);
378
+ return;
379
+ }
380
+ if (!result.started) {
381
+ console.error(result.error);
382
+ process.exit(1);
383
+ }
384
+ console.log(`Daemon started (PID ${result.pid})`);
385
+ console.log(`Polling every 5 minutes. Logs: ${DAEMON_LOG}`);
386
+ }
387
+ function daemonStop() {
388
+ const result = stopDaemonSilent();
389
+ if (!result.stopped) {
390
+ console.log("Daemon is not running.");
391
+ return;
392
+ }
393
+ console.log(`Stopped daemon (PID ${result.pid})`);
394
+ }
395
+ function daemonStatus() {
396
+ const info = getDaemonStatusInfo();
397
+ if (info.running) {
398
+ console.log(`Daemon: running (PID ${info.pid})`);
399
+ } else {
400
+ console.log("Daemon: not running");
401
+ }
402
+ if (info.lastSnapshot && info.lastSnapshotAgeMs !== null) {
403
+ const age = Math.round(info.lastSnapshotAgeMs / 1e3);
404
+ const ageStr = age < 60 ? `${age}s` : age < 3600 ? `${Math.round(age / 60)}m` : `${Math.round(age / 3600)}h`;
405
+ console.log(`Last snapshot: ${ageStr} ago`);
406
+ console.log(` 5h: ${info.lastSnapshot.five_hour.utilization?.toFixed(1) ?? "\u2014"}%`);
407
+ console.log(` 7d: ${info.lastSnapshot.seven_day.utilization?.toFixed(1) ?? "\u2014"}%`);
408
+ } else {
409
+ console.log("Last snapshot: none yet");
410
+ }
411
+ if (info.usageLogSize !== null) {
412
+ console.log(`Usage log: ${USAGE_LOG} (${info.usageLogSize} bytes)`);
413
+ }
414
+ }
415
+ function daemonLogs() {
416
+ if (!existsSync3(DAEMON_LOG)) {
417
+ console.log("No daemon logs yet.");
418
+ return;
419
+ }
420
+ const content = readFileSync3(DAEMON_LOG, "utf8");
421
+ const lines = content.trim().split("\n").slice(-20);
422
+ for (const line of lines) process.stdout.write(line + "\n");
423
+ }
424
+ var STATE_DIR2, DAEMON_PID, USAGE_LOG, DAEMON_LOG;
425
+ var init_daemon = __esm({
426
+ "src/commands/daemon.ts"() {
427
+ "use strict";
428
+ init_pid();
429
+ init_storage();
430
+ STATE_DIR2 = join2(homedir2(), ".cclens");
431
+ DAEMON_PID = join2(STATE_DIR2, "daemon.pid");
432
+ USAGE_LOG = join2(STATE_DIR2, "usage.jsonl");
433
+ DAEMON_LOG = join2(STATE_DIR2, "daemon.log");
434
+ }
435
+ });
436
+
256
437
  // src/commands/start.ts
257
438
  var start_exports = {};
258
439
  __export(start_exports, {
@@ -262,33 +443,48 @@ async function start(args2) {
262
443
  const portFlag = args2.indexOf("--port");
263
444
  const port = portFlag !== -1 ? parseInt(args2[portFlag + 1], 10) : void 0;
264
445
  const noOpen = args2.includes("--no-open");
446
+ const noDaemon = args2.includes("--no-daemon");
265
447
  try {
266
448
  await checkForUpdate();
267
449
  } catch {
268
450
  }
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;
451
+ const status2 = getServerStatus();
452
+ let serverUrl;
453
+ let serverPid;
454
+ if (status2.running) {
455
+ serverUrl = `http://localhost:${status2.port}`;
456
+ serverPid = status2.pid;
457
+ console.log(`Server: already running on ${serverUrl} (PID ${serverPid})`);
458
+ } else {
459
+ console.log("Starting Fleetlens...");
460
+ try {
461
+ const result = await startServer({ port });
462
+ serverUrl = `http://localhost:${result.port}`;
463
+ serverPid = result.pid;
464
+ console.log(`Server: started on ${serverUrl} (PID ${serverPid})`);
465
+ } catch (err) {
466
+ console.error(`Failed to start server: ${err.message}`);
467
+ process.exit(1);
468
+ }
275
469
  }
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);
470
+ if (!noDaemon) {
471
+ const daemon2 = startDaemonSilent();
472
+ if (daemon2.alreadyRunning) {
473
+ console.log(`Daemon: already running (PID ${daemon2.pid})`);
474
+ } else if (daemon2.started) {
475
+ console.log(`Daemon: started (PID ${daemon2.pid})`);
476
+ } else {
477
+ console.warn(`Daemon: failed to start \u2014 ${daemon2.error}`);
478
+ }
285
479
  }
480
+ if (!noOpen) openBrowser(serverUrl);
286
481
  }
287
482
  var init_start = __esm({
288
483
  "src/commands/start.ts"() {
289
484
  "use strict";
290
485
  init_server();
291
486
  init_updater();
487
+ init_daemon();
292
488
  }
293
489
  });
294
490
 
@@ -298,20 +494,70 @@ __export(stop_exports, {
298
494
  stop: () => stop
299
495
  });
300
496
  async function stop() {
301
- const status = getServerStatus();
302
- if (!status.running) {
303
- console.log("fleetlens is not running.");
304
- return;
497
+ let didSomething = false;
498
+ const status2 = getServerStatus();
499
+ if (status2.running) {
500
+ const result = stopServer();
501
+ if (result.stopped) {
502
+ console.log(`Server: stopped (PID ${result.pid})`);
503
+ didSomething = true;
504
+ }
505
+ } else {
506
+ console.log("Server: not running");
305
507
  }
306
- const result = stopServer();
307
- if (result.stopped) {
308
- console.log(`Stopped fleetlens (PID ${result.pid})`);
508
+ const daemon2 = stopDaemonSilent();
509
+ if (daemon2.stopped) {
510
+ console.log(`Daemon: stopped (PID ${daemon2.pid})`);
511
+ didSomething = true;
512
+ } else {
513
+ console.log("Daemon: not running");
514
+ }
515
+ if (!didSomething) {
516
+ console.log("Fleetlens is not running.");
309
517
  }
310
518
  }
311
519
  var init_stop = __esm({
312
520
  "src/commands/stop.ts"() {
313
521
  "use strict";
314
522
  init_server();
523
+ init_daemon();
524
+ }
525
+ });
526
+
527
+ // src/commands/status.ts
528
+ var status_exports = {};
529
+ __export(status_exports, {
530
+ status: () => status
531
+ });
532
+ async function status() {
533
+ const server = getServerStatus();
534
+ if (server.running) {
535
+ const url = `http://localhost:${server.port}`;
536
+ console.log(`Server: running on ${url} (PID ${server.pid})`);
537
+ } else {
538
+ console.log("Server: not running");
539
+ }
540
+ const daemon2 = getDaemonStatusInfo();
541
+ if (daemon2.running) {
542
+ console.log(`Daemon: running (PID ${daemon2.pid})`);
543
+ } else {
544
+ console.log("Daemon: not running");
545
+ }
546
+ if (daemon2.lastSnapshot && daemon2.lastSnapshotAgeMs !== null) {
547
+ const age = Math.round(daemon2.lastSnapshotAgeMs / 1e3);
548
+ const ageStr = age < 60 ? `${age}s` : age < 3600 ? `${Math.round(age / 60)}m` : `${Math.round(age / 3600)}h`;
549
+ console.log(`Latest: ${ageStr} ago`);
550
+ console.log(` 5h: ${daemon2.lastSnapshot.five_hour.utilization?.toFixed(1) ?? "\u2014"}%`);
551
+ console.log(` 7d: ${daemon2.lastSnapshot.seven_day.utilization?.toFixed(1) ?? "\u2014"}%`);
552
+ } else {
553
+ console.log("Latest: no usage snapshot yet");
554
+ }
555
+ }
556
+ var init_status = __esm({
557
+ "src/commands/status.ts"() {
558
+ "use strict";
559
+ init_server();
560
+ init_daemon();
315
561
  }
316
562
  });
317
563
 
@@ -325,9 +571,9 @@ async function web(args2) {
325
571
  const positional = args2.filter((a) => !a.startsWith("--"));
326
572
  const rawPath = positional[0] ?? "";
327
573
  const path2 = rawPath.startsWith("/") ? rawPath : rawPath ? `/${rawPath}` : "";
328
- const status = getServerStatus();
329
- if (status.running) {
330
- const url = `http://localhost:${status.port}${path2}`;
574
+ const status2 = getServerStatus();
575
+ if (status2.running) {
576
+ const url = `http://localhost:${status2.port}${path2}`;
331
577
  console.log(noOpen ? `Server running at ${url}` : `Opening ${url}`);
332
578
  if (!noOpen) openBrowser(url);
333
579
  return;
@@ -998,8 +1244,8 @@ async function printStats(sinceDate) {
998
1244
  const output = renderTable(rows, "Claude Code Token Usage Report \u2014 Daily");
999
1245
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1000
1246
  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";
1247
+ const status2 = getServerStatus();
1248
+ const serverLine = status2.running ? `Server running on http://localhost:${status2.port}` : "Server not running";
1003
1249
  console.log(output);
1004
1250
  console.log(` ${todaySessions} sessions today \xB7 ${serverLine}`);
1005
1251
  console.log("");
@@ -1087,9 +1333,9 @@ var init_stats = __esm({
1087
1333
 
1088
1334
  // src/usage/token.ts
1089
1335
  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";
1336
+ import { readFileSync as readFileSync4 } from "node:fs";
1337
+ import { homedir as homedir3, platform } from "node:os";
1338
+ import { join as join3 } from "node:path";
1093
1339
  function readOAuthToken() {
1094
1340
  if (platform() === "darwin") {
1095
1341
  return readFromMacKeychain() ?? readFromCredentialsFile();
@@ -1110,12 +1356,12 @@ function readFromMacKeychain() {
1110
1356
  }
1111
1357
  function readFromCredentialsFile() {
1112
1358
  const candidates = [
1113
- join2(homedir2(), ".claude", ".credentials.json"),
1114
- join2(homedir2(), ".config", "claude", "credentials.json")
1359
+ join3(homedir3(), ".claude", ".credentials.json"),
1360
+ join3(homedir3(), ".config", "claude", "credentials.json")
1115
1361
  ];
1116
1362
  for (const path2 of candidates) {
1117
1363
  try {
1118
- const blob = readFileSync2(path2, "utf8");
1364
+ const blob = readFileSync4(path2, "utf8");
1119
1365
  const token = extractToken(blob);
1120
1366
  if (token) return token;
1121
1367
  } catch {
@@ -1309,48 +1555,18 @@ var init_format = __esm({
1309
1555
  }
1310
1556
  });
1311
1557
 
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
1558
  // src/commands/usage.ts
1343
1559
  var usage_exports = {};
1344
1560
  __export(usage_exports, {
1345
1561
  usage: () => usage
1346
1562
  });
1347
- import { join as join3 } from "node:path";
1348
- import { homedir as homedir3 } from "node:os";
1563
+ import { join as join4 } from "node:path";
1564
+ import { homedir as homedir4 } from "node:os";
1349
1565
  async function usage(args2) {
1350
1566
  const save = args2.includes("--save");
1351
1567
  try {
1352
1568
  const snapshot = await fetchUsage();
1353
- if (save) appendSnapshot(USAGE_LOG, snapshot);
1569
+ if (save) appendSnapshot(USAGE_LOG2, snapshot);
1354
1570
  process.stdout.write(formatUsage(snapshot));
1355
1571
  } catch (err) {
1356
1572
  if (err instanceof UsageApiError) {
@@ -1360,131 +1576,14 @@ async function usage(args2) {
1360
1576
  throw err;
1361
1577
  }
1362
1578
  }
1363
- var USAGE_LOG;
1579
+ var USAGE_LOG2;
1364
1580
  var init_usage = __esm({
1365
1581
  "src/commands/usage.ts"() {
1366
1582
  "use strict";
1367
1583
  init_api();
1368
1584
  init_format();
1369
1585
  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");
1586
+ USAGE_LOG2 = join4(homedir4(), ".cclens", "usage.jsonl");
1488
1587
  }
1489
1588
  });
1490
1589
 
@@ -1503,6 +1602,11 @@ async function main() {
1503
1602
  await stop2();
1504
1603
  break;
1505
1604
  }
1605
+ case "status": {
1606
+ const { status: status2 } = await Promise.resolve().then(() => (init_status(), status_exports));
1607
+ await status2();
1608
+ break;
1609
+ }
1506
1610
  case "web": {
1507
1611
  const { web: web2 } = await Promise.resolve().then(() => (init_web(), web_exports));
1508
1612
  await web2(args.slice(1));
@@ -1531,21 +1635,28 @@ async function main() {
1531
1635
  case "version":
1532
1636
  case "--version":
1533
1637
  case "-v":
1534
- console.log(`fleetlens ${"0.1.3"}`);
1638
+ console.log(`fleetlens ${"0.2.0"}`);
1535
1639
  break;
1536
1640
  case "help":
1537
1641
  case "--help":
1538
1642
  case "-h":
1539
1643
  console.log(`Usage: fleetlens <command>
1540
1644
 
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')
1645
+ Common:
1646
+ start [--port N] [--no-open] Start dashboard + usage daemon
1647
+ stop Stop dashboard + usage daemon
1648
+ status Show server + daemon + latest snapshot
1545
1649
  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
1650
+
1651
+ Terminal:
1652
+ stats [--live] [-s D] [--days N] Daily token usage table
1653
+ usage [--save] Plan utilization (5h/7d) in one shot
1654
+
1655
+ Advanced:
1656
+ web [page] [--no-open] Open dashboard in browser without auto-starting daemon
1657
+ start --no-daemon Start only the web server (no daemon)
1658
+ daemon <start|stop|status|logs> Manage the usage daemon by itself
1659
+
1549
1660
  version Print version`);
1550
1661
  break;
1551
1662
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetlens",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "fleetlens — local-only dashboard and usage tracker for Claude Code sessions and agent fleets",
5
5
  "license": "MIT",
6
6
  "type": "module",