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.
Files changed (33) hide show
  1. package/bin/skills/artifact/SKILL.md +19 -5
  2. package/bin/skills/crew/SKILL.md +42 -11
  3. package/dist/{acpBackend-BZ1MJhQV.mjs → acpBackend-UaBD4zL3.mjs} +3 -0
  4. package/dist/{agentCommands-B2Nl7Fof.mjs → agentCommands-C_KvvYFp.mjs} +5 -5
  5. package/dist/{claudeAuth-iAerdaMr.mjs → claudeAuth-DMDvPNhO.mjs} +55 -6
  6. package/dist/cli.mjs +164 -86
  7. package/dist/{commands-D69mnkYu.mjs → commands-B19lVZjm.mjs} +10 -3
  8. package/dist/{commands-BB1Kcyt-.mjs → commands-Bmoy6Rlw.mjs} +34 -4
  9. package/dist/{commands-CiRCwjkJ.mjs → commands-CFBKkfX8.mjs} +9 -9
  10. package/dist/{commands-D1pZq3d4.mjs → commands-CcdoQIkb.mjs} +126 -25
  11. package/dist/{commands-BOpoVBOM.mjs → commands-DH_YQrAP.mjs} +5 -21
  12. package/dist/{commands-H4PsTGn8.mjs → commands-DRxANaS5.mjs} +65 -19
  13. package/dist/{commands-C2aODeVc.mjs → commands-nkvTnUJj.mjs} +29 -13
  14. package/dist/{detectIsolation-CzoDCEnh.mjs → detectIsolation-CrV8IFDv.mjs} +36 -16
  15. package/dist/{fleet-DM_Q7w69.mjs → fleet-Ca-UjJNL.mjs} +40 -15
  16. package/dist/friendlyName-B-OEwt0t.mjs +341 -0
  17. package/dist/{headlessCli-C8HzQvis.mjs → headlessCli-BpTXz6FD.mjs} +24 -16
  18. package/dist/{hookSettings-DYMTUgVA.mjs → hookSettings-CvvUhwCK.mjs} +294 -55
  19. package/dist/{friendlyName-1UZcyDhu.mjs → inboxGuard-CDqmIypF.mjs} +32 -347
  20. package/dist/index.mjs +10 -9
  21. package/dist/{loopVerify-B47sSjxe.mjs → loopVerify-CxAKJB5a.mjs} +1 -1
  22. package/dist/{notifyCommands-D-IKIxiq.mjs → notifyCommands-CCUE18Q-.mjs} +1 -1
  23. package/dist/package-DTTWvJbp.mjs +64 -0
  24. package/dist/{rpc-Cf0grcru.mjs → rpc-CAlAHQAy.mjs} +8 -3
  25. package/dist/{rpc-BB6RF4-F.mjs → rpc-CacreLQ8.mjs} +13 -9
  26. package/dist/{run-ChdsgaSD.mjs → run-Bc81iurl.mjs} +27 -8
  27. package/dist/{run-DkjwF-zA.mjs → run-DKOND4dO.mjs} +222 -73
  28. package/dist/{scheduler-BY30ZrB9.mjs → scheduler-IyLWBF8T.mjs} +28 -10
  29. package/dist/{serveAuth-DbVLRCRR.mjs → serveAuth-4M0vwMyj.mjs} +53 -11
  30. package/dist/{serveCommands-CUHUiYnZ.mjs → serveCommands-B-Ym_QXc.mjs} +8 -8
  31. package/dist/{store-Bn0ByYev.mjs → store-D8dok6_d.mjs} +62 -14
  32. package/package.json +3 -3
  33. 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-D1pZq3d4.mjs';
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, f as addComment, h as addIssue, n as resolveCrewIssueOnMerge } from './store-Bn0ByYev.mjs';
7
- import { s as shortId } from './friendlyName-1UZcyDhu.mjs';
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-B47sSjxe.mjs';
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 './flushExit-C-i6SGjj.mjs';
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 projectRoot2 = canonicalProjectRoot(leadDir);
328
- base = opts.baseBranch || currentBranch(projectRoot2) || "main";
328
+ const worktreeRoot = canonicalProjectRoot(leadDir);
329
+ base = opts.baseBranch || currentBranch(worktreeRoot) || "main";
329
330
  try {
330
- wt = createWorktree(projectRoot2);
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(lead.directory || opts.directory || childDir);
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, chmod, access, mkdtemp } from 'node:fs/promises';
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 chmod(nonoPath, 493);
130
+ const stageDir = await mkdtemp(join(SVAMP_BIN_DIR, ".nono-stage-"));
129
131
  try {
130
- await access(nonoPath);
131
- console.log(`[isolation] nono ${version} installed at ${nonoPath}`);
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
- } catch {
134
- console.warn("[isolation] Download completed but nono binary not found");
135
- return false;
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.stdout?.trim().split("\n").slice(-1)[0] || r.result.stderr?.trim().slice(-200) || "").replace(/\s+/g, " ");
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.stdout?.trim().split("\n").slice(-1)[0] || r.result.stderr?.trim().slice(-200) || "").replace(/\s+/g, " ");
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.stdout?.trim().split("\n").slice(-1)[0] || r.result.stderr?.trim().slice(-200) || "").replace(/\s+/g, " ");
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.stdout?.trim().split("\n").slice(-1)[0] || r.result.stderr?.trim().slice(-200) || "").replace(/\s+/g, " ");
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.stdout?.trim().split("\n").slice(-1)[0] || r.result.stderr?.trim().slice(-200) || "").replace(/\s+/g, " ");
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.stdout?.trim().split("\n").slice(-1)[0] || r.result.stderr?.trim().slice(-200) || "").replace(/\s+/g, " ");
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.stdout?.trim().split("\n").slice(-1)[0] || r.result.stderr?.trim().slice(-200) || "").replace(/\s+/g, " ");
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 };