whoburnedmore 0.9.22 → 0.9.23

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.
Files changed (3) hide show
  1. package/README.md +19 -2
  2. package/dist/index.js +340 -136
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -53,10 +53,27 @@ npx whoburnedmore uninstall-sync turn off the background sync
53
53
 
54
54
  After your first run, a background sync keeps your page fresh automatically
55
55
  every 15 minutes (`uninstall-sync` to stop). The installed background job runs
56
- the latest published `whoburnedmore` package on each sync tick, so future CLI
57
- fixes are picked up automatically after the next refresh. The server endpoint is
56
+ the exact CLI version that installed it, with npm lifecycle scripts disabled.
57
+ Run an updated CLI in the foreground to repair the job and advance its version;
58
+ background jobs do not automatically download a newer release. The server endpoint is
58
59
  overridable with `WHOBURNEDMORE_API`.
59
60
 
61
+ On Windows, sync runs while you are signed in using a windowless Windows Script
62
+ Host launcher. It keeps your normal user/network access and writes output and exit
63
+ codes to `~/.config/whoburnedmore/sync.log` (or `WHOBURNEDMORE_CONFIG_DIR/sync.log`).
64
+ The log rotates to `sync.log.1` after 256 KiB. Windows Script Host and Windows
65
+ PowerShell must be available; managed machines may restrict them.
66
+
67
+ To repair an older Windows task that opens a console, run `whoburnedmore install-sync`
68
+ from an updated installation. `whoburnedmore status` reports stale definitions and
69
+ the last successful upload. If a run still fails, inspect `sync.log` or run
70
+ `whoburnedmore sync` in a terminal. A task result of `1` alone does not identify
71
+ whether npm, collection, authentication, or an upload failed. Setting the task's
72
+ `Hidden` property only hides it in Task Scheduler; changing its logon type to S4U
73
+ can restrict network/encrypted-file access and is not the windowless-sync fix.
74
+ Tasks explicitly disabled in Task Scheduler stay disabled during automatic repair;
75
+ `install-sync` explicitly enables them again.
76
+
60
77
  ## Privacy & transparency
61
78
 
62
79
  This repository exists so you can read exactly what the CLI does before you run it. It
package/dist/index.js CHANGED
@@ -290,19 +290,19 @@ async function redeemServerInstall(token, anonKey) {
290
290
  }
291
291
 
292
292
  // src/autosync.ts
293
- import { spawnSync } from "node:child_process";
293
+ import { spawnSync as spawnSync2 } from "node:child_process";
294
294
  import { createRequire } from "node:module";
295
295
  import {
296
- existsSync as existsSync2,
296
+ existsSync as existsSync3,
297
297
  mkdirSync as mkdirSync2,
298
- readFileSync,
298
+ readFileSync as readFileSync2,
299
299
  renameSync as renameSync2,
300
- rmSync as rmSync2,
300
+ rmSync as rmSync3,
301
301
  statSync,
302
- writeFileSync as writeFileSync2
302
+ writeFileSync as writeFileSync3
303
303
  } from "node:fs";
304
304
  import { homedir as homedir2, platform } from "node:os";
305
- import { dirname as dirname2, join as join2, posix, win32 } from "node:path";
305
+ import { dirname as dirname2, join as join3, posix, resolve, win32 as win322 } from "node:path";
306
306
 
307
307
  // src/config.ts
308
308
  import { createHash, randomBytes as randomBytes2 } from "node:crypto";
@@ -464,6 +464,203 @@ function clearAuth(dir = defaultConfigDir()) {
464
464
  saveConfig(dir, rest);
465
465
  }
466
466
 
467
+ // src/windows-autosync.ts
468
+ import { spawnSync } from "node:child_process";
469
+ import { existsSync as existsSync2, mkdtempSync, readFileSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
470
+ import { tmpdir } from "node:os";
471
+ import { join as join2, win32 } from "node:path";
472
+ var WINDOWS_TASK_NAME = "whoburnedmore-sync";
473
+ var psQuote = (value) => `'${value.replaceAll("'", "''")}'`;
474
+ var encodePowerShell = (script) => Buffer.from(script, "utf16le").toString("base64");
475
+ function windowsLauncherPath(opts) {
476
+ return join2(opts.configDir, "sync-launcher.js");
477
+ }
478
+ function windowsHostPath(opts) {
479
+ return win32.join(opts.systemRoot, "System32", "wscript.exe");
480
+ }
481
+ function resolveWindowsNpmCli(opts = {}) {
482
+ const nodePath = opts.nodePath ?? process.execPath;
483
+ const env = opts.env ?? process.env;
484
+ const exists = opts.exists ?? existsSync2;
485
+ const candidates = [
486
+ // Prefer the npm belonging to the captured node runtime over an npx cache.
487
+ win32.join(win32.dirname(nodePath), "node_modules", "npm", "bin", "npm-cli.js"),
488
+ env.npm_execpath,
489
+ env.APPDATA && win32.join(env.APPDATA, "npm", "node_modules", "npm", "bin", "npm-cli.js")
490
+ ];
491
+ for (const candidate of candidates) {
492
+ if (candidate && win32.isAbsolute(candidate) && win32.basename(candidate).toLowerCase() === "npm-cli.js" && exists(candidate)) return candidate;
493
+ }
494
+ throw new Error("could not locate npm-cli.js for background sync; repair your Node/npm installation and run `whoburnedmore install-sync` again");
495
+ }
496
+ function buildWindowsLauncher(opts) {
497
+ const log = join2(opts.configDir, "sync.log");
498
+ const script = [
499
+ "$ErrorActionPreference = 'Stop'",
500
+ // Node emits UTF-8; Windows PowerShell 5.1 otherwise decodes native output
501
+ // using the console's legacy code page before writing our UTF-8 log.
502
+ `$log = ${psQuote(log)}`,
503
+ "try {",
504
+ " [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding -ArgumentList $false",
505
+ ` Set-Location -LiteralPath ${psQuote(opts.configDir)}`,
506
+ " if ((Test-Path -LiteralPath $log) -and (Get-Item -LiteralPath $log).Length -gt 262144) {",
507
+ " Move-Item -LiteralPath $log -Destination ($log + '.1') -Force",
508
+ " }",
509
+ ...opts.envPairs.map(([key, value]) => ` $env:${key} = ${psQuote(value)}`),
510
+ // Use Windows' inherited PATH, not the colon-separated Unix sync PATH.
511
+ ` $env:Path = ${psQuote(win32.dirname(opts.nodePath) + ";")} + $env:Path`,
512
+ " ('[' + [DateTime]::UtcNow.ToString('o') + '] sync started') | Out-File -LiteralPath $log -Append -Encoding utf8",
513
+ // PS 5.1 turns native stderr into error records. Continue preserves npm's
514
+ // true exit code even when a successful run emits warnings on stderr.
515
+ " $ErrorActionPreference = 'Continue'",
516
+ " $LASTEXITCODE = 1",
517
+ ` & ${[opts.nodePath, opts.npmCliPath, ...opts.commandArgs].map(psQuote).join(" ")} 2>&1 | Out-File -LiteralPath $log -Append -Encoding utf8`,
518
+ " $code = $LASTEXITCODE",
519
+ " ('[' + [DateTime]::UtcNow.ToString('o') + '] sync exited ' + $code) | Out-File -LiteralPath $log -Append -Encoding utf8",
520
+ " exit $code",
521
+ "} catch {",
522
+ " $_ | Out-File -LiteralPath $log -Append -Encoding utf8",
523
+ " exit 1",
524
+ "}"
525
+ ].join("\r\n");
526
+ const command = `"${win32.join(opts.systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")}" -NoLogo -NoProfile -NonInteractive -EncodedCommand ${encodePowerShell(script)}`;
527
+ return `// Generated by whoburnedmore; run through wscript, not node.\r
528
+ var shell = new ActiveXObject("WScript.Shell");\r
529
+ WScript.Quit(shell.Run(${JSON.stringify(command)}, 0, true));\r
530
+ `;
531
+ }
532
+ function xmlEscape(value) {
533
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
534
+ }
535
+ function windowsTaskArguments(opts) {
536
+ return `//B //Nologo //E:JScript "${windowsLauncherPath(opts)}"`;
537
+ }
538
+ function buildWindowsTaskXml(opts, start = /* @__PURE__ */ new Date()) {
539
+ return `<?xml version="1.0" encoding="UTF-16"?>
540
+ <Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
541
+ <Triggers><TimeTrigger>
542
+ <Repetition><Interval>PT${opts.intervalMinutes}M</Interval><StopAtDurationEnd>false</StopAtDurationEnd></Repetition>
543
+ <StartBoundary>${start.toISOString()}</StartBoundary><Enabled>true</Enabled>
544
+ </TimeTrigger></Triggers>
545
+ <Principals><Principal id="Author"><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals>
546
+ <Settings>
547
+ <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
548
+ <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
549
+ <StartWhenAvailable>true</StartWhenAvailable><Enabled>true</Enabled>
550
+ <ExecutionTimeLimit>PT14M</ExecutionTimeLimit>
551
+ </Settings>
552
+ <Actions Context="Author"><Exec>
553
+ <Command>${xmlEscape(windowsHostPath(opts))}</Command>
554
+ <Arguments>${xmlEscape(windowsTaskArguments(opts))}</Arguments>
555
+ <WorkingDirectory>${xmlEscape(opts.configDir)}</WorkingDirectory>
556
+ </Exec></Actions>
557
+ </Task>
558
+ `;
559
+ }
560
+ function readWindowsTask() {
561
+ const res = spawnSync("schtasks.exe", ["/Query", "/TN", WINDOWS_TASK_NAME, "/XML"], {
562
+ windowsHide: true,
563
+ timeout: 15e3
564
+ });
565
+ if (res.error) throw new Error(`could not inspect scheduled task: ${res.error.message}`);
566
+ if (res.status !== 0) {
567
+ const probe = spawnSync("schtasks.exe", ["/Query"], {
568
+ encoding: "utf8",
569
+ windowsHide: true,
570
+ timeout: 15e3
571
+ });
572
+ if (probe.status !== 0) throw new Error(`could not inspect scheduled tasks: ${probe.stderr || probe.error?.message || "scheduler unavailable"}`);
573
+ return null;
574
+ }
575
+ return decodeTaskXml(res.stdout);
576
+ }
577
+ function decodeTaskXml(output) {
578
+ if (typeof output === "string") return output.replace(/^\uFEFF/, "");
579
+ const utf16 = output[0] === 255 && output[1] === 254 || output[1] === 0;
580
+ return output.toString(utf16 ? "utf16le" : "utf8").replace(/^\uFEFF/, "");
581
+ }
582
+ function xmlValues(xml, tag) {
583
+ return [...xml.matchAll(new RegExp(`<${tag}(?:\\s[^>]*)?>([\\s\\S]*?)</${tag}>`, "g"))].map((match) => match[1].trim().replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&quot;", '"').replaceAll("&apos;", "'").replaceAll("&amp;", "&"));
584
+ }
585
+ function windowsTaskEnabled(xml) {
586
+ const settings = xmlValues(xml, "Settings")[0] ?? "";
587
+ return xmlValues(settings, "Enabled")[0] !== "false";
588
+ }
589
+ function windowsTaskDrift(installed, launcher, opts) {
590
+ if (installed === null) return "absent";
591
+ if (!windowsTaskEnabled(installed)) return "ok";
592
+ if (launcher !== buildWindowsLauncher(opts)) return "drift";
593
+ const triggers = xmlValues(installed, "Triggers")[0] ?? "";
594
+ const repetition = xmlValues(triggers, "Repetition")[0] ?? "";
595
+ const settings = xmlValues(installed, "Settings")[0] ?? "";
596
+ const principal = xmlValues(installed, "Principal")[0] ?? "";
597
+ const actions = xmlValues(installed, "Actions")[0] ?? "";
598
+ const fields = [
599
+ [actions, "Command", "", windowsHostPath(opts)],
600
+ [actions, "Arguments", "", windowsTaskArguments(opts)],
601
+ [actions, "WorkingDirectory", "", opts.configDir],
602
+ [repetition, "Interval", "", `PT${opts.intervalMinutes}M`],
603
+ [repetition, "StopAtDurationEnd", "false", "false"],
604
+ [triggers, "Enabled", "true", "true"],
605
+ [principal, "LogonType", "", "InteractiveToken"],
606
+ [principal, "RunLevel", "LeastPrivilege", "LeastPrivilege"],
607
+ [settings, "MultipleInstancesPolicy", "IgnoreNew", "IgnoreNew"],
608
+ [settings, "DisallowStartIfOnBatteries", "true", "false"],
609
+ [settings, "StopIfGoingOnBatteries", "true", "false"],
610
+ [settings, "StartWhenAvailable", "false", "true"],
611
+ [settings, "ExecutionTimeLimit", "PT72H", "PT14M"]
612
+ ];
613
+ for (const [scope, tag, fallback, expected] of fields) {
614
+ const values = xmlValues(scope, tag);
615
+ if (values.length > 1 || (values[0] ?? fallback) !== expected) return "drift";
616
+ }
617
+ if (xmlValues(triggers, "EndBoundary").length || xmlValues(repetition, "Duration").length || xmlValues(triggers, "TimeTrigger").length !== 1 || xmlValues(triggers, "StartBoundary").length !== 1 || xmlValues(actions, "Exec").length !== 1) return "drift";
618
+ return "ok";
619
+ }
620
+ function readWindowsLauncher(opts) {
621
+ try {
622
+ return readFileSync(windowsLauncherPath(opts), "utf8");
623
+ } catch {
624
+ return null;
625
+ }
626
+ }
627
+ function installWindowsSync(opts) {
628
+ if (!existsSync2(windowsHostPath(opts))) throw new Error("Windows Script Host is required for windowless background sync");
629
+ const launcher = windowsLauncherPath(opts);
630
+ const previous = readWindowsLauncher(opts);
631
+ const temp = mkdtempSync(join2(tmpdir(), "whoburnedmore-task-"));
632
+ try {
633
+ if (!writePrivateFileAtomic(launcher, buildWindowsLauncher(opts), 1024 * 1024)) {
634
+ throw new Error("could not write the Windows background-sync launcher");
635
+ }
636
+ const xmlPath = join2(temp, "task.xml");
637
+ writeFileSync2(xmlPath, "\uFEFF" + buildWindowsTaskXml(opts), "utf16le");
638
+ const res = spawnSync("schtasks.exe", ["/Create", "/F", "/TN", WINDOWS_TASK_NAME, "/XML", xmlPath], {
639
+ encoding: "utf8",
640
+ windowsHide: true,
641
+ timeout: 3e4
642
+ });
643
+ if (res.status !== 0) throw new Error(`could not create scheduled task: ${res.stderr?.trim() || res.error?.message || res.stdout?.trim() || "unknown scheduler error"}`);
644
+ } catch (error) {
645
+ if (previous === null) rmSync2(launcher, { force: true });
646
+ else writePrivateFileAtomic(launcher, previous, 1024 * 1024);
647
+ throw error;
648
+ } finally {
649
+ rmSync2(temp, { recursive: true, force: true });
650
+ }
651
+ }
652
+ function uninstallWindowsSync(configDir) {
653
+ if (readWindowsTask() !== null) {
654
+ const res = spawnSync("schtasks.exe", ["/Delete", "/F", "/TN", WINDOWS_TASK_NAME], {
655
+ encoding: "utf8",
656
+ windowsHide: true,
657
+ timeout: 15e3
658
+ });
659
+ if (res.status !== 0) throw new Error(`could not remove scheduled task: ${res.stderr || res.error?.message || "unknown scheduler error"}`);
660
+ }
661
+ rmSync2(join2(configDir, "sync-launcher.js"), { force: true });
662
+ }
663
+
467
664
  // src/autosync.ts
468
665
  var SYNC_INTERVAL_MINUTES = 15;
469
666
  function syncIntervalLabel(mins = SYNC_INTERVAL_MINUTES) {
@@ -524,13 +721,13 @@ function syncEnv(opts) {
524
721
  return pairs;
525
722
  }
526
723
  function syncLogPath() {
527
- return join2(defaultConfigDir(), "sync.log");
724
+ return join3(defaultConfigDir(), "sync.log");
528
725
  }
529
726
  function buildLaunchdPlist(commandArgs = syncCommandArgs(), logPath = syncLogPath(), envPairs = syncEnv({ npmPath: commandArgs[0] })) {
530
- const programArguments = commandArgs.map((arg) => ` <string>${xmlEscape(arg)}</string>`).join("\n");
727
+ const programArguments = commandArgs.map((arg) => ` <string>${xmlEscape2(arg)}</string>`).join("\n");
531
728
  const envEntries = envPairs.map(
532
- ([k, v]) => ` <key>${xmlEscape(k)}</key>
533
- <string>${xmlEscape(v)}</string>`
729
+ ([k, v]) => ` <key>${xmlEscape2(k)}</key>
730
+ <string>${xmlEscape2(v)}</string>`
534
731
  ).join("\n");
535
732
  return `<?xml version="1.0" encoding="UTF-8"?>
536
733
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -572,19 +769,19 @@ ${envEntries}
572
769
  <key>Nice</key>
573
770
  <integer>0</integer>
574
771
  <key>StandardOutPath</key>
575
- <string>${xmlEscape(logPath)}</string>
772
+ <string>${xmlEscape2(logPath)}</string>
576
773
  <key>StandardErrorPath</key>
577
- <string>${xmlEscape(logPath)}</string>
774
+ <string>${xmlEscape2(logPath)}</string>
578
775
  </dict>
579
776
  </plist>
580
777
  `;
581
778
  }
582
779
  function launchAgentPath() {
583
- return join2(homedir2(), "Library", "LaunchAgents", `${LABEL}.plist`);
780
+ return join3(homedir2(), "Library", "LaunchAgents", `${LABEL}.plist`);
584
781
  }
585
782
  function isUsableNode(p) {
586
- if (!existsSync2(p)) return false;
587
- const res = spawnSync(p, ["-v"], { encoding: "utf8" });
783
+ if (!existsSync3(p)) return false;
784
+ const res = spawnSync2(p, ["-v"], { encoding: "utf8" });
588
785
  if (res.status !== 0 || typeof res.stdout !== "string") return false;
589
786
  const major = Number(res.stdout.trim().replace(/^v/, "").split(".")[0]);
590
787
  return Number.isFinite(major) && major >= 20;
@@ -599,8 +796,8 @@ function resolveNodePath(opts) {
599
796
  return execPath;
600
797
  }
601
798
  function isUsableNpm(p) {
602
- if (!existsSync2(p)) return false;
603
- const res = spawnSync(p, ["--version"], { encoding: "utf8" });
799
+ if (!existsSync3(p)) return false;
800
+ const res = spawnSync2(p, ["--version"], { encoding: "utf8" });
604
801
  return res.status === 0;
605
802
  }
606
803
  function resolveNpmPath(opts) {
@@ -611,7 +808,7 @@ function resolveNpmPath(opts) {
611
808
  for (const c of candidates) {
612
809
  if (check(c)) return c;
613
810
  }
614
- const sibling = os === "win32" ? win32.join(win32.dirname(execPath), "npm.cmd") : posix.join(posix.dirname(execPath), "npm");
811
+ const sibling = os === "win32" ? win322.join(win322.dirname(execPath), "npm.cmd") : posix.join(posix.dirname(execPath), "npm");
615
812
  if (check(sibling)) return sibling;
616
813
  return os === "win32" ? "npm.cmd" : "npm";
617
814
  }
@@ -628,19 +825,25 @@ function syncCommandArgs(npmPath = resolveNpmPath()) {
628
825
  "sync"
629
826
  ];
630
827
  }
631
- function xmlEscape(value) {
828
+ function windowsSyncOptions() {
829
+ const configDir = resolve(defaultConfigDir());
830
+ const env = { ...process.env, WHOBURNEDMORE_CONFIG_DIR: configDir };
831
+ return {
832
+ nodePath: process.execPath,
833
+ npmCliPath: resolveWindowsNpmCli(),
834
+ configDir,
835
+ commandArgs: syncCommandArgs("npm").slice(1),
836
+ envPairs: syncEnv({ npmPath: "npm", env }).filter(([key]) => key !== "PATH"),
837
+ intervalMinutes: SYNC_INTERVAL_MINUTES,
838
+ systemRoot: process.env.SystemRoot || "C:\\Windows"
839
+ };
840
+ }
841
+ function xmlEscape2(value) {
632
842
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll(">", "&gt;");
633
843
  }
634
844
  function shellQuote(value) {
635
845
  return `'${value.replaceAll("'", `'"'"'`)}'`;
636
846
  }
637
- function windowsQuote(value) {
638
- const escaped = value.replace(/(\\*)"/g, '$1$1\\"').replace(/\\+$/g, (slashes) => `${slashes}${slashes}`);
639
- return `"${escaped}"`;
640
- }
641
- function windowsCommandLine(args) {
642
- return args.map(windowsQuote).join(" ");
643
- }
644
847
  function expectedDarwinPlist() {
645
848
  return buildLaunchdPlist(syncCommandArgs());
646
849
  }
@@ -656,10 +859,10 @@ function installAutoSync() {
656
859
  mkdirSync2(defaultConfigDir(), { recursive: true });
657
860
  if (os === "darwin") {
658
861
  const plistPath = launchAgentPath();
659
- mkdirSync2(join2(homedir2(), "Library", "LaunchAgents"), { recursive: true });
660
- writeFileSync2(plistPath, expectedDarwinPlist());
661
- spawnSync("launchctl", ["unload", plistPath], { stdio: "ignore" });
662
- spawnSync("launchctl", ["load", plistPath], { stdio: "ignore" });
862
+ mkdirSync2(join3(homedir2(), "Library", "LaunchAgents"), { recursive: true });
863
+ writeFileSync3(plistPath, expectedDarwinPlist());
864
+ spawnSync2("launchctl", ["unload", plistPath], { stdio: "ignore" });
865
+ spawnSync2("launchctl", ["load", plistPath], { stdio: "ignore" });
663
866
  return `launchd agent installed (${plistPath}), syncing every ${syncIntervalLabel()}`;
664
867
  }
665
868
  if (os === "linux") {
@@ -672,20 +875,8 @@ function installAutoSync() {
672
875
  );
673
876
  }
674
877
  if (os === "win32") {
675
- const res = spawnSync("schtasks", [
676
- "/Create",
677
- "/F",
678
- "/SC",
679
- "MINUTE",
680
- "/MO",
681
- String(SYNC_INTERVAL_MINUTES),
682
- "/TN",
683
- "whoburnedmore-sync",
684
- "/TR",
685
- windowsCommandLine(syncCommandArgs())
686
- ]);
687
- if (res.status !== 0) throw new Error("could not create scheduled task");
688
- return `scheduled task installed, syncing every ${syncIntervalLabel()}`;
878
+ installWindowsSync(windowsSyncOptions());
879
+ return `windowless scheduled task installed, syncing every ${syncIntervalLabel()} while signed in (log: ${syncLogPath()})`;
689
880
  }
690
881
  throw new Error(`auto-sync is not supported on ${os}`);
691
882
  }
@@ -701,14 +892,14 @@ function expectedLinuxCronLine(opts) {
701
892
  }
702
893
  var SYSTEMD_UNIT = "whoburnedmore-sync";
703
894
  function systemdUserDir() {
704
- const base = process.env.XDG_CONFIG_HOME?.trim() || join2(homedir2(), ".config");
705
- return join2(base, "systemd", "user");
895
+ const base = process.env.XDG_CONFIG_HOME?.trim() || join3(homedir2(), ".config");
896
+ return join3(base, "systemd", "user");
706
897
  }
707
898
  function systemdServicePath() {
708
- return join2(systemdUserDir(), `${SYSTEMD_UNIT}.service`);
899
+ return join3(systemdUserDir(), `${SYSTEMD_UNIT}.service`);
709
900
  }
710
901
  function systemdTimerPath() {
711
- return join2(systemdUserDir(), `${SYSTEMD_UNIT}.timer`);
902
+ return join3(systemdUserDir(), `${SYSTEMD_UNIT}.timer`);
712
903
  }
713
904
  function systemdQuote(value) {
714
905
  return `"${value.replaceAll("\\", "\\\\").replaceAll("%", "%%").replaceAll('"', '\\"')}"`;
@@ -739,27 +930,27 @@ WantedBy=timers.target
739
930
  `;
740
931
  }
741
932
  function binaryExists(cmd, probeArgs = ["--version"]) {
742
- const res = spawnSync(cmd, probeArgs, { stdio: "ignore" });
933
+ const res = spawnSync2(cmd, probeArgs, { stdio: "ignore" });
743
934
  return !res.error;
744
935
  }
745
936
  function linuxSyncMechanism() {
746
- const cron = spawnSync("crontab", ["-l"], { encoding: "utf8" });
937
+ const cron = spawnSync2("crontab", ["-l"], { encoding: "utf8" });
747
938
  if (!cron.error && cron.status === 0 && cron.stdout.includes("whoburnedmore")) {
748
939
  return "cron";
749
940
  }
750
- if (existsSync2(systemdTimerPath())) return "systemd";
941
+ if (existsSync3(systemdTimerPath())) return "systemd";
751
942
  return "none";
752
943
  }
753
944
  function tryInstallCron() {
754
945
  if (!binaryExists("crontab", ["-l"])) return null;
755
946
  const line = expectedLinuxCronLine();
756
- const current = spawnSync("crontab", ["-l"], { encoding: "utf8" });
947
+ const current = spawnSync2("crontab", ["-l"], { encoding: "utf8" });
757
948
  const existing = current.status === 0 ? current.stdout : "";
758
949
  const kept = existing.split("\n").filter((l) => !l.includes("whoburnedmore")).join("\n");
759
950
  const next = `${kept.trimEnd()}
760
951
  ${line}
761
952
  `.replace(/^\n+/, "");
762
- const res = spawnSync("crontab", ["-"], { input: next });
953
+ const res = spawnSync2("crontab", ["-"], { input: next });
763
954
  if (res.status !== 0) return null;
764
955
  return `cron entry installed, syncing every ${syncIntervalLabel()}`;
765
956
  }
@@ -767,20 +958,20 @@ function tryInstallSystemd() {
767
958
  if (!binaryExists("systemctl", ["--user", "--version"])) return null;
768
959
  try {
769
960
  mkdirSync2(systemdUserDir(), { recursive: true });
770
- writeFileSync2(systemdServicePath(), buildSystemdService());
771
- writeFileSync2(systemdTimerPath(), buildSystemdTimer());
961
+ writeFileSync3(systemdServicePath(), buildSystemdService());
962
+ writeFileSync3(systemdTimerPath(), buildSystemdTimer());
772
963
  } catch {
773
964
  return null;
774
965
  }
775
- spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
776
- const res = spawnSync(
966
+ spawnSync2("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
967
+ const res = spawnSync2(
777
968
  "systemctl",
778
969
  ["--user", "enable", "--now", `${SYSTEMD_UNIT}.timer`],
779
970
  { stdio: "ignore" }
780
971
  );
781
972
  if (res.status !== 0) {
782
- rmSync2(systemdServicePath(), { force: true });
783
- rmSync2(systemdTimerPath(), { force: true });
973
+ rmSync3(systemdServicePath(), { force: true });
974
+ rmSync3(systemdTimerPath(), { force: true });
784
975
  return null;
785
976
  }
786
977
  return `systemd user timer installed, syncing every ${syncIntervalLabel()} (run \`loginctl enable-linger\` to keep syncing while logged out)`;
@@ -789,56 +980,57 @@ function uninstallAutoSync() {
789
980
  const os = platform();
790
981
  if (os === "darwin") {
791
982
  const plistPath = launchAgentPath();
792
- if (existsSync2(plistPath)) {
793
- spawnSync("launchctl", ["unload", plistPath], { stdio: "ignore" });
794
- rmSync2(plistPath, { force: true });
983
+ if (existsSync3(plistPath)) {
984
+ spawnSync2("launchctl", ["unload", plistPath], { stdio: "ignore" });
985
+ rmSync3(plistPath, { force: true });
795
986
  }
796
987
  return "launchd agent removed";
797
988
  }
798
989
  if (os === "linux") {
799
990
  let removed = false;
800
- const current = spawnSync("crontab", ["-l"], { encoding: "utf8" });
991
+ const current = spawnSync2("crontab", ["-l"], { encoding: "utf8" });
801
992
  if (!current.error && current.status === 0 && current.stdout.includes("whoburnedmore")) {
802
993
  const next = current.stdout.split("\n").filter((l) => !l.includes("whoburnedmore")).join("\n");
803
- spawnSync("crontab", ["-"], { input: next });
994
+ spawnSync2("crontab", ["-"], { input: next });
804
995
  removed = true;
805
996
  }
806
- if (existsSync2(systemdTimerPath())) {
807
- spawnSync(
997
+ if (existsSync3(systemdTimerPath())) {
998
+ spawnSync2(
808
999
  "systemctl",
809
1000
  ["--user", "disable", "--now", `${SYSTEMD_UNIT}.timer`],
810
1001
  { stdio: "ignore" }
811
1002
  );
812
- rmSync2(systemdServicePath(), { force: true });
813
- rmSync2(systemdTimerPath(), { force: true });
814
- spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
1003
+ rmSync3(systemdServicePath(), { force: true });
1004
+ rmSync3(systemdTimerPath(), { force: true });
1005
+ spawnSync2("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
815
1006
  removed = true;
816
1007
  }
817
1008
  return removed ? "background sync removed" : "nothing to remove";
818
1009
  }
819
1010
  if (os === "win32") {
820
- spawnSync("schtasks", ["/Delete", "/F", "/TN", "whoburnedmore-sync"]);
1011
+ uninstallWindowsSync(defaultConfigDir());
821
1012
  return "scheduled task removed";
822
1013
  }
823
1014
  return "nothing to remove";
824
1015
  }
825
1016
  function autoSyncInstalled() {
826
- if (platform() === "darwin") return existsSync2(launchAgentPath());
1017
+ if (platform() === "darwin") return existsSync3(launchAgentPath());
827
1018
  if (platform() === "linux") {
828
1019
  return linuxSyncMechanism() !== "none";
829
1020
  }
830
1021
  if (platform() === "win32") {
831
- const res = spawnSync("schtasks", ["/Query", "/TN", "whoburnedmore-sync"], {
832
- stdio: "ignore"
833
- });
834
- return res.status === 0;
1022
+ try {
1023
+ return readWindowsTask() !== null;
1024
+ } catch {
1025
+ return false;
1026
+ }
835
1027
  }
836
1028
  return false;
837
1029
  }
838
1030
  function readInstalledSystemd() {
839
1031
  try {
840
- return `${readFileSync(systemdServicePath(), "utf8")}
841
- ${readFileSync(systemdTimerPath(), "utf8")}`;
1032
+ return `${readFileSync2(systemdServicePath(), "utf8")}
1033
+ ${readFileSync2(systemdTimerPath(), "utf8")}`;
842
1034
  } catch {
843
1035
  return null;
844
1036
  }
@@ -850,12 +1042,12 @@ ${buildSystemdTimer()}`;
850
1042
  function readInstalledAgent() {
851
1043
  if (platform() === "darwin") {
852
1044
  const p = launchAgentPath();
853
- return existsSync2(p) ? readFileSync(p, "utf8") : null;
1045
+ return existsSync3(p) ? readFileSync2(p, "utf8") : null;
854
1046
  }
855
1047
  if (platform() === "linux") {
856
1048
  const mech = linuxSyncMechanism();
857
1049
  if (mech === "cron") {
858
- const current = spawnSync("crontab", ["-l"], { encoding: "utf8" });
1050
+ const current = spawnSync2("crontab", ["-l"], { encoding: "utf8" });
859
1051
  if (current.error || current.status !== 0) return null;
860
1052
  return current.stdout.split("\n").find((l) => l.includes("whoburnedmore")) ?? null;
861
1053
  }
@@ -872,6 +1064,13 @@ function expectedAgent() {
872
1064
  return null;
873
1065
  }
874
1066
  function autoSyncDrift() {
1067
+ if (platform() === "win32") {
1068
+ const installed = readWindowsTask();
1069
+ if (installed === null) return "absent";
1070
+ if (!windowsTaskEnabled(installed)) return "ok";
1071
+ const opts = windowsSyncOptions();
1072
+ return windowsTaskDrift(installed, readWindowsLauncher(opts), opts);
1073
+ }
875
1074
  const expected = expectedAgent();
876
1075
  if (expected === null) return autoSyncInstalled() ? "ok" : "absent";
877
1076
  return plistDrift(readInstalledAgent(), expected);
@@ -884,7 +1083,7 @@ function reconcileAutoSync() {
884
1083
  }
885
1084
  function rotateLogIfLarge(path = syncLogPath(), capBytes = 256 * 1024) {
886
1085
  try {
887
- if (!existsSync2(path)) return false;
1086
+ if (!existsSync3(path)) return false;
888
1087
  if (statSync(path).size <= capBytes) return false;
889
1088
  renameSync2(path, `${path}.1`);
890
1089
  return true;
@@ -893,8 +1092,12 @@ function rotateLogIfLarge(path = syncLogPath(), capBytes = 256 * 1024) {
893
1092
  }
894
1093
  }
895
1094
  function autoSyncLoaded() {
1095
+ if (platform() === "win32") {
1096
+ const task = readWindowsTask();
1097
+ return task !== null && windowsTaskEnabled(task);
1098
+ }
896
1099
  if (platform() === "darwin") {
897
- const res = spawnSync("launchctl", ["list"], { encoding: "utf8" });
1100
+ const res = spawnSync2("launchctl", ["list"], { encoding: "utf8" });
898
1101
  return res.status === 0 && res.stdout.includes(LABEL);
899
1102
  }
900
1103
  return autoSyncInstalled();
@@ -945,18 +1148,18 @@ async function daemonLoop(deps) {
945
1148
  // src/collect.ts
946
1149
  import { execFile } from "node:child_process";
947
1150
  import { createRequire as createRequire4 } from "node:module";
948
- import { dirname as dirname6, join as join13 } from "node:path";
1151
+ import { dirname as dirname6, join as join14 } from "node:path";
949
1152
  import { promisify } from "node:util";
950
1153
 
951
1154
  // src/attribution.ts
952
1155
  import { readdirSync, statSync as statSync2 } from "node:fs";
953
1156
  import { homedir as homedir4 } from "node:os";
954
- import { join as join5 } from "node:path";
1157
+ import { join as join6 } from "node:path";
955
1158
 
956
1159
  // src/native/claude.ts
957
1160
  import { readdir } from "node:fs/promises";
958
1161
  import { homedir as homedir3 } from "node:os";
959
- import { join as join4 } from "node:path";
1162
+ import { join as join5 } from "node:path";
960
1163
 
961
1164
  // src/pricing-data.generated.ts
962
1165
  var MODEL_PRICES = {
@@ -2304,11 +2507,11 @@ import {
2304
2507
  mkdirSync as mkdirSync3,
2305
2508
  renameSync as renameSync3,
2306
2509
  unlinkSync,
2307
- writeFileSync as writeFileSync3
2510
+ writeFileSync as writeFileSync4
2308
2511
  } from "node:fs";
2309
2512
  import { open, stat } from "node:fs/promises";
2310
2513
  import { randomBytes as randomBytes3 } from "node:crypto";
2311
- import { dirname as dirname3, join as join3 } from "node:path";
2514
+ import { dirname as dirname3, join as join4 } from "node:path";
2312
2515
  var MAX_NATIVE_FILE_BYTES = 64 * 1024 * 1024;
2313
2516
  var MAX_NATIVE_CACHE_BYTES = 128 * 1024 * 1024;
2314
2517
  var READ_CHUNK_BYTES = 64 * 1024;
@@ -2348,7 +2551,7 @@ async function readTextFileCapped(path, opts = {}) {
2348
2551
  function nativeCachePath(reader, env = process.env) {
2349
2552
  const override = env.WHOBURNEDMORE_CONFIG_DIR?.trim();
2350
2553
  const dir = override || defaultConfigDir();
2351
- return join3(dir, `native-cache-${reader}.json`);
2554
+ return join4(dir, `native-cache-${reader}.json`);
2352
2555
  }
2353
2556
  async function loadCache(path, version, maxBytes) {
2354
2557
  try {
@@ -2371,7 +2574,7 @@ function saveCache(path, version, files, maxBytes) {
2371
2574
  tmp = `${path}.tmp-${process.pid}-${randomBytes3(8).toString("hex")}`;
2372
2575
  const serialized = JSON.stringify({ v: version, files });
2373
2576
  if (Buffer.byteLength(serialized, "utf8") > maxBytes) return;
2374
- writeFileSync3(tmp, serialized, { encoding: "utf8", flag: "wx", mode: 384 });
2577
+ writeFileSync4(tmp, serialized, { encoding: "utf8", flag: "wx", mode: 384 });
2375
2578
  chmodSync2(tmp, 384);
2376
2579
  renameSync3(tmp, path);
2377
2580
  tmp = null;
@@ -2558,10 +2761,10 @@ function finalizeClaudeEntries(acc) {
2558
2761
  }
2559
2762
  function resolveClaudeConfigRoots(env = process.env) {
2560
2763
  const override = env.CLAUDE_CONFIG_DIR;
2561
- return override ? override.split(",").map((s) => s.trim()).filter(Boolean) : [join4(homedir3(), ".claude"), join4(homedir3(), ".config", "claude")];
2764
+ return override ? override.split(",").map((s) => s.trim()).filter(Boolean) : [join5(homedir3(), ".claude"), join5(homedir3(), ".config", "claude")];
2562
2765
  }
2563
2766
  function resolveClaudeProjectDirs(env = process.env) {
2564
- return resolveClaudeConfigRoots(env).map((r) => join4(r, "projects"));
2767
+ return resolveClaudeConfigRoots(env).map((r) => join5(r, "projects"));
2565
2768
  }
2566
2769
  async function listJsonl(dir) {
2567
2770
  let dirents;
@@ -2572,7 +2775,7 @@ async function listJsonl(dir) {
2572
2775
  }
2573
2776
  const out = [];
2574
2777
  for (const d of dirents) {
2575
- const full = join4(dir, d.name);
2778
+ const full = join5(dir, d.name);
2576
2779
  if (d.isDirectory()) {
2577
2780
  out.push(...await listJsonl(full));
2578
2781
  } else if (d.isFile() && d.name.endsWith(".jsonl")) {
@@ -2693,8 +2896,8 @@ async function collectClaudeRequests(env = process.env, opts = {}) {
2693
2896
  }
2694
2897
 
2695
2898
  // src/attribution.ts
2696
- var CLAUDE_PROJECTS = join5(homedir4(), ".claude", "projects");
2697
- var CODEX_SESSIONS = join5(homedir4(), ".codex", "sessions");
2899
+ var CLAUDE_PROJECTS = join6(homedir4(), ".claude", "projects");
2900
+ var CODEX_SESSIONS = join6(homedir4(), ".codex", "sessions");
2698
2901
  var MAX_FILES = 5e3;
2699
2902
  var TIME_BUDGET_MS = NATIVE_READ_BUDGET_MS;
2700
2903
  var ATTRIBUTION_CACHE_VERSION = 1;
@@ -2941,8 +3144,8 @@ function parseCodexAttrFile(content) {
2941
3144
  function claudeProjectDirs() {
2942
3145
  const dirs = [CLAUDE_PROJECTS];
2943
3146
  const cfg = process.env.CLAUDE_CONFIG_DIR;
2944
- if (cfg) dirs.push(join5(cfg, "projects"));
2945
- dirs.push(join5(homedir4(), ".config", "claude", "projects"));
3147
+ if (cfg) dirs.push(join6(cfg, "projects"));
3148
+ dirs.push(join6(homedir4(), ".config", "claude", "projects"));
2946
3149
  return [...new Set(dirs)];
2947
3150
  }
2948
3151
  function listTranscripts(dir) {
@@ -2957,7 +3160,7 @@ function listTranscripts(dir) {
2957
3160
  }
2958
3161
  for (const e of entries) {
2959
3162
  if (out.length >= MAX_FILES) return;
2960
- const p = join5(d, e.name);
3163
+ const p = join6(d, e.name);
2961
3164
  if (e.isDirectory()) walk(p);
2962
3165
  else if (e.isFile() && e.name.endsWith(".jsonl")) {
2963
3166
  try {
@@ -3014,7 +3217,7 @@ async function collectAttribution(env = process.env) {
3014
3217
  // src/continue.ts
3015
3218
  import { readdir as readdir2 } from "node:fs/promises";
3016
3219
  import { homedir as homedir5 } from "node:os";
3017
- import { join as join6 } from "node:path";
3220
+ import { join as join7 } from "node:path";
3018
3221
  function num2(n) {
3019
3222
  const v = Math.round(Number(n));
3020
3223
  return Number.isFinite(v) && v > 0 ? v : 0;
@@ -3112,7 +3315,7 @@ async function listJsonl2(root, opts) {
3112
3315
  if (visitedEntries > maxEntries || opts.now() > opts.deadline) {
3113
3316
  return { files: [], aborted: true };
3114
3317
  }
3115
- const full = join6(current.dir, d.name);
3318
+ const full = join7(current.dir, d.name);
3116
3319
  if (d.isDirectory()) {
3117
3320
  if (current.depth >= 32) return { files: [], aborted: true };
3118
3321
  queue.push({ dir: full, depth: current.depth + 1 });
@@ -3127,10 +3330,10 @@ async function listJsonl2(root, opts) {
3127
3330
  var CONTINUE_CACHE_VERSION = 2;
3128
3331
  async function collectContinue(opts = {}) {
3129
3332
  const env = opts.env ?? process.env;
3130
- const home = opts.continueDir ?? join6(env.HOME || homedir5(), ".continue");
3333
+ const home = opts.continueDir ?? join7(env.HOME || homedir5(), ".continue");
3131
3334
  const now = opts.now ?? Date.now;
3132
3335
  const deadline = now() + (opts.budgetMs ?? NATIVE_READ_BUDGET_MS);
3133
- const discovery = await listJsonl2(join6(home, "dev_data"), {
3336
+ const discovery = await listJsonl2(join7(home, "dev_data"), {
3134
3337
  deadline,
3135
3338
  now,
3136
3339
  maxFiles: Math.max(1, opts.maxFiles ?? 1e4)
@@ -3190,16 +3393,16 @@ async function collectContinue(opts = {}) {
3190
3393
  }
3191
3394
 
3192
3395
  // src/cursor.ts
3193
- import { spawnSync as spawnSync3 } from "node:child_process";
3194
- import { existsSync as existsSync3, realpathSync, statSync as statSync3 } from "node:fs";
3396
+ import { spawnSync as spawnSync4 } from "node:child_process";
3397
+ import { existsSync as existsSync4, realpathSync, statSync as statSync3 } from "node:fs";
3195
3398
  import { createRequire as createRequire3 } from "node:module";
3196
3399
  import { homedir as homedir6, platform as platform2 } from "node:os";
3197
- import { join as join8 } from "node:path";
3400
+ import { join as join9 } from "node:path";
3198
3401
 
3199
3402
  // src/tokscale.ts
3200
- import { spawnSync as spawnSync2 } from "node:child_process";
3403
+ import { spawnSync as spawnSync3 } from "node:child_process";
3201
3404
  import { createRequire as createRequire2 } from "node:module";
3202
- import { dirname as dirname4, join as join7 } from "node:path";
3405
+ import { dirname as dirname4, join as join8 } from "node:path";
3203
3406
  var LOOKBACK_DAYS = 30;
3204
3407
  function num3(n) {
3205
3408
  const v = Math.round(Number(n));
@@ -3243,7 +3446,7 @@ function resolveTokscaleBin() {
3243
3446
  const pkg = require4("tokscale/package.json");
3244
3447
  const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.tokscale ?? "";
3245
3448
  if (!rel) return null;
3246
- const binPath = join7(dirname4(pkgPath), rel);
3449
+ const binPath = join8(dirname4(pkgPath), rel);
3247
3450
  if (/\.(c|m)?js$/.test(binPath)) {
3248
3451
  return { cmd: process.execPath, prefixArgs: [binPath] };
3249
3452
  }
@@ -3253,7 +3456,7 @@ function resolveTokscaleBin() {
3253
3456
  }
3254
3457
  }
3255
3458
  function runTokscaleDay(bin, day) {
3256
- const res = spawnSync2(
3459
+ const res = spawnSync3(
3257
3460
  bin.cmd,
3258
3461
  [
3259
3462
  ...bin.prefixArgs,
@@ -3268,7 +3471,7 @@ function runTokscaleDay(bin, day) {
3268
3471
  "model",
3269
3472
  "--no-spinner"
3270
3473
  ],
3271
- { encoding: "utf8", maxBuffer: 32 * 1024 * 1024, timeout: 6e4 }
3474
+ { encoding: "utf8", windowsHide: true, maxBuffer: 32 * 1024 * 1024, timeout: 6e4 }
3272
3475
  );
3273
3476
  if (res.status !== 0 || !res.stdout) return null;
3274
3477
  try {
@@ -3304,8 +3507,8 @@ var MAX_CURSOR_PAGE_BYTES = 4 * 1024 * 1024;
3304
3507
  function cursorDbPath() {
3305
3508
  const home = homedir6();
3306
3509
  const os = platform2();
3307
- const p = os === "darwin" ? join8(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb") : os === "win32" ? join8(process.env.APPDATA ?? join8(home, "AppData", "Roaming"), "Cursor", "User", "globalStorage", "state.vscdb") : join8(process.env.XDG_CONFIG_HOME ?? join8(home, ".config"), "Cursor", "User", "globalStorage", "state.vscdb");
3308
- return existsSync3(p) ? p : null;
3510
+ const p = os === "darwin" ? join9(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb") : os === "win32" ? join9(process.env.APPDATA ?? join9(home, "AppData", "Roaming"), "Cursor", "User", "globalStorage", "state.vscdb") : join9(process.env.XDG_CONFIG_HOME ?? join9(home, ".config"), "Cursor", "User", "globalStorage", "state.vscdb");
3511
+ return existsSync4(p) ? p : null;
3309
3512
  }
3310
3513
  function trustedSqliteBinaries() {
3311
3514
  const candidates = platform2() === "darwin" ? ["/usr/bin/sqlite3", "/opt/homebrew/bin/sqlite3", "/usr/local/bin/sqlite3"] : platform2() === "linux" ? ["/usr/bin/sqlite3", "/usr/local/bin/sqlite3"] : [];
@@ -3332,10 +3535,10 @@ function readCursorToken(db) {
3332
3535
  }
3333
3536
  for (const sqlite3 of trustedSqliteBinaries()) {
3334
3537
  try {
3335
- const res = spawnSync3(
3538
+ const res = spawnSync4(
3336
3539
  sqlite3,
3337
3540
  [db, "SELECT value FROM ItemTable WHERE key='cursorAuth/accessToken';"],
3338
- { encoding: "utf8", timeout: 1e4 }
3541
+ { encoding: "utf8", windowsHide: true, timeout: 1e4 }
3339
3542
  );
3340
3543
  const out = res.stdout?.trim();
3341
3544
  if (res.status === 0 && out) return out;
@@ -3466,7 +3669,7 @@ async function collectCursor(opts) {
3466
3669
  // src/native/codex.ts
3467
3670
  import { readdir as readdir3 } from "node:fs/promises";
3468
3671
  import { homedir as homedir7 } from "node:os";
3469
- import { basename, join as join9, resolve } from "node:path";
3672
+ import { basename, join as join10, resolve as resolve2 } from "node:path";
3470
3673
  function num5(n) {
3471
3674
  const v = Math.round(Number(n));
3472
3675
  return Number.isFinite(v) && v > 0 ? v : 0;
@@ -3601,11 +3804,11 @@ function finalizeCodexEntries(acc) {
3601
3804
  return entries;
3602
3805
  }
3603
3806
  function resolveCodexHome(env = process.env) {
3604
- return env.CODEX_HOME && env.CODEX_HOME.trim() ? env.CODEX_HOME.trim() : join9(homedir7(), ".codex");
3807
+ return env.CODEX_HOME && env.CODEX_HOME.trim() ? env.CODEX_HOME.trim() : join10(homedir7(), ".codex");
3605
3808
  }
3606
3809
  function resolveCodexSessionsDirs(env = process.env) {
3607
- const home = resolve(resolveCodexHome(env));
3608
- return [join9(home, "sessions"), join9(home, "archived_sessions")];
3810
+ const home = resolve2(resolveCodexHome(env));
3811
+ return [join10(home, "sessions"), join10(home, "archived_sessions")];
3609
3812
  }
3610
3813
  async function listJsonl3(dir) {
3611
3814
  let dirents;
@@ -3616,7 +3819,7 @@ async function listJsonl3(dir) {
3616
3819
  }
3617
3820
  const out = [];
3618
3821
  for (const d of dirents) {
3619
- const full = join9(dir, d.name);
3822
+ const full = join10(dir, d.name);
3620
3823
  if (d.isDirectory()) out.push(...await listJsonl3(full));
3621
3824
  else if (d.isFile() && d.name.endsWith(".jsonl")) out.push(full);
3622
3825
  }
@@ -3731,7 +3934,7 @@ async function collectCodexNative(env = process.env, opts = {}) {
3731
3934
  // src/native/vscode-agents.ts
3732
3935
  import { readdir as readdir4 } from "node:fs/promises";
3733
3936
  import { homedir as homedir8, platform as platform3 } from "node:os";
3734
- import { join as join10 } from "node:path";
3937
+ import { join as join11 } from "node:path";
3735
3938
  var VSCODE_AGENTS = [
3736
3939
  { tool: "cline", extIds: ["saoudrizwan.claude-dev"] },
3737
3940
  { tool: "roo", extIds: ["rooveterinaryinc.roo-cline"] }
@@ -3846,14 +4049,14 @@ function vscodeGlobalStorageRoots(env = process.env) {
3846
4049
  const apps = ["Code", "Code - Insiders", "Cursor", "VSCodium", "Windsurf"];
3847
4050
  const home = env.HOME || homedir8();
3848
4051
  const os = platform3();
3849
- const base = (app) => os === "darwin" ? join10(home, "Library", "Application Support", app, "User", "globalStorage") : os === "win32" ? join10(env.APPDATA ?? join10(home, "AppData", "Roaming"), app, "User", "globalStorage") : join10(env.XDG_CONFIG_HOME ?? join10(home, ".config"), app, "User", "globalStorage");
4052
+ const base = (app) => os === "darwin" ? join11(home, "Library", "Application Support", app, "User", "globalStorage") : os === "win32" ? join11(env.APPDATA ?? join11(home, "AppData", "Roaming"), app, "User", "globalStorage") : join11(env.XDG_CONFIG_HOME ?? join11(home, ".config"), app, "User", "globalStorage");
3850
4053
  return apps.map(base);
3851
4054
  }
3852
4055
  async function listTaskFiles(roots, extIds) {
3853
4056
  const files = [];
3854
4057
  for (const root of roots) {
3855
4058
  for (const extId of extIds) {
3856
- const tasksDir = join10(root, extId, "tasks");
4059
+ const tasksDir = join11(root, extId, "tasks");
3857
4060
  let taskDirs;
3858
4061
  try {
3859
4062
  const dirents = await readdir4(tasksDir, { withFileTypes: true });
@@ -3861,7 +4064,7 @@ async function listTaskFiles(roots, extIds) {
3861
4064
  } catch {
3862
4065
  continue;
3863
4066
  }
3864
- for (const t of taskDirs) files.push(join10(tasksDir, t, "ui_messages.json"));
4067
+ for (const t of taskDirs) files.push(join11(tasksDir, t, "ui_messages.json"));
3865
4068
  }
3866
4069
  }
3867
4070
  return files;
@@ -3932,7 +4135,7 @@ async function collectVscodeAgent(opts) {
3932
4135
  // src/pricing-live.ts
3933
4136
  import { chmod, mkdir, rename, unlink, writeFile } from "node:fs/promises";
3934
4137
  import { randomBytes as randomBytes4 } from "node:crypto";
3935
- import { dirname as dirname5, join as join11 } from "node:path";
4138
+ import { dirname as dirname5, join as join12 } from "node:path";
3936
4139
 
3937
4140
  // node_modules/zod/v3/external.js
3938
4141
  var external_exports = {};
@@ -8371,7 +8574,7 @@ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
8371
8574
  var FETCH_TIMEOUT_MS = 5e3;
8372
8575
  var MAX_PRICING_BYTES = 32 * 1024 * 1024;
8373
8576
  function pricingCachePath(dir = defaultConfigDir()) {
8374
- return join11(dir, "pricing-cache.json");
8577
+ return join12(dir, "pricing-cache.json");
8375
8578
  }
8376
8579
  async function readCache(path) {
8377
8580
  try {
@@ -8436,7 +8639,7 @@ async function loadLivePricing(env = process.env, now = Date.now, cachePath = pr
8436
8639
  }
8437
8640
 
8438
8641
  // src/provenance-store.ts
8439
- import { join as join12 } from "node:path";
8642
+ import { join as join13 } from "node:path";
8440
8643
  var MAX_PROVENANCE_STORE_BYTES = 4 * 1024 * 1024;
8441
8644
  var PROVENANCE_STORE_VERSION = 1;
8442
8645
  var KEY_SEP = "|";
@@ -8447,7 +8650,7 @@ function entryTokens(e) {
8447
8650
  function provenanceStorePath(env = process.env) {
8448
8651
  const override = env.WHOBURNEDMORE_CONFIG_DIR?.trim();
8449
8652
  const dir = override || defaultConfigDir();
8450
- return join12(dir, "native-cache-provenance.json");
8653
+ return join13(dir, "native-cache-provenance.json");
8451
8654
  }
8452
8655
  function loadProvenanceStore(path) {
8453
8656
  try {
@@ -8784,7 +8987,7 @@ function resolveCcusageBin() {
8784
8987
  const pkgPath = require4.resolve("ccusage/package.json");
8785
8988
  const pkg = require4("ccusage/package.json");
8786
8989
  const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.ccusage ?? "ccusage";
8787
- const binPath = join13(dirname6(pkgPath), rel);
8990
+ const binPath = join14(dirname6(pkgPath), rel);
8788
8991
  if (/\.(c|m)?js$/.test(binPath)) {
8789
8992
  return { cmd: process.execPath, prefixArgs: [binPath] };
8790
8993
  }
@@ -8929,6 +9132,7 @@ async function runCcusageOnce(cmd, args, env, timeoutMs = CCUSAGE_TIMEOUT_MS) {
8929
9132
  try {
8930
9133
  const { stdout } = await execFileAsync(cmd, args, {
8931
9134
  encoding: "utf8",
9135
+ windowsHide: true,
8932
9136
  maxBuffer: CCUSAGE_MAX_BUFFER_BYTES,
8933
9137
  // A single source shouldn't be able to hang the whole run: a hung source
8934
9138
  // gets killed and (if transient) retried once below rather than stalling
@@ -9151,14 +9355,14 @@ async function collectAll(onProgress, opts) {
9151
9355
  }
9152
9356
 
9153
9357
  // src/antigravity.ts
9154
- import { existsSync as existsSync4 } from "node:fs";
9358
+ import { existsSync as existsSync5 } from "node:fs";
9155
9359
  import { homedir as homedir9 } from "node:os";
9156
- import { join as join14 } from "node:path";
9360
+ import { join as join15 } from "node:path";
9157
9361
  function antigravityDataDir(env = process.env) {
9158
- return join14(env.HOME || homedir9(), ".gemini", "antigravity");
9362
+ return join15(env.HOME || homedir9(), ".gemini", "antigravity");
9159
9363
  }
9160
9364
  function detectAntigravity(env = process.env) {
9161
- return existsSync4(antigravityDataDir(env));
9365
+ return existsSync5(antigravityDataDir(env));
9162
9366
  }
9163
9367
  function antigravityNoticeLines() {
9164
9368
  return [
@@ -9252,7 +9456,7 @@ function agentStatusReport(now = Date.now()) {
9252
9456
  }
9253
9457
 
9254
9458
  // src/local-dashboard.ts
9255
- import { join as join15 } from "node:path";
9459
+ import { join as join16 } from "node:path";
9256
9460
 
9257
9461
  // src/output.ts
9258
9462
  function formatTokens(n) {
@@ -9298,7 +9502,7 @@ function signedInNextStepLines(result) {
9298
9502
  // src/local-dashboard.ts
9299
9503
  var MAX_LOCAL_DASHBOARD_BYTES = 16 * 1024 * 1024;
9300
9504
  function writeLocalDashboard(dir, html) {
9301
- const file = join15(dir, "dashboard.html");
9505
+ const file = join16(dir, "dashboard.html");
9302
9506
  if (!writePrivateFileAtomic(file, html, MAX_LOCAL_DASHBOARD_BYTES)) {
9303
9507
  throw new Error("could not write the private local dashboard");
9304
9508
  }
@@ -9730,7 +9934,7 @@ async function run(flags) {
9730
9934
  }
9731
9935
  }
9732
9936
  function sleep(ms) {
9733
- return new Promise((resolve2) => setTimeout(resolve2, ms));
9937
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
9734
9938
  }
9735
9939
  async function refreshCliTokenFromConfig() {
9736
9940
  const cfg = loadConfig();
@@ -9962,14 +10166,14 @@ async function linkServerInstall(token) {
9962
10166
  }
9963
10167
  function waitOrAbort(ms, signal) {
9964
10168
  if (signal.aborted) return Promise.resolve();
9965
- return new Promise((resolve2) => {
10169
+ return new Promise((resolve3) => {
9966
10170
  const onAbort = () => {
9967
10171
  clearTimeout(timer);
9968
- resolve2();
10172
+ resolve3();
9969
10173
  };
9970
10174
  const timer = setTimeout(() => {
9971
10175
  signal.removeEventListener("abort", onAbort);
9972
- resolve2();
10176
+ resolve3();
9973
10177
  }, ms);
9974
10178
  signal.addEventListener("abort", onAbort, { once: true });
9975
10179
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whoburnedmore",
3
- "version": "0.9.22",
3
+ "version": "0.9.23",
4
4
  "description": "Find out who burned more — submit your AI coding-agent token usage to the public leaderboard at whoburnedmore.com",
5
5
  "type": "module",
6
6
  "bin": {