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.
- package/README.md +19 -2
- package/dist/index.js +340 -136
- 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
|
|
57
|
-
|
|
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
|
|
296
|
+
existsSync as existsSync3,
|
|
297
297
|
mkdirSync as mkdirSync2,
|
|
298
|
-
readFileSync,
|
|
298
|
+
readFileSync as readFileSync2,
|
|
299
299
|
renameSync as renameSync2,
|
|
300
|
-
rmSync as
|
|
300
|
+
rmSync as rmSync3,
|
|
301
301
|
statSync,
|
|
302
|
-
writeFileSync as
|
|
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
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
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("<", "<").replaceAll(">", ">").replaceAll(""", '"').replaceAll("'", "'").replaceAll("&", "&"));
|
|
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
|
|
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>${
|
|
727
|
+
const programArguments = commandArgs.map((arg) => ` <string>${xmlEscape2(arg)}</string>`).join("\n");
|
|
531
728
|
const envEntries = envPairs.map(
|
|
532
|
-
([k, v]) => ` <key>${
|
|
533
|
-
<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>${
|
|
772
|
+
<string>${xmlEscape2(logPath)}</string>
|
|
576
773
|
<key>StandardErrorPath</key>
|
|
577
|
-
<string>${
|
|
774
|
+
<string>${xmlEscape2(logPath)}</string>
|
|
578
775
|
</dict>
|
|
579
776
|
</plist>
|
|
580
777
|
`;
|
|
581
778
|
}
|
|
582
779
|
function launchAgentPath() {
|
|
583
|
-
return
|
|
780
|
+
return join3(homedir2(), "Library", "LaunchAgents", `${LABEL}.plist`);
|
|
584
781
|
}
|
|
585
782
|
function isUsableNode(p) {
|
|
586
|
-
if (!
|
|
587
|
-
const res =
|
|
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 (!
|
|
603
|
-
const res =
|
|
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" ?
|
|
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
|
|
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("&", "&").replaceAll("<", "<").replaceAll('"', """).replaceAll("'", "'").replaceAll(">", ">");
|
|
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(
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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
|
-
|
|
676
|
-
|
|
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() ||
|
|
705
|
-
return
|
|
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
|
|
899
|
+
return join3(systemdUserDir(), `${SYSTEMD_UNIT}.service`);
|
|
709
900
|
}
|
|
710
901
|
function systemdTimerPath() {
|
|
711
|
-
return
|
|
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 =
|
|
933
|
+
const res = spawnSync2(cmd, probeArgs, { stdio: "ignore" });
|
|
743
934
|
return !res.error;
|
|
744
935
|
}
|
|
745
936
|
function linuxSyncMechanism() {
|
|
746
|
-
const cron =
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
771
|
-
|
|
961
|
+
writeFileSync3(systemdServicePath(), buildSystemdService());
|
|
962
|
+
writeFileSync3(systemdTimerPath(), buildSystemdTimer());
|
|
772
963
|
} catch {
|
|
773
964
|
return null;
|
|
774
965
|
}
|
|
775
|
-
|
|
776
|
-
const res =
|
|
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
|
-
|
|
783
|
-
|
|
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 (
|
|
793
|
-
|
|
794
|
-
|
|
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 =
|
|
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
|
-
|
|
994
|
+
spawnSync2("crontab", ["-"], { input: next });
|
|
804
995
|
removed = true;
|
|
805
996
|
}
|
|
806
|
-
if (
|
|
807
|
-
|
|
997
|
+
if (existsSync3(systemdTimerPath())) {
|
|
998
|
+
spawnSync2(
|
|
808
999
|
"systemctl",
|
|
809
1000
|
["--user", "disable", "--now", `${SYSTEMD_UNIT}.timer`],
|
|
810
1001
|
{ stdio: "ignore" }
|
|
811
1002
|
);
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
832
|
-
|
|
833
|
-
}
|
|
834
|
-
|
|
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 `${
|
|
841
|
-
${
|
|
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
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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) : [
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
2697
|
-
var 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(
|
|
2945
|
-
dirs.push(
|
|
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 =
|
|
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
|
|
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 =
|
|
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 ??
|
|
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(
|
|
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
|
|
3194
|
-
import { existsSync as
|
|
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
|
|
3400
|
+
import { join as join9 } from "node:path";
|
|
3198
3401
|
|
|
3199
3402
|
// src/tokscale.ts
|
|
3200
|
-
import { spawnSync as
|
|
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
|
|
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 =
|
|
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 =
|
|
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" ?
|
|
3308
|
-
return
|
|
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 =
|
|
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
|
|
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() :
|
|
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 =
|
|
3608
|
-
return [
|
|
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 =
|
|
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
|
|
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" ?
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
9358
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
9155
9359
|
import { homedir as homedir9 } from "node:os";
|
|
9156
|
-
import { join as
|
|
9360
|
+
import { join as join15 } from "node:path";
|
|
9157
9361
|
function antigravityDataDir(env = process.env) {
|
|
9158
|
-
return
|
|
9362
|
+
return join15(env.HOME || homedir9(), ".gemini", "antigravity");
|
|
9159
9363
|
}
|
|
9160
9364
|
function detectAntigravity(env = process.env) {
|
|
9161
|
-
return
|
|
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
|
|
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 =
|
|
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((
|
|
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((
|
|
10169
|
+
return new Promise((resolve3) => {
|
|
9966
10170
|
const onAbort = () => {
|
|
9967
10171
|
clearTimeout(timer);
|
|
9968
|
-
|
|
10172
|
+
resolve3();
|
|
9969
10173
|
};
|
|
9970
10174
|
const timer = setTimeout(() => {
|
|
9971
10175
|
signal.removeEventListener("abort", onAbort);
|
|
9972
|
-
|
|
10176
|
+
resolve3();
|
|
9973
10177
|
}, ms);
|
|
9974
10178
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
9975
10179
|
});
|
package/package.json
CHANGED