@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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/sleds.mjs +255 -84
  3. 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
- if (!fiveHour && !sevenDay && !spendLimit)
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 mkdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
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
- mkdirSync5(skillDir(o.home), { recursive: true });
1017
- writeFileSync5(skillPath(o.home), SKILL_MD);
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 existsSync5, mkdirSync as mkdirSync6, rmSync, writeFileSync as writeFileSync6 } from "node:fs";
1385
- import { join as join5 } from "node:path";
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) => join5(home, "Library", "LaunchAgents", `${REVERT_LABEL}.plist`);
1546
+ var plistPath = (home) => join6(home, "Library", "LaunchAgents", `${REVERT_LABEL}.plist`);
1388
1547
  var xmlEscape = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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
- mkdirSync6(join5(d.home, "Library", "LaunchAgents"), { recursive: true });
1585
+ mkdirSync7(join6(d.home, "Library", "LaunchAgents"), { recursive: true });
1427
1586
  d.exec("launchctl", ["unload", p]);
1428
- writeFileSync6(p, buildLaunchdPlist(d.sledsArgv, deadline));
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 (existsSync5(p)) {
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 existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync5, readdirSync, writeFileSync as writeFileSync7 } from "node:fs";
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 join7 } from "node:path";
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 join6 } from "node:path";
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 = join6(tmpdir(), `sleds-cookies-${process.pid}-${Date.now()}.sqlite`);
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 || !existsSync6(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 = join7(dir, e.name);
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 = join7(home, APP_SUPPORT, b.appDir);
2027
- if (!existsSync6(root))
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 = join7(root, "Local State");
2036
- const localState = existsSync6(localStatePath) ? readFileSync5(localStatePath, "utf8") : null;
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(join7(root, dir)).find((p) => existsSync6(p));
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 execFileSync("security", ["find-generic-password", "-w", "-s", service], {
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 = execFileSync("curl", usageCurlArgs(org, cookie), {
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 = join7(home, ".config", "sleds", `${label.slice("ai.sleds.".length)}.log`);
2245
+ const log = join8(home, ".config", "sleds", `${label.slice("ai.sleds.".length)}.log`);
2087
2246
  const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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 join7(home, "Library", "LaunchAgents", `${launchLabel(source)}.plist`);
2267
+ return join8(home, "Library", "LaunchAgents", `${launchLabel(source)}.plist`);
2109
2268
  }
2110
- function install(home, argv, source, log) {
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 = join7(home, "Library", "LaunchAgents");
2118
- mkdirSync7(dir, { recursive: true });
2276
+ const dir = join8(home, "Library", "LaunchAgents");
2277
+ mkdirSync8(dir, { recursive: true });
2119
2278
  const path = plistPath2(home, source);
2120
- writeFileSync7(path, launchAgentPlist(bun, script, home, source), { mode: 384 });
2279
+ writeFileSync8(path, launchAgentPlist(bun, script, home, source), { mode: 384 });
2121
2280
  try {
2122
- execFileSync("launchctl", ["unload", path], { stdio: "ignore" });
2281
+ execFileSync2("launchctl", ["unload", path], { stdio: "ignore" });
2123
2282
  } catch {}
2124
- execFileSync("launchctl", ["load", "-w", path]);
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 uninstall(home, source, log) {
2287
+ function uninstall2(home, source, log) {
2129
2288
  const path = plistPath2(home, source);
2130
2289
  try {
2131
- execFileSync("launchctl", ["unload", "-w", path], { stdio: "ignore" });
2290
+ execFileSync2("launchctl", ["unload", "-w", path], { stdio: "ignore" });
2132
2291
  } catch {}
2133
- log(existsSync6(path) ? `seat-sync timer removed (${path} can be deleted)` : "no seat-sync timer");
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 uninstall(o.home, source, log);
2305
+ return uninstall2(o.home, source, log);
2147
2306
  if (source.install)
2148
- return install(o.home, [process.execPath, process.argv[1] ?? ""], source, log);
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 existsSync7 } from "node:fs";
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 = existsSync7(settingsPath(o.home)) ? readSettings(o.home) : {};
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 existsSync8, rmSync as rmSync3 } from "node:fs";
2482
- var uninstall2 = (o) => {
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 (existsSync8(settingsPath(o.home))) {
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 (existsSync8(file)) {
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 (existsSync8(skill)) {
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 (used by infra/swiftbar/sleds.1m.sh).
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 shellQuote = (s) => /^[\w./-]+$/.test(s) ? s : `'${s.replace(/'/g, "'\\''")}'`;
2626
- var statusLineCommand = (argv) => [...argv, "statusline"].map(shellQuote).join(" ");
2627
- var seatEventCommand = (argv) => [...argv, "seat-event"].map(shellQuote).join(" ");
2628
- var specEventCommand = (argv) => [...argv, "spec-event"].map(shellQuote).join(" ");
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 = homedir();
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 uninstall2({ home });
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.0",
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": "./dist/sleds.mjs" },
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"],