@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 +2 -0
- package/dist/sleds.mjs +70 -19
- 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.
|
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 ??
|
|
1265
|
-
|
|
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 ??
|
|
1409
|
-
|
|
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
|
|
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
|
-
|
|
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}/
|
|
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}/
|
|
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 ??
|
|
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
|
-
|
|
1558
|
-
|
|
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 /
|
|
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.
|
|
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.
|
|
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": "
|
|
27
|
+
"publishConfig": { "access": "restricted" },
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "bun scripts/build.ts",
|
|
30
30
|
"prepack": "bun scripts/build.ts",
|