whoburnedmore 0.8.9 → 0.9.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/dist/index.js +236 -28
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -21,12 +21,17 @@ function parseBoard(args) {
|
|
|
21
21
|
function parseOrg(args) {
|
|
22
22
|
return parseValueFlag(args, "--org");
|
|
23
23
|
}
|
|
24
|
+
function parsePass(args) {
|
|
25
|
+
return parseValueFlag(args, "--pass") ?? parseValueFlag(args, "--code");
|
|
26
|
+
}
|
|
24
27
|
function parseInstallToken(args) {
|
|
25
28
|
return parseValueFlag(args, "--token");
|
|
26
29
|
}
|
|
27
30
|
function applyScope(payload, flags) {
|
|
28
31
|
if (flags.board) payload.board = flags.board;
|
|
29
32
|
if (flags.org) payload.org = flags.org;
|
|
33
|
+
if (flags.org && flags.orgCode)
|
|
34
|
+
payload.orgCode = flags.orgCode;
|
|
30
35
|
return payload;
|
|
31
36
|
}
|
|
32
37
|
function parseValueFlag(args, name) {
|
|
@@ -46,7 +51,7 @@ function resolveCommand(args) {
|
|
|
46
51
|
if (args.includes("--version") || args.includes("-v") || args.includes("version")) {
|
|
47
52
|
return "version";
|
|
48
53
|
}
|
|
49
|
-
const valueFlags = /* @__PURE__ */ new Set(["--board", "--org", "--token"]);
|
|
54
|
+
const valueFlags = /* @__PURE__ */ new Set(["--board", "--org", "--token", "--pass", "--code"]);
|
|
50
55
|
for (let i = 0; i < args.length; i++) {
|
|
51
56
|
const a = args[i];
|
|
52
57
|
if (a.startsWith("-")) {
|
|
@@ -121,6 +126,11 @@ function claimUrl(dashboardUrl, anonKey) {
|
|
|
121
126
|
function boardClaimUrl(boardUrl, slug, anonKey) {
|
|
122
127
|
return `${boardUrl}#k=${encodeURIComponent(anonKey)}&u=${encodeURIComponent(slug)}`;
|
|
123
128
|
}
|
|
129
|
+
function resolveOpenTarget(result, anonKey) {
|
|
130
|
+
const baseUrl = result.orgBoardUrl ?? result.boardUrl ?? result.dashboardUrl;
|
|
131
|
+
const target = result.orgBoardUrl ? boardClaimUrl(result.orgBoardUrl, result.slug, anonKey) : result.boardUrl ? boardClaimUrl(result.boardUrl, result.slug, anonKey) : claimUrl(result.dashboardUrl, anonKey);
|
|
132
|
+
return { baseUrl, target };
|
|
133
|
+
}
|
|
124
134
|
async function anonVisibility(anonKey, listed) {
|
|
125
135
|
const { status, body } = await post(
|
|
126
136
|
"/v1/anon/visibility",
|
|
@@ -357,16 +367,13 @@ function installAutoSync() {
|
|
|
357
367
|
return `launchd agent installed (${plistPath}), syncing every ${syncIntervalLabel()}`;
|
|
358
368
|
}
|
|
359
369
|
if (os === "linux") {
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const res = spawnSync("crontab", ["-"], { input: next });
|
|
368
|
-
if (res.status !== 0) throw new Error("could not install crontab entry");
|
|
369
|
-
return `cron entry installed, syncing every ${syncIntervalLabel()}`;
|
|
370
|
+
const viaCron = tryInstallCron();
|
|
371
|
+
if (viaCron) return viaCron;
|
|
372
|
+
const viaSystemd = tryInstallSystemd();
|
|
373
|
+
if (viaSystemd) return viaSystemd;
|
|
374
|
+
throw new Error(
|
|
375
|
+
"could not install background sync: no usable crontab or systemd user timer. Run `whoburnedmore daemon` under your process manager (systemd service, Docker CMD, pm2 or nohup) to keep syncing."
|
|
376
|
+
);
|
|
370
377
|
}
|
|
371
378
|
if (os === "win32") {
|
|
372
379
|
const res = spawnSync("schtasks", [
|
|
@@ -393,6 +400,90 @@ function expectedLinuxCronLine(opts) {
|
|
|
393
400
|
const command = syncCommandArgs(opts?.npmPath).map(shellQuote).join(" ");
|
|
394
401
|
return `${cronSchedule()} ${command} >${shellQuote(opts?.logPath ?? syncLogPath())} 2>&1`;
|
|
395
402
|
}
|
|
403
|
+
var SYSTEMD_UNIT = "whoburnedmore-sync";
|
|
404
|
+
function systemdUserDir() {
|
|
405
|
+
const base = process.env.XDG_CONFIG_HOME?.trim() || join2(homedir2(), ".config");
|
|
406
|
+
return join2(base, "systemd", "user");
|
|
407
|
+
}
|
|
408
|
+
function systemdServicePath() {
|
|
409
|
+
return join2(systemdUserDir(), `${SYSTEMD_UNIT}.service`);
|
|
410
|
+
}
|
|
411
|
+
function systemdTimerPath() {
|
|
412
|
+
return join2(systemdUserDir(), `${SYSTEMD_UNIT}.timer`);
|
|
413
|
+
}
|
|
414
|
+
function systemdQuote(value) {
|
|
415
|
+
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
416
|
+
}
|
|
417
|
+
function buildSystemdService(commandArgs = syncCommandArgs()) {
|
|
418
|
+
const execStart = commandArgs.map(systemdQuote).join(" ");
|
|
419
|
+
return `[Unit]
|
|
420
|
+
Description=whoburnedmore background token-usage sync
|
|
421
|
+
|
|
422
|
+
[Service]
|
|
423
|
+
Type=oneshot
|
|
424
|
+
ExecStart=${execStart}
|
|
425
|
+
`;
|
|
426
|
+
}
|
|
427
|
+
function buildSystemdTimer(mins = SYNC_INTERVAL_MINUTES) {
|
|
428
|
+
return `[Unit]
|
|
429
|
+
Description=whoburnedmore background token-usage sync timer
|
|
430
|
+
|
|
431
|
+
[Timer]
|
|
432
|
+
OnBootSec=1min
|
|
433
|
+
OnUnitActiveSec=${mins}min
|
|
434
|
+
Persistent=true
|
|
435
|
+
|
|
436
|
+
[Install]
|
|
437
|
+
WantedBy=timers.target
|
|
438
|
+
`;
|
|
439
|
+
}
|
|
440
|
+
function binaryExists(cmd, probeArgs = ["--version"]) {
|
|
441
|
+
const res = spawnSync(cmd, probeArgs, { stdio: "ignore" });
|
|
442
|
+
return !res.error;
|
|
443
|
+
}
|
|
444
|
+
function linuxSyncMechanism() {
|
|
445
|
+
const cron = spawnSync("crontab", ["-l"], { encoding: "utf8" });
|
|
446
|
+
if (!cron.error && cron.status === 0 && cron.stdout.includes("whoburnedmore")) {
|
|
447
|
+
return "cron";
|
|
448
|
+
}
|
|
449
|
+
if (existsSync2(systemdTimerPath())) return "systemd";
|
|
450
|
+
return "none";
|
|
451
|
+
}
|
|
452
|
+
function tryInstallCron() {
|
|
453
|
+
if (!binaryExists("crontab", ["-l"])) return null;
|
|
454
|
+
const line = expectedLinuxCronLine();
|
|
455
|
+
const current = spawnSync("crontab", ["-l"], { encoding: "utf8" });
|
|
456
|
+
const existing = current.status === 0 ? current.stdout : "";
|
|
457
|
+
const kept = existing.split("\n").filter((l) => !l.includes("whoburnedmore")).join("\n");
|
|
458
|
+
const next = `${kept.trimEnd()}
|
|
459
|
+
${line}
|
|
460
|
+
`.replace(/^\n+/, "");
|
|
461
|
+
const res = spawnSync("crontab", ["-"], { input: next });
|
|
462
|
+
if (res.status !== 0) return null;
|
|
463
|
+
return `cron entry installed, syncing every ${syncIntervalLabel()}`;
|
|
464
|
+
}
|
|
465
|
+
function tryInstallSystemd() {
|
|
466
|
+
if (!binaryExists("systemctl", ["--user", "--version"])) return null;
|
|
467
|
+
try {
|
|
468
|
+
mkdirSync2(systemdUserDir(), { recursive: true });
|
|
469
|
+
writeFileSync2(systemdServicePath(), buildSystemdService());
|
|
470
|
+
writeFileSync2(systemdTimerPath(), buildSystemdTimer());
|
|
471
|
+
} catch {
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
|
|
475
|
+
const res = spawnSync(
|
|
476
|
+
"systemctl",
|
|
477
|
+
["--user", "enable", "--now", `${SYSTEMD_UNIT}.timer`],
|
|
478
|
+
{ stdio: "ignore" }
|
|
479
|
+
);
|
|
480
|
+
if (res.status !== 0) {
|
|
481
|
+
rmSync(systemdServicePath(), { force: true });
|
|
482
|
+
rmSync(systemdTimerPath(), { force: true });
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
return `systemd user timer installed, syncing every ${syncIntervalLabel()} (run \`loginctl enable-linger\` to keep syncing while logged out)`;
|
|
486
|
+
}
|
|
396
487
|
function uninstallAutoSync() {
|
|
397
488
|
const os = platform();
|
|
398
489
|
if (os === "darwin") {
|
|
@@ -404,12 +495,25 @@ function uninstallAutoSync() {
|
|
|
404
495
|
return "launchd agent removed";
|
|
405
496
|
}
|
|
406
497
|
if (os === "linux") {
|
|
498
|
+
let removed = false;
|
|
407
499
|
const current = spawnSync("crontab", ["-l"], { encoding: "utf8" });
|
|
408
|
-
if (current.status === 0 && current.stdout.includes("whoburnedmore")) {
|
|
500
|
+
if (!current.error && current.status === 0 && current.stdout.includes("whoburnedmore")) {
|
|
409
501
|
const next = current.stdout.split("\n").filter((l) => !l.includes("whoburnedmore")).join("\n");
|
|
410
502
|
spawnSync("crontab", ["-"], { input: next });
|
|
503
|
+
removed = true;
|
|
504
|
+
}
|
|
505
|
+
if (existsSync2(systemdTimerPath())) {
|
|
506
|
+
spawnSync(
|
|
507
|
+
"systemctl",
|
|
508
|
+
["--user", "disable", "--now", `${SYSTEMD_UNIT}.timer`],
|
|
509
|
+
{ stdio: "ignore" }
|
|
510
|
+
);
|
|
511
|
+
rmSync(systemdServicePath(), { force: true });
|
|
512
|
+
rmSync(systemdTimerPath(), { force: true });
|
|
513
|
+
spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
|
|
514
|
+
removed = true;
|
|
411
515
|
}
|
|
412
|
-
return "
|
|
516
|
+
return removed ? "background sync removed" : "nothing to remove";
|
|
413
517
|
}
|
|
414
518
|
if (os === "win32") {
|
|
415
519
|
spawnSync("schtasks", ["/Delete", "/F", "/TN", "whoburnedmore-sync"]);
|
|
@@ -420,8 +524,7 @@ function uninstallAutoSync() {
|
|
|
420
524
|
function autoSyncInstalled() {
|
|
421
525
|
if (platform() === "darwin") return existsSync2(launchAgentPath());
|
|
422
526
|
if (platform() === "linux") {
|
|
423
|
-
|
|
424
|
-
return current.status === 0 && current.stdout.includes("whoburnedmore");
|
|
527
|
+
return linuxSyncMechanism() !== "none";
|
|
425
528
|
}
|
|
426
529
|
if (platform() === "win32") {
|
|
427
530
|
const res = spawnSync("schtasks", ["/Query", "/TN", "whoburnedmore-sync"], {
|
|
@@ -431,22 +534,40 @@ function autoSyncInstalled() {
|
|
|
431
534
|
}
|
|
432
535
|
return false;
|
|
433
536
|
}
|
|
537
|
+
function readInstalledSystemd() {
|
|
538
|
+
try {
|
|
539
|
+
return `${readFileSync2(systemdServicePath(), "utf8")}
|
|
540
|
+
${readFileSync2(systemdTimerPath(), "utf8")}`;
|
|
541
|
+
} catch {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
function expectedSystemd() {
|
|
546
|
+
return `${buildSystemdService()}
|
|
547
|
+
${buildSystemdTimer()}`;
|
|
548
|
+
}
|
|
434
549
|
function readInstalledAgent() {
|
|
435
550
|
if (platform() === "darwin") {
|
|
436
551
|
const p = launchAgentPath();
|
|
437
552
|
return existsSync2(p) ? readFileSync2(p, "utf8") : null;
|
|
438
553
|
}
|
|
439
554
|
if (platform() === "linux") {
|
|
440
|
-
const
|
|
441
|
-
if (
|
|
442
|
-
|
|
443
|
-
|
|
555
|
+
const mech = linuxSyncMechanism();
|
|
556
|
+
if (mech === "cron") {
|
|
557
|
+
const current = spawnSync("crontab", ["-l"], { encoding: "utf8" });
|
|
558
|
+
if (current.error || current.status !== 0) return null;
|
|
559
|
+
return current.stdout.split("\n").find((l) => l.includes("whoburnedmore")) ?? null;
|
|
560
|
+
}
|
|
561
|
+
if (mech === "systemd") return readInstalledSystemd();
|
|
562
|
+
return null;
|
|
444
563
|
}
|
|
445
564
|
return null;
|
|
446
565
|
}
|
|
447
566
|
function expectedAgent() {
|
|
448
567
|
if (platform() === "darwin") return expectedDarwinPlist();
|
|
449
|
-
if (platform() === "linux")
|
|
568
|
+
if (platform() === "linux") {
|
|
569
|
+
return linuxSyncMechanism() === "systemd" ? expectedSystemd() : expectedLinuxCronLine();
|
|
570
|
+
}
|
|
450
571
|
return null;
|
|
451
572
|
}
|
|
452
573
|
function autoSyncDrift() {
|
|
@@ -531,6 +652,24 @@ function printBanner() {
|
|
|
531
652
|
console.log();
|
|
532
653
|
}
|
|
533
654
|
|
|
655
|
+
// src/daemon.ts
|
|
656
|
+
async function daemonLoop(deps) {
|
|
657
|
+
let cycles = 0;
|
|
658
|
+
while (!deps.isStopped()) {
|
|
659
|
+
cycles++;
|
|
660
|
+
try {
|
|
661
|
+
await deps.runOnce();
|
|
662
|
+
deps.log("synced");
|
|
663
|
+
} catch (err) {
|
|
664
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
665
|
+
deps.log(`sync failed: ${message} \u2014 retrying next cycle`);
|
|
666
|
+
}
|
|
667
|
+
if (deps.isStopped()) break;
|
|
668
|
+
await deps.wait(deps.intervalMs);
|
|
669
|
+
}
|
|
670
|
+
return cycles;
|
|
671
|
+
}
|
|
672
|
+
|
|
534
673
|
// src/collect.ts
|
|
535
674
|
import { execFile } from "node:child_process";
|
|
536
675
|
import { createRequire as createRequire3 } from "node:module";
|
|
@@ -5691,7 +5830,14 @@ var SubmitPayload = external_exports.object({
|
|
|
5691
5830
|
/** Optional friends-board code (from `--board=<code>`): auto-join this board on submit. */
|
|
5692
5831
|
board: external_exports.string().min(1).max(32).optional(),
|
|
5693
5832
|
/** Optional organization slug (from `--org=<slug>`): auto-join this org on submit. */
|
|
5694
|
-
org: external_exports.string().min(2).max(32).optional()
|
|
5833
|
+
org: external_exports.string().min(2).max(32).optional(),
|
|
5834
|
+
/**
|
|
5835
|
+
* Optional org join password (from `--pass=<code>` / `--code=<code>`): required
|
|
5836
|
+
* to attach a CLI run to an `org`. Back-compat: omittable — a run with no org
|
|
5837
|
+
* never needs it, and a wrong/missing code only skips the org attach (the
|
|
5838
|
+
* personal submit still succeeds).
|
|
5839
|
+
*/
|
|
5840
|
+
orgCode: external_exports.string().min(1).max(64).optional()
|
|
5695
5841
|
});
|
|
5696
5842
|
var AnonSubmitPayload = SubmitPayload.extend({
|
|
5697
5843
|
/** Client-generated secret (hex). The server stores only its hash. */
|
|
@@ -5740,13 +5886,15 @@ var RESERVED_SLUGS = /* @__PURE__ */ new Set([
|
|
|
5740
5886
|
]);
|
|
5741
5887
|
var SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,30}[a-z0-9])?$/;
|
|
5742
5888
|
function isValidSlug(slug) {
|
|
5743
|
-
return typeof slug === "string" && slug.length >= 2 && slug.length <= 32 && SLUG_RE.test(slug) &&
|
|
5889
|
+
return typeof slug === "string" && slug.length >= 2 && slug.length <= 32 && SLUG_RE.test(slug) && // Reject consecutive hyphens — the regex above permits "a--b", which makes
|
|
5890
|
+
// for confusing subdomains and is never a deliberate slug.
|
|
5891
|
+
!slug.includes("--") && !RESERVED_SLUGS.has(slug);
|
|
5744
5892
|
}
|
|
5745
5893
|
var OrgSlug = external_exports.string().min(2).max(32).refine(isValidSlug, "invalid or reserved slug");
|
|
5746
5894
|
var OrgWindow = external_exports.object({
|
|
5747
5895
|
startDate: DateString.nullable().optional(),
|
|
5748
5896
|
endDate: DateString.nullable().optional()
|
|
5749
|
-
});
|
|
5897
|
+
}).refine((w) => !(w.startDate && w.endDate) || w.endDate >= w.startDate, { message: "endDate must be on or after startDate", path: ["endDate"] });
|
|
5750
5898
|
var OrgJoinPolicy = external_exports.object({
|
|
5751
5899
|
allowCodeJoin: external_exports.boolean().default(true),
|
|
5752
5900
|
allowDomainJoin: external_exports.boolean().default(false),
|
|
@@ -6205,8 +6353,7 @@ async function run(flags) {
|
|
|
6205
6353
|
}
|
|
6206
6354
|
} catch {
|
|
6207
6355
|
}
|
|
6208
|
-
const baseUrl = result
|
|
6209
|
-
const target = result.boardUrl ? boardClaimUrl(result.boardUrl, result.slug, anonKey) : claimUrl(result.dashboardUrl, anonKey);
|
|
6356
|
+
const { baseUrl, target } = resolveOpenTarget(result, anonKey);
|
|
6210
6357
|
const trusted = isTrustedWebUrl(baseUrl);
|
|
6211
6358
|
if (!flags.quiet) {
|
|
6212
6359
|
console.log(
|
|
@@ -6274,6 +6421,53 @@ async function linkServerInstall(token) {
|
|
|
6274
6421
|
}
|
|
6275
6422
|
console.log(` Profile: ${linked.profileUrl}`);
|
|
6276
6423
|
}
|
|
6424
|
+
function waitOrAbort(ms, signal) {
|
|
6425
|
+
if (signal.aborted) return Promise.resolve();
|
|
6426
|
+
return new Promise((resolve) => {
|
|
6427
|
+
const onAbort = () => {
|
|
6428
|
+
clearTimeout(timer);
|
|
6429
|
+
resolve();
|
|
6430
|
+
};
|
|
6431
|
+
const timer = setTimeout(() => {
|
|
6432
|
+
signal.removeEventListener("abort", onAbort);
|
|
6433
|
+
resolve();
|
|
6434
|
+
}, ms);
|
|
6435
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
6436
|
+
});
|
|
6437
|
+
}
|
|
6438
|
+
async function runDaemon() {
|
|
6439
|
+
ensureAnonKey();
|
|
6440
|
+
const controller = new AbortController();
|
|
6441
|
+
const onSignal = () => controller.abort();
|
|
6442
|
+
process.on("SIGINT", onSignal);
|
|
6443
|
+
process.on("SIGTERM", onSignal);
|
|
6444
|
+
console.log(pc2.bold(" whoburnedmore daemon"));
|
|
6445
|
+
console.log(
|
|
6446
|
+
pc2.dim(
|
|
6447
|
+
` Syncing in the foreground every ${syncIntervalLabel()} \u2014 run this under systemd, a Docker CMD, pm2 or nohup to keep a server on the leaderboard. Ctrl-C to stop.`
|
|
6448
|
+
)
|
|
6449
|
+
);
|
|
6450
|
+
if (!process.env.WHOBURNEDMORE_CONFIG_DIR) {
|
|
6451
|
+
console.log(
|
|
6452
|
+
pc2.dim(
|
|
6453
|
+
" Tip: set WHOBURNEDMORE_CONFIG_DIR to a persistent path so this machine's identity survives container/VM restarts."
|
|
6454
|
+
)
|
|
6455
|
+
);
|
|
6456
|
+
}
|
|
6457
|
+
console.log();
|
|
6458
|
+
const cycles = await daemonLoop({
|
|
6459
|
+
intervalMs: SYNC_INTERVAL_MINUTES * 6e4,
|
|
6460
|
+
isStopped: () => controller.signal.aborted,
|
|
6461
|
+
log: (line) => console.log(` ${pc2.dim((/* @__PURE__ */ new Date()).toISOString())} ${line}`),
|
|
6462
|
+
wait: (ms) => waitOrAbort(ms, controller.signal),
|
|
6463
|
+
runOnce: async () => {
|
|
6464
|
+
rotateLogIfLarge();
|
|
6465
|
+
await run({ dryRun: false, noSubmit: false, local: false, quiet: true });
|
|
6466
|
+
}
|
|
6467
|
+
});
|
|
6468
|
+
console.log();
|
|
6469
|
+
console.log(pc2.dim(` Daemon stopped after ${cycles} sync cycle${cycles === 1 ? "" : "s"}.`));
|
|
6470
|
+
}
|
|
6277
6471
|
async function main() {
|
|
6278
6472
|
const major = Number(process.versions.node.split(".")[0]);
|
|
6279
6473
|
if (major < 20) {
|
|
@@ -6282,14 +6476,16 @@ async function main() {
|
|
|
6282
6476
|
return;
|
|
6283
6477
|
}
|
|
6284
6478
|
const args = process.argv.slice(2);
|
|
6285
|
-
const
|
|
6479
|
+
const baseCommand = resolveCommand(args);
|
|
6480
|
+
const command = args.includes("--watch") && (baseCommand === "run" || baseCommand === "sync") ? "daemon" : baseCommand;
|
|
6286
6481
|
const flags = {
|
|
6287
6482
|
dryRun: args.includes("--dry-run"),
|
|
6288
6483
|
noSubmit: args.includes("--no-submit"),
|
|
6289
6484
|
local: args.includes("--local"),
|
|
6290
6485
|
quiet: command === "sync",
|
|
6291
6486
|
board: parseBoard(args),
|
|
6292
|
-
org: parseOrg(args)
|
|
6487
|
+
org: parseOrg(args),
|
|
6488
|
+
orgCode: parsePass(args)
|
|
6293
6489
|
};
|
|
6294
6490
|
switch (command) {
|
|
6295
6491
|
case "run":
|
|
@@ -6304,6 +6500,9 @@ async function main() {
|
|
|
6304
6500
|
case "link":
|
|
6305
6501
|
await linkServerInstall(parseInstallToken(args));
|
|
6306
6502
|
break;
|
|
6503
|
+
case "daemon":
|
|
6504
|
+
await runDaemon();
|
|
6505
|
+
break;
|
|
6307
6506
|
case "status":
|
|
6308
6507
|
case "doctor": {
|
|
6309
6508
|
for (const line of agentStatusReport()) console.log(line);
|
|
@@ -6361,7 +6560,8 @@ function printHelp() {
|
|
|
6361
6560
|
npx whoburnedmore --local build the dashboard on your machine and open it (offline)
|
|
6362
6561
|
npx whoburnedmore --dry-run print exactly what would be sent, send nothing
|
|
6363
6562
|
npx whoburnedmore --no-submit collect locally, send nothing (no dashboard)
|
|
6364
|
-
npx whoburnedmore link --token=TOKEN link this server to your signed-in account
|
|
6563
|
+
npx whoburnedmore link --token=TOKEN link this server/VM to your signed-in account
|
|
6564
|
+
npx whoburnedmore daemon keep syncing in the foreground (VMs/containers with no cron)
|
|
6365
6565
|
npx whoburnedmore private hide your dashboard from the leaderboard
|
|
6366
6566
|
npx whoburnedmore public put it back on the leaderboard
|
|
6367
6567
|
npx whoburnedmore remove delete your dashboard and its data
|
|
@@ -6376,6 +6576,14 @@ function printHelp() {
|
|
|
6376
6576
|
daily aggregate numbers (date, tool, model, token counts, est. cost) ever leave
|
|
6377
6577
|
your machine \u2014 never prompts, code, or file names. With --local, nothing leaves
|
|
6378
6578
|
your machine at all.
|
|
6579
|
+
|
|
6580
|
+
${pc2.bold("servers & VMs")}
|
|
6581
|
+
Generate a one-time \`link\` command from your profile on whoburnedmore.com and
|
|
6582
|
+
run it inside the VM to bind that machine to your account. On a persistent VM
|
|
6583
|
+
background sync uses cron or a systemd user timer automatically; in a container
|
|
6584
|
+
or any host without a scheduler, run \`whoburnedmore daemon\` under your process
|
|
6585
|
+
manager instead. Set WHOBURNEDMORE_CONFIG_DIR to a persistent path so the
|
|
6586
|
+
machine identity survives restarts. See docs/SERVER-VM-SETUP.md.
|
|
6379
6587
|
`);
|
|
6380
6588
|
}
|
|
6381
6589
|
main().catch((err) => {
|