@sleds/cli 0.1.6 → 0.1.11

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 +3 -1
  2. package/dist/sleds.mjs +225 -37
  3. package/package.json +2 -2
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.
@@ -10,7 +12,7 @@ SLEDS sits in front of Claude Code. It meters what you spend, sizes a job before
10
12
  npx @sleds/cli init --signup
11
13
  ```
12
14
 
13
- No org? That's the whole setup: your email plus your own Anthropic API key. SLEDS checks the key, stores it encrypted, and it's the only key your seat ever spends. If your team already runs SLEDS, use the seat token your admin gave you instead:
15
+ No org? `--signup` asks: just you, or your team. Just you is your email plus your own Anthropic API key. SLEDS checks the key, stores it encrypted, and it's the only key your seat ever spends. Your team is a work email and a team name — you become its owner; a platform key is optional (a Team plan without one still gets seat tracking, hold and hand-off at a wall). If your team already runs SLEDS, use the seat token your admin gave you instead:
14
16
 
15
17
  ```bash
16
18
  npx @sleds/cli init --token sled_…
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();
@@ -273,8 +305,36 @@ var parseConfig = (raw) => {
273
305
  cfg.schedule = schedule;
274
306
  if (raw.priorStatusLine !== undefined)
275
307
  cfg.priorStatusLine = raw.priorStatusLine;
308
+ if (raw.wiring === "seat-first" || raw.wiring === "proxy-always")
309
+ cfg.wiring = raw.wiring;
310
+ const org = parseOrgInfo(raw.org);
311
+ if (org)
312
+ cfg.org = org;
276
313
  return cfg;
277
314
  };
315
+ var parseOrgInfo = (raw) => {
316
+ if (!isObject2(raw) || !isObject2(raw.capabilities))
317
+ return;
318
+ const name = optString(raw.name);
319
+ const slug = optString(raw.slug);
320
+ const plan = raw.plan;
321
+ const c = raw.capabilities;
322
+ const src = c.upstreamSource;
323
+ if (name === undefined || slug === undefined || plan !== "team" && plan !== "enterprise" && plan !== "api" && plan !== "individual" || src !== "own" && src !== "shared" && src !== "none")
324
+ return;
325
+ return {
326
+ name,
327
+ slug,
328
+ plan,
329
+ capabilities: {
330
+ upstreamKey: c.upstreamKey === true,
331
+ adminKey: c.adminKey === true,
332
+ seats: c.seats !== false,
333
+ upstreamSource: src
334
+ },
335
+ mode: raw.mode === "proxy-always" ? "proxy-always" : "seat-first"
336
+ };
337
+ };
278
338
  var readConfig = (home) => {
279
339
  const p = configPath(home);
280
340
  if (!existsSync2(p))
@@ -798,6 +858,8 @@ var postJson = async (o, path, body) => {
798
858
  var postSeatWindow = (o, w) => postJson(o, "/proxy/seat", seatWindowBody(w));
799
859
  var postSeatWall = (o, input) => postJson(o, "/proxy/seat-wall", input);
800
860
  var SEAT_WALL_LINE = "Seat limit hit. sleds on --until-reset to continue on the org key.";
861
+ var SEAT_WALL_LINE_NO_KEY = "Seat limit hit. Hold until the reset, or /sled 5 to hand off — this org has no platform key to switch to.";
862
+ var seatWallLine = (hasUpstreamKey) => hasUpstreamKey ? SEAT_WALL_LINE : SEAT_WALL_LINE_NO_KEY;
801
863
  var readStdin = (timeoutMs, stdin = process.stdin) => new Promise((resolve3) => {
802
864
  if (stdin.isTTY) {
803
865
  resolve3("");
@@ -934,7 +996,7 @@ var isDanglingLink = (p) => {
934
996
  // src/commands/doctor.ts
935
997
  import { execFileSync as execFileSync3 } from "node:child_process";
936
998
  import { existsSync as existsSync6, lstatSync as lstatSync2, readFileSync as readFileSync5, readdirSync, readlinkSync } from "node:fs";
937
- import { join as join7 } from "node:path";
999
+ import { join as join7, resolve as resolve5 } from "node:path";
938
1000
 
939
1001
  // src/commands/bar.ts
940
1002
  import { execFileSync as execFileSync2 } from "node:child_process";
@@ -1164,6 +1226,20 @@ async function runDoctor(o) {
1164
1226
  detail: `seat-event ${seat2 ? "on" : "missing"} · spec-event ${spec ? "on" : "missing"}`,
1165
1227
  fix: seat2 && spec ? undefined : "sleds hooks"
1166
1228
  });
1229
+ const want = env.SLEDS_HOME?.trim() ? resolve5(home, env.SLEDS_HOME.trim()) : undefined;
1230
+ const cmds = sledsCommandsIn(settings);
1231
+ const wrong = cmds.filter((c) => {
1232
+ const got = bakedHome(c);
1233
+ return want ? got !== want : got !== undefined;
1234
+ });
1235
+ if (cmds.length > 0) {
1236
+ checks.push({
1237
+ level: wrong.length === 0 ? "ok" : "fail",
1238
+ name: "commands",
1239
+ 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`,
1240
+ fix: wrong.length === 0 ? undefined : `${overrides.length ? `${overrides.join(" ")} ` : ""}sleds hooks (re-writes the status line + hooks with this seat baked in)`
1241
+ });
1242
+ }
1167
1243
  const skill = existsSync6(skillPath(home));
1168
1244
  checks.push({
1169
1245
  level: skill ? "ok" : "warn",
@@ -1249,8 +1325,6 @@ var doctor = async (o) => {
1249
1325
  };
1250
1326
 
1251
1327
  // src/commands/hooks.ts
1252
- var DEFAULT_SEAT_EVENT_COMMAND = "sleds seat-event";
1253
- var DEFAULT_SPEC_EVENT_COMMAND = "sleds spec-event";
1254
1328
  var hooks = (o) => {
1255
1329
  const log = o.log ?? console.log;
1256
1330
  const error = o.error ?? console.error;
@@ -1261,9 +1335,13 @@ var hooks = (o) => {
1261
1335
  const settings = readSettings(o.home);
1262
1336
  const had = hasSeatHook(settings);
1263
1337
  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));
1338
+ const withSeat = applySeatHook(settings, o.seatEventCommand ?? sledsCommand("seat-event", o.home, o.env));
1339
+ const withSpec = applySpecHook(withSeat, o.specEventCommand ?? sledsCommand("spec-event", o.home, o.env));
1340
+ const hadLine = o.refreshStatusLine !== false && isSledsStatusLine(withSpec[STATUS_LINE_KEY]);
1341
+ writeSettings(o.home, hadLine ? applyStatusLine(withSpec, sledsCommand("statusline", o.home, o.env)).settings : withSpec);
1266
1342
  log(`${had ? "refreshed" : "set"} hooks.Notification (${SEAT_EVENT_MATCHER}) in ${settingsPath(o.home)}`);
1343
+ if (hadLine)
1344
+ log(`refreshed statusLine in ${settingsPath(o.home)}`);
1267
1345
  log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(o.home)}`);
1268
1346
  return 0;
1269
1347
  };
@@ -1302,8 +1380,11 @@ When the proxy is active it answers a walled request with its options. The user
1302
1380
  /sled 3 run overnight at half price
1303
1381
  /sled 4 switch to a cheaper model for the rest of the session (offered when one saves ≥15%)
1304
1382
  /sled 5 hand off to an idle coworker, or park for the team (offered on a team of two or more)
1383
+ /sled 6 defer to reset: the job runs itself when the seat resets — same model, done in minutes, full price (offered when the wall has a reset and the org has a key)
1305
1384
  \`\`\`
1306
1385
 
1386
+ A hand-off carries a brief (the packet): what, note, cost so far, how to pick it up. The \`handoffs\` MCP tool and the dashboard's Jobs page show it; read it before taking one.
1387
+
1307
1388
  ## Window reset
1308
1389
 
1309
1390
  When the user mentions their window has reset, or the deadline shown by \`sleds status\` has passed:
@@ -1316,7 +1397,7 @@ then restart \`claude\`. Settings go back to exactly what they were.
1316
1397
 
1317
1398
  ## Check
1318
1399
 
1319
- \`sleds status\` — on/off, since, reverts at, proxy health, wall mode.
1400
+ \`sleds status\` — on/off, since, reverts at, proxy health, wall mode, and the org's shape (\`Org:\` / \`Wiring:\`). An org with no platform key runs seat-first and its walls offer hold (\`/sled 2\`) and hand-off (\`/sled 5\`) only — continue, overnight and switch need a key an admin adds on the dashboard.
1320
1401
 
1321
1402
  The status line's \`seat wk 52% · 5h 12%\` is the user's own Pro/Max window as Claude Code reports it (weekly, then 5-hour); \`window\` is the org key's headroom through the proxy. At \`wk 100%\` the answer is \`sleds on --until-reset\`.
1322
1403
 
@@ -1393,8 +1474,6 @@ From Cowork or any MCP client (\`sleds mcp\`): the \`handoffs\` tool lists what
1393
1474
  `;
1394
1475
 
1395
1476
  // src/commands/hooks.ts
1396
- var DEFAULT_SEAT_EVENT_COMMAND2 = "sleds seat-event";
1397
- var DEFAULT_SPEC_EVENT_COMMAND2 = "sleds spec-event";
1398
1477
  var hooks2 = (o) => {
1399
1478
  const log = o.log ?? console.log;
1400
1479
  const error = o.error ?? console.error;
@@ -1405,29 +1484,38 @@ var hooks2 = (o) => {
1405
1484
  const settings = readSettings(o.home);
1406
1485
  const had = hasSeatHook(settings);
1407
1486
  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));
1487
+ const withSeat = applySeatHook(settings, o.seatEventCommand ?? sledsCommand("seat-event", o.home, o.env));
1488
+ const withSpec = applySpecHook(withSeat, o.specEventCommand ?? sledsCommand("spec-event", o.home, o.env));
1489
+ const hadLine = o.refreshStatusLine !== false && isSledsStatusLine(withSpec[STATUS_LINE_KEY]);
1490
+ writeSettings(o.home, hadLine ? applyStatusLine(withSpec, sledsCommand("statusline", o.home, o.env)).settings : withSpec);
1410
1491
  log(`${had ? "refreshed" : "set"} hooks.Notification (${SEAT_EVENT_MATCHER}) in ${settingsPath(o.home)}`);
1492
+ if (hadLine)
1493
+ log(`refreshed statusLine in ${settingsPath(o.home)}`);
1411
1494
  log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(o.home)}`);
1412
1495
  return 0;
1413
1496
  };
1414
1497
 
1415
1498
  // src/commands/init.ts
1416
1499
  var DEFAULT_PROXY_URL = "http://localhost:8787";
1417
- var DEFAULT_STATUS_LINE_COMMAND = "sleds statusline";
1418
- var promptStdin = (question) => new Promise((resolve5) => {
1500
+ var promptStdin = (question) => new Promise((resolve6) => {
1419
1501
  const rl = createInterface({ input: process.stdin, output: process.stderr });
1420
1502
  rl.question(question, (answer) => {
1421
1503
  rl.close();
1422
- resolve5(answer.trim());
1504
+ resolve6(answer.trim());
1423
1505
  });
1424
1506
  });
1425
1507
  async function signupIndividual(proxyUrl, email, credential, fetchFn = fetch) {
1508
+ return signupRequest(proxyUrl, { email, apiKey: credential }, fetchFn);
1509
+ }
1510
+ async function signupTeam(proxyUrl, email, team, credential, fetchFn = fetch) {
1511
+ return signupRequest(proxyUrl, { email, team, ...credential ? { apiKey: credential } : {} }, fetchFn);
1512
+ }
1513
+ async function signupRequest(proxyUrl, payload, fetchFn) {
1426
1514
  try {
1427
1515
  const res = await fetchFn(`${proxyUrl}/signup`, {
1428
1516
  method: "POST",
1429
1517
  headers: { "content-type": "application/json" },
1430
- body: JSON.stringify({ email, apiKey: credential }),
1518
+ body: JSON.stringify(payload),
1431
1519
  signal: AbortSignal.timeout(20000)
1432
1520
  });
1433
1521
  const body = await res.json().catch(() => ({}));
@@ -1448,6 +1536,9 @@ var SIGNUP_REASONS = {
1448
1536
  key_rejected: "Anthropic did not accept that API key. Check it at console.anthropic.com → API keys.",
1449
1537
  rate_limited: "too many signups from this network in the last hour. Try again later.",
1450
1538
  not_configured: "the proxy is not set up for self-serve signup yet.",
1539
+ team: "the team needs a name (2–80 characters).",
1540
+ personal_email: "a team needs a work email — that domain is a personal mailbox. Sign up as an individual instead, or use your company address.",
1541
+ org_exists: "someone at your company already set up SLEDS. Ask them for a seat (Members → invite, Admin → Mint a seat) and run `sleds init --token …`.",
1451
1542
  unavailable: "the proxy could not reach its control plane. Try again in a moment.",
1452
1543
  unreachable: "could not reach the proxy."
1453
1544
  };
@@ -1473,6 +1564,20 @@ function normalizeProxyUrl(raw) {
1473
1564
  return null;
1474
1565
  }
1475
1566
  }
1567
+ async function fetchOrgInfo(url, token, fetchFn = fetch) {
1568
+ try {
1569
+ const res = await fetchFn(`${url}/proxy/meter`, {
1570
+ headers: { authorization: `Bearer ${token}` },
1571
+ signal: AbortSignal.timeout(1e4)
1572
+ });
1573
+ if (!res.ok)
1574
+ return null;
1575
+ const body = await res.json();
1576
+ return body.org?.capabilities ? body.org : null;
1577
+ } catch {
1578
+ return null;
1579
+ }
1580
+ }
1476
1581
  async function probeProxy(url, fetchFn = fetch, opts = {}) {
1477
1582
  const tries = opts.tries ?? 2;
1478
1583
  const timeoutMs = opts.timeoutMs ?? 1e4;
@@ -1504,18 +1609,40 @@ var resolveToken = async (o) => {
1504
1609
  const url = normalizeProxyUrl(o.url ?? env.SLEDS_PROXY_URL ?? DEFAULT_PROXY_URL);
1505
1610
  if (!url)
1506
1611
  return null;
1507
- const email = (await o.prompt("Your email: ")).trim();
1612
+ const log = o.log ?? console.log;
1613
+ const err = o.error ?? console.error;
1614
+ let team = o.team?.trim() || null;
1615
+ if (team === null && !o.email) {
1616
+ const which = (await o.prompt("Set up SLEDS for (1) just you, or (2) your team? [1/2]: ")).trim();
1617
+ if (which === "2") {
1618
+ team = (await o.prompt("Team name (e.g. your company): ")).trim();
1619
+ if (!team)
1620
+ return null;
1621
+ }
1622
+ }
1623
+ const email = (o.email ?? await o.prompt(team ? "Your work email: " : "Your email: ")).trim();
1508
1624
  if (!email.includes("@"))
1509
1625
  return null;
1626
+ if (team) {
1627
+ const credential2 = (await o.prompt("Your organization's Anthropic API key, if you have one (sk-ant-…; press enter to skip — a Team plan without one still works, walls offer hold and hand-off): ")).trim();
1628
+ const r2 = await signupTeam(url, email, team, credential2 || null, o.fetchFn);
1629
+ if (!r2.ok) {
1630
+ err(signupFailureText(r2));
1631
+ return null;
1632
+ }
1633
+ log(`Created ${team} with you as owner (${email}).${credential2 ? " Your platform key is stored encrypted as the org's upstream key." : " No platform key yet — add one on the dashboard (Admin → Keys) to unlock continue / overnight / switch at a wall."}`);
1634
+ log("Invite your team on the dashboard (Members → invite by email, Admin → Mint a seat); each person runs `sleds init --token …`.");
1635
+ return { token: r2.token, source: "signup" };
1636
+ }
1510
1637
  const credential = (await o.prompt("Your Anthropic API key (sk-ant-…, stored encrypted, spends only your account): ")).trim();
1511
1638
  if (!credential)
1512
1639
  return null;
1513
1640
  const r = await signupIndividual(url, email, credential, o.fetchFn);
1514
1641
  if (!r.ok) {
1515
- (o.error ?? console.error)(signupFailureText(r));
1642
+ err(signupFailureText(r));
1516
1643
  return null;
1517
1644
  }
1518
- (o.log ?? console.log)(r.created ? `Created your individual seat (${email}). Your key is stored encrypted and is the only key this seat spends.` : `Re-issued your individual seat (${email}); the previous token is revoked.`);
1645
+ log(r.created ? `Created your individual seat (${email}). Your key is stored encrypted and is the only key this seat spends.` : `Re-issued your individual seat (${email}); the previous token is revoked.`);
1519
1646
  return { token: r.token, source: "signup" };
1520
1647
  };
1521
1648
  var init = async (o) => {
@@ -1545,17 +1672,26 @@ var init = async (o) => {
1545
1672
  return 2;
1546
1673
  }
1547
1674
  const wrote = [];
1675
+ const org = await fetchOrgInfo(url, resolved.token, o.fetchFn);
1676
+ const wiring = o.proxyAlways ? "proxy-always" : o.seatFirst ? "seat-first" : org?.mode ?? "proxy-always";
1677
+ if (wiring === "proxy-always" && org && !org.capabilities.upstreamKey) {
1678
+ error(`${org.name} has no platform key for the proxy to forward with; --proxy-always would break every request. Using seat-first.`);
1679
+ }
1680
+ const finalWiring = wiring === "proxy-always" && org && !org.capabilities.upstreamKey ? "seat-first" : wiring;
1548
1681
  if (backupSettings(o.home))
1549
1682
  wrote.push(`backed up settings to ${backupPath(o.home)}`);
1550
- const withProxy = applyProxy(readSettings(o.home), { url, token: resolved.token });
1551
- const statusLine = applyStatusLine(withProxy, o.statusLineCommand ?? DEFAULT_STATUS_LINE_COMMAND);
1683
+ const current = readSettings(o.home);
1684
+ const wired = finalWiring === "proxy-always" ? applyProxy(current, { url, token: resolved.token }) : removeProxy(current);
1685
+ const statusLine = applyStatusLine(wired, o.statusLineCommand ?? sledsCommand("statusline", o.home, o.env));
1552
1686
  writeSettings(o.home, statusLine.settings);
1553
- wrote.push(`set env.ANTHROPIC_BASE_URL and env.ANTHROPIC_AUTH_TOKEN in ${settingsPath(o.home)}`);
1687
+ wrote.push(finalWiring === "proxy-always" ? `set env.ANTHROPIC_BASE_URL and env.ANTHROPIC_AUTH_TOKEN in ${settingsPath(o.home)} (everything through the proxy)` : `left Claude Code on your seat (no ANTHROPIC_BASE_URL in ${settingsPath(o.home)}); \`sleds on\` switches to the proxy at a wall`);
1554
1688
  wrote.push(statusLine.prior === undefined ? `set statusLine in ${settingsPath(o.home)}` : `set statusLine in ${settingsPath(o.home)} (previous one kept for uninstall)`);
1555
1689
  hooks2({
1556
1690
  home: o.home,
1557
- seatEventCommand: o.seatEventCommand ?? DEFAULT_SEAT_EVENT_COMMAND2,
1558
- specEventCommand: o.specEventCommand ?? DEFAULT_SPEC_EVENT_COMMAND2,
1691
+ env: o.env,
1692
+ refreshStatusLine: false,
1693
+ seatEventCommand: o.seatEventCommand,
1694
+ specEventCommand: o.specEventCommand,
1559
1695
  log: (line) => wrote.push(line),
1560
1696
  error
1561
1697
  });
@@ -1568,20 +1704,37 @@ var init = async (o) => {
1568
1704
  ...prior,
1569
1705
  token: resolved.token,
1570
1706
  proxyUrl: url,
1707
+ wiring: finalWiring,
1708
+ ...org ? { org } : {},
1571
1709
  ...statusLine.prior !== undefined ? { priorStatusLine: statusLine.prior } : {}
1572
1710
  });
1573
1711
  wrote.push(`saved token and proxy URL to ${configPath(o.home)} (0600)`);
1574
1712
  mkdirSync6(skillDir(o.home), { recursive: true });
1575
1713
  writeFileSync6(skillPath(o.home), SKILL_MD);
1576
1714
  wrote.push(`installed skill at ${skillPath(o.home)}`);
1577
- log("Claude Code now runs through SLEDS on the org key.");
1578
- log("");
1579
- for (const w of wrote)
1580
- log(` ${w}`);
1581
- log("");
1582
- log(`Every request goes to ${url} and is metered to this seat. Run \`sleds status\` to check.`);
1583
- log("The status line shows your meter: spend today, seat limits, window, allowance, queued jobs.");
1584
- log("Prefer to burn your own Pro/Max/Team seat first? `sleds off` puts Claude Code back on it; `sleds on --until-reset +5h` switches to the org key at a wall.");
1715
+ const orgLine = org ? `${org.name} · ${org.plan}${org.capabilities.upstreamKey ? ` · ${org.capabilities.upstreamSource === "own" ? "org key" : "shared key"}` : " · no platform key"}` : "org unknown (proxy did not answer /proxy/meter)";
1716
+ if (finalWiring === "proxy-always") {
1717
+ log(`Claude Code now runs through SLEDS on the org key. (${orgLine})`);
1718
+ log("");
1719
+ for (const w of wrote)
1720
+ log(` ${w}`);
1721
+ log("");
1722
+ log(`Every request goes to ${url} and is metered to this seat. Run \`sleds status\` to check.`);
1723
+ log("The status line shows your meter: spend today, seat limits, window, allowance, queued jobs.");
1724
+ log("Prefer your seat first? `sleds init --seat-first` leaves Claude Code on it and switches only at a wall.");
1725
+ } else {
1726
+ log(`SLEDS is watching your seat. Nothing changes until a wall. (${orgLine})`);
1727
+ log("");
1728
+ for (const w of wrote)
1729
+ log(` ${w}`);
1730
+ log("");
1731
+ log("Claude Code keeps running on your seat; the status line reports its quota to SLEDS on every turn.");
1732
+ if (org && !org.capabilities.upstreamKey) {
1733
+ log("This org has no platform key, so at a wall the choices are hold until reset or hand off to a teammate. An admin can add a key on the dashboard to unlock continue / overnight / switch.");
1734
+ } else {
1735
+ log("At a wall: `sleds on --until-reset +5h` (or /sled 1) switches to the org key through the proxy until the reset; `sleds off` puts you back.");
1736
+ }
1737
+ }
1585
1738
  return 0;
1586
1739
  };
1587
1740
 
@@ -1941,8 +2094,17 @@ async function handoffsTool(deps) {
1941
2094
  if (handoffs.length === 0)
1942
2095
  return { text: "No open hand-offs on your team. Park one with the handoff tool." };
1943
2096
  const now = (deps.now ?? Date.now)();
1944
- return { text: handoffLines(handoffs, now).slice(1).join(`
1945
- `) };
2097
+ const packets = handoffs.map((h) => str2(h.packet)).filter((p) => Boolean(p)).join(`
2098
+
2099
+ ---
2100
+
2101
+ `);
2102
+ return {
2103
+ text: [handoffLines(handoffs, now).slice(1).join(`
2104
+ `), packets ? `
2105
+ ${packets}` : ""].join(`
2106
+ `)
2107
+ };
1946
2108
  }
1947
2109
  async function handoffTool(deps, args) {
1948
2110
  if (!meterSource(deps.home))
@@ -2261,6 +2423,10 @@ var on = (o) => {
2261
2423
  error("Saved token is not a sled_ token. Run `sleds init --token <sled_…>`.");
2262
2424
  return 2;
2263
2425
  }
2426
+ if (cfg.org && !cfg.org.capabilities.upstreamKey) {
2427
+ error(`${cfg.org.name} has no platform key, so there is no org key to switch to. Hold until the reset, or hand off with /sled 5. An admin can add a key on the dashboard.`);
2428
+ return 2;
2429
+ }
2264
2430
  const url = (o.url ?? cfg.proxyUrl ?? env.SLEDS_PROXY_URL ?? DEFAULT_PROXY_URL2).replace(/\/+$/, "");
2265
2431
  let untilResetMs;
2266
2432
  if (o.untilReset !== undefined) {
@@ -2356,10 +2522,11 @@ var seatEvent = async (o) => {
2356
2522
  if (week && week.resetsAt > now)
2357
2523
  writeSeatState(o.home, { ...state, lastWallResetsAt: week.resetsAt });
2358
2524
  } catch {}
2359
- log(SEAT_WALL_LINE);
2525
+ const line = seatWallLine(readConfig(o.home).org?.capabilities.upstreamKey !== false);
2526
+ log(line);
2360
2527
  if ((o.platform ?? process.platform) === "darwin" && o.exec) {
2361
2528
  try {
2362
- o.exec("osascript", osascriptArgs(NOTIFICATION_TITLE, SEAT_WALL_LINE));
2529
+ o.exec("osascript", osascriptArgs(NOTIFICATION_TITLE, line));
2363
2530
  } catch {}
2364
2531
  }
2365
2532
  return 0;
@@ -3056,6 +3223,10 @@ var formatStatus = (s) => {
3056
3223
  }
3057
3224
  if (s.email)
3058
3225
  lines.push(row2("Seat:", s.email));
3226
+ if (s.org)
3227
+ lines.push(row2("Org:", `${s.org.name} · ${s.org.plan} · ${s.org.upstream === "none" ? "no platform key (walls: hold or hand off)" : s.org.upstream === "own" ? "org key" : "shared key"}`));
3228
+ if (s.wiring)
3229
+ lines.push(row2("Wiring:", s.wiring === "seat-first" ? "seat-first — Claude Code on your seat; the proxy only after `sleds on`" : "proxy-always — every request through the proxy"));
3059
3230
  if (s.mode === "off") {
3060
3231
  lines.push(s.pointed ? "Claude Code is pointed at the proxy by settings.json (set by `sleds init`)." : "Claude Code is on your seat. Hit a limit? `sleds on --until-reset +5h`.");
3061
3232
  }
@@ -3087,6 +3258,14 @@ var status = async (o) => {
3087
3258
  info.tokenMasked = maskToken(token);
3088
3259
  if (cfg.email)
3089
3260
  info.email = cfg.email;
3261
+ if (cfg.wiring)
3262
+ info.wiring = cfg.wiring;
3263
+ if (cfg.org)
3264
+ info.org = {
3265
+ name: cfg.org.name,
3266
+ plan: cfg.org.plan,
3267
+ upstream: cfg.org.capabilities.upstreamSource
3268
+ };
3090
3269
  if (cfg.reason)
3091
3270
  info.reason = cfg.reason;
3092
3271
  if (o.tz)
@@ -3243,9 +3422,14 @@ commands:
3243
3422
  --token <sled_\u2026> seat token (else SLEDS_TOKEN, ~/.sleds-token, or prompt)
3244
3423
  --url <proxy-url> proxy base URL (else SLEDS_PROXY_URL, default ${DEFAULT_PROXY_URL})
3245
3424
  --no-probe skip the /healthz check of the proxy URL
3425
+ --seat-first keep Claude Code on your seat; the proxy only at a wall (default for seat plans)
3426
+ --proxy-always every request through the proxy on the org key (default for API-plan orgs)
3246
3427
  --non-interactive never prompt; exit 2 if no token is available
3247
- --signup no org? create an individual seat: your email + your own
3248
- Anthropic API key (validated, stored encrypted, the only key it spends)
3428
+ --signup no org? create one: (1) just you \u2014 your email + your own Anthropic
3429
+ API key (validated, stored encrypted, the only key it spends), or
3430
+ (2) your team \u2014 work email + team name, platform key optional
3431
+ --team "<name>" with --signup: create the team org (you become its owner)
3432
+ --email <you@work.com> with --signup: skip the email prompt
3249
3433
  on Hit a limit on your seat? Switch Claude Code to the org key through the proxy.
3250
3434
  --until-reset <when> revert at this time: +5h, +90m, or an ISO timestamp
3251
3435
  --reason "<text>" note shown by \`sleds status\`
@@ -3333,7 +3517,11 @@ var main = async (argv) => {
3333
3517
  url: stringFlag(flags, "url"),
3334
3518
  nonInteractive: flags["non-interactive"] === true,
3335
3519
  signup: flags.signup === true,
3520
+ team: typeof flags.team === "string" ? flags.team : undefined,
3521
+ email: typeof flags.email === "string" ? flags.email : undefined,
3336
3522
  noProbe: flags["no-probe"] === true,
3523
+ proxyAlways: flags["proxy-always"] === true,
3524
+ seatFirst: flags["seat-first"] === true,
3337
3525
  prompt: promptStdin,
3338
3526
  statusLineCommand: statusLineCommand(sledsArgv()),
3339
3527
  seatEventCommand: seatEventCommand(sledsArgv()),
@@ -3384,7 +3572,7 @@ var main = async (argv) => {
3384
3572
  case "version":
3385
3573
  case "--version":
3386
3574
  case "-v":
3387
- console.log(`sleds ${"0.1.6"}`);
3575
+ console.log(`sleds ${"0.1.11"}`);
3388
3576
  return 0;
3389
3577
  default:
3390
3578
  console.error(`unknown command: ${cmd}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleds/cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.11",
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",