whoburnedmore 0.8.1 → 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 +275 -108
- 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) {
|
|
@@ -24,6 +24,15 @@ function parseBoard(args) {
|
|
|
24
24
|
}
|
|
25
25
|
return void 0;
|
|
26
26
|
}
|
|
27
|
+
function resolveCommand(args) {
|
|
28
|
+
if (args.includes("--help") || args.includes("-h") || args.includes("help")) {
|
|
29
|
+
return "help";
|
|
30
|
+
}
|
|
31
|
+
if (args.includes("--version") || args.includes("-v") || args.includes("version")) {
|
|
32
|
+
return "version";
|
|
33
|
+
}
|
|
34
|
+
return args.find((a) => !a.startsWith("-")) ?? "run";
|
|
35
|
+
}
|
|
27
36
|
|
|
28
37
|
// src/api.ts
|
|
29
38
|
function apiBase() {
|
|
@@ -94,14 +103,28 @@ async function anonRemove(anonKey) {
|
|
|
94
103
|
|
|
95
104
|
// src/autosync.ts
|
|
96
105
|
import { spawnSync } from "node:child_process";
|
|
97
|
-
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";
|
|
98
115
|
import { homedir as homedir2, platform } from "node:os";
|
|
99
116
|
import { join as join2 } from "node:path";
|
|
100
117
|
import { fileURLToPath } from "node:url";
|
|
101
118
|
|
|
102
119
|
// src/config.ts
|
|
103
120
|
import { randomBytes } from "node:crypto";
|
|
104
|
-
import {
|
|
121
|
+
import {
|
|
122
|
+
chmodSync,
|
|
123
|
+
existsSync,
|
|
124
|
+
mkdirSync,
|
|
125
|
+
readFileSync,
|
|
126
|
+
writeFileSync
|
|
127
|
+
} from "node:fs";
|
|
105
128
|
import { homedir } from "node:os";
|
|
106
129
|
import { join } from "node:path";
|
|
107
130
|
function defaultConfigDir() {
|
|
@@ -114,6 +137,8 @@ function loadConfig(dir = defaultConfigDir()) {
|
|
|
114
137
|
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
115
138
|
const config = {};
|
|
116
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;
|
|
117
142
|
return Object.keys(config).length > 0 ? config : null;
|
|
118
143
|
} catch {
|
|
119
144
|
return null;
|
|
@@ -123,6 +148,10 @@ function saveConfig(dir = defaultConfigDir(), config = {}) {
|
|
|
123
148
|
mkdirSync(dir, { recursive: true });
|
|
124
149
|
const file = join(dir, "config.json");
|
|
125
150
|
writeFileSync(file, JSON.stringify(config, null, 2), { mode: 384 });
|
|
151
|
+
try {
|
|
152
|
+
chmodSync(file, 384);
|
|
153
|
+
} catch {
|
|
154
|
+
}
|
|
126
155
|
}
|
|
127
156
|
function ensureAnonKey(dir = defaultConfigDir()) {
|
|
128
157
|
const config = loadConfig(dir) ?? {};
|
|
@@ -131,10 +160,19 @@ function ensureAnonKey(dir = defaultConfigDir()) {
|
|
|
131
160
|
saveConfig(dir, { ...config, anonKey });
|
|
132
161
|
return anonKey;
|
|
133
162
|
}
|
|
163
|
+
function recordSync(dir = defaultConfigDir(), when = Date.now()) {
|
|
164
|
+
const config = loadConfig(dir) ?? {};
|
|
165
|
+
saveConfig(dir, { ...config, lastSyncAt: when });
|
|
166
|
+
}
|
|
134
167
|
|
|
135
168
|
// src/autosync.ts
|
|
136
169
|
var SYNC_INTERVAL_HOURS = 1;
|
|
137
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
|
+
];
|
|
138
176
|
function syncLogPath() {
|
|
139
177
|
return join2(defaultConfigDir(), "sync.log");
|
|
140
178
|
}
|
|
@@ -175,28 +213,53 @@ function launchAgentPath() {
|
|
|
175
213
|
function cliScriptPath() {
|
|
176
214
|
return fileURLToPath(new URL("./index.js", import.meta.url));
|
|
177
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
|
+
}
|
|
178
242
|
function installAutoSync() {
|
|
179
243
|
const os = platform();
|
|
180
244
|
mkdirSync2(defaultConfigDir(), { recursive: true });
|
|
181
245
|
if (os === "darwin") {
|
|
182
246
|
const plistPath = launchAgentPath();
|
|
183
247
|
mkdirSync2(join2(homedir2(), "Library", "LaunchAgents"), { recursive: true });
|
|
184
|
-
writeFileSync2(plistPath,
|
|
248
|
+
writeFileSync2(plistPath, expectedDarwinPlist());
|
|
185
249
|
spawnSync("launchctl", ["unload", plistPath], { stdio: "ignore" });
|
|
186
250
|
spawnSync("launchctl", ["load", plistPath], { stdio: "ignore" });
|
|
187
251
|
return `launchd agent installed (${plistPath}), syncing every ${SYNC_INTERVAL_HOURS}h`;
|
|
188
252
|
}
|
|
189
253
|
if (os === "linux") {
|
|
190
|
-
const line =
|
|
254
|
+
const line = expectedLinuxCronLine();
|
|
191
255
|
const current = spawnSync("crontab", ["-l"], { encoding: "utf8" });
|
|
192
256
|
const existing = current.status === 0 ? current.stdout : "";
|
|
193
|
-
|
|
194
|
-
|
|
257
|
+
const kept = existing.split("\n").filter((l) => !l.includes("whoburnedmore")).join("\n");
|
|
258
|
+
const next = `${kept.trimEnd()}
|
|
195
259
|
${line}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
260
|
+
`.replace(/^\n+/, "");
|
|
261
|
+
const res = spawnSync("crontab", ["-"], { input: next });
|
|
262
|
+
if (res.status !== 0) throw new Error("could not install crontab entry");
|
|
200
263
|
return `cron entry installed, syncing every ${SYNC_INTERVAL_HOURS}h`;
|
|
201
264
|
}
|
|
202
265
|
if (os === "win32") {
|
|
@@ -210,13 +273,16 @@ ${line}
|
|
|
210
273
|
"/TN",
|
|
211
274
|
"whoburnedmore-sync",
|
|
212
275
|
"/TR",
|
|
213
|
-
`"${
|
|
276
|
+
`"${resolveNodePath()}" "${cliScriptPath()}" sync`
|
|
214
277
|
]);
|
|
215
278
|
if (res.status !== 0) throw new Error("could not create scheduled task");
|
|
216
279
|
return `scheduled task installed, syncing every ${SYNC_INTERVAL_HOURS}h`;
|
|
217
280
|
}
|
|
218
281
|
throw new Error(`auto-sync is not supported on ${os}`);
|
|
219
282
|
}
|
|
283
|
+
function expectedLinuxCronLine() {
|
|
284
|
+
return `0 */${SYNC_INTERVAL_HOURS} * * * "${resolveNodePath()}" "${cliScriptPath()}" sync >"${syncLogPath()}" 2>&1`;
|
|
285
|
+
}
|
|
220
286
|
function uninstallAutoSync() {
|
|
221
287
|
const os = platform();
|
|
222
288
|
if (os === "darwin") {
|
|
@@ -255,6 +321,52 @@ function autoSyncInstalled() {
|
|
|
255
321
|
}
|
|
256
322
|
return false;
|
|
257
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
|
+
}
|
|
258
370
|
|
|
259
371
|
// src/banner.ts
|
|
260
372
|
import pc from "picocolors";
|
|
@@ -281,7 +393,7 @@ import { dirname as dirname2, join as join6 } from "node:path";
|
|
|
281
393
|
import { promisify } from "node:util";
|
|
282
394
|
|
|
283
395
|
// src/attribution.ts
|
|
284
|
-
import { readFileSync as
|
|
396
|
+
import { readFileSync as readFileSync3, readdirSync, statSync as statSync2 } from "node:fs";
|
|
285
397
|
import { homedir as homedir3 } from "node:os";
|
|
286
398
|
import { basename, join as join3 } from "node:path";
|
|
287
399
|
|
|
@@ -321,7 +433,8 @@ function createAccumulator() {
|
|
|
321
433
|
messageCount: 0,
|
|
322
434
|
subagentMessages: 0,
|
|
323
435
|
subagentTokens: 0,
|
|
324
|
-
totalTokens: 0
|
|
436
|
+
totalTokens: 0,
|
|
437
|
+
userMessageCount: 0
|
|
325
438
|
},
|
|
326
439
|
titles: /* @__PURE__ */ new Map(),
|
|
327
440
|
sessionMessages: /* @__PURE__ */ new Map()
|
|
@@ -339,10 +452,24 @@ function recordTokens(usage) {
|
|
|
339
452
|
};
|
|
340
453
|
return n(u.input_tokens) + n(u.output_tokens) + n(u.cache_creation_input_tokens) + n(u.cache_read_input_tokens);
|
|
341
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
|
+
}
|
|
342
466
|
function processRecord(rec, acc, ctx) {
|
|
343
467
|
if (!rec || typeof rec !== "object") return;
|
|
344
468
|
const r = rec;
|
|
345
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
|
+
}
|
|
346
473
|
if (typeof r.attributionSkill === "string" && r.attributionSkill) {
|
|
347
474
|
const s = r.attributionSkill.slice(0, 128);
|
|
348
475
|
const sk = acc.skills.get(s) ?? { count: 0, tokens: 0 };
|
|
@@ -518,13 +645,13 @@ function processCodexRecord(rec, acc, ctx) {
|
|
|
518
645
|
function readLines(file) {
|
|
519
646
|
let size = 0;
|
|
520
647
|
try {
|
|
521
|
-
size =
|
|
648
|
+
size = statSync2(file).size;
|
|
522
649
|
} catch {
|
|
523
650
|
return [];
|
|
524
651
|
}
|
|
525
652
|
if (size > MAX_FILE_BYTES) return [];
|
|
526
653
|
try {
|
|
527
|
-
return
|
|
654
|
+
return readFileSync3(file, "utf8").split("\n");
|
|
528
655
|
} catch {
|
|
529
656
|
return [];
|
|
530
657
|
}
|
|
@@ -552,7 +679,7 @@ function listTranscripts(dir) {
|
|
|
552
679
|
if (e.isDirectory()) walk(p);
|
|
553
680
|
else if (e.isFile() && e.name.endsWith(".jsonl")) {
|
|
554
681
|
try {
|
|
555
|
-
out.push({ path: p, mtime:
|
|
682
|
+
out.push({ path: p, mtime: statSync2(p).mtimeMs });
|
|
556
683
|
} catch {
|
|
557
684
|
}
|
|
558
685
|
}
|
|
@@ -1145,6 +1272,68 @@ async function collectAll(onProgress) {
|
|
|
1145
1272
|
};
|
|
1146
1273
|
}
|
|
1147
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
|
+
|
|
1148
1337
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
1149
1338
|
var external_exports = {};
|
|
1150
1339
|
__export(external_exports, {
|
|
@@ -5264,7 +5453,13 @@ var AgentStat = external_exports.object({
|
|
|
5264
5453
|
/** Tokens spent inside subagent sidechains. */
|
|
5265
5454
|
subagentTokens: external_exports.number().int().nonnegative(),
|
|
5266
5455
|
/** Total tokens observed across transcripts (denominator for the share). */
|
|
5267
|
-
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()
|
|
5268
5463
|
});
|
|
5269
5464
|
var SkillStat = external_exports.object({
|
|
5270
5465
|
name: external_exports.string().min(1).max(128),
|
|
@@ -5308,7 +5503,6 @@ var LeaderboardPeriod = external_exports.enum(["today", "7d", "30d", "all"]);
|
|
|
5308
5503
|
var LeaderboardMetric = external_exports.enum(["tokens", "cost"]);
|
|
5309
5504
|
|
|
5310
5505
|
// src/output.ts
|
|
5311
|
-
import pc2 from "picocolors";
|
|
5312
5506
|
function formatTokens(n) {
|
|
5313
5507
|
if (n >= 1e9) return `${(n / 1e9).toFixed(2)}B`;
|
|
5314
5508
|
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
@@ -5318,40 +5512,21 @@ function formatTokens(n) {
|
|
|
5318
5512
|
function formatUSD(n) {
|
|
5319
5513
|
return `$${n.toLocaleString("en-US", { maximumFractionDigits: 2 })}`;
|
|
5320
5514
|
}
|
|
5321
|
-
function
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
byTool.set(e.tool, agg);
|
|
5337
|
-
}
|
|
5338
|
-
console.log();
|
|
5339
|
-
console.log(pc2.bold(pc2.yellow(" \u{1F525} your burn report")));
|
|
5340
|
-
console.log();
|
|
5341
|
-
const rows = [...byTool.entries()].sort((a, b) => b[1].tokens - a[1].tokens);
|
|
5342
|
-
for (const [tool, agg] of rows) {
|
|
5343
|
-
console.log(
|
|
5344
|
-
` ${pc2.cyan(tool.padEnd(10))} ${formatTokens(agg.tokens).padStart(9)} tokens ${formatUSD(agg.cost).padStart(10)} ${String(agg.days.size).padStart(4)} days`
|
|
5345
|
-
);
|
|
5346
|
-
}
|
|
5347
|
-
console.log(pc2.dim(" " + "\u2500".repeat(46)));
|
|
5348
|
-
console.log(
|
|
5349
|
-
` ${pc2.bold("total".padEnd(10))} ${pc2.bold(formatTokens(totalTokens).padStart(9))} tokens ${pc2.bold(formatUSD(totalCost).padStart(10))}`
|
|
5350
|
-
);
|
|
5351
|
-
if (todayTokens > 0) {
|
|
5352
|
-
console.log(` ${pc2.dim("today".padEnd(10))} ${formatTokens(todayTokens).padStart(9)} tokens`);
|
|
5353
|
-
}
|
|
5354
|
-
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
|
+
];
|
|
5355
5530
|
}
|
|
5356
5531
|
|
|
5357
5532
|
// src/local-dashboard.ts
|
|
@@ -5592,14 +5767,12 @@ var LOADING_VIBES = [
|
|
|
5592
5767
|
];
|
|
5593
5768
|
function startProgress() {
|
|
5594
5769
|
if (!process.stdout.isTTY) {
|
|
5595
|
-
let
|
|
5770
|
+
let announced = false;
|
|
5596
5771
|
return {
|
|
5597
|
-
onProgress: (
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
console.log(pc3.dim(` counting local token usage\u2026 ${pct}%`));
|
|
5602
|
-
}
|
|
5772
|
+
onProgress: () => {
|
|
5773
|
+
if (announced) return;
|
|
5774
|
+
announced = true;
|
|
5775
|
+
console.log(pc2.dim(" Counting your token usage\u2026"));
|
|
5603
5776
|
},
|
|
5604
5777
|
stop: () => {
|
|
5605
5778
|
}
|
|
@@ -5620,9 +5793,9 @@ function startProgress() {
|
|
|
5620
5793
|
shown += (target - shown) * 0.3;
|
|
5621
5794
|
if (target - shown < 4e-3) shown = target;
|
|
5622
5795
|
const filled = Math.round(shown * width);
|
|
5623
|
-
const bar =
|
|
5796
|
+
const bar = pc2.yellow("\u2588".repeat(filled)) + pc2.dim("\u2591".repeat(width - filled));
|
|
5624
5797
|
const pct = String(Math.round(shown * 100)).padStart(3);
|
|
5625
|
-
process.stdout.write(`\r ${bar} ${pct}% ${
|
|
5798
|
+
process.stdout.write(`\r ${bar} ${pct}% ${pc2.dim(vibe())}\x1B[K`);
|
|
5626
5799
|
};
|
|
5627
5800
|
render();
|
|
5628
5801
|
const timer = setInterval(render, 60);
|
|
@@ -5646,7 +5819,7 @@ function openBrowser(url) {
|
|
|
5646
5819
|
async function confirm(question) {
|
|
5647
5820
|
if (!process.stdin.isTTY) return false;
|
|
5648
5821
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
5649
|
-
const answer = (await rl.question(`${question} ${
|
|
5822
|
+
const answer = (await rl.question(`${question} ${pc2.dim("[Y/n]")} `)).trim();
|
|
5650
5823
|
rl.close();
|
|
5651
5824
|
return answer === "" || /^y(es)?$/i.test(answer);
|
|
5652
5825
|
}
|
|
@@ -5662,20 +5835,20 @@ function showLocalDashboard(payload) {
|
|
|
5662
5835
|
})
|
|
5663
5836
|
);
|
|
5664
5837
|
console.log();
|
|
5665
|
-
console.log(` Local dashboard: ${
|
|
5666
|
-
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."));
|
|
5667
5840
|
openBrowser(`file://${file}`);
|
|
5668
5841
|
}
|
|
5669
5842
|
async function run(flags) {
|
|
5670
5843
|
if (!flags.quiet) {
|
|
5671
5844
|
printBanner();
|
|
5672
|
-
console.log(
|
|
5845
|
+
console.log(pc2.dim(` whoburnedmore v${VERSION} \xB7 ${flags.local ? "local mode" : apiBase()}`));
|
|
5673
5846
|
if (!flags.dryRun && !flags.noSubmit && !flags.local) {
|
|
5674
5847
|
console.log(
|
|
5675
|
-
|
|
5848
|
+
pc2.dim(" Counting your token usage and posting your rank \u2014 only daily totals leave your machine, never your prompts or code.")
|
|
5676
5849
|
);
|
|
5677
5850
|
console.log(
|
|
5678
|
-
|
|
5851
|
+
pc2.dim(" (`--local` keeps it fully offline \xB7 `private`/`remove` pull it anytime \xB7 details: whoburnedmore.com/trust)")
|
|
5679
5852
|
);
|
|
5680
5853
|
}
|
|
5681
5854
|
console.log();
|
|
@@ -5688,11 +5861,11 @@ async function run(flags) {
|
|
|
5688
5861
|
} finally {
|
|
5689
5862
|
progress.stop();
|
|
5690
5863
|
}
|
|
5691
|
-
const { entries, sessions, blocks,
|
|
5864
|
+
const { entries, sessions, blocks, tools, skills, projects, agent, attributionComplete } = collected;
|
|
5692
5865
|
if (entries.length === 0) {
|
|
5693
5866
|
console.log();
|
|
5694
5867
|
console.log(" Nothing to burn yet \u2014 no local usage found from any coding agent.");
|
|
5695
|
-
console.log(
|
|
5868
|
+
console.log(pc2.dim(" Use Claude Code, Codex, Gemini CLI (or friends) and come back."));
|
|
5696
5869
|
return;
|
|
5697
5870
|
}
|
|
5698
5871
|
const payload = { cliVersion: VERSION, entries };
|
|
@@ -5706,11 +5879,10 @@ async function run(flags) {
|
|
|
5706
5879
|
payload.attributionComplete = true;
|
|
5707
5880
|
if (flags.board) payload.board = flags.board;
|
|
5708
5881
|
if (flags.dryRun) {
|
|
5709
|
-
console.log(
|
|
5882
|
+
console.log(pc2.dim("\n --dry-run: this exact payload would be sent, nothing else:\n"));
|
|
5710
5883
|
console.log(JSON.stringify(payload, null, 2));
|
|
5711
5884
|
return;
|
|
5712
5885
|
}
|
|
5713
|
-
if (!flags.quiet) printSummary(entries);
|
|
5714
5886
|
if (flags.local) {
|
|
5715
5887
|
showLocalDashboard(payload);
|
|
5716
5888
|
if (!flags.quiet && process.stdin.isTTY) {
|
|
@@ -5719,55 +5891,46 @@ async function run(flags) {
|
|
|
5719
5891
|
ensureAnonKey,
|
|
5720
5892
|
anonSubmit,
|
|
5721
5893
|
openBrowser,
|
|
5722
|
-
log: (line) => console.log(
|
|
5894
|
+
log: (line) => console.log(pc2.dim(line))
|
|
5723
5895
|
});
|
|
5724
5896
|
}
|
|
5725
5897
|
return;
|
|
5726
5898
|
}
|
|
5727
5899
|
if (flags.noSubmit) {
|
|
5728
|
-
console.log(
|
|
5900
|
+
console.log(pc2.dim(" --no-submit: skipped the dashboard."));
|
|
5729
5901
|
return;
|
|
5730
5902
|
}
|
|
5731
5903
|
const anonKey = ensureAnonKey();
|
|
5732
5904
|
const result = await anonSubmit(anonKey, payload);
|
|
5905
|
+
try {
|
|
5906
|
+
recordSync();
|
|
5907
|
+
} catch {
|
|
5908
|
+
}
|
|
5733
5909
|
const target = result.boardUrl ?? claimUrl(result.dashboardUrl, anonKey);
|
|
5734
5910
|
if (!flags.quiet) {
|
|
5735
|
-
console.log(pc3.dim(" Opening your dashboard in your browser\u2026"));
|
|
5736
|
-
openBrowser(target);
|
|
5737
|
-
}
|
|
5738
|
-
console.log(
|
|
5739
|
-
` Submitted ${pc3.bold(String(result.upserted))} day-entries from ${toolsFound.join(", ")}.`
|
|
5740
|
-
);
|
|
5741
|
-
if (result.boardUrl) {
|
|
5742
5911
|
console.log(
|
|
5743
|
-
|
|
5912
|
+
pc2.green(" \u2713 Synced securely.") + pc2.dim(" Only your daily totals left this machine \u2014 never your prompts, code, or file names.")
|
|
5744
5913
|
);
|
|
5745
|
-
console.log(
|
|
5746
|
-
|
|
5747
|
-
}
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
);
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
pc3.dim(" Claim it (name + X) on the web to own your rank, or make it private / remove it.")
|
|
5755
|
-
);
|
|
5756
|
-
console.log(
|
|
5757
|
-
pc3.dim(" Manage anytime: `npx whoburnedmore private` \xB7 `npx whoburnedmore public` \xB7 `npx whoburnedmore remove`.")
|
|
5758
|
-
);
|
|
5759
|
-
}
|
|
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);
|
|
5760
5923
|
}
|
|
5761
|
-
if (!flags.quiet
|
|
5924
|
+
if (!flags.quiet) {
|
|
5762
5925
|
try {
|
|
5763
|
-
|
|
5926
|
+
reconcileAutoSync();
|
|
5764
5927
|
} catch {
|
|
5765
5928
|
}
|
|
5766
5929
|
}
|
|
5767
5930
|
if (!flags.quiet) {
|
|
5768
5931
|
console.log();
|
|
5769
5932
|
console.log(
|
|
5770
|
-
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.")
|
|
5771
5934
|
);
|
|
5772
5935
|
}
|
|
5773
5936
|
}
|
|
@@ -5779,7 +5942,7 @@ async function main() {
|
|
|
5779
5942
|
return;
|
|
5780
5943
|
}
|
|
5781
5944
|
const args = process.argv.slice(2);
|
|
5782
|
-
const command =
|
|
5945
|
+
const command = resolveCommand(args);
|
|
5783
5946
|
const flags = {
|
|
5784
5947
|
dryRun: args.includes("--dry-run"),
|
|
5785
5948
|
noSubmit: args.includes("--no-submit"),
|
|
@@ -5793,9 +5956,15 @@ async function main() {
|
|
|
5793
5956
|
break;
|
|
5794
5957
|
case "sync": {
|
|
5795
5958
|
if (!loadConfig()) return;
|
|
5959
|
+
rotateLogIfLarge();
|
|
5796
5960
|
await run({ ...flags, noSubmit: false, dryRun: false, local: false });
|
|
5797
5961
|
break;
|
|
5798
5962
|
}
|
|
5963
|
+
case "status":
|
|
5964
|
+
case "doctor": {
|
|
5965
|
+
for (const line of agentStatusReport()) console.log(line);
|
|
5966
|
+
break;
|
|
5967
|
+
}
|
|
5799
5968
|
case "private":
|
|
5800
5969
|
case "public": {
|
|
5801
5970
|
const cfg = loadConfig();
|
|
@@ -5826,12 +5995,9 @@ async function main() {
|
|
|
5826
5995
|
console.log(` ${uninstallAutoSync()}`);
|
|
5827
5996
|
break;
|
|
5828
5997
|
case "help":
|
|
5829
|
-
case "--help":
|
|
5830
5998
|
printHelp();
|
|
5831
5999
|
break;
|
|
5832
6000
|
case "version":
|
|
5833
|
-
case "--version":
|
|
5834
|
-
case "-v":
|
|
5835
6001
|
console.log(VERSION);
|
|
5836
6002
|
break;
|
|
5837
6003
|
default:
|
|
@@ -5842,17 +6008,18 @@ async function main() {
|
|
|
5842
6008
|
}
|
|
5843
6009
|
function printHelp() {
|
|
5844
6010
|
console.log(`
|
|
5845
|
-
${
|
|
6011
|
+
${pc2.bold("whoburnedmore")} \u2014 who burned more tokens, you or them?
|
|
5846
6012
|
|
|
5847
|
-
${
|
|
6013
|
+
${pc2.bold("usage")}
|
|
5848
6014
|
npx whoburnedmore burn + land on the public leaderboard, open your dashboard
|
|
5849
6015
|
npx whoburnedmore --board=CODE compare with friends \u2014 join their board (no sign-in)
|
|
5850
6016
|
npx whoburnedmore --local build the dashboard on your machine and open it (offline)
|
|
5851
6017
|
npx whoburnedmore --dry-run print exactly what would be sent, send nothing
|
|
5852
|
-
npx whoburnedmore --no-submit
|
|
6018
|
+
npx whoburnedmore --no-submit collect locally, send nothing (no dashboard)
|
|
5853
6019
|
npx whoburnedmore private hide your dashboard from the leaderboard
|
|
5854
6020
|
npx whoburnedmore public put it back on the leaderboard
|
|
5855
6021
|
npx whoburnedmore remove delete your dashboard and its data
|
|
6022
|
+
npx whoburnedmore status check background-sync health (last sync, staleness)
|
|
5856
6023
|
npx whoburnedmore uninstall-sync turn off the background sync
|
|
5857
6024
|
npx whoburnedmore install-sync turn it back on after uninstalling
|
|
5858
6025
|
|
|
@@ -5866,7 +6033,7 @@ function printHelp() {
|
|
|
5866
6033
|
`);
|
|
5867
6034
|
}
|
|
5868
6035
|
main().catch((err) => {
|
|
5869
|
-
console.error(
|
|
6036
|
+
console.error(pc2.red(`
|
|
5870
6037
|
${err.message}
|
|
5871
6038
|
`));
|
|
5872
6039
|
process.exitCode = 1;
|