@sleds/cli 0.1.44 → 0.1.45
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/dist/sleds.mjs +414 -46
- package/package.json +1 -1
package/dist/sleds.mjs
CHANGED
|
@@ -802,6 +802,13 @@ var init_verbs = __esm(() => {
|
|
|
802
802
|
app: "Work › the item › click a field",
|
|
803
803
|
how: "The filer, an approver or an admin changes an item from chat or by clicking the field in the app; every change says who and through what (via Claude · Cowork, via ChatGPT, in the app) and the record keeps before and after. A change to the ask, done or smallest marks the band stale with a Re-size at hand — never re-sized silently. Worth is the person's word in their words ('high · Acme renewal', '$5k–20k'; null clears it); on an epic, `carry: true` puts it on each child that has none, each child's record signed the same way. A mirrored ticket is the tracker's word and is not changed here."
|
|
804
804
|
},
|
|
805
|
+
{
|
|
806
|
+
id: "reopen",
|
|
807
|
+
group: "meter",
|
|
808
|
+
want: "Put back an item that landed by mistake",
|
|
809
|
+
mcp: "reopen {project, reason}",
|
|
810
|
+
how: "SL-1819. For an item a skipped node or a wrong actual landed. It goes back on the sled it was on — or to ready on no sled, when that sled is gone from the org — the false actual is cleared and the band stands. A reopen always says why; the why goes on the item's record. The filer, an approver or an admin. Only a landed item reopens."
|
|
811
|
+
},
|
|
805
812
|
{
|
|
806
813
|
id: "archive",
|
|
807
814
|
group: "meter",
|
|
@@ -2079,7 +2086,7 @@ ${state}`)}, fillcolor="${fill}", color="${border}"${style}];`);
|
|
|
2079
2086
|
`)}
|
|
2080
2087
|
`;
|
|
2081
2088
|
}
|
|
2082
|
-
var ROUTE_APPROACH_MAX = 2000, PLAN_APPROACH_MAX = 1200, PLAN_LIST_MAX = 8, PLAN_LINE_MAX = 240, CONTROL, MAX_NODES = 24, DEPTH_WARN = 4, norm2 = (s) => s.trim().toUpperCase(), AREA_ROOTS, NOT_AREAS, sameRepo = (a, b) => a.length === 0 || b.length === 0 || a.some((r) => b.includes(r)), DOT_COLORS, ROUTE_DRAFT_RULE = "Draft the route: PR-sized nodes, file-disjoint where they run in parallel, each with a title, the files or areas it touches, the repo, and the nodes it waits on (by key: A, B, C…). Prefer wide waves over chains deeper than four. Make the tough calls once, so each node's session follows them: an `approach` paragraph for the whole route, and on each node a `plan` — its approach, what it `provides` that later nodes rely on (names, shapes, files), what it `expects` from the nodes it waits on, and the `doneChecks` that say it landed. Show the graph and the plans as text and ask for a yes; file nothing before it. Then `route {q, approach, nodes: [{key, title, touches, repos, blockedBy, plan: {approach, provides, expects, doneChecks}}], confirm: true}`.";
|
|
2089
|
+
var ROUTE_APPROACH_MAX = 2000, PLAN_APPROACH_MAX = 1200, PLAN_LIST_MAX = 8, PLAN_LINE_MAX = 240, CONTROL, MAX_NODES = 24, DEPTH_WARN = 4, norm2 = (s) => s.trim().toUpperCase(), AREA_ROOTS, NOT_AREAS, sameRepo = (a, b) => a.length === 0 || b.length === 0 || a.some((r) => b.includes(r)), DOT_COLORS, ROUTE_DRAFT_RULE = "Draft the route: PR-sized nodes, file-disjoint where they run in parallel, each with a title, the files or areas it touches, the repo, and the nodes it waits on (by key: A, B, C…). Prefer wide waves over chains deeper than four. Make the tough calls once, so each node's session follows them: an `approach` paragraph for the whole route, and on each node a `plan` — its approach, what it `provides` that later nodes rely on (names, shapes, files), what it `expects` from the nodes it waits on, and the `doneChecks` that say it landed. Show the graph and the plans as text and ask for a yes; file nothing before it. An item with children takes no nodes: its children are the work, and their order lives in their blockedBy. Then `route {q, approach, nodes: [{key, title, touches, repos, blockedBy, plan: {approach, provides, expects, doneChecks}}], confirm: true}`.";
|
|
2083
2090
|
var init_route = __esm(() => {
|
|
2084
2091
|
CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
|
|
2085
2092
|
AREA_ROOTS = new Set(["apps", "packages", "services", "libs", "crates", "modules"]);
|
|
@@ -2151,6 +2158,7 @@ var init_annotations = __esm(() => {
|
|
|
2151
2158
|
finish: add("Land the work"),
|
|
2152
2159
|
buddy: add("Pair with a buddy"),
|
|
2153
2160
|
pickup: add("Pick up a hand-off"),
|
|
2161
|
+
reopen: add("Reopen an item", true),
|
|
2154
2162
|
update: change("Change an item", true),
|
|
2155
2163
|
archive: change("Archive an item", true),
|
|
2156
2164
|
withdraw: change("Withdraw a hand-off", true)
|
|
@@ -3223,6 +3231,27 @@ async function updateTool(ctx, args) {
|
|
|
3223
3231
|
text: `${key} updated: ${changed.join(", ")}.${carried}${stale ? " The band was sized before this change — say so, and offer to re-size (estimate with the same source)." : ""}`
|
|
3224
3232
|
};
|
|
3225
3233
|
}
|
|
3234
|
+
async function reopenTool(ctx, args) {
|
|
3235
|
+
if (!ctx.client)
|
|
3236
|
+
return NOT_SET_UP;
|
|
3237
|
+
const project = str5(args.project)?.trim();
|
|
3238
|
+
if (!project)
|
|
3239
|
+
return { text: "Which item? Pass project: its key (QW-142).", isError: true };
|
|
3240
|
+
const reason = str5(args.reason)?.trim();
|
|
3241
|
+
if (!reason)
|
|
3242
|
+
return {
|
|
3243
|
+
text: "Say why it is reopened — pass reason, one line. It goes on the item's record.",
|
|
3244
|
+
isError: true
|
|
3245
|
+
};
|
|
3246
|
+
const res = await postSaying(ctx, "/proxy/reopen", { project, reason }, "Could not reopen the item.");
|
|
3247
|
+
if ("fail" in res)
|
|
3248
|
+
return res.fail;
|
|
3249
|
+
const key = str5(res.json.key) ?? project;
|
|
3250
|
+
const sled = str5(res.json.sled);
|
|
3251
|
+
return {
|
|
3252
|
+
text: `Reopened ${key} — ${sled ? `back on ${sled}'s sled` : "back to ready, on no sled"}. The false actual is cleared; the band stands.`
|
|
3253
|
+
};
|
|
3254
|
+
}
|
|
3226
3255
|
function carriedLine(c) {
|
|
3227
3256
|
if (!isObject4(c))
|
|
3228
3257
|
return "";
|
|
@@ -3531,9 +3560,21 @@ async function itemTool(ctx, args) {
|
|
|
3531
3560
|
text: `Nothing on the sled answers to "${q}". Try the key (QW-142) or a word from the title with status.`,
|
|
3532
3561
|
isError: true
|
|
3533
3562
|
};
|
|
3534
|
-
const md = str5(r.markdown) ?? "";
|
|
3535
3563
|
if (asFile)
|
|
3536
|
-
return { text:
|
|
3564
|
+
return { text: str5(r.markdown) ?? "", structured: { key: str5(r.key) ?? q, export: true } };
|
|
3565
|
+
const envs = isObject4(r.whereLive) && Array.isArray(r.whereLive.envs) ? r.whereLive.envs : [];
|
|
3566
|
+
const live = envs.filter(isObject4).filter((e) => {
|
|
3567
|
+
const s = isObject4(e.state) ? e.state : null;
|
|
3568
|
+
return !(s && (s.kind === "no_code" || s.state === "no_code"));
|
|
3569
|
+
}).map((e) => `- ${str5(e.name) ?? str5(e.slug) ?? "?"}: ${str5(e.words) ?? ""}`);
|
|
3570
|
+
const body = str5(r.markdown) ?? "";
|
|
3571
|
+
const section = live.length ? `## Where it's live
|
|
3572
|
+
${live.join(`
|
|
3573
|
+
`)}
|
|
3574
|
+
|
|
3575
|
+
` : "";
|
|
3576
|
+
const title = body.match(/^# [^\n]*\n+/)?.[0] ?? "";
|
|
3577
|
+
const md = `${title}${section}${body.slice(title.length)}`;
|
|
3537
3578
|
const readiness = isObject4(r.readiness) ? r.readiness : null;
|
|
3538
3579
|
const line = readiness ? str5(readiness.line) : undefined;
|
|
3539
3580
|
const state = readiness ? str5(readiness.state) : undefined;
|
|
@@ -4274,6 +4315,8 @@ async function callTool(name, args, ctx) {
|
|
|
4274
4315
|
return attachTool(ctx, args);
|
|
4275
4316
|
case "update":
|
|
4276
4317
|
return updateTool(ctx, args);
|
|
4318
|
+
case "reopen":
|
|
4319
|
+
return reopenTool(ctx, args);
|
|
4277
4320
|
case "archive":
|
|
4278
4321
|
return archiveTool(ctx, args);
|
|
4279
4322
|
case "comment":
|
|
@@ -4679,7 +4722,7 @@ var init_server = __esm(() => {
|
|
|
4679
4722
|
},
|
|
4680
4723
|
{
|
|
4681
4724
|
name: "route",
|
|
4682
|
-
description: "The route — an item's internal order as PR-sized nodes with blockers (spec 26). With only `q`: the route as filed (each node's state, what it waits on, the ready set, where each run can be watched), or, when none is filed, the pack and the drafting rule so you draft one. Drafting: PR-sized nodes, file-disjoint where they run in parallel, each with a title, the files or areas it touches, the repo, and the nodes it waits on by key (A, B, C…); prefer wide waves over chains deeper than four. Make the tough calls once: an `approach` for the route and a `plan` on each node (its approach, what it provides to later nodes, what it expects from the nodes it waits on, its done-checks), so each node's session follows them instead of re-planning. Show the graph and the plans as text and ask for a yes; file nothing before it. Then call again with `nodes` and `confirm: true`. Re-drafting shows what changed; a node with a run or a PR is never removed. Ready is computed from landed blockers, never set. With nobody to ask (a drafting session an engine started, spec 26 §5.1): call with `nodes` and `propose: true` instead — the route is held on the item for a person's yes and nothing is filed; then stop. A read of an item with a proposal shows it as drafted, not filed.",
|
|
4725
|
+
description: "The route — an item's internal order as PR-sized nodes with blockers (spec 26). With only `q`: the route as filed (each node's state, what it waits on, the ready set, where each run can be watched), or, when none is filed, the pack and the drafting rule so you draft one. Drafting: PR-sized nodes, file-disjoint where they run in parallel, each with a title, the files or areas it touches, the repo, and the nodes it waits on by key (A, B, C…); prefer wide waves over chains deeper than four. Make the tough calls once: an `approach` for the route and a `plan` on each node (its approach, what it provides to later nodes, what it expects from the nodes it waits on, its done-checks), so each node's session follows them instead of re-planning. Show the graph and the plans as text and ask for a yes; file nothing before it. Then call again with `nodes` and `confirm: true`. An item with children is refused nodes — its children are the work: set their order with blockedBy on them. The item lands only when every child has landed. Re-drafting shows what changed; a node with a run or a PR is never removed. Ready is computed from landed blockers, never set. With nobody to ask (a drafting session an engine started, spec 26 §5.1): call with `nodes` and `propose: true` instead — the route is held on the item for a person's yes and nothing is filed; then stop. A read of an item with a proposal shows it as drafted, not filed.",
|
|
4683
4726
|
inputSchema: {
|
|
4684
4727
|
type: "object",
|
|
4685
4728
|
properties: {
|
|
@@ -4967,6 +5010,21 @@ var init_server = __esm(() => {
|
|
|
4967
5010
|
required: ["project"]
|
|
4968
5011
|
}
|
|
4969
5012
|
},
|
|
5013
|
+
{
|
|
5014
|
+
name: "reopen",
|
|
5015
|
+
description: "Put back an item that landed by mistake — a skipped node closed its route, or an actual was sent against the wrong item. It goes back on the sled it was on (or to ready, when that sled is gone), the false actual is cleared and the band stands. Pass the item's key and why: a reopen always says why, and the why goes on the item's record. The filer, an approver or an admin may. Only a landed item can be reopened.",
|
|
5016
|
+
inputSchema: {
|
|
5017
|
+
type: "object",
|
|
5018
|
+
properties: {
|
|
5019
|
+
project: { type: "string", description: "The item's key — QW-142 — or its project key." },
|
|
5020
|
+
reason: {
|
|
5021
|
+
type: "string",
|
|
5022
|
+
description: "Why it is reopened, one line — e.g. 'a skipped node landed it; six children are open'."
|
|
5023
|
+
}
|
|
5024
|
+
},
|
|
5025
|
+
required: ["project", "reason"]
|
|
5026
|
+
}
|
|
5027
|
+
},
|
|
4970
5028
|
{
|
|
4971
5029
|
name: "item",
|
|
4972
5030
|
description: "Read one item whole, as one markdown document — the pack: everything an engineer or an agent needs to finish the work from this document alone. Eight parts: the ask, where the code is, how to run it (the repo's setup profile), how to know it is done (the gate), how to ship it (the playbook), what the org expects, what is known already (the Field Book — notes from earlier runs on this item, its feature and what landed beside it, the questions, the thread), and the rules of the road. Carries its readiness and whether it is packable. Pass the item's key (QW-142) or its title. Call this before building from an item, and before `check`.",
|
|
@@ -6416,7 +6474,54 @@ var init_pack2 = __esm(() => {
|
|
|
6416
6474
|
});
|
|
6417
6475
|
|
|
6418
6476
|
// ../../packages/shared/src/deploys/deploys.ts
|
|
6419
|
-
|
|
6477
|
+
function parseDeployBody(json) {
|
|
6478
|
+
if (!json || typeof json !== "object" || Array.isArray(json))
|
|
6479
|
+
return bad("body must be a JSON object");
|
|
6480
|
+
const j = json;
|
|
6481
|
+
const repo = typeof j.repo === "string" ? j.repo.trim().toLowerCase() : "";
|
|
6482
|
+
if (!REPO_RE.test(repo))
|
|
6483
|
+
return bad("repo must be owner/name");
|
|
6484
|
+
const sha = typeof j.sha === "string" ? j.sha.trim().toLowerCase() : "";
|
|
6485
|
+
if (!SHA_RE.test(sha))
|
|
6486
|
+
return bad("sha must be 7–40 hex characters");
|
|
6487
|
+
const environment = typeof j.environment === "string" ? j.environment.trim() : "";
|
|
6488
|
+
if (!ENV_SLUG_RE.test(environment))
|
|
6489
|
+
return bad("environment must be a slug: lower-case letters, digits, - and _");
|
|
6490
|
+
const body = { repo, sha, environment };
|
|
6491
|
+
if (j.service !== undefined && j.service !== null) {
|
|
6492
|
+
const service = typeof j.service === "string" ? j.service.trim() : "";
|
|
6493
|
+
if (!SERVICE_RE.test(service))
|
|
6494
|
+
return bad("service must be a short name");
|
|
6495
|
+
body.service = service;
|
|
6496
|
+
}
|
|
6497
|
+
if (j.at !== undefined && j.at !== null) {
|
|
6498
|
+
const at = typeof j.at === "string" ? Date.parse(j.at) : typeof j.at === "number" ? j.at : Number.NaN;
|
|
6499
|
+
if (!Number.isFinite(at))
|
|
6500
|
+
return bad("at must be an ISO time");
|
|
6501
|
+
body.at = at;
|
|
6502
|
+
}
|
|
6503
|
+
if (j.url !== undefined && j.url !== null) {
|
|
6504
|
+
let ok = false;
|
|
6505
|
+
if (typeof j.url === "string") {
|
|
6506
|
+
try {
|
|
6507
|
+
ok = new URL(j.url).protocol === "https:";
|
|
6508
|
+
} catch {
|
|
6509
|
+
ok = false;
|
|
6510
|
+
}
|
|
6511
|
+
}
|
|
6512
|
+
if (!ok)
|
|
6513
|
+
return bad("url must be an https link");
|
|
6514
|
+
body.url = j.url;
|
|
6515
|
+
}
|
|
6516
|
+
return { ok: true, body };
|
|
6517
|
+
}
|
|
6518
|
+
var REPO_RE, SHA_RE, ENV_SLUG_RE, SERVICE_RE, bad = (error) => ({ ok: false, error });
|
|
6519
|
+
var init_deploys = __esm(() => {
|
|
6520
|
+
REPO_RE = /^[a-z0-9_.-]+\/[a-z0-9_.-]+$/;
|
|
6521
|
+
SHA_RE = /^[0-9a-f]{7,40}$/;
|
|
6522
|
+
ENV_SLUG_RE = /^[a-z0-9][a-z0-9_-]{0,39}$/;
|
|
6523
|
+
SERVICE_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/;
|
|
6524
|
+
});
|
|
6420
6525
|
|
|
6421
6526
|
// ../../packages/shared/src/newrelic/newrelic.ts
|
|
6422
6527
|
var init_newrelic = () => {};
|
|
@@ -6817,7 +6922,7 @@ function nodePrompt(o) {
|
|
|
6817
6922
|
...claim.pack.includes(PR_HEADER_OPEN) ? [
|
|
6818
6923
|
`- The PR body starts with the pack's PR header block (\`${PR_HEADER_OPEN}\` … \`${PR_HEADER_CLOSE}\`, in its part 5), above everything else. The pack wrote it before your work existed: when you make a mock (a screenshot or an artifact) or write a doc, put its link on that part's line in place of \`None\` — not only lower in the body (SL-1746). When you edit the body later, keep it on top.`
|
|
6819
6924
|
] : [],
|
|
6820
|
-
"- Run the repo's own gate before you push. Never merge: a person (or the musher, when the attachment says so) does.",
|
|
6925
|
+
o.gate ? `- Run the repo's gate before you push: exactly \`${o.gate}\`, as given. Never wrap it — no \`$?\`, \`$(...)\`, \`bash -c\` or \`; echo\` around it — or it stops at a permission dialog; read its result from the exit and the output. Never merge: a person (or the musher, when the attachment says so) does.` : "- Run the repo's own gate before you push. Never merge: a person (or the musher, when the attachment says so) does.",
|
|
6821
6926
|
"- Stay inside this node's scope. Work another node needs goes in the PR description, not in this branch.",
|
|
6822
6927
|
`- End every comment and reply you post on the PR with the line \`${ENGINE_MARK}\`, so the engine knows it is yours and not a reviewer's.`,
|
|
6823
6928
|
"- The approved brief and this route are the go-ahead: a person said yes to them before this session started. Build, commit, push and open the PR; a park is for a question the record does not settle, not for a go-ahead to build or push.",
|
|
@@ -9693,9 +9798,127 @@ var defer = async (o) => {
|
|
|
9693
9798
|
return 0;
|
|
9694
9799
|
};
|
|
9695
9800
|
|
|
9801
|
+
// src/commands/deploy.ts
|
|
9802
|
+
init_src();
|
|
9803
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
9804
|
+
var DEPLOYS_URL = "https://hooks.sleds.ai/deploys";
|
|
9805
|
+
var USAGE = "usage: sleds deploy --env <slug> [--service <name>] [--sha <sha>] [--repo <owner/name>]";
|
|
9806
|
+
var FLAGS = ["env", "service", "sha", "repo"];
|
|
9807
|
+
var set = (x) => x?.trim() ? x.trim() : undefined;
|
|
9808
|
+
function repoFromRemote(remote) {
|
|
9809
|
+
const m = /^(?:[a-z+]+:\/\/)?(?:[^@/]+@)?[^/:]+[:/](.+?)(?:\.git)?\/?$/i.exec(remote.trim());
|
|
9810
|
+
const parts = m?.[1]?.split("/").filter(Boolean) ?? [];
|
|
9811
|
+
if (parts.length < 2)
|
|
9812
|
+
return null;
|
|
9813
|
+
return `${parts[parts.length - 2]}/${parts[parts.length - 1]}`.toLowerCase();
|
|
9814
|
+
}
|
|
9815
|
+
function resolveDeploy(argv, env, git) {
|
|
9816
|
+
const flags = {};
|
|
9817
|
+
for (let i = 0;i < argv.length; i++) {
|
|
9818
|
+
const a = argv[i] ?? "";
|
|
9819
|
+
const m = /^--([a-z]+)(?:=(.*))?$/.exec(a);
|
|
9820
|
+
const name = m?.[1];
|
|
9821
|
+
if (!m || !name || !FLAGS.includes(name))
|
|
9822
|
+
return { ok: false, error: `unknown argument ${a}` };
|
|
9823
|
+
const value = m[2] ?? argv[++i];
|
|
9824
|
+
if (!set(value))
|
|
9825
|
+
return { ok: false, error: `--${name} needs a value` };
|
|
9826
|
+
flags[name] = value?.trim();
|
|
9827
|
+
}
|
|
9828
|
+
if (!flags.env)
|
|
9829
|
+
return { ok: false, error: "--env <slug> is required" };
|
|
9830
|
+
const vercelRepo = set(env.VERCEL_GIT_REPO_OWNER) && set(env.VERCEL_GIT_REPO_SLUG) ? `${set(env.VERCEL_GIT_REPO_OWNER)}/${set(env.VERCEL_GIT_REPO_SLUG)}` : undefined;
|
|
9831
|
+
const remote = () => {
|
|
9832
|
+
const url = git(["remote", "get-url", "origin"]);
|
|
9833
|
+
return url && repoFromRemote(url) || undefined;
|
|
9834
|
+
};
|
|
9835
|
+
const repo = flags.repo ?? set(env.GITHUB_REPOSITORY) ?? vercelRepo ?? remote();
|
|
9836
|
+
if (!repo)
|
|
9837
|
+
return { ok: false, error: "no repo: pass --repo owner/name (no origin remote)" };
|
|
9838
|
+
const sha = flags.sha ?? set(env.SOURCE_VERSION) ?? set(env.HEROKU_SLUG_COMMIT) ?? set(env.VERCEL_GIT_COMMIT_SHA) ?? set(env.GITHUB_SHA) ?? set(git(["rev-parse", "HEAD"]) ?? undefined);
|
|
9839
|
+
if (!sha)
|
|
9840
|
+
return { ok: false, error: "no sha: pass --sha (not a git checkout)" };
|
|
9841
|
+
return parseDeployBody({
|
|
9842
|
+
repo,
|
|
9843
|
+
sha,
|
|
9844
|
+
environment: flags.env,
|
|
9845
|
+
...flags.service ? { service: flags.service } : {}
|
|
9846
|
+
});
|
|
9847
|
+
}
|
|
9848
|
+
var defaultGit = (args) => {
|
|
9849
|
+
try {
|
|
9850
|
+
return execFileSync2("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
9851
|
+
} catch {
|
|
9852
|
+
return null;
|
|
9853
|
+
}
|
|
9854
|
+
};
|
|
9855
|
+
var defaultPost = async (url, token, body) => {
|
|
9856
|
+
try {
|
|
9857
|
+
const res = await fetch(url, {
|
|
9858
|
+
method: "POST",
|
|
9859
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
9860
|
+
body: JSON.stringify(body),
|
|
9861
|
+
signal: AbortSignal.timeout(15000)
|
|
9862
|
+
});
|
|
9863
|
+
let json = null;
|
|
9864
|
+
try {
|
|
9865
|
+
json = await res.json();
|
|
9866
|
+
} catch {}
|
|
9867
|
+
return { status: res.status, json };
|
|
9868
|
+
} catch (e) {
|
|
9869
|
+
return { error: e instanceof Error ? e.message : String(e) };
|
|
9870
|
+
}
|
|
9871
|
+
};
|
|
9872
|
+
function deployLine(body, json) {
|
|
9873
|
+
const where = `${body.repo}${body.service ? ` (${body.service})` : ""} → ${body.environment}`;
|
|
9874
|
+
if (typeof json.ignored === "string")
|
|
9875
|
+
return `ignored: ${json.ignored}`;
|
|
9876
|
+
if (json.duplicate)
|
|
9877
|
+
return `${where}: already at ${body.sha.slice(0, 7)}`;
|
|
9878
|
+
const prs = Array.isArray(json.newlyDeployed) ? json.newlyDeployed : [];
|
|
9879
|
+
const list = prs.map((p) => `#${p.number}`).join(", ");
|
|
9880
|
+
if (json.rollback)
|
|
9881
|
+
return `${where}: rollback to ${body.sha.slice(0, 7)}${list ? ` (carries ${list} again)` : ""}`;
|
|
9882
|
+
return prs.length ? `${where}: newly deployed ${list}` : `${where}: ${body.sha.slice(0, 7)} recorded, no newly deployed PRs`;
|
|
9883
|
+
}
|
|
9884
|
+
var deployCmd = async (o) => {
|
|
9885
|
+
const log = o.log ?? console.log;
|
|
9886
|
+
const error = o.error ?? console.error;
|
|
9887
|
+
const env = o.env ?? process.env;
|
|
9888
|
+
const token = set(env.SLEDS_DEPLOY_TOKEN);
|
|
9889
|
+
if (!token) {
|
|
9890
|
+
error("SLEDS_DEPLOY_TOKEN is not set — mint one in Settings › Deploys");
|
|
9891
|
+
return 2;
|
|
9892
|
+
}
|
|
9893
|
+
const resolved = resolveDeploy(o.argv, env, o.git ?? defaultGit);
|
|
9894
|
+
if (!resolved.ok) {
|
|
9895
|
+
error(resolved.error);
|
|
9896
|
+
error(USAGE);
|
|
9897
|
+
return 2;
|
|
9898
|
+
}
|
|
9899
|
+
const body = resolved.body;
|
|
9900
|
+
const url = set(env.SLEDS_DEPLOYS_URL) ?? DEPLOYS_URL;
|
|
9901
|
+
const r = await (o.post ?? defaultPost)(url, token, body);
|
|
9902
|
+
if ("error" in r) {
|
|
9903
|
+
error(`warning: SLEDS unreachable, deploy not recorded — ${r.error}`);
|
|
9904
|
+
return 0;
|
|
9905
|
+
}
|
|
9906
|
+
if (r.status === 200 && r.json) {
|
|
9907
|
+
log(deployLine(body, r.json));
|
|
9908
|
+
return 0;
|
|
9909
|
+
}
|
|
9910
|
+
const why = typeof r.json?.error === "string" ? r.json.error : `HTTP ${r.status}`;
|
|
9911
|
+
if (r.status >= 400 && r.status < 500) {
|
|
9912
|
+
error(r.status === 401 ? `SLEDS refused the deploy token (${why})` : why);
|
|
9913
|
+
return 1;
|
|
9914
|
+
}
|
|
9915
|
+
error(`warning: SLEDS answered ${why}, deploy not recorded`);
|
|
9916
|
+
return 0;
|
|
9917
|
+
};
|
|
9918
|
+
|
|
9696
9919
|
// src/commands/doctor.ts
|
|
9697
9920
|
init_src();
|
|
9698
|
-
import { execFileSync as
|
|
9921
|
+
import { execFileSync as execFileSync4, spawnSync as spawnSync5 } from "node:child_process";
|
|
9699
9922
|
import { randomBytes } from "node:crypto";
|
|
9700
9923
|
import {
|
|
9701
9924
|
existsSync as existsSync17,
|
|
@@ -9710,7 +9933,7 @@ import { tmpdir } from "node:os";
|
|
|
9710
9933
|
import { join as join20, resolve as resolve9 } from "node:path";
|
|
9711
9934
|
|
|
9712
9935
|
// src/agentMcp.ts
|
|
9713
|
-
import { execFileSync as
|
|
9936
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
9714
9937
|
import { existsSync as existsSync10, mkdirSync as mkdirSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "node:fs";
|
|
9715
9938
|
import { dirname as dirname8, join as join10, resolve as resolve6 } from "node:path";
|
|
9716
9939
|
init_settings();
|
|
@@ -9778,7 +10001,7 @@ function applyCodexMcp2(toml, home, env = process.env) {
|
|
|
9778
10001
|
`;
|
|
9779
10002
|
}
|
|
9780
10003
|
var defaultRunClaude2 = (args) => {
|
|
9781
|
-
|
|
10004
|
+
execFileSync3("claude", args, { stdio: ["ignore", "ignore", "pipe"], timeout: 20000 });
|
|
9782
10005
|
};
|
|
9783
10006
|
async function offerMcp2(o) {
|
|
9784
10007
|
const env = o.env ?? process.env;
|
|
@@ -10233,6 +10456,7 @@ var agentShellCommand = (o) => {
|
|
|
10233
10456
|
const effort = o.effort ? ` --effort ${shq2(o.effort)}` : "";
|
|
10234
10457
|
return `${sandboxedAgent("claude", o.sandbox, o.mainCheckout)}${hookSettingsFlag(o)}${mcpConfigFlag(o)} --permission-mode ${shq2(o.permissionMode)}${model}${effort} ${prompt}`;
|
|
10235
10458
|
};
|
|
10459
|
+
var execLine = (cmd) => /^[A-Za-z_][A-Za-z0-9_]*=/.test(cmd) ? `exec env ${cmd}` : `exec ${cmd}`;
|
|
10236
10460
|
var sandboxedAgent = (agent, sandbox, mainCheckout) => {
|
|
10237
10461
|
const kind = sandboxAgentKind(agent);
|
|
10238
10462
|
const line = kind === "claude" ? `${agent}${claudeAddDirFlag(mainCheckout)}` : agent;
|
|
@@ -10705,7 +10929,7 @@ var parseOrcaTerminal = (env) => {
|
|
|
10705
10929
|
};
|
|
10706
10930
|
var orcaLauncher = (o) => `#!/bin/sh
|
|
10707
10931
|
# sleds engine: ${o.name}
|
|
10708
|
-
|
|
10932
|
+
${execLine(agentShellCommand(o))}
|
|
10709
10933
|
`;
|
|
10710
10934
|
var orcaCommand = (launcher) => `sh ${shq2(launcher)}; exit`;
|
|
10711
10935
|
var GONE = /exit|clos|gone|dead|stopp|kill/i;
|
|
@@ -10907,7 +11131,7 @@ function beatBody(state, flags, host = {}, cap = {}) {
|
|
|
10907
11131
|
name: flags.name,
|
|
10908
11132
|
os: host.os ?? process.platform,
|
|
10909
11133
|
runner: flags.runner,
|
|
10910
|
-
version: host.version ?? "0.1.
|
|
11134
|
+
version: host.version ?? "0.1.45" ?? "dev",
|
|
10911
11135
|
running: live.length,
|
|
10912
11136
|
items: live.slice(0, BEAT_ITEMS).map((r) => `${r.item}/${r.node}`),
|
|
10913
11137
|
...cap.atOnce !== undefined ? { atOnce: cap.atOnce } : {},
|
|
@@ -11900,7 +12124,7 @@ function keysIn(states, state) {
|
|
|
11900
12124
|
}
|
|
11901
12125
|
function statesLanded(states) {
|
|
11902
12126
|
const all = Object.values(states);
|
|
11903
|
-
return all.length > 0 && all.every((s) => s === "landed" || s === "skipped");
|
|
12127
|
+
return all.length > 0 && all.every((s) => s === "landed" || s === "skipped") && all.some((s) => s === "landed");
|
|
11904
12128
|
}
|
|
11905
12129
|
function armedTerms(armed) {
|
|
11906
12130
|
if (!isObject8(armed))
|
|
@@ -12865,6 +13089,37 @@ function packInstallOf(pack, repo) {
|
|
|
12865
13089
|
}
|
|
12866
13090
|
return first;
|
|
12867
13091
|
}
|
|
13092
|
+
function packGateOf(pack, repo) {
|
|
13093
|
+
let inDone = false;
|
|
13094
|
+
let first;
|
|
13095
|
+
const want = repo?.trim().toLowerCase();
|
|
13096
|
+
for (const line of pack.split(`
|
|
13097
|
+
`)) {
|
|
13098
|
+
if (/^##\s/.test(line)) {
|
|
13099
|
+
inDone = /^##\s+4\./.test(line);
|
|
13100
|
+
continue;
|
|
13101
|
+
}
|
|
13102
|
+
if (!inDone)
|
|
13103
|
+
continue;
|
|
13104
|
+
const m = line.match(/^\s*-\s*([^:`]+?):\s*`(.+)`\s*$/);
|
|
13105
|
+
if (!m?.[1] || !m[2])
|
|
13106
|
+
continue;
|
|
13107
|
+
const name = m[1].trim().toLowerCase();
|
|
13108
|
+
if (name === "done means" || name.startsWith("try it"))
|
|
13109
|
+
continue;
|
|
13110
|
+
if (want && name === want)
|
|
13111
|
+
return m[2];
|
|
13112
|
+
first ??= m[2];
|
|
13113
|
+
}
|
|
13114
|
+
return first;
|
|
13115
|
+
}
|
|
13116
|
+
function gateAllowRules(gate) {
|
|
13117
|
+
const whole = gate?.trim();
|
|
13118
|
+
if (!whole)
|
|
13119
|
+
return [];
|
|
13120
|
+
const parts = whole.split(/&&|\|\||;/).map((p) => p.trim()).filter(Boolean);
|
|
13121
|
+
return [...new Set([whole, ...parts])].map((c) => `Bash(${c})`);
|
|
13122
|
+
}
|
|
12868
13123
|
function ensureInstalled(exec, path, fs, o = {}) {
|
|
12869
13124
|
if (!fs.exists(join15(path, "package.json")) || fs.exists(join15(path, "node_modules")))
|
|
12870
13125
|
return null;
|
|
@@ -12957,17 +13212,17 @@ function fingerprint(text) {
|
|
|
12957
13212
|
}
|
|
12958
13213
|
return (h >>> 0).toString(16);
|
|
12959
13214
|
}
|
|
12960
|
-
function sandboxFor(d, name, cwd) {
|
|
13215
|
+
function sandboxFor(d, name, cwd, allow) {
|
|
12961
13216
|
if (!d.sandbox)
|
|
12962
13217
|
return {};
|
|
12963
13218
|
if (!d.writeSandboxSettings)
|
|
12964
13219
|
throw new Error("sessions are sandboxed, but nothing writes their settings — not started");
|
|
12965
|
-
return { sandbox: d.writeSandboxSettings(name, cwd) };
|
|
13220
|
+
return { sandbox: d.writeSandboxSettings(name, cwd, allow) };
|
|
12966
13221
|
}
|
|
12967
|
-
function hooksFor(d, name) {
|
|
13222
|
+
function hooksFor(d, name, allow) {
|
|
12968
13223
|
if (d.sandbox || !d.writeHookSettings)
|
|
12969
13224
|
return {};
|
|
12970
|
-
return { settingsFile: d.writeHookSettings(name) };
|
|
13225
|
+
return { settingsFile: d.writeHookSettings(name, allow) };
|
|
12971
13226
|
}
|
|
12972
13227
|
function mainCheckoutFor(d, cwd) {
|
|
12973
13228
|
const main = d.mainCheckoutOf?.(cwd);
|
|
@@ -13425,6 +13680,8 @@ async function tickOnce(d) {
|
|
|
13425
13680
|
const worktree = d.runner.makesWorktree ? repoPath : d.ensureWorktree(d.exec, repoPath, branch, worktreePath(repoPath, name), base, packInstallOf(claim.pack, claim.repos[0])) ?? worktreePath(repoPath, name);
|
|
13426
13681
|
const prTitle = d.prTitleOf?.(claim);
|
|
13427
13682
|
mcpConfig = engineMcpConfigFor(d, name, claim.runToken, at, worktree);
|
|
13683
|
+
const gate = packGateOf(claim.pack, claim.repos[0]);
|
|
13684
|
+
const allow = gateAllowRules(gate);
|
|
13428
13685
|
const prompt = nodePrompt({
|
|
13429
13686
|
claim,
|
|
13430
13687
|
branch,
|
|
@@ -13433,6 +13690,7 @@ async function tickOnce(d) {
|
|
|
13433
13690
|
sledsTools: d.source.name === "sleds",
|
|
13434
13691
|
engineMcp: Boolean(mcpConfig),
|
|
13435
13692
|
...prTitle ? { prTitle } : {},
|
|
13693
|
+
...gate ? { gate } : {},
|
|
13436
13694
|
...nodePlanContext(await briefsOf(claim.item), claim.node)
|
|
13437
13695
|
});
|
|
13438
13696
|
const promptFile = d.writePrompt(name, prompt);
|
|
@@ -13449,8 +13707,8 @@ async function tickOnce(d) {
|
|
|
13449
13707
|
...launch.effort && !launch.agent ? { effort: launch.effort } : {},
|
|
13450
13708
|
...launch.agent ? { agent: launch.agent } : {},
|
|
13451
13709
|
...mcpConfig ? { mcpConfig, ...d.strictMcp ? { strictMcp: true } : {} } : {},
|
|
13452
|
-
...sandboxFor(d, name, worktree),
|
|
13453
|
-
...hooksFor(d, name),
|
|
13710
|
+
...sandboxFor(d, name, worktree, allow),
|
|
13711
|
+
...hooksFor(d, name, allow),
|
|
13454
13712
|
...mainCheckoutFor(d, worktree),
|
|
13455
13713
|
...claim.group ? { group: claim.group } : {}
|
|
13456
13714
|
};
|
|
@@ -13708,6 +13966,24 @@ function pressAnswer(d, runner, run, options, answer) {
|
|
|
13708
13966
|
return false;
|
|
13709
13967
|
}
|
|
13710
13968
|
}
|
|
13969
|
+
function readFollowed(d, runner, run) {
|
|
13970
|
+
if (!runner.readsSession)
|
|
13971
|
+
return "";
|
|
13972
|
+
let shown = "";
|
|
13973
|
+
try {
|
|
13974
|
+
shown = runner.read(run.handle);
|
|
13975
|
+
} catch {
|
|
13976
|
+
shown = "";
|
|
13977
|
+
}
|
|
13978
|
+
const print = fingerprint(shown);
|
|
13979
|
+
if (run.seen !== print) {
|
|
13980
|
+
run.seen = print;
|
|
13981
|
+
run.seenAt = d.now();
|
|
13982
|
+
run.seenTicks = 0;
|
|
13983
|
+
} else
|
|
13984
|
+
run.seenTicks = (run.seenTicks ?? 0) + 1;
|
|
13985
|
+
return shown;
|
|
13986
|
+
}
|
|
13711
13987
|
async function dropPermission(d, run, why) {
|
|
13712
13988
|
const perms = permissionsOf(d.source);
|
|
13713
13989
|
if (!run.permission || !perms || !run.runId)
|
|
@@ -14532,11 +14808,13 @@ async function follow(d, state, run, claim, runner, finished, attention, changes
|
|
|
14532
14808
|
return true;
|
|
14533
14809
|
await judgeLook(d, run, view);
|
|
14534
14810
|
if (view.state === "MERGED") {
|
|
14811
|
+
await dropPermission(d, run, "the PR merged");
|
|
14535
14812
|
const landed = await ripple(d, state, run, claim, runner, pr.url, finished, decided, view.head, view.mergeCommit);
|
|
14536
14813
|
return !landed;
|
|
14537
14814
|
}
|
|
14538
14815
|
if (view.state === "CLOSED") {
|
|
14539
14816
|
d.log(`${ref(run)}: ${pr.url} was closed without a merge — let go, its session stopped`);
|
|
14817
|
+
await dropPermission(d, run, "the PR closed without a merge");
|
|
14540
14818
|
stopOpen(d, run, runner);
|
|
14541
14819
|
return false;
|
|
14542
14820
|
}
|
|
@@ -14556,6 +14834,8 @@ async function follow(d, state, run, claim, runner, finished, attention, changes
|
|
|
14556
14834
|
kind: "review",
|
|
14557
14835
|
what: `${view.feedback - pr.feedback} new comment${view.feedback - pr.feedback === 1 ? "" : "s"}`
|
|
14558
14836
|
});
|
|
14837
|
+
if (await settlePermission(d, runner, run, readFollowed(d, runner, run)))
|
|
14838
|
+
return true;
|
|
14559
14839
|
const tell = (text, what) => {
|
|
14560
14840
|
let alive = false;
|
|
14561
14841
|
try {
|
|
@@ -15650,7 +15930,7 @@ var headlessScript = (o, log) => {
|
|
|
15650
15930
|
...o.model ? [`--model ${shq2(o.model)}`] : [],
|
|
15651
15931
|
...o.effort ? [`--effort ${shq2(o.effort)}`] : []
|
|
15652
15932
|
].join(" ");
|
|
15653
|
-
return
|
|
15933
|
+
return `${execLine(cmd)} > ${shq2(log)} 2>&1`;
|
|
15654
15934
|
};
|
|
15655
15935
|
var transcriptTail = (log) => {
|
|
15656
15936
|
const lines = log.trimEnd().split(`
|
|
@@ -16567,7 +16847,7 @@ function engineDeps(o, flags, source, runner, stateName) {
|
|
|
16567
16847
|
...flags.strictMcp ? { strictMcp: true } : {},
|
|
16568
16848
|
...flags.sandbox ? {
|
|
16569
16849
|
sandbox: true,
|
|
16570
|
-
writeSandboxSettings: o.writeSandboxSettings ?? ((name, cwd) => {
|
|
16850
|
+
writeSandboxSettings: o.writeSandboxSettings ?? ((name, cwd, allow) => {
|
|
16571
16851
|
const gitCommonDir = gitCommonDirOf(exec, cwd);
|
|
16572
16852
|
const dir = join16(stateDir, "sandbox");
|
|
16573
16853
|
mkdirSync12(dir, { recursive: true, mode: 448 });
|
|
@@ -16576,6 +16856,9 @@ function engineDeps(o, flags, source, runner, stateName) {
|
|
|
16576
16856
|
...SANDBOX_DEFAULT_ALLOW,
|
|
16577
16857
|
...flags.sandboxAllow ?? []
|
|
16578
16858
|
]);
|
|
16859
|
+
walls.permissions.allow = [
|
|
16860
|
+
...new Set([...walls.permissions.allow, ...allow ?? []])
|
|
16861
|
+
];
|
|
16579
16862
|
const settings = withHook(name, { ...walls });
|
|
16580
16863
|
writeFileSync13(p, `${JSON.stringify(settings, null, 2)}
|
|
16581
16864
|
`, { mode: 384 });
|
|
@@ -16583,10 +16866,11 @@ function engineDeps(o, flags, source, runner, stateName) {
|
|
|
16583
16866
|
return { settingsFile: p, gitCommonDir };
|
|
16584
16867
|
})
|
|
16585
16868
|
} : {},
|
|
16586
|
-
writeHookSettings: (name) => {
|
|
16869
|
+
writeHookSettings: (name, allow) => {
|
|
16587
16870
|
mkdirSync12(hooksDir, { recursive: true, mode: 448 });
|
|
16588
16871
|
const p = join16(hooksDir, `${name}.json`);
|
|
16589
|
-
|
|
16872
|
+
const base = allow?.length ? { permissions: { allow: [...allow] } } : {};
|
|
16873
|
+
writeFileSync13(p, `${JSON.stringify(withHook(name, base), null, 2)}
|
|
16590
16874
|
`, { mode: 384 });
|
|
16591
16875
|
chmodSync4(p, 384);
|
|
16592
16876
|
return p;
|
|
@@ -16717,7 +17001,7 @@ async function driveEngine(o, flags, deps, until = {}) {
|
|
|
16717
17001
|
runner: deps.runner.name,
|
|
16718
17002
|
atOnce: flags.atOnce,
|
|
16719
17003
|
interval: flags.interval,
|
|
16720
|
-
cliVersion: "0.1.
|
|
17004
|
+
cliVersion: "0.1.45"
|
|
16721
17005
|
};
|
|
16722
17006
|
const beat = (checkedAt) => {
|
|
16723
17007
|
const m = deps.machine;
|
|
@@ -17619,7 +17903,7 @@ function formatDoctor(checks) {
|
|
|
17619
17903
|
}
|
|
17620
17904
|
function defaultWhich2(cmd) {
|
|
17621
17905
|
try {
|
|
17622
|
-
return
|
|
17906
|
+
return execFileSync4("which", ["-a", cmd], {
|
|
17623
17907
|
encoding: "utf8",
|
|
17624
17908
|
stdio: ["ignore", "pipe", "ignore"]
|
|
17625
17909
|
}).split(`
|
|
@@ -18156,7 +18440,7 @@ function parsePmset(text) {
|
|
|
18156
18440
|
}
|
|
18157
18441
|
var defaultPmset = () => {
|
|
18158
18442
|
try {
|
|
18159
|
-
return parsePmset(
|
|
18443
|
+
return parsePmset(execFileSync4("pmset", ["-g", "custom"], { encoding: "utf8", timeout: 2000 }));
|
|
18160
18444
|
} catch {
|
|
18161
18445
|
return null;
|
|
18162
18446
|
}
|
|
@@ -18691,7 +18975,7 @@ function engineDeps2(o, flags, source, runner, stateName) {
|
|
|
18691
18975
|
...flags.strictMcp ? { strictMcp: true } : {},
|
|
18692
18976
|
...flags.sandbox ? {
|
|
18693
18977
|
sandbox: true,
|
|
18694
|
-
writeSandboxSettings: o.writeSandboxSettings ?? ((name, cwd) => {
|
|
18978
|
+
writeSandboxSettings: o.writeSandboxSettings ?? ((name, cwd, allow) => {
|
|
18695
18979
|
const gitCommonDir = gitCommonDirOf2(exec, cwd);
|
|
18696
18980
|
const dir = join21(stateDir, "sandbox");
|
|
18697
18981
|
mkdirSync16(dir, { recursive: true, mode: 448 });
|
|
@@ -18700,6 +18984,9 @@ function engineDeps2(o, flags, source, runner, stateName) {
|
|
|
18700
18984
|
...SANDBOX_DEFAULT_ALLOW,
|
|
18701
18985
|
...flags.sandboxAllow ?? []
|
|
18702
18986
|
]);
|
|
18987
|
+
walls.permissions.allow = [
|
|
18988
|
+
...new Set([...walls.permissions.allow, ...allow ?? []])
|
|
18989
|
+
];
|
|
18703
18990
|
const settings = withHook(name, { ...walls });
|
|
18704
18991
|
writeFileSync18(p, `${JSON.stringify(settings, null, 2)}
|
|
18705
18992
|
`, { mode: 384 });
|
|
@@ -18707,10 +18994,11 @@ function engineDeps2(o, flags, source, runner, stateName) {
|
|
|
18707
18994
|
return { settingsFile: p, gitCommonDir };
|
|
18708
18995
|
})
|
|
18709
18996
|
} : {},
|
|
18710
|
-
writeHookSettings: (name) => {
|
|
18997
|
+
writeHookSettings: (name, allow) => {
|
|
18711
18998
|
mkdirSync16(hooksDir, { recursive: true, mode: 448 });
|
|
18712
18999
|
const p = join21(hooksDir, `${name}.json`);
|
|
18713
|
-
|
|
19000
|
+
const base = allow?.length ? { permissions: { allow: [...allow] } } : {};
|
|
19001
|
+
writeFileSync18(p, `${JSON.stringify(withHook(name, base), null, 2)}
|
|
18714
19002
|
`, { mode: 384 });
|
|
18715
19003
|
chmodSync5(p, 384);
|
|
18716
19004
|
return p;
|
|
@@ -18850,7 +19138,7 @@ async function driveEngine2(o, flags, deps, until = {}) {
|
|
|
18850
19138
|
runner: deps.runner.name,
|
|
18851
19139
|
atOnce: flags.atOnce,
|
|
18852
19140
|
interval: flags.interval,
|
|
18853
|
-
cliVersion: "0.1.
|
|
19141
|
+
cliVersion: "0.1.45"
|
|
18854
19142
|
};
|
|
18855
19143
|
const beat = (checkedAt) => {
|
|
18856
19144
|
const m = deps.machine;
|
|
@@ -22498,12 +22786,81 @@ var promptEvent = async (o) => {
|
|
|
22498
22786
|
return code;
|
|
22499
22787
|
};
|
|
22500
22788
|
|
|
22789
|
+
// src/commands/reopen.ts
|
|
22790
|
+
var REOPEN_USAGE = 'usage: sleds reopen <key> --reason "<why it is reopened>" [--json]';
|
|
22791
|
+
function parseReopenArgs(argv) {
|
|
22792
|
+
let key;
|
|
22793
|
+
let reason = "";
|
|
22794
|
+
let json = false;
|
|
22795
|
+
for (let i = 0;i < argv.length; i++) {
|
|
22796
|
+
const a = argv[i];
|
|
22797
|
+
if (a === "--json")
|
|
22798
|
+
json = true;
|
|
22799
|
+
else if (a === "--reason") {
|
|
22800
|
+
const value = argv[i + 1];
|
|
22801
|
+
if (value === undefined || value.startsWith("--"))
|
|
22802
|
+
return { error: "--reason needs a value" };
|
|
22803
|
+
reason = value;
|
|
22804
|
+
i++;
|
|
22805
|
+
} else if (a.startsWith("--"))
|
|
22806
|
+
return { error: `unknown flag ${a}` };
|
|
22807
|
+
else if (key === undefined)
|
|
22808
|
+
key = a;
|
|
22809
|
+
else
|
|
22810
|
+
return { error: `one item at a time — "${a}" is extra` };
|
|
22811
|
+
}
|
|
22812
|
+
if (!key)
|
|
22813
|
+
return { error: "which item?" };
|
|
22814
|
+
return { key, reason: reason.trim(), json };
|
|
22815
|
+
}
|
|
22816
|
+
function reopenedLine(key, j) {
|
|
22817
|
+
const shown = typeof j.key === "string" ? j.key : key;
|
|
22818
|
+
return typeof j.sled === "string" && j.sled ? `${shown} reopened — back on ${j.sled}'s sled` : `${shown} reopened — back to ready, on no sled`;
|
|
22819
|
+
}
|
|
22820
|
+
async function reopenCmd(o) {
|
|
22821
|
+
const log = o.log ?? console.log;
|
|
22822
|
+
const error = o.error ?? console.error;
|
|
22823
|
+
const a = parseReopenArgs(o.argv);
|
|
22824
|
+
if ("error" in a) {
|
|
22825
|
+
error(a.error);
|
|
22826
|
+
error(REOPEN_USAGE);
|
|
22827
|
+
return 2;
|
|
22828
|
+
}
|
|
22829
|
+
if (!a.reason) {
|
|
22830
|
+
error(`${a.key}: say why it is reopened — it goes on the item's thread`);
|
|
22831
|
+
error(REOPEN_USAGE);
|
|
22832
|
+
return 1;
|
|
22833
|
+
}
|
|
22834
|
+
const client = engineClient({ home: o.home, ...o.client ? { client: o.client } : {} });
|
|
22835
|
+
if ("error" in client) {
|
|
22836
|
+
error(client.error);
|
|
22837
|
+
return 1;
|
|
22838
|
+
}
|
|
22839
|
+
const r = await client.postFull("/proxy/reopen", { project: a.key, reason: a.reason });
|
|
22840
|
+
if (!r) {
|
|
22841
|
+
error(`${a.key}: SLEDS unreachable — nothing reopened`);
|
|
22842
|
+
return 1;
|
|
22843
|
+
}
|
|
22844
|
+
const ok = r.status < 300 && r.json?.ok !== false;
|
|
22845
|
+
if (a.json)
|
|
22846
|
+
log(JSON.stringify(r.json ?? { ok: false, error: `HTTP ${r.status}` }));
|
|
22847
|
+
if (!ok) {
|
|
22848
|
+
const why = typeof r.json?.error === "string" ? r.json.error : `HTTP ${r.status}`;
|
|
22849
|
+
if (!a.json)
|
|
22850
|
+
error(r.status === 404 && !r.json ? `${a.key}: SLEDS does not know this seat — run \`sleds init\`` : `${a.key}: ${why}`);
|
|
22851
|
+
return 1;
|
|
22852
|
+
}
|
|
22853
|
+
if (!a.json)
|
|
22854
|
+
log(reopenedLine(a.key, r.json ?? {}));
|
|
22855
|
+
return 0;
|
|
22856
|
+
}
|
|
22857
|
+
|
|
22501
22858
|
// src/commands/route.ts
|
|
22502
22859
|
init_src();
|
|
22503
22860
|
import { readFileSync as readFileSync27 } from "node:fs";
|
|
22504
22861
|
import { hostname as hostname12 } from "node:os";
|
|
22505
22862
|
var ARM_USAGE = "usage: sleds route arm <key> [--off] [--json] [--name <engine>] [--at-once <n>] [--runner tmux|headless|command|orca|cmux] [--runner-cmd '<launcher>'] [--agent '<command>'] [--mode <permission-mode>] [--model <id>] [--effort <e>] [--branch '<template>'] [--pr-title '<template>'] [--repos-dir <dir>]";
|
|
22506
|
-
var
|
|
22863
|
+
var USAGE2 = "usage: sleds route <key> [--long | --dot | --json] | sleds route arm <key> [--off] [--json] | sleds route answer <key>/<node> --stdin | sleds route propose <key> --file <nodes.json> | sleds route approve <key> | sleds route redraft <key> | sleds route single <key> | sleds route decisions [<key>] [--flagged] | sleds route decision <key> <id> --seen | --answer --stdin [--route accept|keep|redraft] | --keep-route | --apply [--answer --stdin] | --overrule --stdin | --ready | sleds route run <key> [--at-once <n>] [--runner tmux|headless|command|orca|cmux] [--runner-cmd '<launcher>'] [--agent '<command>'] [--interval <s>] [--lease <min>] [--idle <min>] [--repo <path>] [--repos-dir <dir>] [--mode <permission-mode>] [--model <id>] [--branch '<template>'] [--pr-title '<template>'] [--once]";
|
|
22507
22864
|
var ANSWER_USAGE = "usage: sleds route answer <key>/<node> <answer…> | sleds route answer <key>/<node> --stdin [--json]";
|
|
22508
22865
|
var PROPOSE_USAGE = "usage: sleds route propose <key> (--file <nodes.json> | --stdin) [--name <engine>] [--json]";
|
|
22509
22866
|
var APPROVE_USAGE = "usage: sleds route approve <key> [--file <nodes.json>] [--json]";
|
|
@@ -23058,14 +23415,14 @@ var routeCmd = async (o) => {
|
|
|
23058
23415
|
const parsed = parseEngineArgs(o.argv.filter((a) => a !== "--dot" && a !== "--long" && a !== "--json"), o.host ?? hostname12());
|
|
23059
23416
|
if ("error" in parsed) {
|
|
23060
23417
|
error(parsed.error);
|
|
23061
|
-
error(
|
|
23418
|
+
error(USAGE2);
|
|
23062
23419
|
return 2;
|
|
23063
23420
|
}
|
|
23064
23421
|
const { positional } = parsed;
|
|
23065
23422
|
const isRun = positional[0] === "run";
|
|
23066
23423
|
const key = isRun ? positional[1] : positional[0];
|
|
23067
23424
|
if (!key) {
|
|
23068
|
-
error(
|
|
23425
|
+
error(USAGE2);
|
|
23069
23426
|
return 2;
|
|
23070
23427
|
}
|
|
23071
23428
|
if (asJson) {
|
|
@@ -23321,7 +23678,7 @@ var seatEvent = async (o) => {
|
|
|
23321
23678
|
};
|
|
23322
23679
|
|
|
23323
23680
|
// src/commands/seatSync.ts
|
|
23324
|
-
import { execFileSync as
|
|
23681
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
23325
23682
|
import { existsSync as existsSync26, mkdirSync as mkdirSync22, readFileSync as readFileSync28, readdirSync as readdirSync5, rmSync as rmSync16, writeFileSync as writeFileSync25 } from "node:fs";
|
|
23326
23683
|
import { platform as platform2 } from "node:os";
|
|
23327
23684
|
import { dirname as dirname17, join as join26 } from "node:path";
|
|
@@ -23689,7 +24046,7 @@ async function readCookieRowsBun(home, source) {
|
|
|
23689
24046
|
}
|
|
23690
24047
|
function keychainKey(service) {
|
|
23691
24048
|
try {
|
|
23692
|
-
return
|
|
24049
|
+
return execFileSync5("security", ["find-generic-password", "-w", "-s", service], {
|
|
23693
24050
|
encoding: "utf8"
|
|
23694
24051
|
}).trim();
|
|
23695
24052
|
} catch {
|
|
@@ -23701,7 +24058,7 @@ function decryptJar(rows, safeStoragePw) {
|
|
|
23701
24058
|
}
|
|
23702
24059
|
var curlUsage = (org, cookie) => {
|
|
23703
24060
|
try {
|
|
23704
|
-
const out =
|
|
24061
|
+
const out = execFileSync5("curl", usageCurlArgs(), {
|
|
23705
24062
|
input: usageCurlConfig(org, cookie),
|
|
23706
24063
|
encoding: "utf8",
|
|
23707
24064
|
maxBuffer: 8000000
|
|
@@ -23766,16 +24123,16 @@ function install(home, argv, source, log) {
|
|
|
23766
24123
|
}
|
|
23767
24124
|
writeFileSync25(path, launchAgentPlist(bun, script, home, source), { mode: 384 });
|
|
23768
24125
|
try {
|
|
23769
|
-
|
|
24126
|
+
execFileSync5("launchctl", ["unload", path], { stdio: "ignore" });
|
|
23770
24127
|
} catch {}
|
|
23771
|
-
|
|
24128
|
+
execFileSync5("launchctl", ["load", "-w", path]);
|
|
23772
24129
|
log(`seat-sync timer installed (every ${SYNC_INTERVAL_S / 60} min) → ${path}`);
|
|
23773
24130
|
return 0;
|
|
23774
24131
|
}
|
|
23775
24132
|
function uninstall(home, source, log) {
|
|
23776
24133
|
const path = plistPath2(home, source);
|
|
23777
24134
|
try {
|
|
23778
|
-
|
|
24135
|
+
execFileSync5("launchctl", ["unload", "-w", path], { stdio: "ignore" });
|
|
23779
24136
|
} catch {}
|
|
23780
24137
|
rmSync16(seatTokenPath(home, source), { force: true });
|
|
23781
24138
|
log(existsSync26(path) ? `seat-sync timer removed (${path} can be deleted)` : "no seat-sync timer");
|
|
@@ -24647,7 +25004,7 @@ var verify = async (o) => {
|
|
|
24647
25004
|
};
|
|
24648
25005
|
|
|
24649
25006
|
// src/commands/webhooks.ts
|
|
24650
|
-
var
|
|
25007
|
+
var USAGE3 = "usage: sleds webhooks [--json] | sleds webhooks deliveries <id or name> [--json]";
|
|
24651
25008
|
var str14 = (x) => typeof x === "string" ? x : "";
|
|
24652
25009
|
function ago3(at, now) {
|
|
24653
25010
|
const m = Math.max(0, Math.floor((now - at) / 60000));
|
|
@@ -24698,7 +25055,7 @@ var webhooksCmd = async (o) => {
|
|
|
24698
25055
|
const now = (o.now ?? Date.now)();
|
|
24699
25056
|
const sub = words[0];
|
|
24700
25057
|
if (sub && (sub !== "deliveries" || words.length !== 2)) {
|
|
24701
|
-
error(
|
|
25058
|
+
error(USAGE3);
|
|
24702
25059
|
return 2;
|
|
24703
25060
|
}
|
|
24704
25061
|
const path = sub === "deliveries" ? `/proxy/webhooks/deliveries?hook=${encodeURIComponent(words[1] ?? "")}` : "/proxy/webhooks";
|
|
@@ -24958,7 +25315,7 @@ var isSledsStopHook2 = isSledsHookFor2("stop-event");
|
|
|
24958
25315
|
var isSledsPermissionHook2 = isSledsHookFor2("permission-event");
|
|
24959
25316
|
|
|
24960
25317
|
// src/index.ts
|
|
24961
|
-
var
|
|
25318
|
+
var USAGE4 = `usage: sleds <command> [options]
|
|
24962
25319
|
|
|
24963
25320
|
commands:
|
|
24964
25321
|
init Sign in through the browser, make this machine a seat, install the /sled skill
|
|
@@ -25013,6 +25370,10 @@ commands:
|
|
|
25013
25370
|
--run start Claude Code with the pack as the first prompt
|
|
25014
25371
|
webhooks The org's outbound webhooks (admins); deliveries <id or name> lists one hook's
|
|
25015
25372
|
last hundred deliveries. Registering and Redeliver stay in the app.
|
|
25373
|
+
deploy A release step's one line: tell SLEDS this commit is now running in an environment.
|
|
25374
|
+
--env <slug> the environment (required); --service <name> for a monorepo
|
|
25375
|
+
--sha / --repo else the CI's own variables, then git
|
|
25376
|
+
Token from SLEDS_DEPLOY_TOKEN (Settings \u203A Deploys). SLEDS down never fails a release.
|
|
25016
25377
|
sled --json What is on your sled (mine with each route, proposed, sent), for a front end.
|
|
25017
25378
|
sleds sled top KEY put KEY first on your sled; the engine picks it next
|
|
25018
25379
|
route <key> Print an item's route: its nodes, what each waits on, what is ready, where each
|
|
@@ -25040,6 +25401,9 @@ commands:
|
|
|
25040
25401
|
--mode <permission-mode> Claude Code's (default auto; acceptEdits stops an unattended
|
|
25041
25402
|
session at git push; an account without auto passes acceptEdits)
|
|
25042
25403
|
--model <id> Claude Code's model
|
|
25404
|
+
reopen <key> Put back an item that landed by mistake (a skipped node closed its route): its
|
|
25405
|
+
landing is cleared and it goes back on the sled it came off, or to ready.
|
|
25406
|
+
--reason "<text>" why \u2014 required; it goes on the item's thread (--json)
|
|
25043
25407
|
engine The long-running loop over the routes Run armed on your sled: takes each ready
|
|
25044
25408
|
node, starts a session on its own branch, follows its PR to the merge (CI failures
|
|
25045
25409
|
and review feedback go back to the session; a merge lands the node, closes the
|
|
@@ -25161,11 +25525,11 @@ var wantsHelp = (argv) => {
|
|
|
25161
25525
|
return false;
|
|
25162
25526
|
};
|
|
25163
25527
|
var commandUsage = (cmd) => {
|
|
25164
|
-
const lines =
|
|
25528
|
+
const lines = USAGE4.split(`
|
|
25165
25529
|
`);
|
|
25166
25530
|
const start = lines.findIndex((l) => l === ` ${cmd}` || l.startsWith(` ${cmd} `));
|
|
25167
25531
|
if (start === -1)
|
|
25168
|
-
return
|
|
25532
|
+
return USAGE4;
|
|
25169
25533
|
const section = [lines[start]];
|
|
25170
25534
|
for (const l of lines.slice(start + 1)) {
|
|
25171
25535
|
if (!/^ {3,}\S/.test(l))
|
|
@@ -25221,8 +25585,12 @@ var main = async (argv) => {
|
|
|
25221
25585
|
return packCmd({ home, argv: argv.slice(1) });
|
|
25222
25586
|
case "webhooks":
|
|
25223
25587
|
return webhooksCmd({ home, argv: argv.slice(1) });
|
|
25588
|
+
case "deploy":
|
|
25589
|
+
return deployCmd({ argv: argv.slice(1) });
|
|
25224
25590
|
case "sled":
|
|
25225
25591
|
return sledCmd({ home, argv: argv.slice(1) });
|
|
25592
|
+
case "reopen":
|
|
25593
|
+
return reopenCmd({ home, argv: argv.slice(1) });
|
|
25226
25594
|
case "engine":
|
|
25227
25595
|
case "route": {
|
|
25228
25596
|
if (cmd === "engine" && argv[1] === "status")
|
|
@@ -25415,19 +25783,19 @@ var main = async (argv) => {
|
|
|
25415
25783
|
case "help":
|
|
25416
25784
|
case "--help":
|
|
25417
25785
|
case "-h":
|
|
25418
|
-
console.log(
|
|
25786
|
+
console.log(USAGE4);
|
|
25419
25787
|
console.log(`
|
|
25420
25788
|
${renderHelpText(argv[1])}`);
|
|
25421
25789
|
return 0;
|
|
25422
25790
|
case "version":
|
|
25423
25791
|
case "--version":
|
|
25424
25792
|
case "-v":
|
|
25425
|
-
console.log("sleds 0.1.
|
|
25793
|
+
console.log("sleds 0.1.45");
|
|
25426
25794
|
return 0;
|
|
25427
25795
|
default:
|
|
25428
25796
|
console.error(`unknown command: ${cmd}
|
|
25429
25797
|
`);
|
|
25430
|
-
console.error(
|
|
25798
|
+
console.error(USAGE4);
|
|
25431
25799
|
return 1;
|
|
25432
25800
|
}
|
|
25433
25801
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sleds/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.45",
|
|
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",
|