fleetlens 0.1.2 → 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 +315 -200
  31. package/package.json +1 -1
  32. /package/app/apps/web/.next/static/{2kdRQ_Qfdyey-Xt31t7FV → yIPyeob0Uqi3suT_D6qZD}/_buildManifest.js +0 -0
  33. /package/app/apps/web/.next/static/{2kdRQ_Qfdyey-Xt31t7FV → yIPyeob0Uqi3suT_D6qZD}/_clientMiddlewareManifest.js +0 -0
  34. /package/app/apps/web/.next/static/{2kdRQ_Qfdyey-Xt31t7FV → yIPyeob0Uqi3suT_D6qZD}/_ssgManifest.js +0 -0
package/dist/index.js CHANGED
@@ -196,7 +196,7 @@ function runNpmInstall() {
196
196
  function reExec() {
197
197
  const result = spawnSync(process.argv[0], process.argv.slice(1), {
198
198
  stdio: "inherit",
199
- env: { ...process.env, __CCLENS_UPDATED: "1" }
199
+ env: { ...process.env, __FLEETLENS_UPDATED: "1" }
200
200
  });
201
201
  process.exit(result.status ?? 0);
202
202
  }
@@ -206,29 +206,33 @@ function isDevMode() {
206
206
  }
207
207
  async function checkForUpdate() {
208
208
  if (isDevMode()) return;
209
- if (process.env.__CCLENS_UPDATED === "1") return;
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.2";
212
+ const current = "0.2.0";
213
213
  if (!shouldUpdate(current, latest)) return;
214
- console.log(`Updating cclens ${current} \u2192 ${latest}...`);
214
+ console.log(`Updating ${PACKAGE_NAME} ${current} \u2192 ${latest}...`);
215
215
  const ok = runNpmInstall();
216
216
  if (ok) {
217
217
  console.log("Updated successfully. Restarting...");
218
218
  reExec();
219
219
  } else {
220
- console.warn("Update failed. Starting with current version.");
220
+ console.warn(
221
+ `Update failed. Starting with current version.
222
+ \u2192 Try manually: npm install -g ${PACKAGE_NAME}@latest
223
+ \u2192 Or with sudo if your global npm prefix needs it.`
224
+ );
221
225
  }
222
226
  }
223
227
  async function forceUpdate() {
224
228
  const latest = await fetchLatestVersion();
225
- const current = "0.1.2";
229
+ const current = "0.2.0";
226
230
  if (latest === null) {
227
231
  console.error("Could not reach npm registry. Check your network.");
228
232
  process.exit(1);
229
233
  }
230
234
  if (shouldUpdate(current, latest)) {
231
- console.log(`Updating cclens ${current} \u2192 ${latest}...`);
235
+ console.log(`Updating ${PACKAGE_NAME} ${current} \u2192 ${latest}...`);
232
236
  } else {
233
237
  console.log(`Already on latest (${current}). Reinstalling...`);
234
238
  }
@@ -249,6 +253,187 @@ var init_updater = __esm({
249
253
  }
250
254
  });
251
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
+
252
437
  // src/commands/start.ts
253
438
  var start_exports = {};
254
439
  __export(start_exports, {
@@ -258,33 +443,48 @@ async function start(args2) {
258
443
  const portFlag = args2.indexOf("--port");
259
444
  const port = portFlag !== -1 ? parseInt(args2[portFlag + 1], 10) : void 0;
260
445
  const noOpen = args2.includes("--no-open");
446
+ const noDaemon = args2.includes("--no-daemon");
261
447
  try {
262
448
  await checkForUpdate();
263
449
  } catch {
264
450
  }
265
- const status = getServerStatus();
266
- if (status.running) {
267
- const url = `http://localhost:${status.port}`;
268
- console.log(`fleetlens is already running on ${url} (PID ${status.pid})`);
269
- if (!noOpen) openBrowser(url);
270
- 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
+ }
271
469
  }
272
- console.log("Starting fleetlens...");
273
- try {
274
- const result = await startServer({ port });
275
- const url = `http://localhost:${result.port}`;
276
- console.log(`fleetlens running on ${url} (PID ${result.pid})`);
277
- if (!noOpen) openBrowser(url);
278
- } catch (err) {
279
- console.error(`Failed to start: ${err.message}`);
280
- 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
+ }
281
479
  }
480
+ if (!noOpen) openBrowser(serverUrl);
282
481
  }
283
482
  var init_start = __esm({
284
483
  "src/commands/start.ts"() {
285
484
  "use strict";
286
485
  init_server();
287
486
  init_updater();
487
+ init_daemon();
288
488
  }
289
489
  });
290
490
 
@@ -294,20 +494,70 @@ __export(stop_exports, {
294
494
  stop: () => stop
295
495
  });
296
496
  async function stop() {
297
- const status = getServerStatus();
298
- if (!status.running) {
299
- console.log("fleetlens is not running.");
300
- 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");
507
+ }
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");
301
514
  }
302
- const result = stopServer();
303
- if (result.stopped) {
304
- console.log(`Stopped fleetlens (PID ${result.pid})`);
515
+ if (!didSomething) {
516
+ console.log("Fleetlens is not running.");
305
517
  }
306
518
  }
307
519
  var init_stop = __esm({
308
520
  "src/commands/stop.ts"() {
309
521
  "use strict";
310
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();
311
561
  }
312
562
  });
313
563
 
@@ -321,9 +571,9 @@ async function web(args2) {
321
571
  const positional = args2.filter((a) => !a.startsWith("--"));
322
572
  const rawPath = positional[0] ?? "";
323
573
  const path2 = rawPath.startsWith("/") ? rawPath : rawPath ? `/${rawPath}` : "";
324
- const status = getServerStatus();
325
- if (status.running) {
326
- const url = `http://localhost:${status.port}${path2}`;
574
+ const status2 = getServerStatus();
575
+ if (status2.running) {
576
+ const url = `http://localhost:${status2.port}${path2}`;
327
577
  console.log(noOpen ? `Server running at ${url}` : `Opening ${url}`);
328
578
  if (!noOpen) openBrowser(url);
329
579
  return;
@@ -994,8 +1244,8 @@ async function printStats(sinceDate) {
994
1244
  const output = renderTable(rows, "Claude Code Token Usage Report \u2014 Daily");
995
1245
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
996
1246
  const todaySessions = filtered.filter((s) => s.firstTimestamp?.startsWith(today)).length;
997
- const status = getServerStatus();
998
- 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";
999
1249
  console.log(output);
1000
1250
  console.log(` ${todaySessions} sessions today \xB7 ${serverLine}`);
1001
1251
  console.log("");
@@ -1083,9 +1333,9 @@ var init_stats = __esm({
1083
1333
 
1084
1334
  // src/usage/token.ts
1085
1335
  import { execFileSync } from "node:child_process";
1086
- import { readFileSync as readFileSync2 } from "node:fs";
1087
- import { homedir as homedir2, platform } from "node:os";
1088
- 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";
1089
1339
  function readOAuthToken() {
1090
1340
  if (platform() === "darwin") {
1091
1341
  return readFromMacKeychain() ?? readFromCredentialsFile();
@@ -1106,12 +1356,12 @@ function readFromMacKeychain() {
1106
1356
  }
1107
1357
  function readFromCredentialsFile() {
1108
1358
  const candidates = [
1109
- join2(homedir2(), ".claude", ".credentials.json"),
1110
- join2(homedir2(), ".config", "claude", "credentials.json")
1359
+ join3(homedir3(), ".claude", ".credentials.json"),
1360
+ join3(homedir3(), ".config", "claude", "credentials.json")
1111
1361
  ];
1112
1362
  for (const path2 of candidates) {
1113
1363
  try {
1114
- const blob = readFileSync2(path2, "utf8");
1364
+ const blob = readFileSync4(path2, "utf8");
1115
1365
  const token = extractToken(blob);
1116
1366
  if (token) return token;
1117
1367
  } catch {
@@ -1305,48 +1555,18 @@ var init_format = __esm({
1305
1555
  }
1306
1556
  });
1307
1557
 
1308
- // src/usage/storage.ts
1309
- import { appendFileSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, existsSync as existsSync2 } from "node:fs";
1310
- import { dirname as dirname3 } from "node:path";
1311
- function appendSnapshot(filePath, snapshot) {
1312
- mkdirSync2(dirname3(filePath), { recursive: true });
1313
- appendFileSync(filePath, JSON.stringify(snapshot) + "\n", "utf8");
1314
- }
1315
- function readSnapshots(filePath) {
1316
- if (!existsSync2(filePath)) return [];
1317
- const content = readFileSync3(filePath, "utf8");
1318
- const snapshots = [];
1319
- for (const line of content.split("\n")) {
1320
- if (!line.trim()) continue;
1321
- try {
1322
- snapshots.push(JSON.parse(line));
1323
- } catch {
1324
- }
1325
- }
1326
- return snapshots;
1327
- }
1328
- function latestSnapshot(filePath) {
1329
- const all = readSnapshots(filePath);
1330
- return all.length > 0 ? all[all.length - 1] : null;
1331
- }
1332
- var init_storage = __esm({
1333
- "src/usage/storage.ts"() {
1334
- "use strict";
1335
- }
1336
- });
1337
-
1338
1558
  // src/commands/usage.ts
1339
1559
  var usage_exports = {};
1340
1560
  __export(usage_exports, {
1341
1561
  usage: () => usage
1342
1562
  });
1343
- import { join as join3 } from "node:path";
1344
- import { homedir as homedir3 } from "node:os";
1563
+ import { join as join4 } from "node:path";
1564
+ import { homedir as homedir4 } from "node:os";
1345
1565
  async function usage(args2) {
1346
1566
  const save = args2.includes("--save");
1347
1567
  try {
1348
1568
  const snapshot = await fetchUsage();
1349
- if (save) appendSnapshot(USAGE_LOG, snapshot);
1569
+ if (save) appendSnapshot(USAGE_LOG2, snapshot);
1350
1570
  process.stdout.write(formatUsage(snapshot));
1351
1571
  } catch (err) {
1352
1572
  if (err instanceof UsageApiError) {
@@ -1356,131 +1576,14 @@ async function usage(args2) {
1356
1576
  throw err;
1357
1577
  }
1358
1578
  }
1359
- var USAGE_LOG;
1579
+ var USAGE_LOG2;
1360
1580
  var init_usage = __esm({
1361
1581
  "src/commands/usage.ts"() {
1362
1582
  "use strict";
1363
1583
  init_api();
1364
1584
  init_format();
1365
1585
  init_storage();
1366
- USAGE_LOG = join3(homedir3(), ".cclens", "usage.jsonl");
1367
- }
1368
- });
1369
-
1370
- // src/commands/daemon.ts
1371
- var daemon_exports = {};
1372
- __export(daemon_exports, {
1373
- daemon: () => daemon
1374
- });
1375
- import { spawn as spawn2 } from "node:child_process";
1376
- import { join as join4, dirname as dirname4 } from "node:path";
1377
- import { fileURLToPath as fileURLToPath2 } from "node:url";
1378
- import { existsSync as existsSync3, statSync, readFileSync as readFileSync4 } from "node:fs";
1379
- import { homedir as homedir4 } from "node:os";
1380
- function workerPath() {
1381
- const here = dirname4(fileURLToPath2(import.meta.url));
1382
- return join4(here, "daemon-worker.js");
1383
- }
1384
- async function daemon(args2) {
1385
- const sub = args2[0] ?? "status";
1386
- switch (sub) {
1387
- case "start":
1388
- await daemonStart();
1389
- break;
1390
- case "stop":
1391
- daemonStop();
1392
- break;
1393
- case "status":
1394
- daemonStatus();
1395
- break;
1396
- case "logs":
1397
- daemonLogs();
1398
- break;
1399
- default:
1400
- console.error(`Unknown daemon subcommand: ${sub}`);
1401
- console.error("Usage: fleetlens daemon <start|stop|status|logs>");
1402
- process.exit(1);
1403
- }
1404
- }
1405
- async function daemonStart() {
1406
- cleanStalePid(DAEMON_PID);
1407
- const existing = readPid(DAEMON_PID);
1408
- if (existing !== null && isProcessAlive(existing.pid)) {
1409
- console.log(`Daemon is already running (PID ${existing.pid})`);
1410
- return;
1411
- }
1412
- const script = workerPath();
1413
- if (!existsSync3(script)) {
1414
- console.error(`Daemon worker not found at ${script}. Rebuild with: pnpm -F fleetlens build`);
1415
- process.exit(1);
1416
- }
1417
- const child = spawn2(process.execPath, [script], {
1418
- detached: true,
1419
- stdio: "ignore"
1420
- });
1421
- child.unref();
1422
- const pid = child.pid;
1423
- writePid(DAEMON_PID, pid);
1424
- console.log(`Daemon started (PID ${pid})`);
1425
- console.log(`Polling every 5 minutes. Logs: ${DAEMON_LOG}`);
1426
- }
1427
- function daemonStop() {
1428
- cleanStalePid(DAEMON_PID);
1429
- const entry = readPid(DAEMON_PID);
1430
- if (entry === null) {
1431
- console.log("Daemon is not running.");
1432
- return;
1433
- }
1434
- try {
1435
- process.kill(entry.pid, "SIGTERM");
1436
- } catch {
1437
- }
1438
- removePid(DAEMON_PID);
1439
- console.log(`Stopped daemon (PID ${entry.pid})`);
1440
- }
1441
- function daemonStatus() {
1442
- cleanStalePid(DAEMON_PID);
1443
- const entry = readPid(DAEMON_PID);
1444
- const running = entry !== null && isProcessAlive(entry.pid);
1445
- if (running) {
1446
- console.log(`Daemon: running (PID ${entry.pid})`);
1447
- } else {
1448
- console.log("Daemon: not running");
1449
- }
1450
- const latest = latestSnapshot(USAGE_LOG2);
1451
- if (latest) {
1452
- const age = Math.round((Date.now() - new Date(latest.captured_at).getTime()) / 1e3);
1453
- const ageStr = age < 60 ? `${age}s` : age < 3600 ? `${Math.round(age / 60)}m` : `${Math.round(age / 3600)}h`;
1454
- console.log(`Last snapshot: ${ageStr} ago`);
1455
- console.log(` 5h: ${latest.five_hour.utilization?.toFixed(1) ?? "\u2014"}%`);
1456
- console.log(` 7d: ${latest.seven_day.utilization?.toFixed(1) ?? "\u2014"}%`);
1457
- } else {
1458
- console.log("Last snapshot: none yet");
1459
- }
1460
- if (existsSync3(USAGE_LOG2)) {
1461
- const size = statSync(USAGE_LOG2).size;
1462
- console.log(`Usage log: ${USAGE_LOG2} (${size} bytes)`);
1463
- }
1464
- }
1465
- function daemonLogs() {
1466
- if (!existsSync3(DAEMON_LOG)) {
1467
- console.log("No daemon logs yet.");
1468
- return;
1469
- }
1470
- const content = readFileSync4(DAEMON_LOG, "utf8");
1471
- const lines = content.trim().split("\n").slice(-20);
1472
- for (const line of lines) process.stdout.write(line + "\n");
1473
- }
1474
- var STATE_DIR2, DAEMON_PID, USAGE_LOG2, DAEMON_LOG;
1475
- var init_daemon = __esm({
1476
- "src/commands/daemon.ts"() {
1477
- "use strict";
1478
- init_pid();
1479
- init_storage();
1480
- STATE_DIR2 = join4(homedir4(), ".cclens");
1481
- DAEMON_PID = join4(STATE_DIR2, "daemon.pid");
1482
- USAGE_LOG2 = join4(STATE_DIR2, "usage.jsonl");
1483
- DAEMON_LOG = join4(STATE_DIR2, "daemon.log");
1586
+ USAGE_LOG2 = join4(homedir4(), ".cclens", "usage.jsonl");
1484
1587
  }
1485
1588
  });
1486
1589
 
@@ -1499,6 +1602,11 @@ async function main() {
1499
1602
  await stop2();
1500
1603
  break;
1501
1604
  }
1605
+ case "status": {
1606
+ const { status: status2 } = await Promise.resolve().then(() => (init_status(), status_exports));
1607
+ await status2();
1608
+ break;
1609
+ }
1502
1610
  case "web": {
1503
1611
  const { web: web2 } = await Promise.resolve().then(() => (init_web(), web_exports));
1504
1612
  await web2(args.slice(1));
@@ -1527,21 +1635,28 @@ async function main() {
1527
1635
  case "version":
1528
1636
  case "--version":
1529
1637
  case "-v":
1530
- console.log(`fleetlens ${"0.1.2"}`);
1638
+ console.log(`fleetlens ${"0.2.0"}`);
1531
1639
  break;
1532
1640
  case "help":
1533
1641
  case "--help":
1534
1642
  case "-h":
1535
1643
  console.log(`Usage: fleetlens <command>
1536
1644
 
1537
- Commands:
1538
- start [--port N] [--no-open] Start the dashboard server
1539
- stop Stop the dashboard server
1540
- 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
1541
1649
  update Update to the latest version
1542
- stats [--live] [-s D] [--days N] Show token usage statistics
1543
- usage [--save] Show Claude Code plan utilization (5h/7d)
1544
- 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
+
1545
1660
  version Print version`);
1546
1661
  break;
1547
1662
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetlens",
3
- "version": "0.1.2",
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",