@sleds/cli 0.1.5 → 0.1.7

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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # @sleds/cli
2
2
 
3
+ > Private while SLEDS is in stealth: the package is published with restricted access, so `npm i -g @sleds/cli` / `npx @sleds/cli` need an `npm login` that belongs to the `sleds` org. Public release flips `publishConfig.access` back to `public`.
4
+
3
5
  The cost and limits of AI agent work, decided at the wall.
4
6
 
5
7
  SLEDS sits in front of Claude Code. It meters what you spend, sizes a job before it runs, and when you hit a limit — a rate limit, a weekly cap, exhausted credits — it gives you a choice right there in the terminal: continue on the org key, hold until the window resets, switch to a cheaper model, or run it overnight at half price.
package/dist/sleds.mjs CHANGED
@@ -41,6 +41,38 @@ var claudeDir = (home, env = process.env) => {
41
41
  };
42
42
  var hasHomeOverride = (env = process.env) => Boolean(env.SLEDS_HOME?.trim() || env.CLAUDE_CONFIG_DIR?.trim());
43
43
  var settingsPath = (home) => join(claudeDir(home), "settings.json");
44
+ var sledsCommand = (sub, home, env = process.env) => {
45
+ const parts = [];
46
+ const sh = env.SLEDS_HOME?.trim();
47
+ const cc = env.CLAUDE_CONFIG_DIR?.trim();
48
+ if (sh)
49
+ parts.push(`SLEDS_HOME=${shellWord(resolve(home, sh))}`);
50
+ if (cc)
51
+ parts.push(`CLAUDE_CONFIG_DIR=${shellWord(resolve(home, cc))}`);
52
+ parts.push(`sleds ${sub}`);
53
+ return parts.join(" ");
54
+ };
55
+ var sledsCommandsIn = (settings) => {
56
+ const out = [];
57
+ const visit = (v) => {
58
+ if (Array.isArray(v)) {
59
+ for (const x of v)
60
+ visit(x);
61
+ return;
62
+ }
63
+ if (!isObject(v))
64
+ return;
65
+ if (typeof v.command === "string" && /\bsleds\b/.test(v.command))
66
+ out.push(v.command);
67
+ for (const x of Object.values(v))
68
+ visit(x);
69
+ };
70
+ visit(settings[STATUS_LINE_KEY]);
71
+ visit(settings.hooks);
72
+ return out;
73
+ };
74
+ var bakedHome = (command) => command.match(/(?:^|\s)SLEDS_HOME=('([^']*)'|(\S+))/)?.[2] ?? command.match(/(?:^|\s)SLEDS_HOME=(\S+)/)?.[1];
75
+ var shellWord = (v) => /^[\w./~-]+$/.test(v) ? v : `'${v.replace(/'/g, "'\\''")}'`;
44
76
  var backupPath = (home) => join(claudeDir(home), "settings.json.sleds-backup");
45
77
  var tokenPath = (home, env = process.env) => {
46
78
  const o = env.SLEDS_HOME?.trim();
@@ -934,7 +966,7 @@ var isDanglingLink = (p) => {
934
966
  // src/commands/doctor.ts
935
967
  import { execFileSync as execFileSync3 } from "node:child_process";
936
968
  import { existsSync as existsSync6, lstatSync as lstatSync2, readFileSync as readFileSync5, readdirSync, readlinkSync } from "node:fs";
937
- import { join as join7 } from "node:path";
969
+ import { join as join7, resolve as resolve5 } from "node:path";
938
970
 
939
971
  // src/commands/bar.ts
940
972
  import { execFileSync as execFileSync2 } from "node:child_process";
@@ -1164,6 +1196,20 @@ async function runDoctor(o) {
1164
1196
  detail: `seat-event ${seat2 ? "on" : "missing"} · spec-event ${spec ? "on" : "missing"}`,
1165
1197
  fix: seat2 && spec ? undefined : "sleds hooks"
1166
1198
  });
1199
+ const want = env.SLEDS_HOME?.trim() ? resolve5(home, env.SLEDS_HOME.trim()) : undefined;
1200
+ const cmds = sledsCommandsIn(settings);
1201
+ const wrong = cmds.filter((c) => {
1202
+ const got = bakedHome(c);
1203
+ return want ? got !== want : got !== undefined;
1204
+ });
1205
+ if (cmds.length > 0) {
1206
+ checks.push({
1207
+ level: wrong.length === 0 ? "ok" : "fail",
1208
+ name: "commands",
1209
+ detail: wrong.length === 0 ? `${cmds.length} command${cmds.length === 1 ? "" : "s"} in settings.json ${want ? `carry SLEDS_HOME=${want}` : "run the default seat"}` : `${wrong.length} of ${cmds.length} commands in settings.json ${want ? `do not carry SLEDS_HOME=${want}` : "carry another seat's SLEDS_HOME"} — their reads land on the wrong seat`,
1210
+ fix: wrong.length === 0 ? undefined : `${overrides.length ? `${overrides.join(" ")} ` : ""}sleds hooks (re-writes the status line + hooks with this seat baked in)`
1211
+ });
1212
+ }
1167
1213
  const skill = existsSync6(skillPath(home));
1168
1214
  checks.push({
1169
1215
  level: skill ? "ok" : "warn",
@@ -1249,8 +1295,6 @@ var doctor = async (o) => {
1249
1295
  };
1250
1296
 
1251
1297
  // src/commands/hooks.ts
1252
- var DEFAULT_SEAT_EVENT_COMMAND = "sleds seat-event";
1253
- var DEFAULT_SPEC_EVENT_COMMAND = "sleds spec-event";
1254
1298
  var hooks = (o) => {
1255
1299
  const log = o.log ?? console.log;
1256
1300
  const error = o.error ?? console.error;
@@ -1261,9 +1305,13 @@ var hooks = (o) => {
1261
1305
  const settings = readSettings(o.home);
1262
1306
  const had = hasSeatHook(settings);
1263
1307
  const hadSpec = hasSpecHook(settings);
1264
- const withSeat = applySeatHook(settings, o.seatEventCommand ?? DEFAULT_SEAT_EVENT_COMMAND);
1265
- writeSettings(o.home, applySpecHook(withSeat, o.specEventCommand ?? DEFAULT_SPEC_EVENT_COMMAND));
1308
+ const withSeat = applySeatHook(settings, o.seatEventCommand ?? sledsCommand("seat-event", o.home, o.env));
1309
+ const withSpec = applySpecHook(withSeat, o.specEventCommand ?? sledsCommand("spec-event", o.home, o.env));
1310
+ const hadLine = o.refreshStatusLine !== false && isSledsStatusLine(withSpec[STATUS_LINE_KEY]);
1311
+ writeSettings(o.home, hadLine ? applyStatusLine(withSpec, sledsCommand("statusline", o.home, o.env)).settings : withSpec);
1266
1312
  log(`${had ? "refreshed" : "set"} hooks.Notification (${SEAT_EVENT_MATCHER}) in ${settingsPath(o.home)}`);
1313
+ if (hadLine)
1314
+ log(`refreshed statusLine in ${settingsPath(o.home)}`);
1267
1315
  log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(o.home)}`);
1268
1316
  return 0;
1269
1317
  };
@@ -1393,8 +1441,6 @@ From Cowork or any MCP client (\`sleds mcp\`): the \`handoffs\` tool lists what
1393
1441
  `;
1394
1442
 
1395
1443
  // src/commands/hooks.ts
1396
- var DEFAULT_SEAT_EVENT_COMMAND2 = "sleds seat-event";
1397
- var DEFAULT_SPEC_EVENT_COMMAND2 = "sleds spec-event";
1398
1444
  var hooks2 = (o) => {
1399
1445
  const log = o.log ?? console.log;
1400
1446
  const error = o.error ?? console.error;
@@ -1405,21 +1451,24 @@ var hooks2 = (o) => {
1405
1451
  const settings = readSettings(o.home);
1406
1452
  const had = hasSeatHook(settings);
1407
1453
  const hadSpec = hasSpecHook(settings);
1408
- const withSeat = applySeatHook(settings, o.seatEventCommand ?? DEFAULT_SEAT_EVENT_COMMAND2);
1409
- writeSettings(o.home, applySpecHook(withSeat, o.specEventCommand ?? DEFAULT_SPEC_EVENT_COMMAND2));
1454
+ const withSeat = applySeatHook(settings, o.seatEventCommand ?? sledsCommand("seat-event", o.home, o.env));
1455
+ const withSpec = applySpecHook(withSeat, o.specEventCommand ?? sledsCommand("spec-event", o.home, o.env));
1456
+ const hadLine = o.refreshStatusLine !== false && isSledsStatusLine(withSpec[STATUS_LINE_KEY]);
1457
+ writeSettings(o.home, hadLine ? applyStatusLine(withSpec, sledsCommand("statusline", o.home, o.env)).settings : withSpec);
1410
1458
  log(`${had ? "refreshed" : "set"} hooks.Notification (${SEAT_EVENT_MATCHER}) in ${settingsPath(o.home)}`);
1459
+ if (hadLine)
1460
+ log(`refreshed statusLine in ${settingsPath(o.home)}`);
1411
1461
  log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(o.home)}`);
1412
1462
  return 0;
1413
1463
  };
1414
1464
 
1415
1465
  // src/commands/init.ts
1416
1466
  var DEFAULT_PROXY_URL = "http://localhost:8787";
1417
- var DEFAULT_STATUS_LINE_COMMAND = "sleds statusline";
1418
- var promptStdin = (question) => new Promise((resolve5) => {
1467
+ var promptStdin = (question) => new Promise((resolve6) => {
1419
1468
  const rl = createInterface({ input: process.stdin, output: process.stderr });
1420
1469
  rl.question(question, (answer) => {
1421
1470
  rl.close();
1422
- resolve5(answer.trim());
1471
+ resolve6(answer.trim());
1423
1472
  });
1424
1473
  });
1425
1474
  async function signupIndividual(proxyUrl, email, credential, fetchFn = fetch) {
@@ -1478,7 +1527,7 @@ async function probeProxy(url, fetchFn = fetch, opts = {}) {
1478
1527
  const timeoutMs = opts.timeoutMs ?? 1e4;
1479
1528
  for (let i = 0;i < tries; i++) {
1480
1529
  try {
1481
- const res = await fetchFn(`${url}/health`, { signal: AbortSignal.timeout(timeoutMs) });
1530
+ const res = await fetchFn(`${url}/healthz`, { signal: AbortSignal.timeout(timeoutMs) });
1482
1531
  if (res.ok)
1483
1532
  return true;
1484
1533
  } catch {}
@@ -1541,21 +1590,23 @@ var init = async (o) => {
1541
1590
  return 2;
1542
1591
  }
1543
1592
  if (!o.noProbe && !await probeProxy(url, o.fetchFn)) {
1544
- error(`No SLEDS proxy answered at ${url}/health. Check the URL (or pass --no-probe to save it anyway).`);
1593
+ error(`No SLEDS proxy answered at ${url}/healthz. Check the URL (or pass --no-probe to save it anyway).`);
1545
1594
  return 2;
1546
1595
  }
1547
1596
  const wrote = [];
1548
1597
  if (backupSettings(o.home))
1549
1598
  wrote.push(`backed up settings to ${backupPath(o.home)}`);
1550
1599
  const withProxy = applyProxy(readSettings(o.home), { url, token: resolved.token });
1551
- const statusLine = applyStatusLine(withProxy, o.statusLineCommand ?? DEFAULT_STATUS_LINE_COMMAND);
1600
+ const statusLine = applyStatusLine(withProxy, o.statusLineCommand ?? sledsCommand("statusline", o.home, o.env));
1552
1601
  writeSettings(o.home, statusLine.settings);
1553
1602
  wrote.push(`set env.ANTHROPIC_BASE_URL and env.ANTHROPIC_AUTH_TOKEN in ${settingsPath(o.home)}`);
1554
1603
  wrote.push(statusLine.prior === undefined ? `set statusLine in ${settingsPath(o.home)}` : `set statusLine in ${settingsPath(o.home)} (previous one kept for uninstall)`);
1555
1604
  hooks2({
1556
1605
  home: o.home,
1557
- seatEventCommand: o.seatEventCommand ?? DEFAULT_SEAT_EVENT_COMMAND2,
1558
- specEventCommand: o.specEventCommand ?? DEFAULT_SPEC_EVENT_COMMAND2,
1606
+ env: o.env,
1607
+ refreshStatusLine: false,
1608
+ seatEventCommand: o.seatEventCommand,
1609
+ specEventCommand: o.specEventCommand,
1559
1610
  log: (line) => wrote.push(line),
1560
1611
  error
1561
1612
  });
@@ -3242,7 +3293,7 @@ commands:
3242
3293
  init Point Claude Code at a SLEDS proxy, install the /sled skill and the status line.
3243
3294
  --token <sled_\u2026> seat token (else SLEDS_TOKEN, ~/.sleds-token, or prompt)
3244
3295
  --url <proxy-url> proxy base URL (else SLEDS_PROXY_URL, default ${DEFAULT_PROXY_URL})
3245
- --no-probe skip the /health check of the proxy URL
3296
+ --no-probe skip the /healthz check of the proxy URL
3246
3297
  --non-interactive never prompt; exit 2 if no token is available
3247
3298
  --signup no org? create an individual seat: your email + your own
3248
3299
  Anthropic API key (validated, stored encrypted, the only key it spends)
@@ -3384,7 +3435,7 @@ var main = async (argv) => {
3384
3435
  case "version":
3385
3436
  case "--version":
3386
3437
  case "-v":
3387
- console.log(`sleds ${"0.1.5"}`);
3438
+ console.log(`sleds ${"0.1.7"}`);
3388
3439
  return 0;
3389
3440
  default:
3390
3441
  console.error(`unknown command: ${cmd}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleds/cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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",
@@ -24,7 +24,7 @@
24
24
  "batch",
25
25
  "mcp"
26
26
  ],
27
- "publishConfig": { "access": "public" },
27
+ "publishConfig": { "access": "restricted" },
28
28
  "scripts": {
29
29
  "build": "bun scripts/build.ts",
30
30
  "prepack": "bun scripts/build.ts",