whoburnedmore 0.8.3 → 0.8.5
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/dist/index.js +254 -103
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { createRequire as createRequire4 } from "node:module";
|
|
|
12
12
|
import { platform as platform3 } from "node:os";
|
|
13
13
|
import { join as join7 } from "node:path";
|
|
14
14
|
import { createInterface } from "node:readline/promises";
|
|
15
|
-
import
|
|
15
|
+
import pc2 from "picocolors";
|
|
16
16
|
|
|
17
17
|
// src/args.ts
|
|
18
18
|
function parseBoard(args) {
|
|
@@ -103,7 +103,15 @@ async function anonRemove(anonKey) {
|
|
|
103
103
|
|
|
104
104
|
// src/autosync.ts
|
|
105
105
|
import { spawnSync } from "node:child_process";
|
|
106
|
-
import {
|
|
106
|
+
import {
|
|
107
|
+
existsSync as existsSync2,
|
|
108
|
+
mkdirSync as mkdirSync2,
|
|
109
|
+
readFileSync as readFileSync2,
|
|
110
|
+
renameSync,
|
|
111
|
+
rmSync,
|
|
112
|
+
statSync,
|
|
113
|
+
writeFileSync as writeFileSync2
|
|
114
|
+
} from "node:fs";
|
|
107
115
|
import { homedir as homedir2, platform } from "node:os";
|
|
108
116
|
import { join as join2 } from "node:path";
|
|
109
117
|
import { fileURLToPath } from "node:url";
|
|
@@ -129,6 +137,8 @@ function loadConfig(dir = defaultConfigDir()) {
|
|
|
129
137
|
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
130
138
|
const config = {};
|
|
131
139
|
if (typeof parsed.anonKey === "string") config.anonKey = parsed.anonKey;
|
|
140
|
+
if (typeof parsed.lastSyncAt === "number" && Number.isFinite(parsed.lastSyncAt))
|
|
141
|
+
config.lastSyncAt = parsed.lastSyncAt;
|
|
132
142
|
return Object.keys(config).length > 0 ? config : null;
|
|
133
143
|
} catch {
|
|
134
144
|
return null;
|
|
@@ -150,10 +160,19 @@ function ensureAnonKey(dir = defaultConfigDir()) {
|
|
|
150
160
|
saveConfig(dir, { ...config, anonKey });
|
|
151
161
|
return anonKey;
|
|
152
162
|
}
|
|
163
|
+
function recordSync(dir = defaultConfigDir(), when = Date.now()) {
|
|
164
|
+
const config = loadConfig(dir) ?? {};
|
|
165
|
+
saveConfig(dir, { ...config, lastSyncAt: when });
|
|
166
|
+
}
|
|
153
167
|
|
|
154
168
|
// src/autosync.ts
|
|
155
169
|
var SYNC_INTERVAL_HOURS = 1;
|
|
156
170
|
var LABEL = "com.whoburnedmore.sync";
|
|
171
|
+
var STABLE_NODE_CANDIDATES = [
|
|
172
|
+
"/opt/homebrew/bin/node",
|
|
173
|
+
"/usr/local/bin/node",
|
|
174
|
+
"/usr/bin/node"
|
|
175
|
+
];
|
|
157
176
|
function syncLogPath() {
|
|
158
177
|
return join2(defaultConfigDir(), "sync.log");
|
|
159
178
|
}
|
|
@@ -194,28 +213,53 @@ function launchAgentPath() {
|
|
|
194
213
|
function cliScriptPath() {
|
|
195
214
|
return fileURLToPath(new URL("./index.js", import.meta.url));
|
|
196
215
|
}
|
|
216
|
+
function isUsableNode(p) {
|
|
217
|
+
if (!existsSync2(p)) return false;
|
|
218
|
+
const res = spawnSync(p, ["-v"], { encoding: "utf8" });
|
|
219
|
+
if (res.status !== 0 || typeof res.stdout !== "string") return false;
|
|
220
|
+
const major = Number(res.stdout.trim().replace(/^v/, "").split(".")[0]);
|
|
221
|
+
return Number.isFinite(major) && major >= 20;
|
|
222
|
+
}
|
|
223
|
+
function resolveNodePath(opts) {
|
|
224
|
+
const candidates = opts?.candidates ?? STABLE_NODE_CANDIDATES;
|
|
225
|
+
const check = opts?.check ?? isUsableNode;
|
|
226
|
+
const execPath = opts?.execPath ?? process.execPath;
|
|
227
|
+
for (const c of candidates) {
|
|
228
|
+
if (check(c)) return c;
|
|
229
|
+
}
|
|
230
|
+
return execPath;
|
|
231
|
+
}
|
|
232
|
+
function expectedDarwinPlist() {
|
|
233
|
+
return buildLaunchdPlist(resolveNodePath(), cliScriptPath());
|
|
234
|
+
}
|
|
235
|
+
function plistDrift(installed, expected) {
|
|
236
|
+
if (installed === null) return "absent";
|
|
237
|
+
return installed.trim() === expected.trim() ? "ok" : "drift";
|
|
238
|
+
}
|
|
239
|
+
function reconcileAction(state) {
|
|
240
|
+
return state === "ok" ? "noop" : "install";
|
|
241
|
+
}
|
|
197
242
|
function installAutoSync() {
|
|
198
243
|
const os = platform();
|
|
199
244
|
mkdirSync2(defaultConfigDir(), { recursive: true });
|
|
200
245
|
if (os === "darwin") {
|
|
201
246
|
const plistPath = launchAgentPath();
|
|
202
247
|
mkdirSync2(join2(homedir2(), "Library", "LaunchAgents"), { recursive: true });
|
|
203
|
-
writeFileSync2(plistPath,
|
|
248
|
+
writeFileSync2(plistPath, expectedDarwinPlist());
|
|
204
249
|
spawnSync("launchctl", ["unload", plistPath], { stdio: "ignore" });
|
|
205
250
|
spawnSync("launchctl", ["load", plistPath], { stdio: "ignore" });
|
|
206
251
|
return `launchd agent installed (${plistPath}), syncing every ${SYNC_INTERVAL_HOURS}h`;
|
|
207
252
|
}
|
|
208
253
|
if (os === "linux") {
|
|
209
|
-
const line =
|
|
254
|
+
const line = expectedLinuxCronLine();
|
|
210
255
|
const current = spawnSync("crontab", ["-l"], { encoding: "utf8" });
|
|
211
256
|
const existing = current.status === 0 ? current.stdout : "";
|
|
212
|
-
|
|
213
|
-
|
|
257
|
+
const kept = existing.split("\n").filter((l) => !l.includes("whoburnedmore")).join("\n");
|
|
258
|
+
const next = `${kept.trimEnd()}
|
|
214
259
|
${line}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
260
|
+
`.replace(/^\n+/, "");
|
|
261
|
+
const res = spawnSync("crontab", ["-"], { input: next });
|
|
262
|
+
if (res.status !== 0) throw new Error("could not install crontab entry");
|
|
219
263
|
return `cron entry installed, syncing every ${SYNC_INTERVAL_HOURS}h`;
|
|
220
264
|
}
|
|
221
265
|
if (os === "win32") {
|
|
@@ -229,13 +273,16 @@ ${line}
|
|
|
229
273
|
"/TN",
|
|
230
274
|
"whoburnedmore-sync",
|
|
231
275
|
"/TR",
|
|
232
|
-
`"${
|
|
276
|
+
`"${resolveNodePath()}" "${cliScriptPath()}" sync`
|
|
233
277
|
]);
|
|
234
278
|
if (res.status !== 0) throw new Error("could not create scheduled task");
|
|
235
279
|
return `scheduled task installed, syncing every ${SYNC_INTERVAL_HOURS}h`;
|
|
236
280
|
}
|
|
237
281
|
throw new Error(`auto-sync is not supported on ${os}`);
|
|
238
282
|
}
|
|
283
|
+
function expectedLinuxCronLine() {
|
|
284
|
+
return `0 */${SYNC_INTERVAL_HOURS} * * * "${resolveNodePath()}" "${cliScriptPath()}" sync >"${syncLogPath()}" 2>&1`;
|
|
285
|
+
}
|
|
239
286
|
function uninstallAutoSync() {
|
|
240
287
|
const os = platform();
|
|
241
288
|
if (os === "darwin") {
|
|
@@ -274,6 +321,52 @@ function autoSyncInstalled() {
|
|
|
274
321
|
}
|
|
275
322
|
return false;
|
|
276
323
|
}
|
|
324
|
+
function readInstalledAgent() {
|
|
325
|
+
if (platform() === "darwin") {
|
|
326
|
+
const p = launchAgentPath();
|
|
327
|
+
return existsSync2(p) ? readFileSync2(p, "utf8") : null;
|
|
328
|
+
}
|
|
329
|
+
if (platform() === "linux") {
|
|
330
|
+
const current = spawnSync("crontab", ["-l"], { encoding: "utf8" });
|
|
331
|
+
if (current.status !== 0) return null;
|
|
332
|
+
const line = current.stdout.split("\n").find((l) => l.includes("whoburnedmore"));
|
|
333
|
+
return line ?? null;
|
|
334
|
+
}
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
function expectedAgent() {
|
|
338
|
+
if (platform() === "darwin") return expectedDarwinPlist();
|
|
339
|
+
if (platform() === "linux") return expectedLinuxCronLine();
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
function autoSyncDrift() {
|
|
343
|
+
const expected = expectedAgent();
|
|
344
|
+
if (expected === null) return autoSyncInstalled() ? "ok" : "absent";
|
|
345
|
+
return plistDrift(readInstalledAgent(), expected);
|
|
346
|
+
}
|
|
347
|
+
function reconcileAutoSync() {
|
|
348
|
+
const state = autoSyncDrift();
|
|
349
|
+
if (reconcileAction(state) === "noop") return "noop";
|
|
350
|
+
installAutoSync();
|
|
351
|
+
return state === "absent" ? "installed" : "reinstalled";
|
|
352
|
+
}
|
|
353
|
+
function rotateLogIfLarge(path = syncLogPath(), capBytes = 256 * 1024) {
|
|
354
|
+
try {
|
|
355
|
+
if (!existsSync2(path)) return false;
|
|
356
|
+
if (statSync(path).size <= capBytes) return false;
|
|
357
|
+
renameSync(path, `${path}.1`);
|
|
358
|
+
return true;
|
|
359
|
+
} catch {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function autoSyncLoaded() {
|
|
364
|
+
if (platform() === "darwin") {
|
|
365
|
+
const res = spawnSync("launchctl", ["list"], { encoding: "utf8" });
|
|
366
|
+
return res.status === 0 && res.stdout.includes(LABEL);
|
|
367
|
+
}
|
|
368
|
+
return autoSyncInstalled();
|
|
369
|
+
}
|
|
277
370
|
|
|
278
371
|
// src/banner.ts
|
|
279
372
|
import pc from "picocolors";
|
|
@@ -300,7 +393,7 @@ import { dirname as dirname2, join as join6 } from "node:path";
|
|
|
300
393
|
import { promisify } from "node:util";
|
|
301
394
|
|
|
302
395
|
// src/attribution.ts
|
|
303
|
-
import { readFileSync as
|
|
396
|
+
import { readFileSync as readFileSync3, readdirSync, statSync as statSync2 } from "node:fs";
|
|
304
397
|
import { homedir as homedir3 } from "node:os";
|
|
305
398
|
import { basename, join as join3 } from "node:path";
|
|
306
399
|
|
|
@@ -340,7 +433,8 @@ function createAccumulator() {
|
|
|
340
433
|
messageCount: 0,
|
|
341
434
|
subagentMessages: 0,
|
|
342
435
|
subagentTokens: 0,
|
|
343
|
-
totalTokens: 0
|
|
436
|
+
totalTokens: 0,
|
|
437
|
+
userMessageCount: 0
|
|
344
438
|
},
|
|
345
439
|
titles: /* @__PURE__ */ new Map(),
|
|
346
440
|
sessionMessages: /* @__PURE__ */ new Map()
|
|
@@ -358,10 +452,24 @@ function recordTokens(usage) {
|
|
|
358
452
|
};
|
|
359
453
|
return n(u.input_tokens) + n(u.output_tokens) + n(u.cache_creation_input_tokens) + n(u.cache_read_input_tokens);
|
|
360
454
|
}
|
|
455
|
+
function hasHumanText(content) {
|
|
456
|
+
const isHuman = (t) => {
|
|
457
|
+
const s = t.trim();
|
|
458
|
+
return s.length > 0 && !s.startsWith("<system-reminder") && !s.startsWith("<command-") && !s.startsWith("Caveat:");
|
|
459
|
+
};
|
|
460
|
+
if (typeof content === "string") return isHuman(content);
|
|
461
|
+
if (!Array.isArray(content)) return false;
|
|
462
|
+
return content.some(
|
|
463
|
+
(b) => b !== null && typeof b === "object" && b.type === "text" && typeof b.text === "string" && isHuman(b.text)
|
|
464
|
+
);
|
|
465
|
+
}
|
|
361
466
|
function processRecord(rec, acc, ctx) {
|
|
362
467
|
if (!rec || typeof rec !== "object") return;
|
|
363
468
|
const r = rec;
|
|
364
469
|
const recTokens = recordTokens(r.message?.usage);
|
|
470
|
+
if ((r.type === "user" || r.message?.role === "user") && r.isSidechain !== true && r.isMeta !== true && hasHumanText(r.message?.content)) {
|
|
471
|
+
acc.agent.userMessageCount += 1;
|
|
472
|
+
}
|
|
365
473
|
if (typeof r.attributionSkill === "string" && r.attributionSkill) {
|
|
366
474
|
const s = r.attributionSkill.slice(0, 128);
|
|
367
475
|
const sk = acc.skills.get(s) ?? { count: 0, tokens: 0 };
|
|
@@ -537,13 +645,13 @@ function processCodexRecord(rec, acc, ctx) {
|
|
|
537
645
|
function readLines(file) {
|
|
538
646
|
let size = 0;
|
|
539
647
|
try {
|
|
540
|
-
size =
|
|
648
|
+
size = statSync2(file).size;
|
|
541
649
|
} catch {
|
|
542
650
|
return [];
|
|
543
651
|
}
|
|
544
652
|
if (size > MAX_FILE_BYTES) return [];
|
|
545
653
|
try {
|
|
546
|
-
return
|
|
654
|
+
return readFileSync3(file, "utf8").split("\n");
|
|
547
655
|
} catch {
|
|
548
656
|
return [];
|
|
549
657
|
}
|
|
@@ -571,7 +679,7 @@ function listTranscripts(dir) {
|
|
|
571
679
|
if (e.isDirectory()) walk(p);
|
|
572
680
|
else if (e.isFile() && e.name.endsWith(".jsonl")) {
|
|
573
681
|
try {
|
|
574
|
-
out.push({ path: p, mtime:
|
|
682
|
+
out.push({ path: p, mtime: statSync2(p).mtimeMs });
|
|
575
683
|
} catch {
|
|
576
684
|
}
|
|
577
685
|
}
|
|
@@ -1164,6 +1272,68 @@ async function collectAll(onProgress) {
|
|
|
1164
1272
|
};
|
|
1165
1273
|
}
|
|
1166
1274
|
|
|
1275
|
+
// src/status.ts
|
|
1276
|
+
function ago(ms) {
|
|
1277
|
+
const mins = Math.round(ms / 6e4);
|
|
1278
|
+
if (mins < 1) return "just now";
|
|
1279
|
+
if (mins < 60) return `${mins}m ago`;
|
|
1280
|
+
const hrs = Math.round(mins / 60);
|
|
1281
|
+
if (hrs < 48) return `${hrs}h ago`;
|
|
1282
|
+
return `${Math.round(hrs / 24)}d ago`;
|
|
1283
|
+
}
|
|
1284
|
+
function buildStatusReport(s) {
|
|
1285
|
+
const lines = [];
|
|
1286
|
+
lines.push(" whoburnedmore \u2014 background sync status");
|
|
1287
|
+
lines.push("");
|
|
1288
|
+
lines.push(
|
|
1289
|
+
s.installed ? ` \u2022 Background agent: installed${s.loaded ? " and loaded" : " but NOT loaded with the scheduler"}` : " \u2022 Background agent: NOT installed \u2014 run `npx whoburnedmore` to set it up"
|
|
1290
|
+
);
|
|
1291
|
+
if (s.installed && s.drift !== "ok") {
|
|
1292
|
+
lines.push(
|
|
1293
|
+
" \u21B3 config is out of date \u2014 it will self-repair on your next run"
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
lines.push(` \u2022 Interval: every ${s.intervalHours}h`);
|
|
1297
|
+
const staleAfterMs = s.intervalHours * 2 * 3600 * 1e3;
|
|
1298
|
+
if (s.lastSyncAt === null) {
|
|
1299
|
+
lines.push(" \u2022 Last sync: never recorded");
|
|
1300
|
+
lines.push(" \u26A0 STALE: no successful sync recorded yet \u2014 run `npx whoburnedmore`");
|
|
1301
|
+
} else {
|
|
1302
|
+
const age = s.now - s.lastSyncAt;
|
|
1303
|
+
lines.push(` \u2022 Last sync: ${ago(age)}`);
|
|
1304
|
+
if (age > staleAfterMs) {
|
|
1305
|
+
lines.push(
|
|
1306
|
+
` \u26A0 STALE: last sync was over ${s.intervalHours * 2}h ago \u2014 your dashboard may be behind. Run \`npx whoburnedmore\`.`
|
|
1307
|
+
);
|
|
1308
|
+
} else {
|
|
1309
|
+
lines.push(" \u2713 Fresh \u2014 your dashboard is up to date.");
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
lines.push(` \u2022 Node: ${s.nodePath}`);
|
|
1313
|
+
if (!s.nodePathStable) {
|
|
1314
|
+
lines.push(
|
|
1315
|
+
" \u26A0 that node path is version-pinned and may break on a node upgrade \u2014 a run will re-point it to a stable path"
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
lines.push(` \u2022 Log: ${s.logPath}`);
|
|
1319
|
+
return lines;
|
|
1320
|
+
}
|
|
1321
|
+
function agentStatusReport(now = Date.now()) {
|
|
1322
|
+
const cfg = loadConfig();
|
|
1323
|
+
const nodePath = resolveNodePath();
|
|
1324
|
+
return buildStatusReport({
|
|
1325
|
+
installed: autoSyncInstalled(),
|
|
1326
|
+
loaded: autoSyncLoaded(),
|
|
1327
|
+
drift: autoSyncDrift(),
|
|
1328
|
+
intervalHours: SYNC_INTERVAL_HOURS,
|
|
1329
|
+
lastSyncAt: typeof cfg?.lastSyncAt === "number" ? cfg.lastSyncAt : null,
|
|
1330
|
+
now,
|
|
1331
|
+
nodePath,
|
|
1332
|
+
nodePathStable: !nodePath.includes("/Cellar/"),
|
|
1333
|
+
logPath: syncLogPath()
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1167
1337
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
1168
1338
|
var external_exports = {};
|
|
1169
1339
|
__export(external_exports, {
|
|
@@ -5283,7 +5453,13 @@ var AgentStat = external_exports.object({
|
|
|
5283
5453
|
/** Tokens spent inside subagent sidechains. */
|
|
5284
5454
|
subagentTokens: external_exports.number().int().nonnegative(),
|
|
5285
5455
|
/** Total tokens observed across transcripts (denominator for the share). */
|
|
5286
|
-
totalTokens: external_exports.number().int().nonnegative()
|
|
5456
|
+
totalTokens: external_exports.number().int().nonnegative(),
|
|
5457
|
+
/**
|
|
5458
|
+
* Messages the human actually sent (their prompts) — non-sidechain user turns
|
|
5459
|
+
* carrying real text, NOT tool results or injected/meta turns. Denominator for
|
|
5460
|
+
* "avg cost per message". Optional (back-compat with older CLIs).
|
|
5461
|
+
*/
|
|
5462
|
+
userMessageCount: external_exports.number().int().nonnegative().optional()
|
|
5287
5463
|
});
|
|
5288
5464
|
var SkillStat = external_exports.object({
|
|
5289
5465
|
name: external_exports.string().min(1).max(128),
|
|
@@ -5327,7 +5503,6 @@ var LeaderboardPeriod = external_exports.enum(["today", "7d", "30d", "all"]);
|
|
|
5327
5503
|
var LeaderboardMetric = external_exports.enum(["tokens", "cost"]);
|
|
5328
5504
|
|
|
5329
5505
|
// src/output.ts
|
|
5330
|
-
import pc2 from "picocolors";
|
|
5331
5506
|
function formatTokens(n) {
|
|
5332
5507
|
if (n >= 1e9) return `${(n / 1e9).toFixed(2)}B`;
|
|
5333
5508
|
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
@@ -5337,40 +5512,21 @@ function formatTokens(n) {
|
|
|
5337
5512
|
function formatUSD(n) {
|
|
5338
5513
|
return `$${n.toLocaleString("en-US", { maximumFractionDigits: 2 })}`;
|
|
5339
5514
|
}
|
|
5340
|
-
function
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
byTool.set(e.tool, agg);
|
|
5356
|
-
}
|
|
5357
|
-
console.log();
|
|
5358
|
-
console.log(pc2.bold(pc2.yellow(" \u{1F525} your burn report")));
|
|
5359
|
-
console.log();
|
|
5360
|
-
const rows = [...byTool.entries()].sort((a, b) => b[1].tokens - a[1].tokens);
|
|
5361
|
-
for (const [tool, agg] of rows) {
|
|
5362
|
-
console.log(
|
|
5363
|
-
` ${pc2.cyan(tool.padEnd(10))} ${formatTokens(agg.tokens).padStart(9)} tokens ${formatUSD(agg.cost).padStart(10)} ${String(agg.days.size).padStart(4)} days`
|
|
5364
|
-
);
|
|
5365
|
-
}
|
|
5366
|
-
console.log(pc2.dim(" " + "\u2500".repeat(46)));
|
|
5367
|
-
console.log(
|
|
5368
|
-
` ${pc2.bold("total".padEnd(10))} ${pc2.bold(formatTokens(totalTokens).padStart(9))} tokens ${pc2.bold(formatUSD(totalCost).padStart(10))}`
|
|
5369
|
-
);
|
|
5370
|
-
if (todayTokens > 0) {
|
|
5371
|
-
console.log(` ${pc2.dim("today".padEnd(10))} ${formatTokens(todayTokens).padStart(9)} tokens`);
|
|
5372
|
-
}
|
|
5373
|
-
console.log();
|
|
5515
|
+
function submitNextStepLines(result) {
|
|
5516
|
+
if (result.boardUrl) {
|
|
5517
|
+
const code = result.boardCode ?? result.boardUrl.split("/").filter(Boolean).pop() ?? "";
|
|
5518
|
+
return [
|
|
5519
|
+
` \u{1F91D} You're on the board: ${result.boardUrl}`,
|
|
5520
|
+
" \u2192 Open it to see who burned more.",
|
|
5521
|
+
` \u2192 Get a friend on it \u2014 have them run: npx whoburnedmore --board=${code}`,
|
|
5522
|
+
" \u2192 Sign in on the page and add your X to claim your spot and own your rank."
|
|
5523
|
+
];
|
|
5524
|
+
}
|
|
5525
|
+
return [
|
|
5526
|
+
` Your dashboard: ${result.dashboardUrl}`,
|
|
5527
|
+
" \u2192 Sign in and add your X on the page to get on the leaderboard and claim your rank.",
|
|
5528
|
+
" Private until you do. Manage anytime: `npx whoburnedmore private` \xB7 `public` \xB7 `remove`."
|
|
5529
|
+
];
|
|
5374
5530
|
}
|
|
5375
5531
|
|
|
5376
5532
|
// src/local-dashboard.ts
|
|
@@ -5611,14 +5767,12 @@ var LOADING_VIBES = [
|
|
|
5611
5767
|
];
|
|
5612
5768
|
function startProgress() {
|
|
5613
5769
|
if (!process.stdout.isTTY) {
|
|
5614
|
-
let
|
|
5770
|
+
let announced = false;
|
|
5615
5771
|
return {
|
|
5616
|
-
onProgress: (
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
console.log(pc3.dim(` counting local token usage\u2026 ${pct}%`));
|
|
5621
|
-
}
|
|
5772
|
+
onProgress: () => {
|
|
5773
|
+
if (announced) return;
|
|
5774
|
+
announced = true;
|
|
5775
|
+
console.log(pc2.dim(" Counting your token usage\u2026"));
|
|
5622
5776
|
},
|
|
5623
5777
|
stop: () => {
|
|
5624
5778
|
}
|
|
@@ -5639,9 +5793,9 @@ function startProgress() {
|
|
|
5639
5793
|
shown += (target - shown) * 0.3;
|
|
5640
5794
|
if (target - shown < 4e-3) shown = target;
|
|
5641
5795
|
const filled = Math.round(shown * width);
|
|
5642
|
-
const bar =
|
|
5796
|
+
const bar = pc2.yellow("\u2588".repeat(filled)) + pc2.dim("\u2591".repeat(width - filled));
|
|
5643
5797
|
const pct = String(Math.round(shown * 100)).padStart(3);
|
|
5644
|
-
process.stdout.write(`\r ${bar} ${pct}% ${
|
|
5798
|
+
process.stdout.write(`\r ${bar} ${pct}% ${pc2.dim(vibe())}\x1B[K`);
|
|
5645
5799
|
};
|
|
5646
5800
|
render();
|
|
5647
5801
|
const timer = setInterval(render, 60);
|
|
@@ -5665,7 +5819,7 @@ function openBrowser(url) {
|
|
|
5665
5819
|
async function confirm(question) {
|
|
5666
5820
|
if (!process.stdin.isTTY) return false;
|
|
5667
5821
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
5668
|
-
const answer = (await rl.question(`${question} ${
|
|
5822
|
+
const answer = (await rl.question(`${question} ${pc2.dim("[Y/n]")} `)).trim();
|
|
5669
5823
|
rl.close();
|
|
5670
5824
|
return answer === "" || /^y(es)?$/i.test(answer);
|
|
5671
5825
|
}
|
|
@@ -5681,20 +5835,20 @@ function showLocalDashboard(payload) {
|
|
|
5681
5835
|
})
|
|
5682
5836
|
);
|
|
5683
5837
|
console.log();
|
|
5684
|
-
console.log(` Local dashboard: ${
|
|
5685
|
-
console.log(
|
|
5838
|
+
console.log(` Local dashboard: ${pc2.cyan(`file://${file}`)}`);
|
|
5839
|
+
console.log(pc2.dim(" Re-run `npx whoburnedmore --local` to refresh it. Nothing left your machine."));
|
|
5686
5840
|
openBrowser(`file://${file}`);
|
|
5687
5841
|
}
|
|
5688
5842
|
async function run(flags) {
|
|
5689
5843
|
if (!flags.quiet) {
|
|
5690
5844
|
printBanner();
|
|
5691
|
-
console.log(
|
|
5845
|
+
console.log(pc2.dim(` whoburnedmore v${VERSION} \xB7 ${flags.local ? "local mode" : apiBase()}`));
|
|
5692
5846
|
if (!flags.dryRun && !flags.noSubmit && !flags.local) {
|
|
5693
5847
|
console.log(
|
|
5694
|
-
|
|
5848
|
+
pc2.dim(" Counting your token usage and posting your rank \u2014 only daily totals leave your machine, never your prompts or code.")
|
|
5695
5849
|
);
|
|
5696
5850
|
console.log(
|
|
5697
|
-
|
|
5851
|
+
pc2.dim(" (`--local` keeps it fully offline \xB7 `private`/`remove` pull it anytime \xB7 details: whoburnedmore.com/trust)")
|
|
5698
5852
|
);
|
|
5699
5853
|
}
|
|
5700
5854
|
console.log();
|
|
@@ -5707,11 +5861,11 @@ async function run(flags) {
|
|
|
5707
5861
|
} finally {
|
|
5708
5862
|
progress.stop();
|
|
5709
5863
|
}
|
|
5710
|
-
const { entries, sessions, blocks,
|
|
5864
|
+
const { entries, sessions, blocks, tools, skills, projects, agent, attributionComplete } = collected;
|
|
5711
5865
|
if (entries.length === 0) {
|
|
5712
5866
|
console.log();
|
|
5713
5867
|
console.log(" Nothing to burn yet \u2014 no local usage found from any coding agent.");
|
|
5714
|
-
console.log(
|
|
5868
|
+
console.log(pc2.dim(" Use Claude Code, Codex, Gemini CLI (or friends) and come back."));
|
|
5715
5869
|
return;
|
|
5716
5870
|
}
|
|
5717
5871
|
const payload = { cliVersion: VERSION, entries };
|
|
@@ -5725,11 +5879,10 @@ async function run(flags) {
|
|
|
5725
5879
|
payload.attributionComplete = true;
|
|
5726
5880
|
if (flags.board) payload.board = flags.board;
|
|
5727
5881
|
if (flags.dryRun) {
|
|
5728
|
-
console.log(
|
|
5882
|
+
console.log(pc2.dim("\n --dry-run: this exact payload would be sent, nothing else:\n"));
|
|
5729
5883
|
console.log(JSON.stringify(payload, null, 2));
|
|
5730
5884
|
return;
|
|
5731
5885
|
}
|
|
5732
|
-
if (!flags.quiet) printSummary(entries);
|
|
5733
5886
|
if (flags.local) {
|
|
5734
5887
|
showLocalDashboard(payload);
|
|
5735
5888
|
if (!flags.quiet && process.stdin.isTTY) {
|
|
@@ -5738,55 +5891,46 @@ async function run(flags) {
|
|
|
5738
5891
|
ensureAnonKey,
|
|
5739
5892
|
anonSubmit,
|
|
5740
5893
|
openBrowser,
|
|
5741
|
-
log: (line) => console.log(
|
|
5894
|
+
log: (line) => console.log(pc2.dim(line))
|
|
5742
5895
|
});
|
|
5743
5896
|
}
|
|
5744
5897
|
return;
|
|
5745
5898
|
}
|
|
5746
5899
|
if (flags.noSubmit) {
|
|
5747
|
-
console.log(
|
|
5900
|
+
console.log(pc2.dim(" --no-submit: skipped the dashboard."));
|
|
5748
5901
|
return;
|
|
5749
5902
|
}
|
|
5750
5903
|
const anonKey = ensureAnonKey();
|
|
5751
5904
|
const result = await anonSubmit(anonKey, payload);
|
|
5905
|
+
try {
|
|
5906
|
+
recordSync();
|
|
5907
|
+
} catch {
|
|
5908
|
+
}
|
|
5752
5909
|
const target = result.boardUrl ?? claimUrl(result.dashboardUrl, anonKey);
|
|
5753
5910
|
if (!flags.quiet) {
|
|
5754
|
-
console.log(pc3.dim(" Opening your dashboard in your browser\u2026"));
|
|
5755
|
-
openBrowser(target);
|
|
5756
|
-
}
|
|
5757
|
-
console.log(
|
|
5758
|
-
` Submitted ${pc3.bold(String(result.upserted))} day-entries from ${toolsFound.join(", ")}.`
|
|
5759
|
-
);
|
|
5760
|
-
if (result.boardUrl) {
|
|
5761
|
-
console.log(
|
|
5762
|
-
` You burned ${pc3.bold(formatTokens(result.totalTokens))} tokens \u2014 \u{1F91D} you're on the friends board:`
|
|
5763
|
-
);
|
|
5764
|
-
console.log(` ${pc3.cyan(result.boardUrl)}`);
|
|
5765
|
-
console.log(pc3.dim(` Your dashboard: ${result.dashboardUrl}`));
|
|
5766
|
-
} else {
|
|
5767
5911
|
console.log(
|
|
5768
|
-
|
|
5912
|
+
pc2.green(" \u2713 Synced securely.") + pc2.dim(" Only your daily totals left this machine \u2014 never your prompts, code, or file names.")
|
|
5769
5913
|
);
|
|
5770
|
-
console.log(
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
);
|
|
5778
|
-
}
|
|
5914
|
+
console.log(pc2.dim(" Opening your dashboard in your browser\u2026"));
|
|
5915
|
+
openBrowser(target);
|
|
5916
|
+
}
|
|
5917
|
+
const lines = submitNextStepLines(result);
|
|
5918
|
+
for (const line of lines) {
|
|
5919
|
+
if (line.includes("\u2192")) console.log(pc2.bold(line));
|
|
5920
|
+
else if (line.startsWith(" Private until you do")) {
|
|
5921
|
+
if (!flags.quiet) console.log(pc2.dim(line));
|
|
5922
|
+
} else console.log(line);
|
|
5779
5923
|
}
|
|
5780
|
-
if (!flags.quiet
|
|
5924
|
+
if (!flags.quiet) {
|
|
5781
5925
|
try {
|
|
5782
|
-
|
|
5926
|
+
reconcileAutoSync();
|
|
5783
5927
|
} catch {
|
|
5784
5928
|
}
|
|
5785
5929
|
}
|
|
5786
5930
|
if (!flags.quiet) {
|
|
5787
5931
|
console.log();
|
|
5788
5932
|
console.log(
|
|
5789
|
-
autoSyncInstalled() ?
|
|
5933
|
+
autoSyncInstalled() ? pc2.dim(" Background sync is on \u2014 your page updates automatically every hour (`npx whoburnedmore uninstall-sync` to stop).") : pc2.dim(" Re-run anytime to update your page.")
|
|
5790
5934
|
);
|
|
5791
5935
|
}
|
|
5792
5936
|
}
|
|
@@ -5812,9 +5956,15 @@ async function main() {
|
|
|
5812
5956
|
break;
|
|
5813
5957
|
case "sync": {
|
|
5814
5958
|
if (!loadConfig()) return;
|
|
5959
|
+
rotateLogIfLarge();
|
|
5815
5960
|
await run({ ...flags, noSubmit: false, dryRun: false, local: false });
|
|
5816
5961
|
break;
|
|
5817
5962
|
}
|
|
5963
|
+
case "status":
|
|
5964
|
+
case "doctor": {
|
|
5965
|
+
for (const line of agentStatusReport()) console.log(line);
|
|
5966
|
+
break;
|
|
5967
|
+
}
|
|
5818
5968
|
case "private":
|
|
5819
5969
|
case "public": {
|
|
5820
5970
|
const cfg = loadConfig();
|
|
@@ -5858,17 +6008,18 @@ async function main() {
|
|
|
5858
6008
|
}
|
|
5859
6009
|
function printHelp() {
|
|
5860
6010
|
console.log(`
|
|
5861
|
-
${
|
|
6011
|
+
${pc2.bold("whoburnedmore")} \u2014 who burned more tokens, you or them?
|
|
5862
6012
|
|
|
5863
|
-
${
|
|
6013
|
+
${pc2.bold("usage")}
|
|
5864
6014
|
npx whoburnedmore burn + land on the public leaderboard, open your dashboard
|
|
5865
6015
|
npx whoburnedmore --board=CODE compare with friends \u2014 join their board (no sign-in)
|
|
5866
6016
|
npx whoburnedmore --local build the dashboard on your machine and open it (offline)
|
|
5867
6017
|
npx whoburnedmore --dry-run print exactly what would be sent, send nothing
|
|
5868
|
-
npx whoburnedmore --no-submit
|
|
6018
|
+
npx whoburnedmore --no-submit collect locally, send nothing (no dashboard)
|
|
5869
6019
|
npx whoburnedmore private hide your dashboard from the leaderboard
|
|
5870
6020
|
npx whoburnedmore public put it back on the leaderboard
|
|
5871
6021
|
npx whoburnedmore remove delete your dashboard and its data
|
|
6022
|
+
npx whoburnedmore status check background-sync health (last sync, staleness)
|
|
5872
6023
|
npx whoburnedmore uninstall-sync turn off the background sync
|
|
5873
6024
|
npx whoburnedmore install-sync turn it back on after uninstalling
|
|
5874
6025
|
|
|
@@ -5882,7 +6033,7 @@ function printHelp() {
|
|
|
5882
6033
|
`);
|
|
5883
6034
|
}
|
|
5884
6035
|
main().catch((err) => {
|
|
5885
|
-
console.error(
|
|
6036
|
+
console.error(pc2.red(`
|
|
5886
6037
|
${err.message}
|
|
5887
6038
|
`));
|
|
5888
6039
|
process.exitCode = 1;
|