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