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.
- package/app/apps/web/.next/BUILD_ID +1 -1
- package/app/apps/web/.next/build-manifest.json +3 -3
- package/app/apps/web/.next/prerender-manifest.json +3 -3
- package/app/apps/web/.next/server/app/_global-error.html +1 -1
- package/app/apps/web/.next/server/app/_global-error.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/_0yyvgac._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_06j3c.0._.js +1 -1
- package/app/apps/web/.next/server/middleware-build-manifest.js +3 -3
- package/app/apps/web/.next/server/pages/500.html +1 -1
- package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
- package/app/apps/web/.next/static/chunks/{0ipd1ph4bgubq.js → 0dtwmo5wu8b96.js} +1 -1
- package/app/apps/web/app/layout.tsx +2 -2
- package/app/apps/web/components/sidebar.tsx +4 -2
- package/app/apps/web/package.json +1 -1
- package/app/apps/web/tsconfig.tsbuildinfo +1 -1
- package/dist/index.js +398 -198
- package/package.json +1 -1
- /package/app/apps/web/.next/static/{xYJud5MeHVY9ZFuYH3d2a → Ha1-RttFUdwakgsbK-ky-}/_buildManifest.js +0 -0
- /package/app/apps/web/.next/static/{xYJud5MeHVY9ZFuYH3d2a → Ha1-RttFUdwakgsbK-ky-}/_clientMiddlewareManifest.js +0 -0
- /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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
|
307
|
-
if (
|
|
308
|
-
console.log(`
|
|
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
|
|
329
|
-
if (
|
|
330
|
-
const url = `http://localhost:${
|
|
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
|
|
1002
|
-
const serverLine =
|
|
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
|
|
1091
|
-
import { homedir as
|
|
1092
|
-
import { join as
|
|
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
|
-
|
|
1114
|
-
|
|
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 =
|
|
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
|
|
1348
|
-
import { homedir as
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1542
|
-
start [--port N] [--no-open] Start
|
|
1543
|
-
stop Stop
|
|
1544
|
-
|
|
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
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
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:
|