svamp-cli 0.2.346 → 0.2.358
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/bin/skills/artifact/SKILL.md +19 -5
- package/bin/skills/crew/SKILL.md +42 -11
- package/dist/{acpBackend-BZ1MJhQV.mjs → acpBackend-UaBD4zL3.mjs} +3 -0
- package/dist/{agentCommands-B2Nl7Fof.mjs → agentCommands-C_KvvYFp.mjs} +5 -5
- package/dist/{claudeAuth-iAerdaMr.mjs → claudeAuth-DMDvPNhO.mjs} +55 -6
- package/dist/cli.mjs +164 -86
- package/dist/{commands-D69mnkYu.mjs → commands-B19lVZjm.mjs} +10 -3
- package/dist/{commands-BB1Kcyt-.mjs → commands-Bmoy6Rlw.mjs} +34 -4
- package/dist/{commands-CiRCwjkJ.mjs → commands-CFBKkfX8.mjs} +9 -9
- package/dist/{commands-D1pZq3d4.mjs → commands-CcdoQIkb.mjs} +126 -25
- package/dist/{commands-BOpoVBOM.mjs → commands-DH_YQrAP.mjs} +5 -21
- package/dist/{commands-H4PsTGn8.mjs → commands-DRxANaS5.mjs} +65 -19
- package/dist/{commands-C2aODeVc.mjs → commands-nkvTnUJj.mjs} +29 -13
- package/dist/{detectIsolation-CzoDCEnh.mjs → detectIsolation-CrV8IFDv.mjs} +36 -16
- package/dist/{fleet-DM_Q7w69.mjs → fleet-Ca-UjJNL.mjs} +40 -15
- package/dist/friendlyName-B-OEwt0t.mjs +341 -0
- package/dist/{headlessCli-C8HzQvis.mjs → headlessCli-BpTXz6FD.mjs} +24 -16
- package/dist/{hookSettings-DYMTUgVA.mjs → hookSettings-CvvUhwCK.mjs} +294 -55
- package/dist/{friendlyName-1UZcyDhu.mjs → inboxGuard-CDqmIypF.mjs} +32 -347
- package/dist/index.mjs +10 -9
- package/dist/{loopVerify-B47sSjxe.mjs → loopVerify-CxAKJB5a.mjs} +1 -1
- package/dist/{notifyCommands-D-IKIxiq.mjs → notifyCommands-CCUE18Q-.mjs} +1 -1
- package/dist/package-DTTWvJbp.mjs +64 -0
- package/dist/{rpc-Cf0grcru.mjs → rpc-CAlAHQAy.mjs} +8 -3
- package/dist/{rpc-BB6RF4-F.mjs → rpc-CacreLQ8.mjs} +13 -9
- package/dist/{run-ChdsgaSD.mjs → run-Bc81iurl.mjs} +27 -8
- package/dist/{run-DkjwF-zA.mjs → run-DKOND4dO.mjs} +222 -73
- package/dist/{scheduler-BY30ZrB9.mjs → scheduler-IyLWBF8T.mjs} +28 -10
- package/dist/{serveAuth-DbVLRCRR.mjs → serveAuth-4M0vwMyj.mjs} +53 -11
- package/dist/{serveCommands-CUHUiYnZ.mjs → serveCommands-B-Ym_QXc.mjs} +8 -8
- package/dist/{store-Bn0ByYev.mjs → store-D8dok6_d.mjs} +62 -14
- package/package.json +3 -3
- package/dist/package-DtuINqf5.mjs +0 -64
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
|
-
import { connectAndGetMachine, resolveSessionId, createWorktree, connectAndResolveSession } from './commands-
|
|
3
|
+
import { c as connectAndGetMachine, r as resolveSessionId, b as createWorktree, a as connectAndResolveSession } from './commands-CcdoQIkb.mjs';
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
5
|
import { w as withFileLock } from './fileLock-BMPwCrB6.mjs';
|
|
6
|
-
import { u as updateIssue,
|
|
7
|
-
import { s as shortId } from './friendlyName-
|
|
6
|
+
import { u as updateIssue, b as addComment, i as addIssue, o as resolveCrewIssueOnMerge } from './store-D8dok6_d.mjs';
|
|
7
|
+
import { s as shortId } from './friendlyName-B-OEwt0t.mjs';
|
|
8
8
|
import { w as wantsHelp } from './wantsHelp-6bMOeNUz.mjs';
|
|
9
9
|
import 'node:os';
|
|
10
10
|
import './hyphaClient-ybivOjZo.mjs';
|
|
11
11
|
import './types-57J3Re0n.mjs';
|
|
12
12
|
import './securityContext-Cm5UTCuD.mjs';
|
|
13
|
-
import './loopVerify-
|
|
13
|
+
import './loopVerify-CxAKJB5a.mjs';
|
|
14
14
|
import './instance-CeHz5C8v.mjs';
|
|
15
15
|
import 'fs';
|
|
16
16
|
import 'path';
|
|
17
17
|
import 'os';
|
|
18
|
-
import './
|
|
18
|
+
import './inboxGuard-CDqmIypF.mjs';
|
|
19
19
|
import 'node:crypto';
|
|
20
|
+
import './flushExit-C-i6SGjj.mjs';
|
|
20
21
|
|
|
21
22
|
function git(cwd, args) {
|
|
22
23
|
return execFileSync("git", args, { cwd, stdio: "pipe", encoding: "utf-8" }).toString();
|
|
@@ -306,6 +307,7 @@ async function featureStart(brief, opts = {}) {
|
|
|
306
307
|
const standalone = !!opts.noWorktree;
|
|
307
308
|
let childDir;
|
|
308
309
|
let base = "";
|
|
310
|
+
const leadDir = opts.directory || lead.directory;
|
|
309
311
|
if (standalone) {
|
|
310
312
|
const dir = opts.directory;
|
|
311
313
|
if (!dir) {
|
|
@@ -319,15 +321,14 @@ async function featureStart(brief, opts = {}) {
|
|
|
319
321
|
}
|
|
320
322
|
console.log(`Standalone child folder: ${childDir} (no worktree)`);
|
|
321
323
|
} else {
|
|
322
|
-
const leadDir = opts.directory || lead.directory;
|
|
323
324
|
if (!leadDir) {
|
|
324
325
|
console.error("Could not determine the lead project directory.");
|
|
325
326
|
process.exit(1);
|
|
326
327
|
}
|
|
327
|
-
const
|
|
328
|
-
base = opts.baseBranch || currentBranch(
|
|
328
|
+
const worktreeRoot = canonicalProjectRoot(leadDir);
|
|
329
|
+
base = opts.baseBranch || currentBranch(worktreeRoot) || "main";
|
|
329
330
|
try {
|
|
330
|
-
wt = createWorktree(
|
|
331
|
+
wt = createWorktree(worktreeRoot);
|
|
331
332
|
} catch (e) {
|
|
332
333
|
console.error(`Failed to create worktree: ${e.message}`);
|
|
333
334
|
process.exit(1);
|
|
@@ -335,7 +336,7 @@ async function featureStart(brief, opts = {}) {
|
|
|
335
336
|
childDir = wt.path;
|
|
336
337
|
console.log(`Worktree: ${wt.branch} \u2192 ${wt.path}`);
|
|
337
338
|
}
|
|
338
|
-
const projectRoot = canonicalProjectRoot(
|
|
339
|
+
const projectRoot = canonicalProjectRoot(leadDir || childDir);
|
|
339
340
|
const result = await machine.spawnSession({
|
|
340
341
|
directory: childDir,
|
|
341
342
|
agent: "claude",
|
|
@@ -629,7 +630,14 @@ async function featureMerge(childPartial, opts = {}) {
|
|
|
629
630
|
notifyLead,
|
|
630
631
|
childId,
|
|
631
632
|
`<crew-freeze>Your lead is merging branch ${branch} into ${base}. Stop editing files now; do not commit until told.</crew-freeze>`,
|
|
632
|
-
"crew: freeze for merge"
|
|
633
|
+
"crew: freeze for merge",
|
|
634
|
+
// #1619: URGENT. This is a pre-emption notice, and the state it must interrupt —
|
|
635
|
+
// a child mid-work — is by definition NOT idle, so at 'normal' urgency
|
|
636
|
+
// classifyInbound QUEUES it until the child stops on its own. waitForChildIdle
|
|
637
|
+
// below is supposed to observe the child halting BECAUSE of this message; a queued
|
|
638
|
+
// message causes no halt, so the wait could only ever time out, and mergeBack can
|
|
639
|
+
// then reach `git worktree remove --force` on a tree the child is still writing.
|
|
640
|
+
"urgent"
|
|
633
641
|
);
|
|
634
642
|
} catch {
|
|
635
643
|
}
|
|
@@ -649,7 +657,10 @@ async function featureMerge(childPartial, opts = {}) {
|
|
|
649
657
|
Merge was not applied: ${r.detail}
|
|
650
658
|
Resolve and run \`svamp feature done\` again.
|
|
651
659
|
</crew-rework>`,
|
|
652
|
-
"crew: rework needed"
|
|
660
|
+
"crew: rework needed",
|
|
661
|
+
// #1619: URGENT — the comment above says 're-wake the child', which at
|
|
662
|
+
// 'normal' urgency is exactly what did not happen.
|
|
663
|
+
"urgent"
|
|
653
664
|
);
|
|
654
665
|
} catch {
|
|
655
666
|
}
|
|
@@ -665,7 +676,12 @@ Resolve and run \`svamp feature done\` again.
|
|
|
665
676
|
Merge could not be applied (${r.stage}): ${r.detail}
|
|
666
677
|
Stop editing and hold \u2014 do not keep working. Once the blocker is resolved, the lead can retry \`svamp feature merge\`.
|
|
667
678
|
</crew-blocked>`,
|
|
668
|
-
"crew: merge blocked"
|
|
679
|
+
"crew: merge blocked",
|
|
680
|
+
// #1619: URGENT. #0353's whole point is to STOP a child that is 'silently
|
|
681
|
+
// LOOPING against an un-merged branch, burning tokens toward the cost
|
|
682
|
+
// ceiling'. A looping child is never idle, so a queued STOP notice arrived
|
|
683
|
+
// only once it was no longer needed.
|
|
684
|
+
"urgent"
|
|
669
685
|
);
|
|
670
686
|
} catch {
|
|
671
687
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
|
-
import { mkdir, rm, readFile,
|
|
2
|
+
import { mkdir, rm, readFile, mkdtemp, access, chmod, rename } from 'node:fs/promises';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
@@ -65,6 +65,16 @@ function getNonoAssetName(version) {
|
|
|
65
65
|
}
|
|
66
66
|
return null;
|
|
67
67
|
}
|
|
68
|
+
async function probeNonoRuns(binaryPath) {
|
|
69
|
+
try {
|
|
70
|
+
const { stdout } = await execFileAsync(binaryPath, ["--version"], { timeout: 5e3 });
|
|
71
|
+
return { ok: true, version: stdout.trim().split("\n")[0] };
|
|
72
|
+
} catch (e) {
|
|
73
|
+
const stderr = typeof e?.stderr === "string" ? e.stderr.trim() : "";
|
|
74
|
+
const first = stderr.split("\n").find((l) => l.trim()) || e?.message || "unknown error";
|
|
75
|
+
return { ok: false, reason: first.trim() };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
68
78
|
async function installNono() {
|
|
69
79
|
const platform = process.platform;
|
|
70
80
|
if (platform !== "darwin" && platform !== "linux") {
|
|
@@ -116,23 +126,33 @@ async function installNono() {
|
|
|
116
126
|
return false;
|
|
117
127
|
}
|
|
118
128
|
console.log(`[isolation] nono ${version} checksum verified (${actual.slice(0, 12)}\u2026)`);
|
|
119
|
-
await execFileAsync("tar", [
|
|
120
|
-
"xzf",
|
|
121
|
-
tarball,
|
|
122
|
-
"-C",
|
|
123
|
-
SVAMP_BIN_DIR
|
|
124
|
-
], { timeout: 15e3 });
|
|
125
|
-
await rm(tarball, { force: true }).catch(() => {
|
|
126
|
-
});
|
|
127
129
|
const nonoPath = join(SVAMP_BIN_DIR, "nono");
|
|
128
|
-
await
|
|
130
|
+
const stageDir = await mkdtemp(join(SVAMP_BIN_DIR, ".nono-stage-"));
|
|
129
131
|
try {
|
|
130
|
-
await
|
|
131
|
-
|
|
132
|
+
await execFileAsync("tar", ["xzf", tarball, "-C", stageDir], { timeout: 15e3 });
|
|
133
|
+
await rm(tarball, { force: true }).catch(() => {
|
|
134
|
+
});
|
|
135
|
+
const staged = join(stageDir, "nono");
|
|
136
|
+
try {
|
|
137
|
+
await access(staged);
|
|
138
|
+
} catch {
|
|
139
|
+
console.warn("[isolation] Download completed but nono binary not found in the archive");
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
await chmod(staged, 493);
|
|
143
|
+
const probe = await probeNonoRuns(staged);
|
|
144
|
+
if (!probe.ok) {
|
|
145
|
+
console.error(
|
|
146
|
+
`[isolation] nono ${version} downloaded and checksum-verified, but it CANNOT RUN on this host: ${probe.reason}. Refusing to install \u2014 the previous binary (if any) is untouched. This host needs a nono build compatible with its libc; sessions will fall back to docker/podman, where --allow-write and --allow-domain are NOT enforced (#0878).`
|
|
147
|
+
);
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
await rename(staged, nonoPath);
|
|
151
|
+
console.log(`[isolation] nono ${version} installed at ${nonoPath} (verified runnable: ${probe.version || "ok"})`);
|
|
132
152
|
return true;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
153
|
+
} finally {
|
|
154
|
+
await rm(stageDir, { recursive: true, force: true }).catch(() => {
|
|
155
|
+
});
|
|
136
156
|
}
|
|
137
157
|
} catch (e) {
|
|
138
158
|
console.warn(`[isolation] Failed to install nono: ${e.message}`);
|
|
@@ -316,4 +336,4 @@ async function detectIsolationCapabilities() {
|
|
|
316
336
|
return { available, preferred, details };
|
|
317
337
|
}
|
|
318
338
|
|
|
319
|
-
export { detectIsolationCapabilities, installNono, isolationDisabledByEnv, verifyWithHardTimeout };
|
|
339
|
+
export { detectIsolationCapabilities, installNono, isolationDisabledByEnv, probeNonoRuns, verifyWithHardTimeout };
|
|
@@ -37,6 +37,26 @@ function readDaemonState() {
|
|
|
37
37
|
function getSelfMachineId() {
|
|
38
38
|
return readDaemonState()?.machineId || null;
|
|
39
39
|
}
|
|
40
|
+
function rowDetail(result) {
|
|
41
|
+
const one = (t) => (t || "").trim().replace(/\s+/g, " ");
|
|
42
|
+
const err = one(result.stderr).slice(-200);
|
|
43
|
+
if (result.exitCode === 124 || /timed out and was killed/i.test(err)) {
|
|
44
|
+
return `TIMED OUT (may have partially completed) \u2014 ${err || "no stderr"}`;
|
|
45
|
+
}
|
|
46
|
+
const last = one((result.stdout || "").trim().split("\n").slice(-1)[0]);
|
|
47
|
+
return last || err || "";
|
|
48
|
+
}
|
|
49
|
+
const INSTALL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
50
|
+
const SAFE_SKILL_NAME = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
51
|
+
const SAFE_VERSION = /^[0-9][0-9a-zA-Z.+-]*$/;
|
|
52
|
+
function assertSafeFleetArg(value, kind, pattern) {
|
|
53
|
+
if (typeof value !== "string" || !pattern.test(value)) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`refusing to fan out: ${kind} ${JSON.stringify(value)} is not a plain ${kind} (expected ${pattern.source}). This string would be interpolated into a shell command run on every machine.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
40
60
|
function partitionSelf(machines, selfId) {
|
|
41
61
|
if (!selfId) return { targets: machines, skipped: [] };
|
|
42
62
|
return {
|
|
@@ -234,8 +254,9 @@ async function fleetUpgradeClaude(opts) {
|
|
|
234
254
|
`);
|
|
235
255
|
try {
|
|
236
256
|
printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
|
|
257
|
+
assertSafeFleetArg(version, "version", SAFE_VERSION);
|
|
237
258
|
const cmd = `claude install ${version} 2>&1 || claude update 2>&1`;
|
|
238
|
-
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0));
|
|
259
|
+
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0, INSTALL_TIMEOUT_MS));
|
|
239
260
|
let failures = 0;
|
|
240
261
|
for (const r of rows) {
|
|
241
262
|
if (!r.ok) {
|
|
@@ -244,7 +265,7 @@ async function fleetUpgradeClaude(opts) {
|
|
|
244
265
|
continue;
|
|
245
266
|
}
|
|
246
267
|
const ok = r.result.exitCode === 0;
|
|
247
|
-
const detail = (r.result
|
|
268
|
+
const detail = rowDetail(r.result);
|
|
248
269
|
printResultRow(r.machine, ok ? "OK" : "FAIL", detail);
|
|
249
270
|
if (!ok) failures += 1;
|
|
250
271
|
}
|
|
@@ -271,8 +292,9 @@ async function fleetUpgradeCodex(opts) {
|
|
|
271
292
|
`);
|
|
272
293
|
try {
|
|
273
294
|
printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
|
|
295
|
+
assertSafeFleetArg(version, "version", SAFE_VERSION);
|
|
274
296
|
const cmd = `bash -lc '${NPM_PREFIX_RESOLVER}; npm install -g $PREFIX_ARG @openai/codex@${version} 2>&1 | tail -5'`;
|
|
275
|
-
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0));
|
|
297
|
+
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0, INSTALL_TIMEOUT_MS));
|
|
276
298
|
let failures = 0;
|
|
277
299
|
for (const r of rows) {
|
|
278
300
|
if (!r.ok) {
|
|
@@ -281,7 +303,7 @@ async function fleetUpgradeCodex(opts) {
|
|
|
281
303
|
continue;
|
|
282
304
|
}
|
|
283
305
|
const ok = r.result.exitCode === 0;
|
|
284
|
-
const detail = (r.result
|
|
306
|
+
const detail = rowDetail(r.result);
|
|
285
307
|
printResultRow(r.machine, ok ? "OK" : "FAIL", detail);
|
|
286
308
|
if (!ok) failures += 1;
|
|
287
309
|
}
|
|
@@ -318,7 +340,7 @@ async function fleetUpgradeNono(opts) {
|
|
|
318
340
|
try {
|
|
319
341
|
printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
|
|
320
342
|
const cmd = `svamp daemon install-nono 2>&1 | tail -5`;
|
|
321
|
-
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0));
|
|
343
|
+
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0, INSTALL_TIMEOUT_MS));
|
|
322
344
|
let failures = 0;
|
|
323
345
|
for (const r of rows) {
|
|
324
346
|
if (!r.ok) {
|
|
@@ -327,7 +349,7 @@ async function fleetUpgradeNono(opts) {
|
|
|
327
349
|
continue;
|
|
328
350
|
}
|
|
329
351
|
const ok = r.result.exitCode === 0;
|
|
330
|
-
const detail = (r.result
|
|
352
|
+
const detail = rowDetail(r.result);
|
|
331
353
|
printResultRow(r.machine, ok ? "OK" : "FAIL", detail);
|
|
332
354
|
if (!ok) failures += 1;
|
|
333
355
|
}
|
|
@@ -347,6 +369,7 @@ async function fleetUpgradeKimi(opts) {
|
|
|
347
369
|
await server.disconnect();
|
|
348
370
|
return;
|
|
349
371
|
}
|
|
372
|
+
assertSafeFleetArg(version, "version", SAFE_VERSION);
|
|
350
373
|
console.log(`Reconciling Kimi to ${version} on ${machines.length} machine(s)...
|
|
351
374
|
`);
|
|
352
375
|
console.log(`Note: Kimi is a global npm package (${KIMI_NPM_PACKAGE}); machines that never use it can ignore this.`);
|
|
@@ -355,7 +378,7 @@ async function fleetUpgradeKimi(opts) {
|
|
|
355
378
|
try {
|
|
356
379
|
printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
|
|
357
380
|
const cmd = `bash -lc '${NPM_PREFIX_RESOLVER}; npm install -g $PREFIX_ARG ${KIMI_NPM_PACKAGE}@${version} 2>&1 | tail -5'`;
|
|
358
|
-
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0));
|
|
381
|
+
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0, INSTALL_TIMEOUT_MS));
|
|
359
382
|
let failures = 0;
|
|
360
383
|
for (const r of rows) {
|
|
361
384
|
if (!r.ok) {
|
|
@@ -364,7 +387,7 @@ async function fleetUpgradeKimi(opts) {
|
|
|
364
387
|
continue;
|
|
365
388
|
}
|
|
366
389
|
const ok = r.result.exitCode === 0;
|
|
367
|
-
const detail = (r.result
|
|
390
|
+
const detail = rowDetail(r.result);
|
|
368
391
|
printResultRow(r.machine, ok ? "OK" : "FAIL", detail);
|
|
369
392
|
if (!ok) failures += 1;
|
|
370
393
|
}
|
|
@@ -395,8 +418,9 @@ async function fleetUpgradeSvamp(opts) {
|
|
|
395
418
|
try {
|
|
396
419
|
printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
|
|
397
420
|
for (const m of skipped) printResultRow(m, "SKIP", 'self \u2014 restart separately with "svamp daemon restart"');
|
|
421
|
+
assertSafeFleetArg(version, "version", SAFE_VERSION);
|
|
398
422
|
const cmd = `bash -lc '${NPM_PREFIX_RESOLVER}; npm install -g $PREFIX_ARG svamp-cli@${version} 2>&1 | tail -5 && ${restartCmd} 2>&1 | tail -5'`;
|
|
399
|
-
const rows = await fanOut(targets, async (m) => await m.rpc.bash(cmd, void 0));
|
|
423
|
+
const rows = await fanOut(targets, async (m) => await m.rpc.bash(cmd, void 0, INSTALL_TIMEOUT_MS));
|
|
400
424
|
let failures = 0;
|
|
401
425
|
for (const r of rows) {
|
|
402
426
|
if (!r.ok) {
|
|
@@ -405,7 +429,7 @@ async function fleetUpgradeSvamp(opts) {
|
|
|
405
429
|
continue;
|
|
406
430
|
}
|
|
407
431
|
const ok = r.result.exitCode === 0;
|
|
408
|
-
const detail = (r.result
|
|
432
|
+
const detail = rowDetail(r.result);
|
|
409
433
|
printResultRow(r.machine, ok ? "OK" : "FAIL", detail);
|
|
410
434
|
if (!ok) failures += 1;
|
|
411
435
|
}
|
|
@@ -445,7 +469,7 @@ async function fleetDaemonRestart(opts) {
|
|
|
445
469
|
console.log(`Restarting daemons on ${machines.length} machine(s) \u2014 ${mode}...
|
|
446
470
|
`);
|
|
447
471
|
printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
|
|
448
|
-
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0));
|
|
472
|
+
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0, INSTALL_TIMEOUT_MS));
|
|
449
473
|
let failures = 0;
|
|
450
474
|
for (const r of rows) {
|
|
451
475
|
if (!r.ok) {
|
|
@@ -454,7 +478,7 @@ async function fleetDaemonRestart(opts) {
|
|
|
454
478
|
continue;
|
|
455
479
|
}
|
|
456
480
|
const ok = r.result.exitCode === 0;
|
|
457
|
-
const detail = (r.result
|
|
481
|
+
const detail = rowDetail(r.result);
|
|
458
482
|
printResultRow(r.machine, ok ? "OK" : "FAIL", detail);
|
|
459
483
|
if (!ok) failures += 1;
|
|
460
484
|
}
|
|
@@ -480,8 +504,9 @@ async function fleetPushSkill(name) {
|
|
|
480
504
|
`);
|
|
481
505
|
try {
|
|
482
506
|
printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
|
|
507
|
+
assertSafeFleetArg(name, "skill name", SAFE_SKILL_NAME);
|
|
483
508
|
const cmd = `svamp skills install ${name} --force 2>&1`;
|
|
484
|
-
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0));
|
|
509
|
+
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0, INSTALL_TIMEOUT_MS));
|
|
485
510
|
let failures = 0;
|
|
486
511
|
for (const r of rows) {
|
|
487
512
|
if (!r.ok) {
|
|
@@ -490,7 +515,7 @@ async function fleetPushSkill(name) {
|
|
|
490
515
|
continue;
|
|
491
516
|
}
|
|
492
517
|
const ok = r.result.exitCode === 0;
|
|
493
|
-
const detail = (r.result
|
|
518
|
+
const detail = rowDetail(r.result);
|
|
494
519
|
printResultRow(r.machine, ok ? "OK" : "FAIL", detail);
|
|
495
520
|
if (!ok) failures += 1;
|
|
496
521
|
}
|
|
@@ -502,4 +527,4 @@ ${rows.length - failures}/${rows.length} updated.`);
|
|
|
502
527
|
}
|
|
503
528
|
}
|
|
504
529
|
|
|
505
|
-
export { fleetDaemonRestart, fleetDaemonRestartCommand, fleetExec, fleetPushSkill, fleetStatus, fleetUpgradeClaude, fleetUpgradeCodex, fleetUpgradeKimi, fleetUpgradeNono, fleetUpgradeSvamp, parseVersionOutput, partitionSelf };
|
|
530
|
+
export { assertSafeFleetArg, fleetDaemonRestart, fleetDaemonRestartCommand, fleetExec, fleetPushSkill, fleetStatus, fleetUpgradeClaude, fleetUpgradeCodex, fleetUpgradeKimi, fleetUpgradeNono, fleetUpgradeSvamp, parseVersionOutput, partitionSelf, rowDetail };
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
const ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
4
|
+
const LOWER_ALPHABET = "abcdefghijklmnopqrstuvwxyz";
|
|
5
|
+
function shortId(length = 10, alphabet = ALPHABET) {
|
|
6
|
+
const maxUnbiased = Math.floor(256 / alphabet.length) * alphabet.length;
|
|
7
|
+
let out = "";
|
|
8
|
+
while (out.length < length) {
|
|
9
|
+
const buf = randomBytes(length - out.length);
|
|
10
|
+
for (let i = 0; i < buf.length && out.length < length; i++) {
|
|
11
|
+
const b = buf[i];
|
|
12
|
+
if (b < maxUnbiased) out += alphabet[b % alphabet.length];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ADJECTIVES = [
|
|
19
|
+
"able",
|
|
20
|
+
"amber",
|
|
21
|
+
"amused",
|
|
22
|
+
"ancient",
|
|
23
|
+
"arctic",
|
|
24
|
+
"autumn",
|
|
25
|
+
"azure",
|
|
26
|
+
"blithe",
|
|
27
|
+
"bold",
|
|
28
|
+
"brave",
|
|
29
|
+
"breezy",
|
|
30
|
+
"bright",
|
|
31
|
+
"brisk",
|
|
32
|
+
"calm",
|
|
33
|
+
"candid",
|
|
34
|
+
"cheery",
|
|
35
|
+
"chill",
|
|
36
|
+
"clever",
|
|
37
|
+
"cobalt",
|
|
38
|
+
"cosmic",
|
|
39
|
+
"cozy",
|
|
40
|
+
"crimson",
|
|
41
|
+
"crisp",
|
|
42
|
+
"curious",
|
|
43
|
+
"dapper",
|
|
44
|
+
"daring",
|
|
45
|
+
"dawn",
|
|
46
|
+
"deft",
|
|
47
|
+
"dewy",
|
|
48
|
+
"eager",
|
|
49
|
+
"early",
|
|
50
|
+
"easy",
|
|
51
|
+
"electric",
|
|
52
|
+
"fancy",
|
|
53
|
+
"feisty",
|
|
54
|
+
"fleet",
|
|
55
|
+
"fond",
|
|
56
|
+
"frosty",
|
|
57
|
+
"gallant",
|
|
58
|
+
"gentle",
|
|
59
|
+
"giddy",
|
|
60
|
+
"glad",
|
|
61
|
+
"gleaming",
|
|
62
|
+
"golden",
|
|
63
|
+
"graceful",
|
|
64
|
+
"grand",
|
|
65
|
+
"hardy",
|
|
66
|
+
"hazel",
|
|
67
|
+
"hearty",
|
|
68
|
+
"honest",
|
|
69
|
+
"humble",
|
|
70
|
+
"jolly",
|
|
71
|
+
"jovial",
|
|
72
|
+
"keen",
|
|
73
|
+
"kind",
|
|
74
|
+
"lively",
|
|
75
|
+
"loyal",
|
|
76
|
+
"lucky",
|
|
77
|
+
"lunar",
|
|
78
|
+
"mellow",
|
|
79
|
+
"merry",
|
|
80
|
+
"mighty",
|
|
81
|
+
"mint",
|
|
82
|
+
"misty",
|
|
83
|
+
"nimble",
|
|
84
|
+
"noble",
|
|
85
|
+
"opal",
|
|
86
|
+
"patient",
|
|
87
|
+
"peppy",
|
|
88
|
+
"placid",
|
|
89
|
+
"plucky",
|
|
90
|
+
"polar",
|
|
91
|
+
"prim",
|
|
92
|
+
"proud",
|
|
93
|
+
"quick",
|
|
94
|
+
"quiet",
|
|
95
|
+
"quirky",
|
|
96
|
+
"radiant",
|
|
97
|
+
"rapid",
|
|
98
|
+
"ready",
|
|
99
|
+
"regal",
|
|
100
|
+
"rosy",
|
|
101
|
+
"royal",
|
|
102
|
+
"rugged",
|
|
103
|
+
"sage",
|
|
104
|
+
"sandy",
|
|
105
|
+
"scarlet",
|
|
106
|
+
"serene",
|
|
107
|
+
"sharp",
|
|
108
|
+
"shiny",
|
|
109
|
+
"silent",
|
|
110
|
+
"silver",
|
|
111
|
+
"sleek",
|
|
112
|
+
"smooth",
|
|
113
|
+
"snappy",
|
|
114
|
+
"snowy",
|
|
115
|
+
"solar",
|
|
116
|
+
"spry",
|
|
117
|
+
"stellar",
|
|
118
|
+
"sturdy",
|
|
119
|
+
"sunny",
|
|
120
|
+
"swift",
|
|
121
|
+
"tender",
|
|
122
|
+
"tidal",
|
|
123
|
+
"tidy",
|
|
124
|
+
"tranquil",
|
|
125
|
+
"trusty",
|
|
126
|
+
"upbeat",
|
|
127
|
+
"valiant",
|
|
128
|
+
"vivid",
|
|
129
|
+
"warm",
|
|
130
|
+
"whimsical",
|
|
131
|
+
"wise",
|
|
132
|
+
"witty",
|
|
133
|
+
"zesty",
|
|
134
|
+
"zippy"
|
|
135
|
+
];
|
|
136
|
+
const ANIMALS = [
|
|
137
|
+
"ant",
|
|
138
|
+
"badger",
|
|
139
|
+
"bat",
|
|
140
|
+
"bear",
|
|
141
|
+
"beaver",
|
|
142
|
+
"bee",
|
|
143
|
+
"bison",
|
|
144
|
+
"boar",
|
|
145
|
+
"bobcat",
|
|
146
|
+
"buffalo",
|
|
147
|
+
"camel",
|
|
148
|
+
"caribou",
|
|
149
|
+
"cat",
|
|
150
|
+
"cheetah",
|
|
151
|
+
"cobra",
|
|
152
|
+
"condor",
|
|
153
|
+
"cougar",
|
|
154
|
+
"coyote",
|
|
155
|
+
"crab",
|
|
156
|
+
"crane",
|
|
157
|
+
"cricket",
|
|
158
|
+
"crow",
|
|
159
|
+
"deer",
|
|
160
|
+
"dingo",
|
|
161
|
+
"dolphin",
|
|
162
|
+
"donkey",
|
|
163
|
+
"dove",
|
|
164
|
+
"dragon",
|
|
165
|
+
"duck",
|
|
166
|
+
"eagle",
|
|
167
|
+
"eel",
|
|
168
|
+
"egret",
|
|
169
|
+
"elk",
|
|
170
|
+
"falcon",
|
|
171
|
+
"ferret",
|
|
172
|
+
"finch",
|
|
173
|
+
"fox",
|
|
174
|
+
"frog",
|
|
175
|
+
"gecko",
|
|
176
|
+
"gibbon",
|
|
177
|
+
"goat",
|
|
178
|
+
"goose",
|
|
179
|
+
"gopher",
|
|
180
|
+
"hare",
|
|
181
|
+
"hawk",
|
|
182
|
+
"hedgehog",
|
|
183
|
+
"heron",
|
|
184
|
+
"hippo",
|
|
185
|
+
"horse",
|
|
186
|
+
"ibex",
|
|
187
|
+
"ibis",
|
|
188
|
+
"iguana",
|
|
189
|
+
"jackal",
|
|
190
|
+
"jaguar",
|
|
191
|
+
"jay",
|
|
192
|
+
"kestrel",
|
|
193
|
+
"koala",
|
|
194
|
+
"krill",
|
|
195
|
+
"lark",
|
|
196
|
+
"lemur",
|
|
197
|
+
"leopard",
|
|
198
|
+
"lion",
|
|
199
|
+
"llama",
|
|
200
|
+
"lynx",
|
|
201
|
+
"macaw",
|
|
202
|
+
"magpie",
|
|
203
|
+
"mantis",
|
|
204
|
+
"marmot",
|
|
205
|
+
"marten",
|
|
206
|
+
"meerkat",
|
|
207
|
+
"mink",
|
|
208
|
+
"mole",
|
|
209
|
+
"moose",
|
|
210
|
+
"moth",
|
|
211
|
+
"mouse",
|
|
212
|
+
"newt",
|
|
213
|
+
"ocelot",
|
|
214
|
+
"octopus",
|
|
215
|
+
"orca",
|
|
216
|
+
"osprey",
|
|
217
|
+
"otter",
|
|
218
|
+
"owl",
|
|
219
|
+
"ox",
|
|
220
|
+
"panda",
|
|
221
|
+
"panther",
|
|
222
|
+
"parrot",
|
|
223
|
+
"pelican",
|
|
224
|
+
"penguin",
|
|
225
|
+
"pheasant",
|
|
226
|
+
"pigeon",
|
|
227
|
+
"puffin",
|
|
228
|
+
"puma",
|
|
229
|
+
"quail",
|
|
230
|
+
"rabbit",
|
|
231
|
+
"raccoon",
|
|
232
|
+
"ram",
|
|
233
|
+
"raven",
|
|
234
|
+
"robin",
|
|
235
|
+
"salmon",
|
|
236
|
+
"seal",
|
|
237
|
+
"shark",
|
|
238
|
+
"sheep",
|
|
239
|
+
"shrew",
|
|
240
|
+
"skunk",
|
|
241
|
+
"sloth",
|
|
242
|
+
"snail",
|
|
243
|
+
"sparrow",
|
|
244
|
+
"spider",
|
|
245
|
+
"squid",
|
|
246
|
+
"stag",
|
|
247
|
+
"stoat",
|
|
248
|
+
"stork",
|
|
249
|
+
"swan",
|
|
250
|
+
"tapir",
|
|
251
|
+
"tiger",
|
|
252
|
+
"toad",
|
|
253
|
+
"trout",
|
|
254
|
+
"turtle",
|
|
255
|
+
"viper",
|
|
256
|
+
"vole",
|
|
257
|
+
"walrus",
|
|
258
|
+
"weasel",
|
|
259
|
+
"whale",
|
|
260
|
+
"wolf",
|
|
261
|
+
"wombat",
|
|
262
|
+
"wren",
|
|
263
|
+
"yak",
|
|
264
|
+
"zebra"
|
|
265
|
+
];
|
|
266
|
+
function pick(arr) {
|
|
267
|
+
const n = arr.length;
|
|
268
|
+
const max = Math.floor(256 / n) * n;
|
|
269
|
+
let b;
|
|
270
|
+
do {
|
|
271
|
+
b = randomBytes(1)[0];
|
|
272
|
+
} while (b >= max);
|
|
273
|
+
return arr[b % n];
|
|
274
|
+
}
|
|
275
|
+
function generateFriendlyName(taken = []) {
|
|
276
|
+
const used = taken instanceof Set ? taken : new Set(taken);
|
|
277
|
+
for (let i = 0; i < 1e3; i++) {
|
|
278
|
+
const name = `${pick(ADJECTIVES)}-${pick(ANIMALS)}`;
|
|
279
|
+
if (!used.has(name)) return name;
|
|
280
|
+
}
|
|
281
|
+
const base = `${pick(ADJECTIVES)}-${pick(ANIMALS)}`;
|
|
282
|
+
let n = 2;
|
|
283
|
+
while (used.has(`${base}-${n}`)) n++;
|
|
284
|
+
return `${base}-${n}`;
|
|
285
|
+
}
|
|
286
|
+
function sanitizeSegment(s) {
|
|
287
|
+
return String(s || "").toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
288
|
+
}
|
|
289
|
+
function friendlyNameFromId(id) {
|
|
290
|
+
const m = /^(.+)-[a-zA-Z0-9]{6,10}$/.exec(String(id || ""));
|
|
291
|
+
return m ? m[1] : void 0;
|
|
292
|
+
}
|
|
293
|
+
function composeSessionId(friendlyName, takenIds = [], len = 6) {
|
|
294
|
+
const taken = takenIds instanceof Set ? takenIds : new Set(takenIds);
|
|
295
|
+
const A = LOWER_ALPHABET.length;
|
|
296
|
+
const nthSuffix = (i, width2) => {
|
|
297
|
+
let s = "";
|
|
298
|
+
for (let k = 0; k < width2; k++) {
|
|
299
|
+
s = LOWER_ALPHABET[i % A] + s;
|
|
300
|
+
i = Math.floor(i / A);
|
|
301
|
+
}
|
|
302
|
+
return s;
|
|
303
|
+
};
|
|
304
|
+
let width = Math.max(1, len);
|
|
305
|
+
for (; ; ) {
|
|
306
|
+
const space = Math.pow(A, width);
|
|
307
|
+
for (let i = 0; i < 64; i++) {
|
|
308
|
+
const candidate = `${friendlyName}-${shortId(width, LOWER_ALPHABET)}`;
|
|
309
|
+
if (!taken.has(candidate)) return candidate;
|
|
310
|
+
}
|
|
311
|
+
if (space <= 1e6) {
|
|
312
|
+
for (let i = 0; i < space; i++) {
|
|
313
|
+
const candidate = `${friendlyName}-${nthSuffix(i, width)}`;
|
|
314
|
+
if (!taken.has(candidate)) return candidate;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
width++;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function formatHandle(projectName, friendlyName) {
|
|
321
|
+
if (!friendlyName) return void 0;
|
|
322
|
+
const proj = sanitizeSegment(projectName || "");
|
|
323
|
+
return proj ? `${proj}:${friendlyName}` : friendlyName;
|
|
324
|
+
}
|
|
325
|
+
function parseHandle(input) {
|
|
326
|
+
const str = String(input || "").trim().replace(/^@/, "");
|
|
327
|
+
if (!str) return null;
|
|
328
|
+
const i = str.indexOf(":");
|
|
329
|
+
if (i === -1) return { name: str.toLowerCase() };
|
|
330
|
+
return { project: sanitizeSegment(str.slice(0, i)), name: str.slice(i + 1).toLowerCase() };
|
|
331
|
+
}
|
|
332
|
+
function handleMatchesMetadata(parsed, metadata) {
|
|
333
|
+
const fn = String(metadata?.friendlyName || "").toLowerCase();
|
|
334
|
+
if (!fn || fn !== parsed.name) return false;
|
|
335
|
+
if (parsed.project) {
|
|
336
|
+
return sanitizeSegment(metadata?.projectName || "") === parsed.project;
|
|
337
|
+
}
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export { friendlyNameFromId as a, composeSessionId as c, formatHandle as f, generateFriendlyName as g, handleMatchesMetadata as h, parseHandle as p, shortId as s };
|