@sleds/cli 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/sleds.mjs +255 -84
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ sleds meter built today · queued · delivered overnight · estimate acc
|
|
|
28
28
|
sleds on hit a limit on your seat? move Claude Code onto the org key
|
|
29
29
|
sleds off back to your seat
|
|
30
30
|
sleds seat-sync read your Claude subscription's own limits into SLEDS (macOS)
|
|
31
|
-
sleds bar the same numbers for the menu bar (SwiftBar)
|
|
31
|
+
sleds bar the same numbers for the menu bar (SwiftBar); --install links the plugin
|
|
32
32
|
sleds mcp MCP server: usage / estimate / overnight / jobs, for Cowork and other clients
|
|
33
33
|
```
|
|
34
34
|
|
package/dist/sleds.mjs
CHANGED
|
@@ -5,9 +5,23 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
7
|
import { realpathSync } from "fs";
|
|
8
|
-
import { homedir, platform as platform2 } from "os";
|
|
8
|
+
import { homedir as homedir2, platform as platform2 } from "os";
|
|
9
9
|
import { fileURLToPath } from "url";
|
|
10
10
|
|
|
11
|
+
// src/commands/bar.ts
|
|
12
|
+
import { execFileSync } from "node:child_process";
|
|
13
|
+
import {
|
|
14
|
+
chmodSync as chmodSync2,
|
|
15
|
+
existsSync as existsSync5,
|
|
16
|
+
lstatSync,
|
|
17
|
+
mkdirSync as mkdirSync5,
|
|
18
|
+
symlinkSync,
|
|
19
|
+
unlinkSync,
|
|
20
|
+
writeFileSync as writeFileSync5
|
|
21
|
+
} from "node:fs";
|
|
22
|
+
import { homedir } from "node:os";
|
|
23
|
+
import { join as join5 } from "node:path";
|
|
24
|
+
|
|
11
25
|
// src/meter.ts
|
|
12
26
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
13
27
|
import { join as join3 } from "node:path";
|
|
@@ -489,6 +503,93 @@ var loadMeter = async (o) => {
|
|
|
489
503
|
return fresh;
|
|
490
504
|
};
|
|
491
505
|
|
|
506
|
+
// src/bar.ts
|
|
507
|
+
var SEAT_BAR_FRESH_MS = 6 * 60 * 60 * 1000;
|
|
508
|
+
var DASHBOARD_URL = "https://app.sleds.ai";
|
|
509
|
+
var WARM_PCT = 75;
|
|
510
|
+
var HOT_PCT = 90;
|
|
511
|
+
var MONO = "font=Menlo size=12";
|
|
512
|
+
var HOT_COLOR = "#ff453a";
|
|
513
|
+
var WARM_COLOR = "#ff9f0a";
|
|
514
|
+
var freshSeat = (w, now) => {
|
|
515
|
+
if (!w)
|
|
516
|
+
return null;
|
|
517
|
+
return now - w.capturedAt < SEAT_BAR_FRESH_MS ? w : null;
|
|
518
|
+
};
|
|
519
|
+
var modelLabel = (bucket) => bucket.replace(/^(seven_day|five_hour|weekly|daily)_/, "").toLowerCase();
|
|
520
|
+
var seatBars = (w) => {
|
|
521
|
+
if (!w)
|
|
522
|
+
return [];
|
|
523
|
+
const out = [];
|
|
524
|
+
if (w.sevenDay)
|
|
525
|
+
out.push({ label: "wk", bar: w.sevenDay });
|
|
526
|
+
if (w.fiveHour)
|
|
527
|
+
out.push({ label: "5h", bar: w.fiveHour });
|
|
528
|
+
if (w.spendLimit)
|
|
529
|
+
out.push({ label: "spend", bar: w.spendLimit });
|
|
530
|
+
for (const k of Object.keys(w.models ?? {}).sort()) {
|
|
531
|
+
const bar = w.models?.[k];
|
|
532
|
+
if (bar)
|
|
533
|
+
out.push({ label: modelLabel(k), bar });
|
|
534
|
+
}
|
|
535
|
+
return out;
|
|
536
|
+
};
|
|
537
|
+
var hottest = (w) => seatBars(w).reduce((best, cur) => !best || cur.bar.usedPct > best.bar.usedPct ? cur : best, null);
|
|
538
|
+
var toneOf = (pct) => pct === undefined ? null : pct >= HOT_PCT ? "hot" : pct >= WARM_PCT ? "warm" : null;
|
|
539
|
+
var colorParam = (tone) => tone === "hot" ? ` color=${HOT_COLOR}` : tone === "warm" ? ` color=${WARM_COLOR}` : "";
|
|
540
|
+
var barTitle = (m, seat) => {
|
|
541
|
+
const head = m ? usd(m.today.spentUsd) : "offline";
|
|
542
|
+
const wk = seat?.sevenDay ? `wk ${Math.round(seat.sevenDay.usedPct)}%` : null;
|
|
543
|
+
const top = hottest(seat);
|
|
544
|
+
const risk = top && top.label !== "wk" && toneOf(top.bar.usedPct) ? `${top.label} ${Math.round(top.bar.usedPct)}%` : null;
|
|
545
|
+
return [`sleds ${head}`, ...wk ? [wk] : [], ...risk ? [risk] : []].join(SEP);
|
|
546
|
+
};
|
|
547
|
+
var resetIn = (bar, now) => bar.resetsAt > now ? `${SEP}resets in ${shortDuration(bar.resetsAt - now)}` : "";
|
|
548
|
+
var formatBar = (m, seat, now) => {
|
|
549
|
+
const tone = toneOf(hottest(seat)?.bar.usedPct);
|
|
550
|
+
const lines = [`${barTitle(m, seat)}${colorParam(tone)}`, "---"];
|
|
551
|
+
if (m) {
|
|
552
|
+
for (const row2 of formatMeter(m, now))
|
|
553
|
+
lines.push(`${row2} | ${MONO}`);
|
|
554
|
+
} else {
|
|
555
|
+
lines.push(`offline${SEP}proxy did not answer | ${MONO}`);
|
|
556
|
+
}
|
|
557
|
+
const bars = seatBars(seat);
|
|
558
|
+
if (bars.length > 0 || seat?.usedCreditsUsd !== undefined) {
|
|
559
|
+
lines.push("---");
|
|
560
|
+
lines.push(`Seat limits${seat ? `${SEP}${shortDuration(now - seat.capturedAt)} ago` : ""} | ${MONO}`);
|
|
561
|
+
for (const { label, bar } of bars) {
|
|
562
|
+
const pct = Math.round(bar.usedPct);
|
|
563
|
+
lines.push(`${label.padEnd(12)}${String(pct).padStart(3)}%${resetIn(bar, now)} | ${MONO}${colorParam(toneOf(bar.usedPct))}`);
|
|
564
|
+
}
|
|
565
|
+
if (seat?.usedCreditsUsd !== undefined)
|
|
566
|
+
lines.push(`${"credits".padEnd(12)}${usd(seat.usedCreditsUsd)} spent | ${MONO}`);
|
|
567
|
+
}
|
|
568
|
+
lines.push("---");
|
|
569
|
+
lines.push(`Open dashboard | href=${DASHBOARD_URL}`);
|
|
570
|
+
lines.push("Refresh | refresh=true");
|
|
571
|
+
return lines;
|
|
572
|
+
};
|
|
573
|
+
var pluginScript = (nodePath, cliPath) => [
|
|
574
|
+
"#!/bin/bash",
|
|
575
|
+
"# SLEDS menu-bar meter for SwiftBar — written by `sleds bar --install`; re-run it after moving the CLI.",
|
|
576
|
+
"# <swiftbar.title>SLEDS</swiftbar.title>",
|
|
577
|
+
"# <swiftbar.hideAbout>true</swiftbar.hideAbout>",
|
|
578
|
+
"# <swiftbar.hideRunInTerminal>true</swiftbar.hideRunInTerminal>",
|
|
579
|
+
"# <swiftbar.hideSwiftBar>true</swiftbar.hideSwiftBar>",
|
|
580
|
+
'export PATH="$HOME/.bun/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"',
|
|
581
|
+
`if [ -x ${shellQuote(nodePath)} ] && [ -f ${shellQuote(cliPath)} ]; then`,
|
|
582
|
+
` exec ${shellQuote(nodePath)} ${shellQuote(cliPath)} bar`,
|
|
583
|
+
"fi",
|
|
584
|
+
"if command -v sleds >/dev/null 2>&1; then exec sleds bar; fi",
|
|
585
|
+
'echo "sleds not found"',
|
|
586
|
+
'echo "---"',
|
|
587
|
+
'echo "Run \\`npx @sleds/cli bar --install\\` again. | font=Menlo size=12"',
|
|
588
|
+
""
|
|
589
|
+
].join(`
|
|
590
|
+
`);
|
|
591
|
+
var shellQuote = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
592
|
+
|
|
492
593
|
// src/seat.ts
|
|
493
594
|
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
494
595
|
import { join as join4 } from "node:path";
|
|
@@ -528,7 +629,16 @@ var parseSeatWindow = (raw, capturedAt) => {
|
|
|
528
629
|
const fiveHour = parseBar(limits.five_hour ?? limits.fiveHour);
|
|
529
630
|
const sevenDay = parseBar(limits.seven_day ?? limits.sevenDay);
|
|
530
631
|
const spendLimit = parseBar(limits.spend_limit ?? limits.spendLimit);
|
|
531
|
-
|
|
632
|
+
const models = {};
|
|
633
|
+
if (isObject4(limits.models))
|
|
634
|
+
for (const [k, v] of Object.entries(limits.models)) {
|
|
635
|
+
const bar = parseBar(v);
|
|
636
|
+
if (bar)
|
|
637
|
+
models[k] = bar;
|
|
638
|
+
}
|
|
639
|
+
const usedCreditsUsd = num2(limits.usedCreditsUsd);
|
|
640
|
+
const hasModels = Object.keys(models).length > 0;
|
|
641
|
+
if (!fiveHour && !sevenDay && !spendLimit && !hasModels && usedCreditsUsd === undefined)
|
|
532
642
|
return null;
|
|
533
643
|
const out = { capturedAt };
|
|
534
644
|
if (fiveHour)
|
|
@@ -537,6 +647,10 @@ var parseSeatWindow = (raw, capturedAt) => {
|
|
|
537
647
|
out.sevenDay = sevenDay;
|
|
538
648
|
if (spendLimit)
|
|
539
649
|
out.spendLimit = spendLimit;
|
|
650
|
+
if (hasModels)
|
|
651
|
+
out.models = models;
|
|
652
|
+
if (usedCreditsUsd !== undefined)
|
|
653
|
+
out.usedCreditsUsd = usedCreditsUsd;
|
|
540
654
|
return out;
|
|
541
655
|
};
|
|
542
656
|
var seatWindowFromStdin = (text, capturedAt) => {
|
|
@@ -702,40 +816,15 @@ var readStdin = (timeoutMs, stdin = process.stdin) => new Promise((resolve3) =>
|
|
|
702
816
|
stdin.resume();
|
|
703
817
|
});
|
|
704
818
|
|
|
705
|
-
// src/bar.ts
|
|
706
|
-
var SEAT_BAR_FRESH_MS = 6 * 60 * 60 * 1000;
|
|
707
|
-
var DASHBOARD_URL = "https://app.sleds.ai";
|
|
708
|
-
var MONO = "font=Menlo size=12";
|
|
709
|
-
var freshSeat = (w, now) => {
|
|
710
|
-
if (!w)
|
|
711
|
-
return null;
|
|
712
|
-
return now - w.capturedAt < SEAT_BAR_FRESH_MS ? w : null;
|
|
713
|
-
};
|
|
714
|
-
var barTitle = (m, seat) => {
|
|
715
|
-
const head = m ? usd(m.today.spentUsd) : "offline";
|
|
716
|
-
const wk = seat?.sevenDay ? `wk ${Math.round(seat.sevenDay.usedPct)}%` : null;
|
|
717
|
-
return [`sleds ${head}`, ...wk ? [wk] : []].join(SEP);
|
|
718
|
-
};
|
|
719
|
-
var formatBar = (m, seat, now) => {
|
|
720
|
-
const lines = [barTitle(m, seat), "---"];
|
|
721
|
-
if (m) {
|
|
722
|
-
for (const row2 of formatMeter(m, now))
|
|
723
|
-
lines.push(`${row2} | ${MONO}`);
|
|
724
|
-
} else {
|
|
725
|
-
lines.push(`offline${SEP}proxy did not answer | ${MONO}`);
|
|
726
|
-
}
|
|
727
|
-
const frag = formatSeatFragment(seat);
|
|
728
|
-
if (frag)
|
|
729
|
-
lines.push(`${"Limits:".padEnd(12)}${frag.replace(/^seat /, "")} | ${MONO}`);
|
|
730
|
-
lines.push("---");
|
|
731
|
-
lines.push(`Open dashboard | href=${DASHBOARD_URL}`);
|
|
732
|
-
lines.push("Refresh | refresh=true");
|
|
733
|
-
return lines;
|
|
734
|
-
};
|
|
735
|
-
|
|
736
819
|
// src/commands/bar.ts
|
|
820
|
+
var PLUGIN_NAME = "sleds.1m.sh";
|
|
737
821
|
var bar = async (o) => {
|
|
738
822
|
const log = o.log ?? console.log;
|
|
823
|
+
const argv = o.argv ?? [];
|
|
824
|
+
if (argv.includes("--install"))
|
|
825
|
+
return install(o, log);
|
|
826
|
+
if (argv.includes("--uninstall"))
|
|
827
|
+
return uninstall(o, log);
|
|
739
828
|
const now = (o.now ?? Date.now)();
|
|
740
829
|
const seat = freshSeat(readSeatState(o.home).window, now);
|
|
741
830
|
if (!meterSource(o.home)) {
|
|
@@ -749,6 +838,69 @@ var bar = async (o) => {
|
|
|
749
838
|
log(line);
|
|
750
839
|
return 0;
|
|
751
840
|
};
|
|
841
|
+
function swiftBarPluginDir() {
|
|
842
|
+
try {
|
|
843
|
+
const out = execFileSync("defaults", ["read", "com.ameba.SwiftBar", "PluginDirectory"], {
|
|
844
|
+
encoding: "utf8",
|
|
845
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
846
|
+
}).trim();
|
|
847
|
+
return out ? out.replace(/^~(?=$|\/)/, homedir()) : null;
|
|
848
|
+
} catch {
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
var pluginPath = (home) => join5(configDir(home), "swiftbar", PLUGIN_NAME);
|
|
853
|
+
function install(o, log) {
|
|
854
|
+
const script = pluginPath(o.home);
|
|
855
|
+
mkdirSync5(join5(script, ".."), { recursive: true, mode: 448 });
|
|
856
|
+
writeFileSync5(script, pluginScript(o.nodePath ?? process.execPath, o.cliPath ?? process.argv[1] ?? ""), {
|
|
857
|
+
mode: 493
|
|
858
|
+
});
|
|
859
|
+
chmodSync2(script, 493);
|
|
860
|
+
log(`wrote ${script}`);
|
|
861
|
+
const dir = (o.swiftBarPluginDir ?? swiftBarPluginDir)();
|
|
862
|
+
if (!dir) {
|
|
863
|
+
log("SwiftBar is not set up yet: `brew install --cask swiftbar`, launch it once and pick a plugin");
|
|
864
|
+
log("folder, then run `sleds bar --install` again — or symlink the file above into that folder.");
|
|
865
|
+
return 0;
|
|
866
|
+
}
|
|
867
|
+
const link = join5(dir, PLUGIN_NAME);
|
|
868
|
+
try {
|
|
869
|
+
mkdirSync5(dir, { recursive: true });
|
|
870
|
+
if (existsSync5(link) || isDanglingLink(link))
|
|
871
|
+
unlinkSync(link);
|
|
872
|
+
symlinkSync(script, link);
|
|
873
|
+
log(`linked ${link}`);
|
|
874
|
+
log("SwiftBar picks it up within a minute; the item reads `sleds $… · wk …%`.");
|
|
875
|
+
return 0;
|
|
876
|
+
} catch (e) {
|
|
877
|
+
log(`could not link into ${dir}: ${e instanceof Error ? e.message : String(e)}`);
|
|
878
|
+
log(`symlink it yourself: ln -sf ${script} ${link}`);
|
|
879
|
+
return 1;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
function uninstall(o, log) {
|
|
883
|
+
const dir = (o.swiftBarPluginDir ?? swiftBarPluginDir)();
|
|
884
|
+
const link = dir ? join5(dir, PLUGIN_NAME) : null;
|
|
885
|
+
if (link && (existsSync5(link) || isDanglingLink(link))) {
|
|
886
|
+
unlinkSync(link);
|
|
887
|
+
log(`removed ${link}`);
|
|
888
|
+
} else
|
|
889
|
+
log("no SwiftBar plugin link to remove");
|
|
890
|
+
const script = pluginPath(o.home);
|
|
891
|
+
if (existsSync5(script)) {
|
|
892
|
+
unlinkSync(script);
|
|
893
|
+
log(`removed ${script}`);
|
|
894
|
+
}
|
|
895
|
+
return 0;
|
|
896
|
+
}
|
|
897
|
+
var isDanglingLink = (p) => {
|
|
898
|
+
try {
|
|
899
|
+
return lstatSync(p).isSymbolicLink();
|
|
900
|
+
} catch {
|
|
901
|
+
return false;
|
|
902
|
+
}
|
|
903
|
+
};
|
|
752
904
|
|
|
753
905
|
// src/commands/hooks.ts
|
|
754
906
|
var DEFAULT_SEAT_EVENT_COMMAND = "sleds seat-event";
|
|
@@ -771,7 +923,7 @@ var hooks = (o) => {
|
|
|
771
923
|
};
|
|
772
924
|
|
|
773
925
|
// src/commands/init.ts
|
|
774
|
-
import { mkdirSync as
|
|
926
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
775
927
|
import { createInterface } from "node:readline";
|
|
776
928
|
|
|
777
929
|
// src/skill.ts
|
|
@@ -803,6 +955,7 @@ When the proxy is active it answers a walled request with its options. The user
|
|
|
803
955
|
/sled 2 hold; resume when the window resets
|
|
804
956
|
/sled 3 run overnight at half price
|
|
805
957
|
/sled 4 switch to a cheaper model for the rest of the session (offered when one saves ≥15%)
|
|
958
|
+
/sled 5 hand off to an idle coworker, or park for the team (offered on a team of two or more)
|
|
806
959
|
\`\`\`
|
|
807
960
|
|
|
808
961
|
## Window reset
|
|
@@ -873,6 +1026,12 @@ At a wall the proxy prints its options; \`/sled 3\` queues the session for the o
|
|
|
873
1026
|
- \`/sled resume <id>\` — pull a delivered job's output into the session as the assistant's reply and keep going.
|
|
874
1027
|
|
|
875
1028
|
A job that is \`parked\` or \`failed\` shows its reason in \`/sled jobs\`; the admin can see and cancel jobs on the dashboard's Jobs page.
|
|
1029
|
+
|
|
1030
|
+
## Team hand-off
|
|
1031
|
+
|
|
1032
|
+
\`/sled 5\` at a wall hands the snapshotted session to the most idle coworker (or parks it for anyone on the team). The team hears on Slack and the dashboard's Jobs page lists open hand-offs with the pickup command.
|
|
1033
|
+
|
|
1034
|
+
- \`/sled pickup <id>\` — in the coworker's Claude Code session: the handed-off session resumes there on the org key, metered to them. A hand-off meant for someone else, already taken, or without a snapshot gets one line saying so.
|
|
876
1035
|
`;
|
|
877
1036
|
|
|
878
1037
|
// src/commands/hooks.ts
|
|
@@ -1013,8 +1172,8 @@ var init = async (o) => {
|
|
|
1013
1172
|
...statusLine.prior !== undefined ? { priorStatusLine: statusLine.prior } : {}
|
|
1014
1173
|
});
|
|
1015
1174
|
wrote.push(`saved token and proxy URL to ${configPath(o.home)} (0600)`);
|
|
1016
|
-
|
|
1017
|
-
|
|
1175
|
+
mkdirSync6(skillDir(o.home), { recursive: true });
|
|
1176
|
+
writeFileSync6(skillPath(o.home), SKILL_MD);
|
|
1018
1177
|
wrote.push(`installed skill at ${skillPath(o.home)}`);
|
|
1019
1178
|
log("Nothing changes until a wall.");
|
|
1020
1179
|
log("");
|
|
@@ -1381,10 +1540,10 @@ var meter = async (o) => {
|
|
|
1381
1540
|
};
|
|
1382
1541
|
|
|
1383
1542
|
// src/schedule.ts
|
|
1384
|
-
import { existsSync as
|
|
1385
|
-
import { join as
|
|
1543
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync7, rmSync, writeFileSync as writeFileSync7 } from "node:fs";
|
|
1544
|
+
import { join as join6 } from "node:path";
|
|
1386
1545
|
var REVERT_LABEL = "ai.sleds.revert";
|
|
1387
|
-
var plistPath = (home) =>
|
|
1546
|
+
var plistPath = (home) => join6(home, "Library", "LaunchAgents", `${REVERT_LABEL}.plist`);
|
|
1388
1547
|
var xmlEscape = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1389
1548
|
var buildLaunchdPlist = (sledsArgv, deadline) => {
|
|
1390
1549
|
const args = [...sledsArgv, "off"].map((a) => ` <string>${xmlEscape(a)}</string>`).join(`
|
|
@@ -1423,9 +1582,9 @@ var scheduleRevert = (d, deadlineMs) => {
|
|
|
1423
1582
|
if (d.platform === "darwin") {
|
|
1424
1583
|
const p = plistPath(d.home);
|
|
1425
1584
|
try {
|
|
1426
|
-
|
|
1585
|
+
mkdirSync7(join6(d.home, "Library", "LaunchAgents"), { recursive: true });
|
|
1427
1586
|
d.exec("launchctl", ["unload", p]);
|
|
1428
|
-
|
|
1587
|
+
writeFileSync7(p, buildLaunchdPlist(d.sledsArgv, deadline));
|
|
1429
1588
|
const r = d.exec("launchctl", ["load", p]);
|
|
1430
1589
|
if (r.ok)
|
|
1431
1590
|
return { kind: "launchd", detail: `launchd agent ${REVERT_LABEL}` };
|
|
@@ -1452,7 +1611,7 @@ var scheduleRevert = (d, deadlineMs) => {
|
|
|
1452
1611
|
var cancelRevert = (d, kind) => {
|
|
1453
1612
|
if (kind === "launchd" || d.platform === "darwin") {
|
|
1454
1613
|
const p = plistPath(d.home);
|
|
1455
|
-
if (
|
|
1614
|
+
if (existsSync6(p)) {
|
|
1456
1615
|
d.exec("launchctl", ["unload", p]);
|
|
1457
1616
|
rmSync(p, { force: true });
|
|
1458
1617
|
}
|
|
@@ -1693,15 +1852,15 @@ var seatEvent = async (o) => {
|
|
|
1693
1852
|
};
|
|
1694
1853
|
|
|
1695
1854
|
// src/commands/seatSync.ts
|
|
1696
|
-
import { execFileSync } from "node:child_process";
|
|
1697
|
-
import { existsSync as
|
|
1855
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
1856
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync8, readFileSync as readFileSync5, readdirSync, writeFileSync as writeFileSync8 } from "node:fs";
|
|
1698
1857
|
import { platform } from "node:os";
|
|
1699
|
-
import { join as
|
|
1858
|
+
import { join as join8 } from "node:path";
|
|
1700
1859
|
|
|
1701
1860
|
// src/cookieDb.ts
|
|
1702
1861
|
import { copyFileSync, rmSync as rmSync2 } from "node:fs";
|
|
1703
1862
|
import { tmpdir } from "node:os";
|
|
1704
|
-
import { join as
|
|
1863
|
+
import { join as join7 } from "node:path";
|
|
1705
1864
|
var QUERY = "SELECT name, hex(encrypted_value) AS ev FROM cookies WHERE host_key LIKE '%claude.ai%'";
|
|
1706
1865
|
var isBun = () => typeof globalThis.Bun !== "undefined";
|
|
1707
1866
|
async function queryRows(dbPath) {
|
|
@@ -1730,7 +1889,7 @@ function nodeSqlite() {
|
|
|
1730
1889
|
return mod;
|
|
1731
1890
|
}
|
|
1732
1891
|
async function readCookieRows(dbPath) {
|
|
1733
|
-
const copy =
|
|
1892
|
+
const copy = join7(tmpdir(), `sleds-cookies-${process.pid}-${Date.now()}.sqlite`);
|
|
1734
1893
|
copyFileSync(dbPath, copy);
|
|
1735
1894
|
try {
|
|
1736
1895
|
const rows = await queryRows(copy);
|
|
@@ -2003,7 +2162,7 @@ function findFile(root, name) {
|
|
|
2003
2162
|
const stack = [root];
|
|
2004
2163
|
while (stack.length > 0) {
|
|
2005
2164
|
const dir = stack.pop();
|
|
2006
|
-
if (!dir || !
|
|
2165
|
+
if (!dir || !existsSync7(dir))
|
|
2007
2166
|
continue;
|
|
2008
2167
|
let entries;
|
|
2009
2168
|
try {
|
|
@@ -2012,7 +2171,7 @@ function findFile(root, name) {
|
|
|
2012
2171
|
continue;
|
|
2013
2172
|
}
|
|
2014
2173
|
for (const e of entries) {
|
|
2015
|
-
const p =
|
|
2174
|
+
const p = join8(dir, e.name);
|
|
2016
2175
|
if (e.isDirectory())
|
|
2017
2176
|
stack.push(p);
|
|
2018
2177
|
else if (e.name === name)
|
|
@@ -2023,8 +2182,8 @@ function findFile(root, name) {
|
|
|
2023
2182
|
}
|
|
2024
2183
|
function findCookieDb(home, source) {
|
|
2025
2184
|
const b = BROWSERS[source.browser];
|
|
2026
|
-
const root =
|
|
2027
|
-
if (!
|
|
2185
|
+
const root = join8(home, APP_SUPPORT, b.appDir);
|
|
2186
|
+
if (!existsSync7(root))
|
|
2028
2187
|
throw new Error(`no ${source.browser} data at ${root} (is it installed?)`);
|
|
2029
2188
|
if (!b.profiled) {
|
|
2030
2189
|
const db2 = findFile(root, "Cookies");
|
|
@@ -2032,15 +2191,15 @@ function findCookieDb(home, source) {
|
|
|
2032
2191
|
throw new Error(`no cookie store under ${root}`);
|
|
2033
2192
|
return db2;
|
|
2034
2193
|
}
|
|
2035
|
-
const localStatePath =
|
|
2036
|
-
const localState =
|
|
2194
|
+
const localStatePath = join8(root, "Local State");
|
|
2195
|
+
const localState = existsSync7(localStatePath) ? readFileSync5(localStatePath, "utf8") : null;
|
|
2037
2196
|
const wanted = source.profile ?? "Default";
|
|
2038
2197
|
const { dir, ambiguous } = resolveProfileDir(localState, wanted);
|
|
2039
2198
|
if (ambiguous)
|
|
2040
2199
|
throw new Error(`"${wanted}" names ${ambiguous.length} ${source.browser} profiles (${ambiguous.join(", ")}) — pass the directory or the account email instead`);
|
|
2041
2200
|
if (!dir)
|
|
2042
2201
|
throw new Error(`no ${source.browser} profile named "${wanted}" (check chrome://version)`);
|
|
2043
|
-
const db = cookieDbCandidates(
|
|
2202
|
+
const db = cookieDbCandidates(join8(root, dir)).find((p) => existsSync7(p));
|
|
2044
2203
|
if (!db)
|
|
2045
2204
|
throw new Error(`profile "${dir}" has no cookie store yet (open claude.ai in it once)`);
|
|
2046
2205
|
return db;
|
|
@@ -2050,7 +2209,7 @@ async function readCookieRowsBun(home, source) {
|
|
|
2050
2209
|
}
|
|
2051
2210
|
function keychainKey(service) {
|
|
2052
2211
|
try {
|
|
2053
|
-
return
|
|
2212
|
+
return execFileSync2("security", ["find-generic-password", "-w", "-s", service], {
|
|
2054
2213
|
encoding: "utf8"
|
|
2055
2214
|
}).trim();
|
|
2056
2215
|
} catch {
|
|
@@ -2062,7 +2221,7 @@ function decryptJar(rows, safeStoragePw) {
|
|
|
2062
2221
|
}
|
|
2063
2222
|
var curlUsage = (org, cookie) => {
|
|
2064
2223
|
try {
|
|
2065
|
-
const out =
|
|
2224
|
+
const out = execFileSync2("curl", usageCurlArgs(org, cookie), {
|
|
2066
2225
|
encoding: "utf8",
|
|
2067
2226
|
maxBuffer: 8000000
|
|
2068
2227
|
});
|
|
@@ -2083,7 +2242,7 @@ function fetchSeatUsage(jar, org, now, run) {
|
|
|
2083
2242
|
}
|
|
2084
2243
|
function launchAgentPlist(bun, script, home, source) {
|
|
2085
2244
|
const label = launchLabel(source);
|
|
2086
|
-
const log =
|
|
2245
|
+
const log = join8(home, ".config", "sleds", `${label.slice("ai.sleds.".length)}.log`);
|
|
2087
2246
|
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2088
2247
|
const args = [bun, script, "seat-sync", ...sourceArgs(source)].map((a) => ` <string>${esc(a)}</string>`).join(`
|
|
2089
2248
|
`);
|
|
@@ -2105,32 +2264,32 @@ ${args}
|
|
|
2105
2264
|
`;
|
|
2106
2265
|
}
|
|
2107
2266
|
function plistPath2(home, source) {
|
|
2108
|
-
return
|
|
2267
|
+
return join8(home, "Library", "LaunchAgents", `${launchLabel(source)}.plist`);
|
|
2109
2268
|
}
|
|
2110
|
-
function
|
|
2269
|
+
function install2(home, argv, source, log) {
|
|
2111
2270
|
const bun = argv[0] ?? process.execPath;
|
|
2112
2271
|
const script = argv[1] ?? "";
|
|
2113
2272
|
if (!script) {
|
|
2114
2273
|
log("cannot resolve the sleds script path to install the timer");
|
|
2115
2274
|
return 1;
|
|
2116
2275
|
}
|
|
2117
|
-
const dir =
|
|
2118
|
-
|
|
2276
|
+
const dir = join8(home, "Library", "LaunchAgents");
|
|
2277
|
+
mkdirSync8(dir, { recursive: true });
|
|
2119
2278
|
const path = plistPath2(home, source);
|
|
2120
|
-
|
|
2279
|
+
writeFileSync8(path, launchAgentPlist(bun, script, home, source), { mode: 384 });
|
|
2121
2280
|
try {
|
|
2122
|
-
|
|
2281
|
+
execFileSync2("launchctl", ["unload", path], { stdio: "ignore" });
|
|
2123
2282
|
} catch {}
|
|
2124
|
-
|
|
2283
|
+
execFileSync2("launchctl", ["load", "-w", path]);
|
|
2125
2284
|
log(`seat-sync timer installed (every ${SYNC_INTERVAL_S / 60} min) → ${path}`);
|
|
2126
2285
|
return 0;
|
|
2127
2286
|
}
|
|
2128
|
-
function
|
|
2287
|
+
function uninstall2(home, source, log) {
|
|
2129
2288
|
const path = plistPath2(home, source);
|
|
2130
2289
|
try {
|
|
2131
|
-
|
|
2290
|
+
execFileSync2("launchctl", ["unload", "-w", path], { stdio: "ignore" });
|
|
2132
2291
|
} catch {}
|
|
2133
|
-
log(
|
|
2292
|
+
log(existsSync7(path) ? `seat-sync timer removed (${path} can be deleted)` : "no seat-sync timer");
|
|
2134
2293
|
return 0;
|
|
2135
2294
|
}
|
|
2136
2295
|
var seatSync = async (o) => {
|
|
@@ -2143,9 +2302,9 @@ var seatSync = async (o) => {
|
|
|
2143
2302
|
}
|
|
2144
2303
|
const { source } = parsed;
|
|
2145
2304
|
if (source.uninstall)
|
|
2146
|
-
return
|
|
2305
|
+
return uninstall2(o.home, source, log);
|
|
2147
2306
|
if (source.install)
|
|
2148
|
-
return
|
|
2307
|
+
return install2(o.home, [process.execPath, process.argv[1] ?? ""], source, log);
|
|
2149
2308
|
if (platform() !== "darwin" && !o.readCookieRows) {
|
|
2150
2309
|
log("seat-sync is macOS-only for now.");
|
|
2151
2310
|
return 2;
|
|
@@ -2191,6 +2350,9 @@ var seatSync = async (o) => {
|
|
|
2191
2350
|
log(`seat-sync: usage read failed — ${why}`);
|
|
2192
2351
|
return 2;
|
|
2193
2352
|
}
|
|
2353
|
+
try {
|
|
2354
|
+
writeSeatState(o.home, { ...readSeatState(o.home), window: usageAsWindow(usage) });
|
|
2355
|
+
} catch {}
|
|
2194
2356
|
const posted = await postSeat(proxyUrl, token, seatSyncBody(usage), o.fetchFn ?? fetch);
|
|
2195
2357
|
const wk = usage.sevenDay ? `wk ${Math.round(usage.sevenDay.usedPct)}%` : "wk —";
|
|
2196
2358
|
const models = Object.keys(usage.models).length;
|
|
@@ -2198,6 +2360,15 @@ var seatSync = async (o) => {
|
|
|
2198
2360
|
log(posted ? `seat-sync: posted ${wk} · ${models} model bucket${models === 1 ? "" : "s"}${credits}` : `seat-sync: read ${wk} but the proxy did not accept it`);
|
|
2199
2361
|
return posted ? 0 : 2;
|
|
2200
2362
|
};
|
|
2363
|
+
function usageAsWindow(u) {
|
|
2364
|
+
return {
|
|
2365
|
+
capturedAt: u.capturedAt,
|
|
2366
|
+
...u.fiveHour ? { fiveHour: u.fiveHour } : {},
|
|
2367
|
+
...u.sevenDay ? { sevenDay: u.sevenDay } : {},
|
|
2368
|
+
...Object.keys(u.models).length > 0 ? { models: u.models } : {},
|
|
2369
|
+
...u.usedCreditsUsd !== undefined ? { usedCreditsUsd: u.usedCreditsUsd } : {}
|
|
2370
|
+
};
|
|
2371
|
+
}
|
|
2201
2372
|
async function postSeat(proxyUrl, token, body, fetchFn) {
|
|
2202
2373
|
try {
|
|
2203
2374
|
const res = await fetchFn(`${proxyUrl}/proxy/seat`, {
|
|
@@ -2312,7 +2483,7 @@ var specEvent = async (o) => {
|
|
|
2312
2483
|
};
|
|
2313
2484
|
|
|
2314
2485
|
// src/commands/status.ts
|
|
2315
|
-
import { existsSync as
|
|
2486
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
2316
2487
|
var checkHealth = async (url, fetchFn = fetch, timeoutMs = 1000) => {
|
|
2317
2488
|
try {
|
|
2318
2489
|
const res = await fetchFn(`${url}/healthz`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
@@ -2380,7 +2551,7 @@ var status = async (o) => {
|
|
|
2380
2551
|
const log = o.log ?? console.log;
|
|
2381
2552
|
const nowMs = (o.now ?? Date.now)();
|
|
2382
2553
|
const cfg = readConfig(o.home);
|
|
2383
|
-
const settings =
|
|
2554
|
+
const settings = existsSync8(settingsPath(o.home)) ? readSettings(o.home) : {};
|
|
2384
2555
|
const inSettings = readProxy(settings);
|
|
2385
2556
|
const proxyUrl = inSettings?.url ?? cfg.proxyUrl;
|
|
2386
2557
|
const token = inSettings?.token ?? cfg.token;
|
|
@@ -2478,11 +2649,11 @@ var rememberSeat = (o, window, now) => {
|
|
|
2478
2649
|
};
|
|
2479
2650
|
|
|
2480
2651
|
// src/commands/uninstall.ts
|
|
2481
|
-
import { existsSync as
|
|
2482
|
-
var
|
|
2652
|
+
import { existsSync as existsSync9, rmSync as rmSync3 } from "node:fs";
|
|
2653
|
+
var uninstall3 = (o) => {
|
|
2483
2654
|
const log = o.log ?? console.log;
|
|
2484
2655
|
const removed = [];
|
|
2485
|
-
if (
|
|
2656
|
+
if (existsSync9(settingsPath(o.home))) {
|
|
2486
2657
|
let settings = readSettings(o.home);
|
|
2487
2658
|
let changed = false;
|
|
2488
2659
|
if (hasEitherKey(settings)) {
|
|
@@ -2514,13 +2685,13 @@ var uninstall2 = (o) => {
|
|
|
2514
2685
|
writeSettings(o.home, settings);
|
|
2515
2686
|
}
|
|
2516
2687
|
for (const file of [meterCachePath(o.home), seatStatePath(o.home)]) {
|
|
2517
|
-
if (
|
|
2688
|
+
if (existsSync9(file)) {
|
|
2518
2689
|
rmSync3(file, { force: true });
|
|
2519
2690
|
removed.push(file);
|
|
2520
2691
|
}
|
|
2521
2692
|
}
|
|
2522
2693
|
const skill = skillDir(o.home);
|
|
2523
|
-
if (
|
|
2694
|
+
if (existsSync9(skill)) {
|
|
2524
2695
|
rmSync3(skill, { recursive: true, force: true });
|
|
2525
2696
|
removed.push(skill);
|
|
2526
2697
|
}
|
|
@@ -2568,7 +2739,7 @@ commands:
|
|
|
2568
2739
|
uninstall Remove the proxy settings, skill and status line. Keeps ~/.sleds-token.
|
|
2569
2740
|
status Show the switch state, deadline, proxy health and wall mode.
|
|
2570
2741
|
meter Show the pipeline: built today, queued with its band, delivered overnight.
|
|
2571
|
-
bar Print the menu-bar meter in SwiftBar format
|
|
2742
|
+
bar Print the menu-bar meter in SwiftBar format; --install links it into SwiftBar.
|
|
2572
2743
|
mcp Run the MCP server on stdio: tools usage, estimate, overnight, jobs.
|
|
2573
2744
|
Register with \`claude mcp add sleds -- sleds mcp\` (Cowork sees the same server).
|
|
2574
2745
|
seat-sync Read the seat's subscription usage (weekly/5h limits, per-model, credits) from a
|
|
@@ -2622,13 +2793,13 @@ var sledsArgv = () => {
|
|
|
2622
2793
|
const script = process.argv[1];
|
|
2623
2794
|
return script ? [process.execPath, script] : [process.execPath];
|
|
2624
2795
|
};
|
|
2625
|
-
var
|
|
2626
|
-
var statusLineCommand = (argv) => [...argv, "statusline"].map(
|
|
2627
|
-
var seatEventCommand = (argv) => [...argv, "seat-event"].map(
|
|
2628
|
-
var specEventCommand = (argv) => [...argv, "spec-event"].map(
|
|
2796
|
+
var shellQuote2 = (s) => /^[\w./-]+$/.test(s) ? s : `'${s.replace(/'/g, "'\\''")}'`;
|
|
2797
|
+
var statusLineCommand = (argv) => [...argv, "statusline"].map(shellQuote2).join(" ");
|
|
2798
|
+
var seatEventCommand = (argv) => [...argv, "seat-event"].map(shellQuote2).join(" ");
|
|
2799
|
+
var specEventCommand = (argv) => [...argv, "spec-event"].map(shellQuote2).join(" ");
|
|
2629
2800
|
var main = async (argv) => {
|
|
2630
2801
|
const { cmd, flags } = parseArgs(argv);
|
|
2631
|
-
const home =
|
|
2802
|
+
const home = homedir2();
|
|
2632
2803
|
const scheduler = {
|
|
2633
2804
|
platform: platform2(),
|
|
2634
2805
|
home,
|
|
@@ -2670,13 +2841,13 @@ var main = async (argv) => {
|
|
|
2670
2841
|
case "off":
|
|
2671
2842
|
return off({ home, scheduler });
|
|
2672
2843
|
case "uninstall":
|
|
2673
|
-
return
|
|
2844
|
+
return uninstall3({ home });
|
|
2674
2845
|
case "status":
|
|
2675
2846
|
return status({ home });
|
|
2676
2847
|
case "meter":
|
|
2677
2848
|
return meter({ home });
|
|
2678
2849
|
case "bar":
|
|
2679
|
-
return bar({ home });
|
|
2850
|
+
return bar({ home, argv: process.argv.slice(3) });
|
|
2680
2851
|
case "mcp":
|
|
2681
2852
|
return mcp({ home });
|
|
2682
2853
|
case "seat-sync":
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sleds/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "SLEDS CLI — the cost and limits of AI agent work, decided at the wall. Point Claude Code at a SLEDS proxy; estimate before you run; hold, switch model, or run overnight when you hit a limit.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"bin": { "sleds": "
|
|
7
|
+
"bin": { "sleds": "dist/sleds.mjs" },
|
|
8
8
|
"files": ["dist/sleds.mjs", "README.md"],
|
|
9
9
|
"engines": { "node": ">=22.5" },
|
|
10
10
|
"os": ["darwin", "linux"],
|