@outerlayer/cli 0.2.0 → 0.4.1

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 (141) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/README.md +229 -60
  3. package/dist/agent-setup-IFH3FB5X.js +197 -0
  4. package/dist/build-TZUUTRGH.js +9 -0
  5. package/dist/build-info.json +1 -1
  6. package/dist/check-7MFH6HNC.js +94 -0
  7. package/dist/chunk-3TFPDVTI.js +94 -0
  8. package/dist/{chunk-4TNZMO7V.js → chunk-4C4THABW.js} +5566 -754
  9. package/dist/{chunk-R5KBGVII.js → chunk-5QRP3MVS.js} +30 -14
  10. package/dist/chunk-5Y3QQRUH.js +300 -0
  11. package/dist/chunk-774HEQL6.js +178 -0
  12. package/dist/{work-pr-cmd-AZQPWH4H.js → chunk-7WERLFVR.js} +9 -26
  13. package/dist/chunk-ABIWDUMS.js +104 -0
  14. package/dist/chunk-AUFG23AA.js +534 -0
  15. package/dist/chunk-B2G7JHHB.js +42 -0
  16. package/dist/chunk-BCJNHMZT.js +34 -0
  17. package/dist/chunk-BCLJSQCV.js +56 -0
  18. package/dist/chunk-BFESLKPP.js +61 -0
  19. package/dist/chunk-BJ3KTMDH.js +63 -0
  20. package/dist/chunk-BKO6JEZI.js +47 -0
  21. package/dist/chunk-BOLTI6LR.js +37 -0
  22. package/dist/chunk-CBPPJSAR.js +89 -0
  23. package/dist/chunk-DM3VFDS3.js +1461 -0
  24. package/dist/{chunk-65WXAANR.js → chunk-DVDEBNQJ.js} +17 -2
  25. package/dist/{chunk-TFUIDMOB.js → chunk-EABW6AJQ.js} +12 -1
  26. package/dist/{chunk-KFYJV2ZG.js → chunk-EMY4I27X.js} +1 -1
  27. package/dist/{chunk-NTTPJV35.js → chunk-F47JBIAW.js} +3 -1
  28. package/dist/chunk-F6GFZXZ3.js +60 -0
  29. package/dist/{chunk-YOOSBOKS.js → chunk-F7CU5ABH.js} +1 -1
  30. package/dist/{emit-cmd-TWSYYEZI.js → chunk-H5NCNVDA.js} +68 -17
  31. package/dist/chunk-HZRNMGLF.js +2233 -0
  32. package/dist/chunk-LF6MLJCY.js +38 -0
  33. package/dist/{chunk-U32VSRLO.js → chunk-LT5TZHYH.js} +95 -634
  34. package/dist/chunk-LYCDNMOH.js +83 -0
  35. package/dist/chunk-M5POMKKW.js +1051 -0
  36. package/dist/{mcp-install-cmd-FDQH6SEN.js → chunk-MQ3IPHIZ.js} +35 -18
  37. package/dist/{chunk-D77LS3UI.js → chunk-N5FOE5PS.js} +33 -4
  38. package/dist/chunk-N6LURUEF.js +64 -0
  39. package/dist/chunk-OGFGZQA3.js +23 -0
  40. package/dist/chunk-QDQEUVUF.js +9 -0
  41. package/dist/chunk-QOZKPLVJ.js +226 -0
  42. package/dist/chunk-QY22NZUW.js +1764 -0
  43. package/dist/chunk-RA3O54FC.js +30 -0
  44. package/dist/chunk-RFUPN6KX.js +71 -0
  45. package/dist/chunk-SY4GK4SP.js +55 -0
  46. package/dist/chunk-TBT347UY.js +41 -0
  47. package/dist/chunk-TWNWNS2Q.js +1084 -0
  48. package/dist/{chunk-NXLLURA4.js → chunk-UFFXNXLQ.js} +115 -71
  49. package/dist/chunk-USO2DKBD.js +421 -0
  50. package/dist/chunk-W4SQCOZU.js +206 -0
  51. package/dist/{chunk-Q6CL5THG.js → chunk-W5FVUZXV.js} +208 -61
  52. package/dist/chunk-WFJ65NUD.js +383 -0
  53. package/dist/{chunk-A3WLZX2F.js → chunk-WIOZAJ2W.js} +15 -2
  54. package/dist/chunk-WO2BXCTQ.js +83 -0
  55. package/dist/{chunk-I3ETLSNE.js → chunk-XCCLFVXM.js} +120 -11
  56. package/dist/{context-materialize-6WKT3RBQ.js → chunk-XDDW4FRS.js} +134 -26
  57. package/dist/chunk-Y7KJLXYN.js +232 -0
  58. package/dist/chunk-YFHIIH4B.js +102 -0
  59. package/dist/chunk-YYYXRJUW.js +101 -0
  60. package/dist/chunk-ZM2IMMYN.js +76 -0
  61. package/dist/chunk-ZMYPLFG3.js +71 -0
  62. package/dist/chunk-ZNA27WEV.js +470 -0
  63. package/dist/{cli-G7DILYJY.js → cli-W62AFRSW.js} +616 -878
  64. package/dist/{paths-D2VGWWFI.js → cli-build-K56DK4DS.js} +1 -1
  65. package/dist/config-XVJYZ4IQ.js +9 -0
  66. package/dist/connect-cmd-ZTJONP37.js +16 -0
  67. package/dist/context-adopt-UMA4O5NY.js +105 -0
  68. package/dist/context-materialize-G2FUFC72.js +19 -0
  69. package/dist/dist-44CQVWGT.js +6 -0
  70. package/dist/docker-NEGDLU6D.js +7 -0
  71. package/dist/doctor-53F2PYY7.js +43 -0
  72. package/dist/doctor-ZLGZYMDK.js +426 -0
  73. package/dist/{emit-artifact-cmd-UQVT3OKW.js → emit-artifact-cmd-AKZP7TMF.js} +87 -23
  74. package/dist/emit-cmd-DCERAU27.js +9 -0
  75. package/dist/emit-criteria-cmd-7MXQVCKD.js +166 -0
  76. package/dist/{emit-finding-cmd-FDOU2OPD.js → emit-finding-cmd-N2FTGCVC.js} +34 -31
  77. package/dist/{emit-result-cmd-H2T4C2K3.js → emit-result-cmd-4CPDBMN2.js} +34 -62
  78. package/dist/exec-client-4XGXV3XN.js +8 -0
  79. package/dist/guest-init-GENRHNP7.js +8 -0
  80. package/dist/hook-fast-EYENPK6F.js +9 -0
  81. package/dist/{hook-wrap-fast-KXYNX3AD.js → hook-wrap-fast-NPLHJXFK.js} +2 -2
  82. package/dist/host-key-KDYZ5ECC.js +7 -0
  83. package/dist/import-capture-cmd-AVZ4VIVJ.js +68 -0
  84. package/dist/{import-ruler-cmd-7LH2QOMG.js → import-ruler-cmd-GUHL6IY5.js} +1 -1
  85. package/dist/index.js +4 -4
  86. package/dist/init-55Y4LHWF.js +36 -0
  87. package/dist/init-XXEKWZQP.js +221 -0
  88. package/dist/init-cmd-WLST2F7G.js +135 -0
  89. package/dist/install-cmd-7WXOIFDO.js +55 -0
  90. package/dist/lima-XN65D7GN.js +55 -0
  91. package/dist/login-browser-7XGSV7MA.js +10 -0
  92. package/dist/{config-POF7DEQW.js → logout-cmd-QAUFDJ6J.js} +3 -1
  93. package/dist/{logs-TRNPQM42.js → logs-7RYUH5A6.js} +1 -1
  94. package/dist/loop-JFIBP4B7.js +43 -0
  95. package/dist/machine-ZBPT2J3R.js +35 -0
  96. package/dist/mcp-install-cmd-RLK4SO3N.js +9 -0
  97. package/dist/{mcp-serve-cmd-57EZZOTL.js → mcp-serve-cmd-N6UD2KBX.js} +20 -9
  98. package/dist/paths-OYKMVYJP.js +6 -0
  99. package/dist/{pidfile-PTW76F56.js → pidfile-UZRH774M.js} +2 -3
  100. package/dist/real-deps-SU24ZA2K.js +21 -0
  101. package/dist/relay-L76HDX72.js +46 -0
  102. package/dist/settings-J2652U5N.js +7 -0
  103. package/dist/starter-pack-LIZYMKYQ.js +10 -0
  104. package/dist/{status-I27IST4L.js → status-UJXWZRN5.js} +30 -13
  105. package/dist/{statusline-fast-3C5OXHDD.js → statusline-fast-SVTMBMD7.js} +3 -2
  106. package/dist/sync-cmd-4G4A7EVN.js +28 -0
  107. package/dist/version-BWM6VLDI.js +6 -0
  108. package/dist/{watch-3DTPJETH.js → watch-7SKJKGAA.js} +19 -8
  109. package/dist/{work-claim-cmd-5AUNCLS2.js → work-claim-cmd-5F7VZ42N.js} +34 -21
  110. package/dist/work-cmd-QAUZ7MTD.js +17 -0
  111. package/dist/work-comment-cmd-BNCSBTLH.js +106 -0
  112. package/dist/{work-launch-LT663PB3.js → work-launch-YI4CJDAP.js} +1 -1
  113. package/dist/work-open-pr-cmd-VFYZHIIW.js +156 -0
  114. package/dist/work-pr-cmd-PV32ZLSI.js +16 -0
  115. package/package.json +13 -3
  116. package/skill-pack/maintained/amend/SKILL.md +104 -0
  117. package/skill-pack/maintained/emitting-evidence/SKILL.md +108 -0
  118. package/skill-pack/maintained/emitting-evidence/references/agents-snippet.md +20 -0
  119. package/skill-pack/maintained/outerlayer/SKILL.md +55 -0
  120. package/skill-pack/maintained/reporting-findings/SKILL.md +148 -0
  121. package/skill-pack/template/build/SKILL.md +193 -0
  122. package/skill-pack/template/build/references/agent-briefs.md +243 -0
  123. package/skill-pack/template/build/references/criteria-judge.md +91 -0
  124. package/skill-pack/template/build/references/evidence.md +42 -0
  125. package/skill-pack/template/build/references/release.md +74 -0
  126. package/skill-pack/template/build/references/review-briefs.md +275 -0
  127. package/skill-pack/template/build/references/review-loop.md +158 -0
  128. package/skill-pack/template/build/scripts/record-criteria.mjs +235 -0
  129. package/skill-pack/template/spec/SKILL.md +84 -0
  130. package/skill-pack/template/writing-specs/SKILL.md +134 -0
  131. package/dist/chunk-DCNOXRMV.js +0 -589
  132. package/dist/chunk-JJP7YLMN.js +0 -25
  133. package/dist/chunk-OZ7C3XUE.js +0 -34
  134. package/dist/chunk-WQ6VGRGZ.js +0 -150
  135. package/dist/hook-fast-J5LCDHUJ.js +0 -8
  136. package/dist/import-capture-cmd-EUMBGIV3.js +0 -176
  137. package/dist/init-PTBITAUO.js +0 -103
  138. package/dist/login-cmd-IRX6LZT7.js +0 -62
  139. package/dist/loop-ASZRX3CZ.js +0 -648
  140. package/dist/sync-cmd-BBAUZ5JD.js +0 -17
  141. package/dist/work-cmd-2P4BVX47.js +0 -18
@@ -5,7 +5,7 @@ import { existsSync, readFileSync, mkdirSync, copyFileSync, writeFileSync } from
5
5
  import { join, dirname, isAbsolute } from 'path';
6
6
 
7
7
  createRequire(import.meta.url);
8
- var REGISTERED_EVENTS = ["SessionStart", "SessionEnd", "Stop"];
8
+ var REGISTERED_EVENTS = ["SessionStart", "SessionEnd", "Stop", "PostToolUse"];
9
9
  var MARKER = "_outerlayer";
10
10
  function settingsPath(scope, opts = {}) {
11
11
  const home = opts.home ?? homeFromEnv();
@@ -43,6 +43,12 @@ function tokenizeCommand(command) {
43
43
  function quoteBin(bin) {
44
44
  return /\s/.test(bin) ? `"${bin}"` : bin;
45
45
  }
46
+ function isScriptBin(bin) {
47
+ return /\.[cm]?js$/i.test(bin);
48
+ }
49
+ function cliCommand(cliBin) {
50
+ return isScriptBin(cliBin) ? `node ${quoteBin(cliBin)}` : quoteBin(cliBin);
51
+ }
46
52
  function isOurBin(bin, cliBin) {
47
53
  if (cliBin !== void 0 && normalizeBinPath(bin) === normalizeBinPath(cliBin)) return true;
48
54
  const parts = bin.replace(/\\/g, "/").split("/");
@@ -63,30 +69,40 @@ function splitCliInvocation(command, cliBin) {
63
69
  if (typeof command !== "string") return null;
64
70
  const tokens = tokenizeCommand(command.trim());
65
71
  if (!tokens) return null;
66
- let i = 0;
67
72
  const launcher = (tokens[0] ?? "").replace(/\\/g, "/").split("/").pop() ?? "";
68
- if (launcher === "node" || launcher === "node.exe" || launcher === "npx") i = 1;
73
+ const viaNode = launcher === "node" || launcher === "node.exe";
74
+ const viaNpx = launcher === "npx";
75
+ const i = viaNode || viaNpx ? 1 : 0;
69
76
  const bin = tokens[i];
70
77
  if (!bin || !isOurBin(bin, cliBin)) return null;
71
- return { bin, args: tokens.slice(i + 1), viaNpx: launcher === "npx" };
78
+ return { bin, args: tokens.slice(i + 1), viaNpx, viaNode };
72
79
  }
73
80
  function parseCliInvocation(command, cliBin) {
74
81
  return splitCliInvocation(command, cliBin)?.args ?? null;
75
82
  }
76
83
  function ourCommandBins(settings) {
77
- const bins = [];
84
+ return unique(ourCommandLaunches(settings).map((l) => l.bin));
85
+ }
86
+ function ourDirectScriptBins(settings) {
87
+ return unique(ourCommandLaunches(settings).filter((l) => !l.viaNode && isScriptBin(l.bin)).map((l) => l.bin));
88
+ }
89
+ function ourCommandLaunches(settings) {
90
+ const launches = [];
78
91
  const add = (command) => {
79
92
  const parsed = splitCliInvocation(command);
80
93
  if (!parsed || parsed.viaNpx) return;
81
94
  if (!["hook", "hook-wrap", "statusline"].includes(parsed.args[0] ?? "")) return;
82
95
  if (parsed.bin.includes("$") || !isAbsolute(parsed.bin)) return;
83
- if (!bins.includes(parsed.bin)) bins.push(parsed.bin);
96
+ launches.push({ bin: parsed.bin, viaNode: parsed.viaNode });
84
97
  };
85
98
  for (const matchers of Object.values(settings?.hooks ?? {})) {
86
99
  for (const matcher of matchers ?? []) for (const h of matcher.hooks ?? []) add(h.command);
87
100
  }
88
101
  add(settings?.statusLine?.command);
89
- return bins;
102
+ return launches;
103
+ }
104
+ function unique(values) {
105
+ return [...new Set(values)];
90
106
  }
91
107
  function parseLifecycleHookCommand(command, cliBin) {
92
108
  const args = parseCliInvocation(command, cliBin);
@@ -142,7 +158,7 @@ var SettingsParseError = class extends Error {
142
158
  };
143
159
  function ourEntry(cliBin, event) {
144
160
  return {
145
- hooks: [{ type: "command", command: `${quoteBin(cliBin)} hook ${event}`, [MARKER]: true }]
161
+ hooks: [{ type: "command", command: `${cliCommand(cliBin)} hook ${event}`, [MARKER]: true }]
146
162
  };
147
163
  }
148
164
  function pruneOurHooks(blocks, cliBin) {
@@ -229,7 +245,7 @@ function wrapHookCommand(cliBin, h, opts = {}) {
229
245
  if (isWrapped(h, cliBin)) return h;
230
246
  const id = encodeWrappedCommand(h.command);
231
247
  const flag = opts.captureOutput ? " --capture-output" : "";
232
- return { type: "command", command: `${quoteBin(cliBin)} hook-wrap --id ${id}${flag}`, [WRAP_MARKER]: h.command };
248
+ return { type: "command", command: `${cliCommand(cliBin)} hook-wrap --id ${id}${flag}`, [WRAP_MARKER]: h.command };
233
249
  }
234
250
  function unwrapHookCommand(h, cliBin) {
235
251
  const original = wrappedOriginalCommand(h, cliBin);
@@ -280,11 +296,11 @@ function repointWrappedHooks(settings, cliBin) {
280
296
  const h = matcher.hooks[hi];
281
297
  const parsed = splitCliInvocation(h.command, cliBin);
282
298
  if (!parsed || parsed.args[0] !== "hook-wrap") continue;
283
- if (normalizeBinPath(parsed.bin) === normalizeBinPath(cliBin)) continue;
299
+ if (normalizeBinPath(parsed.bin) === normalizeBinPath(cliBin) && parsed.viaNode === isScriptBin(cliBin)) continue;
284
300
  const original = wrappedOriginalCommand(h, cliBin);
285
301
  if (original === null) continue;
286
302
  const flag = parsed.args.includes("--capture-output") ? " --capture-output" : "";
287
- const desired = `${quoteBin(cliBin)} hook-wrap --id ${encodeWrappedCommand(original)}${flag}`;
303
+ const desired = `${cliCommand(cliBin)} hook-wrap --id ${encodeWrappedCommand(original)}${flag}`;
288
304
  matcher.hooks[hi] = { ...h, command: desired, [WRAP_MARKER]: original };
289
305
  changed = true;
290
306
  }
@@ -293,10 +309,10 @@ function repointWrappedHooks(settings, cliBin) {
293
309
  return { next, changed };
294
310
  }
295
311
  function statuslineCommand(cliBin) {
296
- return `${quoteBin(cliBin)} statusline`;
312
+ return `${cliCommand(cliBin)} statusline`;
297
313
  }
298
314
  function statuslineWrapCommand(cliBin, original) {
299
- return `${quoteBin(cliBin)} statusline --wrap-id ${encodeWrappedCommand(original)}`;
315
+ return `${cliCommand(cliBin)} statusline --wrap-id ${encodeWrappedCommand(original)}`;
300
316
  }
301
317
  function isOurStatuslineSlot(slot, cliBin) {
302
318
  if (slot[MARKER] === true) return true;
@@ -377,4 +393,4 @@ function unwrapHooks(settings, cliBin) {
377
393
  return { next, changed, unwrapped };
378
394
  }
379
395
 
380
- export { AUTO_WRAP_EVENTS, REGISTERED_EVENTS, SettingsParseError, cliBinResolvable, decodeWrappedCommand, isOurHookCommand, isOurStatuslineSlot, listHookCandidates, mergeHooks, mergeStatusline, ourCommandBins, readSettings, removeHooks, removeStatusline, repointWrappedHooks, settingsPath, statuslineWrappedOriginal, unwrapHooks, wrapHooks, wrappedOriginalCommand, writeSettings };
396
+ export { AUTO_WRAP_EVENTS, REGISTERED_EVENTS, SettingsParseError, cliBinResolvable, cliCommand, decodeWrappedCommand, encodeWrappedCommand, isOurHookCommand, isOurHookCommandFor, isOurStatuslineSlot, isWrapped, listHookCandidates, mergeHooks, mergeStatusline, ourCommandBins, ourDirectScriptBins, parseLifecycleHookCommand, readSettings, removeHooks, removeStatusline, repointWrappedHooks, settingsPath, statuslineCommand, statuslineWrappedOriginal, unwrapHooks, wrapHooks, wrappedOriginalCommand, writeSettings };
@@ -0,0 +1,300 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { parseSize, MAX_ALLOW_HOSTS, allowHostProblem } from './chunk-F6GFZXZ3.js';
4
+ import { isRefusedInConfig, resolveDestinations, isReservedVariable } from './chunk-AUFG23AA.js';
5
+ import { homeFromEnv } from './chunk-VNVZDWO3.js';
6
+ import { existsSync, statSync, accessSync, constants, readFileSync } from 'fs';
7
+ import { hostname } from 'os';
8
+ import { join } from 'path';
9
+
10
+ createRequire(import.meta.url);
11
+ var BUILTIN_CONTAINER = "builtin:container";
12
+ var BUILTIN_PROCESS = "builtin:process";
13
+ var BUILTIN_VM = "builtin:vm";
14
+ function isBuiltinContainer(hook) {
15
+ return hook === BUILTIN_CONTAINER;
16
+ }
17
+ function isBuiltinProcess(hook) {
18
+ return hook === BUILTIN_PROCESS;
19
+ }
20
+ function isBuiltinVm(hook) {
21
+ return hook === BUILTIN_VM;
22
+ }
23
+ function builtinKind(hook) {
24
+ return isBuiltinContainer(hook) ? "container" : isBuiltinProcess(hook) ? "process" : isBuiltinVm(hook) ? "vm" : "own";
25
+ }
26
+ var RUNNER_QUEUES = ["implement", "amend"];
27
+ var RunnerConfigError = class extends Error {
28
+ };
29
+ var DEFAULT_CONCURRENCY = 1;
30
+ var DEFAULT_POLL_SECONDS = 60;
31
+ var DEFAULT_TIME_LIMIT_MINUTES = 480;
32
+ var DEFAULT_IDLE_LIMIT_MINUTES = 30;
33
+ var DEFAULT_BUILD = { memory: "8g", cpus: 4, pids: 4096, disk: "40g", variables: [] };
34
+ var BUILD_KEYS = ["memory", "cpus", "pids", "disk", "variables", "destinations", "allowHosts"];
35
+ var VARIABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,254}$/;
36
+ var MAX_VARIABLES = 100;
37
+ function defaultRunnerConfigPath(home = homeFromEnv()) {
38
+ return join(home, ".outerlayer", "config.json");
39
+ }
40
+ function fail(configPath, key, reason) {
41
+ throw new RunnerConfigError(`${configPath}: ${key} ${reason}`);
42
+ }
43
+ function readConfigFile(configPath) {
44
+ if (!existsSync(configPath)) return {};
45
+ const text = readFileSync(configPath, "utf8");
46
+ try {
47
+ return JSON.parse(text);
48
+ } catch (err) {
49
+ throw new RunnerConfigError(
50
+ `${configPath}: is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
51
+ );
52
+ }
53
+ }
54
+ function hasRunnerBlock(configPath) {
55
+ try {
56
+ const runner = readConfigFile(configPath).runner;
57
+ return typeof runner === "object" && runner !== null;
58
+ } catch {
59
+ return false;
60
+ }
61
+ }
62
+ function runnerVmOf(configPath) {
63
+ try {
64
+ const vm = readConfigFile(configPath).runnerVm;
65
+ return typeof vm?.instance === "string" && typeof vm.since === "string" ? { instance: vm.instance, since: vm.since } : void 0;
66
+ } catch {
67
+ return void 0;
68
+ }
69
+ }
70
+ var RUNNER_KEYS = [
71
+ "host",
72
+ "queues",
73
+ "concurrency",
74
+ "pollSeconds",
75
+ "timeLimitMinutes",
76
+ "idleLimitMinutes",
77
+ "commands",
78
+ "hooks",
79
+ "build",
80
+ "autoUpdate"
81
+ ];
82
+ function runnerNumbers() {
83
+ return [
84
+ { key: "concurrency", min: 1 },
85
+ // Below five seconds a runner is asking the gateway a question it has
86
+ // just been answered. Above one day the gateway refuses every heartbeat
87
+ // this runner sends (`POST /v1/hosts/heartbeat` caps `pollSeconds` at
88
+ // 86400), so the host would poll and never appear in `GET /v1/hosts`.
89
+ { key: "pollSeconds", min: 5, max: 86400 },
90
+ // A build's item key expires 24 hours after its lease was created, and
91
+ // the time limit is counted from job start, after the claim and the
92
+ // provision hook. The runner still syncs and releases once the limit
93
+ // fires, so the ceiling leaves two hours of the key's life for the
94
+ // provision hook, the final sync and the release.
95
+ { key: "timeLimitMinutes", min: 1, max: 1320 },
96
+ { key: "idleLimitMinutes", min: 1 }
97
+ ];
98
+ }
99
+ function nearestKey(unknown, accepted) {
100
+ const lower = unknown.toLowerCase();
101
+ let best;
102
+ for (const key of accepted) {
103
+ const distance = editDistance(lower, key.toLowerCase());
104
+ if (distance <= Math.max(1, Math.floor(key.length / 4)) && (!best || distance < best.distance)) {
105
+ best = { key, distance };
106
+ }
107
+ }
108
+ return best?.key;
109
+ }
110
+ function editDistance(a, b) {
111
+ const rows = Array.from({ length: a.length + 1 }, (_unused, i) => [i, ...Array(b.length).fill(0)]);
112
+ for (let j = 0; j <= b.length; j++) rows[0][j] = j;
113
+ for (let i = 1; i <= a.length; i++) {
114
+ for (let j = 1; j <= b.length; j++) {
115
+ rows[i][j] = Math.min(
116
+ rows[i - 1][j] + 1,
117
+ rows[i][j - 1] + 1,
118
+ rows[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
119
+ );
120
+ }
121
+ }
122
+ return rows[a.length][b.length];
123
+ }
124
+ function checkHooks(config, env = process.env) {
125
+ const named = [
126
+ ["runner.hooks.provision", config.hooks.provision],
127
+ ["runner.hooks.cleanup", config.hooks.cleanup],
128
+ ["runner.hooks.report", config.hooks.report]
129
+ ];
130
+ for (const [key, path] of named) {
131
+ if (path === void 0) continue;
132
+ if (isBuiltinContainer(path) || isBuiltinProcess(path) || isBuiltinVm(path)) continue;
133
+ if (path.includes("$") || path.startsWith("~")) {
134
+ return `${key}: ${path} \u2014 hooks are executable paths, not shell lines; $HOME and ~ are not expanded`;
135
+ }
136
+ if (!existsSync(path)) return `${key}: ${path} does not exist`;
137
+ if (!statSync(path).isFile()) return `${key}: ${path} is not a file`;
138
+ try {
139
+ accessSync(path, constants.X_OK);
140
+ } catch {
141
+ return `${key}: ${path} is not executable`;
142
+ }
143
+ }
144
+ return checkDestinationVariables(config, env);
145
+ }
146
+ function checkDestinationVariables(config, env) {
147
+ const destinations = config.build?.destinations ?? [];
148
+ for (const [index, destination] of destinations.entries()) {
149
+ for (const name of destination.header.variables) {
150
+ if (env[name] === void 0 || env[name] === "") {
151
+ return `runner.build.destinations[${index}].header: ${name} is not set in the runner's environment`;
152
+ }
153
+ }
154
+ }
155
+ return void 0;
156
+ }
157
+ function loadRunnerConfig(configPath) {
158
+ const config = readConfigFile(configPath);
159
+ if (!config.url || !config.apiKey || !config.appId) {
160
+ fail(configPath, "url/apiKey/appId", "missing \u2014 pipe a factory key into `outerlayer login` for this config file first; a runner uses a factory key, never a person's login");
161
+ }
162
+ const runner = config.runner;
163
+ if (!runner) {
164
+ fail(configPath, "runner", "block is missing");
165
+ }
166
+ for (const key of Object.keys(runner)) {
167
+ if (RUNNER_KEYS.includes(key)) continue;
168
+ const near = nearestKey(key, RUNNER_KEYS);
169
+ fail(configPath, `runner.${key}`, near ? `is not a key this block accepts; did you mean ${near}?` : "is not a key this block accepts");
170
+ }
171
+ for (const { key, min, max = Number.POSITIVE_INFINITY } of runnerNumbers()) {
172
+ const value = runner[key];
173
+ if (value === void 0) continue;
174
+ if (typeof value !== "number" || !Number.isInteger(value) || value < min) {
175
+ fail(configPath, `runner.${key}`, `must be a whole number of at least ${min}`);
176
+ }
177
+ if (value > max) {
178
+ fail(configPath, `runner.${key}`, `must be at most ${max}`);
179
+ }
180
+ }
181
+ if (runner.autoUpdate !== void 0 && typeof runner.autoUpdate !== "boolean") {
182
+ fail(configPath, "runner.autoUpdate", "must be true or false");
183
+ }
184
+ const queues = runner.queues && runner.queues.length > 0 ? runner.queues : [...RUNNER_QUEUES];
185
+ for (const queue of queues) {
186
+ if (!RUNNER_QUEUES.includes(queue)) {
187
+ fail(configPath, "runner.queues", `names an unknown queue "${queue}" \u2014 must be implement or amend`);
188
+ }
189
+ if (!runner.commands?.[queue]) {
190
+ fail(configPath, `runner.commands.${queue}`, "is required for an enabled queue");
191
+ }
192
+ }
193
+ if (!runner.hooks?.provision) fail(configPath, "runner.hooks.provision", "is required");
194
+ if (!runner.hooks?.cleanup) fail(configPath, "runner.hooks.cleanup", "is required");
195
+ checkBuiltinHooks(configPath, runner.hooks);
196
+ const build = resolveBuild(configPath, runner.build);
197
+ if (config.scrub?.literals !== void 0 && !Array.isArray(config.scrub.literals)) {
198
+ fail(configPath, "scrub.literals", "must be a list of strings");
199
+ }
200
+ if (config.scrub?.patterns !== void 0 && !Array.isArray(config.scrub.patterns)) {
201
+ fail(configPath, "scrub.patterns", "must be a list of {label, pattern} entries");
202
+ }
203
+ return {
204
+ configPath,
205
+ url: config.url,
206
+ apiKey: config.apiKey,
207
+ appId: config.appId,
208
+ repos: config.repos,
209
+ scrub: config.scrub,
210
+ host: runner.host ?? hostname(),
211
+ queues,
212
+ concurrency: runner.concurrency ?? DEFAULT_CONCURRENCY,
213
+ pollSeconds: runner.pollSeconds ?? DEFAULT_POLL_SECONDS,
214
+ timeLimitMinutes: runner.timeLimitMinutes ?? DEFAULT_TIME_LIMIT_MINUTES,
215
+ idleLimitMinutes: runner.idleLimitMinutes ?? DEFAULT_IDLE_LIMIT_MINUTES,
216
+ commands: runner.commands ?? {},
217
+ hooks: {
218
+ provision: runner.hooks.provision,
219
+ cleanup: runner.hooks.cleanup,
220
+ report: runner.hooks.report
221
+ },
222
+ build,
223
+ // A host with executables of its own for provision and cleanup chose
224
+ // its own environment, and an update could change what its hooks run
225
+ // under; it opts in. The built-in hooks are the runner's own. Provision
226
+ // and cleanup are built-in together or not at all (checkBuiltinHooks), so
227
+ // provision alone tells which.
228
+ autoUpdate: runner.autoUpdate ?? builtinKind(runner.hooks.provision) !== "own"
229
+ };
230
+ }
231
+ function checkBuiltinHooks(configPath, hooks) {
232
+ for (const key of ["provision", "cleanup", "report"]) {
233
+ const value = hooks[key];
234
+ if (value === void 0 || !value.startsWith("builtin:")) continue;
235
+ if (key === "report") fail(configPath, "runner.hooks.report", `cannot be ${value}: the report hook stays an executable`);
236
+ if (!isBuiltinContainer(value) && !isBuiltinProcess(value) && !isBuiltinVm(value)) {
237
+ fail(configPath, `runner.hooks.${key}`, `names an unknown built-in hook "${value}"; the ones that exist are ${BUILTIN_CONTAINER}, ${BUILTIN_PROCESS} and ${BUILTIN_VM}`);
238
+ }
239
+ }
240
+ const provision = hooks.provision;
241
+ const cleanup = hooks.cleanup;
242
+ if (isBuiltinContainer(provision) !== isBuiltinContainer(cleanup) || isBuiltinProcess(provision) !== isBuiltinProcess(cleanup) || isBuiltinVm(provision) !== isBuiltinVm(cleanup)) {
243
+ fail(configPath, "runner.hooks", `provision and cleanup must both be ${BUILTIN_CONTAINER}, both be ${BUILTIN_PROCESS}, both be ${BUILTIN_VM}, or neither`);
244
+ }
245
+ }
246
+ function resolveBuild(configPath, block) {
247
+ if (block === void 0) return { ...DEFAULT_BUILD, variables: [] };
248
+ if (typeof block !== "object" || block === null || Array.isArray(block)) fail(configPath, "runner.build", "must be an object");
249
+ const given = block;
250
+ for (const key of Object.keys(given)) {
251
+ if (BUILD_KEYS.includes(key)) continue;
252
+ const near = nearestKey(key, BUILD_KEYS);
253
+ fail(configPath, `runner.build.${key}`, near ? `is not a key this block accepts; did you mean ${near}?` : "is not a key this block accepts");
254
+ }
255
+ for (const key of ["memory", "disk"]) {
256
+ const value = given[key];
257
+ if (value === void 0) continue;
258
+ if (typeof value !== "string" || parseSize(value) === void 0) fail(configPath, `runner.build.${key}`, 'must be a size such as "8g" or "512m"');
259
+ }
260
+ if (given.cpus !== void 0 && (typeof given.cpus !== "number" || !Number.isFinite(given.cpus) || given.cpus <= 0)) {
261
+ fail(configPath, "runner.build.cpus", "must be a number above 0");
262
+ }
263
+ if (given.pids !== void 0 && (typeof given.pids !== "number" || !Number.isInteger(given.pids) || given.pids < 1)) {
264
+ fail(configPath, "runner.build.pids", "must be a whole number of at least 1");
265
+ }
266
+ const variables = [];
267
+ if (given.variables !== void 0) {
268
+ if (!Array.isArray(given.variables)) fail(configPath, "runner.build.variables", "must be a list of variable names");
269
+ if (given.variables.length > MAX_VARIABLES) fail(configPath, "runner.build.variables", `names ${given.variables.length} variables; a release records at most ${MAX_VARIABLES}`);
270
+ for (const name of given.variables) {
271
+ if (typeof name !== "string" || !VARIABLE_NAME.test(name)) fail(configPath, "runner.build.variables", `names ${JSON.stringify(name)}, which is not a variable name`);
272
+ if (isRefusedInConfig(name)) fail(configPath, "runner.build.variables", `names ${name}, which the runner sets itself`);
273
+ if (!variables.includes(name)) variables.push(name);
274
+ }
275
+ }
276
+ const destinations = given.destinations === void 0 ? [] : resolveDestinations(given.destinations, variables, isReservedVariable, (key, reason) => fail(configPath, key, reason));
277
+ let allowHosts;
278
+ if (given.allowHosts !== void 0) {
279
+ if (!Array.isArray(given.allowHosts)) fail(configPath, "runner.build.allowHosts", "must be a list of host names");
280
+ if (given.allowHosts.length > MAX_ALLOW_HOSTS) fail(configPath, "runner.build.allowHosts", `names ${given.allowHosts.length} hosts; at most ${MAX_ALLOW_HOSTS}`);
281
+ allowHosts = [];
282
+ for (const entry of given.allowHosts) {
283
+ const problem = allowHostProblem(entry);
284
+ if (problem !== void 0) fail(configPath, "runner.build.allowHosts", `names ${JSON.stringify(entry)}, which ${problem}`);
285
+ const host = entry.toLowerCase();
286
+ if (!allowHosts.includes(host)) allowHosts.push(host);
287
+ }
288
+ }
289
+ return {
290
+ memory: given.memory ?? DEFAULT_BUILD.memory,
291
+ cpus: given.cpus ?? DEFAULT_BUILD.cpus,
292
+ pids: given.pids ?? DEFAULT_BUILD.pids,
293
+ disk: given.disk ?? DEFAULT_BUILD.disk,
294
+ variables,
295
+ ...destinations.length === 0 ? {} : { destinations },
296
+ ...allowHosts === void 0 ? {} : { allowHosts }
297
+ };
298
+ }
299
+
300
+ export { RunnerConfigError, builtinKind, checkHooks, defaultRunnerConfigPath, hasRunnerBlock, isBuiltinContainer, isBuiltinProcess, isBuiltinVm, loadRunnerConfig, resolveBuild, runnerVmOf };
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { RecipeError } from './chunk-QOZKPLVJ.js';
4
+ import { spawn } from 'child_process';
5
+ import { existsSync, mkdirSync, createWriteStream, readFileSync } from 'fs';
6
+ import { join, dirname } from 'path';
7
+ import { createRequire as createRequire$1 } from 'module';
8
+ import { fileURLToPath } from 'url';
9
+
10
+ createRequire(import.meta.url);
11
+ var PACKAGE = "@devcontainers/cli";
12
+ function buildRootFromDist(moduleDir = dirname(fileURLToPath(import.meta.url))) {
13
+ try {
14
+ const info = JSON.parse(readFileSync(join(moduleDir, "build-info.json"), "utf8"));
15
+ return typeof info.root === "string" ? info.root : void 0;
16
+ } catch {
17
+ return void 0;
18
+ }
19
+ }
20
+ function resolveDevcontainerCli(opts = {}) {
21
+ const resolve = opts.resolve ?? createRequire$1(import.meta.url).resolve;
22
+ try {
23
+ const bin = join(dirname(resolve(`${PACKAGE}/package.json`)), "devcontainer.js");
24
+ if (existsSync(bin)) return { command: process.execPath, args: [bin] };
25
+ } catch {
26
+ }
27
+ const buildRoot = opts.buildRoot ?? buildRootFromDist();
28
+ const fallback = buildRoot === void 0 ? void 0 : join(buildRoot, "node_modules", PACKAGE);
29
+ if (fallback !== void 0 && existsSync(join(fallback, "devcontainer.js"))) {
30
+ return { command: process.execPath, args: [join(fallback, "devcontainer.js")] };
31
+ }
32
+ throw new RecipeError(
33
+ "devcontainer_cli_missing",
34
+ `the devcontainer CLI is not installed: looked beside this package and in ${fallback ?? "the checkout this build came from (unknown)"}. Run yarn install in that checkout.`
35
+ );
36
+ }
37
+ function devcontainerBuildInvocation(cli, req) {
38
+ return {
39
+ command: cli.command,
40
+ args: [
41
+ ...cli.args,
42
+ "build",
43
+ "--workspace-folder",
44
+ req.workspace,
45
+ "--config",
46
+ req.config,
47
+ "--image-name",
48
+ req.imageName,
49
+ ...Object.entries(req.labels).flatMap(([key, value]) => ["--label", `${key}=${value}`]),
50
+ "--log-format",
51
+ "text"
52
+ ]
53
+ };
54
+ }
55
+
56
+ // src/runner/image/docker.ts
57
+ var CHILD_ENVIRONMENT_NAMES = [
58
+ "PATH",
59
+ "HOME",
60
+ "USER",
61
+ "LOGNAME",
62
+ "TMPDIR",
63
+ "LANG",
64
+ "LC_ALL",
65
+ "DOCKER_HOST",
66
+ "DOCKER_CONFIG",
67
+ "DOCKER_CONTEXT",
68
+ "DOCKER_CERT_PATH",
69
+ "DOCKER_TLS_VERIFY",
70
+ "DOCKER_API_VERSION",
71
+ "DOCKER_BUILDKIT",
72
+ "XDG_RUNTIME_DIR",
73
+ "XDG_CONFIG_HOME",
74
+ "XDG_DATA_HOME",
75
+ "XDG_CACHE_HOME",
76
+ "XDG_STATE_HOME"
77
+ ];
78
+ function childEnvironment(base) {
79
+ const env = {};
80
+ for (const name of CHILD_ENVIRONMENT_NAMES) {
81
+ const value = base[name];
82
+ if (value !== void 0) env[name] = value;
83
+ }
84
+ return env;
85
+ }
86
+ var spawnRun = (command, args, opts = {}) => new Promise((resolve) => {
87
+ let log;
88
+ if (opts.logPath !== void 0) {
89
+ mkdirSync(dirname(opts.logPath), { recursive: true });
90
+ log = createWriteStream(opts.logPath, { flags: "a" });
91
+ }
92
+ let stdout = "";
93
+ let stderr = "";
94
+ let settled = false;
95
+ const finish = (status, extra = "") => {
96
+ if (settled) return;
97
+ settled = true;
98
+ const result = { status, stdout, stderr: stderr + extra };
99
+ if (log === void 0) {
100
+ resolve(result);
101
+ return;
102
+ }
103
+ if (extra) log.write(extra);
104
+ log.end(() => resolve(result));
105
+ };
106
+ const child = spawn(command, args, {
107
+ env: { ...childEnvironment(process.env), ...opts.env },
108
+ stdio: [opts.input === void 0 ? "ignore" : "pipe", "pipe", "pipe"]
109
+ });
110
+ child.stdout.on("data", (chunk) => {
111
+ stdout += chunk.toString();
112
+ log?.write(chunk);
113
+ });
114
+ child.stderr.on("data", (chunk) => {
115
+ stderr += chunk.toString();
116
+ log?.write(chunk);
117
+ });
118
+ child.on("error", (err) => finish(127, `${command}: ${err.message}
119
+ `));
120
+ child.on("close", (code, signal) => finish(code ?? (signal ? 128 : 1)));
121
+ if (opts.input !== void 0) {
122
+ child.stdin.on("error", () => void 0);
123
+ child.stdin.end(opts.input);
124
+ }
125
+ });
126
+ function toInfo(row) {
127
+ return { id: row.Id, createdAt: row.Created, labels: row.Config?.Labels ?? {} };
128
+ }
129
+ var lines = (text) => [...new Set(text.split("\n").map((l) => l.trim()).filter(Boolean))];
130
+ function createDockerHost(deps = {}) {
131
+ const run = deps.run ?? spawnRun;
132
+ const cli = deps.cli ?? resolveDevcontainerCli;
133
+ const inspectMany = async (refs) => {
134
+ const result = await run("docker", ["image", "inspect", ...refs]);
135
+ return result.status === 0 ? JSON.parse(result.stdout).map(toInfo) : [];
136
+ };
137
+ return {
138
+ async inspect(ref) {
139
+ return (await inspectMany([ref]))[0] ?? null;
140
+ },
141
+ async list(repository) {
142
+ const ids = await run("docker", [
143
+ "image",
144
+ "ls",
145
+ "--all",
146
+ "--no-trunc",
147
+ "--quiet",
148
+ "--filter",
149
+ `label=ai.outerlayer.repository=${repository}`
150
+ ]);
151
+ const found = lines(ids.stdout);
152
+ return found.length === 0 ? [] : inspectMany(found);
153
+ },
154
+ async inUse() {
155
+ const containers = lines((await run("docker", ["ps", "--all", "--no-trunc", "--quiet"])).stdout);
156
+ if (containers.length === 0) return [];
157
+ return lines((await run("docker", ["inspect", "--format", "{{.Image}}", ...containers])).stdout);
158
+ },
159
+ async buildDockerfile(req) {
160
+ const labels = Object.entries(req.labels).flatMap(([key, value]) => ["--label", `${key}=${value}`]);
161
+ const result = await run("docker", ["build", "--tag", req.tag, ...labels, "--file", "-", req.context], {
162
+ logPath: req.logPath,
163
+ input: req.dockerfile
164
+ });
165
+ return result.status;
166
+ },
167
+ async devcontainerBuild(req) {
168
+ const { command, args } = devcontainerBuildInvocation(cli(), req);
169
+ return (await run(command, args, { logPath: req.logPath })).status;
170
+ },
171
+ async remove(id) {
172
+ const result = await run("docker", ["rmi", id]);
173
+ if (result.status !== 0) throw new Error(result.stderr.trim() || `docker rmi ${id} exited ${result.status}`);
174
+ }
175
+ };
176
+ }
177
+
178
+ export { childEnvironment, createDockerHost, resolveDevcontainerCli, spawnRun };
@@ -1,18 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { detectActiveSession } from './chunk-U32VSRLO.js';
4
- import { resolveRepoIdentity } from './chunk-4TNZMO7V.js';
5
- import './chunk-YRYMNBGQ.js';
6
- import './chunk-65WXAANR.js';
3
+ import { detectActiveSession } from './chunk-BFESLKPP.js';
4
+ import { resolveRepoIdentity } from './chunk-4C4THABW.js';
7
5
  import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
8
- import './chunk-ZJSNLVCE.js';
9
- import './chunk-NXLLURA4.js';
10
- import './chunk-I3ETLSNE.js';
11
- import './chunk-YOOSBOKS.js';
12
- import './chunk-R5KBGVII.js';
13
- import { readCloudConfig } from './chunk-TFUIDMOB.js';
6
+ import { defaultFetch } from './chunk-BCLJSQCV.js';
7
+ import { unauthorizedMessage, resolveGatewayCredentials } from './chunk-BOLTI6LR.js';
14
8
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
15
- import './chunk-Z5GEFMRW.js';
16
9
 
17
10
  createRequire(import.meta.url);
18
11
 
@@ -23,19 +16,9 @@ var RESET = "\x1B[0m";
23
16
  var WorkPrCommandError = class extends Error {
24
17
  };
25
18
  function resolveCredentials(opts) {
26
- const home = opts.home ?? homeFromEnv();
27
- const env = opts.env ?? process.env;
28
- const fileConfig = readCloudConfig(home);
29
- const url = opts.url ?? env.OUTERLAYER_URL ?? fileConfig.url;
30
- const apiKey = opts.apiKey ?? env.OUTERLAYER_API_KEY ?? fileConfig.apiKey;
31
- const appId = opts.appId ?? env.OUTERLAYER_APP_ID ?? fileConfig.appId;
32
- if (!url || !apiKey || !appId) {
33
- const missing = [!url && "--url", !apiKey && "--api-key", !appId && "--app-id"].filter(Boolean).join(", ");
34
- throw new WorkPrCommandError(
35
- `missing ${missing}. Run \`outerlayer login --url <your gateway>\` and paste the key at the prompt, or set OUTERLAYER_URL / OUTERLAYER_API_KEY / OUTERLAYER_APP_ID. Mint a key in the dashboard: Settings \u2192 API keys.`
36
- );
37
- }
38
- return { url, apiKey, appId };
19
+ const resolved = resolveGatewayCredentials(opts);
20
+ if (!resolved.ok) throw new WorkPrCommandError(resolved.message);
21
+ return { url: resolved.url, apiKey: resolved.credential, appId: resolved.appId };
39
22
  }
40
23
  function resolveRepository(repoFlag, cwd) {
41
24
  if (repoFlag) return repoFlag;
@@ -69,7 +52,7 @@ async function runWorkPr(opts) {
69
52
  "no active session detected. `work pr` declares a pull request from inside the session that opened it."
70
53
  );
71
54
  }
72
- const fetchImpl = opts.fetchImpl ?? fetch;
55
+ const fetchImpl = opts.fetchImpl ?? defaultFetch;
73
56
  const endpoint = new URL("/v1/work-items/pull-requests", creds.url).toString();
74
57
  let response;
75
58
  try {
@@ -87,7 +70,7 @@ async function runWorkPr(opts) {
87
70
  }
88
71
  if (response.status === 401) {
89
72
  throw new WorkPrCommandError(
90
- "not authorized \u2014 the API key is unknown, expired, or bound to a different app. Mint a fresh key: dashboard \u2192 Settings \u2192 API keys."
73
+ unauthorizedMessage(creds.apiKey)
91
74
  );
92
75
  }
93
76
  let json;