@outerlayer/cli 0.2.0 → 0.4.0

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 +124 -0
  2. package/README.md +226 -59
  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-WQP2CZDW.js +94 -0
  7. package/dist/chunk-2E54Z3P5.js +232 -0
  8. package/dist/chunk-3TT7RQYB.js +55 -0
  9. package/dist/{chunk-U32VSRLO.js → chunk-3YPKXE5L.js} +90 -632
  10. package/dist/{chunk-Q6CL5THG.js → chunk-5DLBAXY5.js} +208 -61
  11. package/dist/{chunk-R5KBGVII.js → chunk-5QRP3MVS.js} +30 -14
  12. package/dist/chunk-5Y3QQRUH.js +300 -0
  13. package/dist/chunk-6Y63SXFO.js +383 -0
  14. package/dist/chunk-774HEQL6.js +178 -0
  15. package/dist/chunk-7A6RVXPZ.js +2233 -0
  16. package/dist/chunk-7YIOOFVJ.js +71 -0
  17. package/dist/chunk-ABIWDUMS.js +104 -0
  18. package/dist/chunk-AUFG23AA.js +534 -0
  19. package/dist/chunk-B2G7JHHB.js +42 -0
  20. package/dist/chunk-BCJNHMZT.js +34 -0
  21. package/dist/chunk-BCLJSQCV.js +56 -0
  22. package/dist/chunk-BFESLKPP.js +61 -0
  23. package/dist/chunk-BJ3KTMDH.js +63 -0
  24. package/dist/chunk-BKO6JEZI.js +47 -0
  25. package/dist/chunk-BOLTI6LR.js +37 -0
  26. package/dist/chunk-CBPPJSAR.js +89 -0
  27. package/dist/chunk-CBZB6XY6.js +102 -0
  28. package/dist/{chunk-65WXAANR.js → chunk-DVDEBNQJ.js} +17 -2
  29. package/dist/{work-pr-cmd-AZQPWH4H.js → chunk-DVYHCMB2.js} +9 -26
  30. package/dist/{chunk-TFUIDMOB.js → chunk-EABW6AJQ.js} +12 -1
  31. package/dist/{chunk-KFYJV2ZG.js → chunk-EMY4I27X.js} +1 -1
  32. package/dist/{chunk-NTTPJV35.js → chunk-F47JBIAW.js} +3 -1
  33. package/dist/chunk-F6GFZXZ3.js +60 -0
  34. package/dist/{chunk-YOOSBOKS.js → chunk-F7CU5ABH.js} +1 -1
  35. package/dist/chunk-FAJSS2WN.js +1357 -0
  36. package/dist/chunk-FS2VYGZT.js +1764 -0
  37. package/dist/chunk-FV2CCGXK.js +9 -0
  38. package/dist/{chunk-4TNZMO7V.js → chunk-G7FSV7JX.js} +3452 -751
  39. package/dist/{emit-cmd-TWSYYEZI.js → chunk-H5NCNVDA.js} +68 -17
  40. package/dist/chunk-HE2D4EY5.js +83 -0
  41. package/dist/chunk-L2DBRKRA.js +94 -0
  42. package/dist/chunk-LF6MLJCY.js +38 -0
  43. package/dist/chunk-M5POMKKW.js +1051 -0
  44. package/dist/{mcp-install-cmd-FDQH6SEN.js → chunk-MQ3IPHIZ.js} +35 -18
  45. package/dist/{chunk-D77LS3UI.js → chunk-N5FOE5PS.js} +33 -4
  46. package/dist/chunk-N6LURUEF.js +64 -0
  47. package/dist/chunk-NYC54VBD.js +1084 -0
  48. package/dist/chunk-OGFGZQA3.js +23 -0
  49. package/dist/chunk-QOZKPLVJ.js +226 -0
  50. package/dist/chunk-RA3O54FC.js +30 -0
  51. package/dist/chunk-TBT347UY.js +41 -0
  52. package/dist/{chunk-NXLLURA4.js → chunk-UFFXNXLQ.js} +115 -71
  53. package/dist/chunk-USO2DKBD.js +421 -0
  54. package/dist/chunk-W4SQCOZU.js +206 -0
  55. package/dist/{chunk-A3WLZX2F.js → chunk-WIOZAJ2W.js} +15 -2
  56. package/dist/chunk-WO2BXCTQ.js +83 -0
  57. package/dist/{chunk-I3ETLSNE.js → chunk-XCCLFVXM.js} +120 -11
  58. package/dist/{context-materialize-6WKT3RBQ.js → chunk-XDDW4FRS.js} +134 -26
  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-NHUXABYH.js} +590 -876
  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-OOG4TPU7.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-RW4UPPC2.js +6 -0
  70. package/dist/docker-NEGDLU6D.js +7 -0
  71. package/dist/doctor-53F2PYY7.js +43 -0
  72. package/dist/doctor-UFVL4PZY.js +426 -0
  73. package/dist/{emit-artifact-cmd-UQVT3OKW.js → emit-artifact-cmd-KTYW2TN2.js} +24 -20
  74. package/dist/emit-cmd-DCERAU27.js +9 -0
  75. package/dist/emit-criteria-cmd-VAUUSAYD.js +166 -0
  76. package/dist/{emit-finding-cmd-FDOU2OPD.js → emit-finding-cmd-UR4ID45Z.js} +34 -31
  77. package/dist/{emit-result-cmd-H2T4C2K3.js → emit-result-cmd-FDTUKGKE.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-DXBCOAJK.js +36 -0
  87. package/dist/init-YEXK35CF.js +221 -0
  88. package/dist/init-cmd-NHR7PRSQ.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-VZOLL4WQ.js +43 -0
  95. package/dist/machine-WSG52J75.js +35 -0
  96. package/dist/mcp-install-cmd-RLK4SO3N.js +9 -0
  97. package/dist/{mcp-serve-cmd-57EZZOTL.js → mcp-serve-cmd-JPTP5FMS.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-2UZKKNB7.js} +30 -13
  105. package/dist/{statusline-fast-3C5OXHDD.js → statusline-fast-SVTMBMD7.js} +3 -2
  106. package/dist/sync-cmd-VPPYCNNM.js +28 -0
  107. package/dist/version-BWM6VLDI.js +6 -0
  108. package/dist/{watch-3DTPJETH.js → watch-5C4ZBBGO.js} +19 -8
  109. package/dist/{work-claim-cmd-5AUNCLS2.js → work-claim-cmd-LSO2B2EX.js} +33 -20
  110. package/dist/work-cmd-UOQO2AD4.js +17 -0
  111. package/dist/work-comment-cmd-WF3EOLAE.js +106 -0
  112. package/dist/{work-launch-LT663PB3.js → work-launch-YI4CJDAP.js} +1 -1
  113. package/dist/work-open-pr-cmd-4SSQZMYG.js +156 -0
  114. package/dist/work-pr-cmd-6AN6RRQJ.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 +99 -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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { emitTree, parseOuterlayerConfig, classifyTree, generatedHeaderSource, CONTEXT_PIN_FILENAME, normalizePath } from './chunk-KFYJV2ZG.js';
4
- import './chunk-Z5GEFMRW.js';
3
+ import { maintainedFilesForExistingSkills, loadMaintainedFiles, writeMaintainedFiles } from './chunk-WO2BXCTQ.js';
4
+ import { emitTree, parseOuterlayerConfig, classifyTree, generatedHeaderSource, CONTEXT_PIN_FILENAME, normalizePath } from './chunk-EMY4I27X.js';
5
5
  import { existsSync, readFileSync, mkdirSync, writeFileSync, copyFileSync, readdirSync } from 'fs';
6
6
  import { join, dirname, relative, sep } from 'path';
7
7
 
@@ -53,19 +53,21 @@ function walk(dir, repoRoot, insideOuterlayer, out) {
53
53
  }
54
54
  }
55
55
  }
56
- function buildEmitInput(repoRoot, targets) {
57
- const discovered = discoverOuterlayerPaths(repoRoot);
56
+ function buildEmitInput(repoRoot, targets, maintained) {
57
+ const bundled = new Map(maintained.map((f) => [f.path, f.content]));
58
+ const discovered = [.../* @__PURE__ */ new Set([...discoverOuterlayerPaths(repoRoot), ...bundled.keys()])].sort();
58
59
  const { entries } = classifyTree(discovered);
59
60
  const classified = new Set(entries.map((e) => e.path));
60
61
  const contents = /* @__PURE__ */ new Map();
61
62
  for (const entry of entries) {
62
63
  if (!CONTENT_KINDS.has(entry.kind)) continue;
63
- contents.set(entry.path, readFileSync(join(repoRoot, entry.path), "utf8"));
64
+ contents.set(entry.path, bundled.get(entry.path) ?? readFileSync(join(repoRoot, entry.path), "utf8"));
64
65
  }
65
66
  const assetPaths = discovered.filter((p) => !classified.has(p));
66
67
  return { input: { entries, contents, assetPaths, targets }, sourcePaths: classified };
67
68
  }
68
- function writeOutputs(repoRoot, result) {
69
+ function writeOutputs(repoRoot, result, maintained) {
70
+ const changedMaintained = writeMaintainedFiles(repoRoot, maintained);
69
71
  const files = [];
70
72
  for (const file of result.files) {
71
73
  const abs = join(repoRoot, file.path);
@@ -80,11 +82,15 @@ function writeOutputs(repoRoot, result) {
80
82
  copyFileSync(join(repoRoot, copy.fromSourcePath), abs);
81
83
  copies.push(copy.toPath);
82
84
  }
83
- return { files, copies };
85
+ return { files, copies, maintained: [...changedMaintained.created, ...changedMaintained.rewritten] };
84
86
  }
85
- function renderSuccess(targets, files, copies) {
87
+ function renderSuccess(targets, files, copies, maintained) {
86
88
  const copySuffix = copies.length > 0 ? `, ${copies.length} ${copies.length === 1 ? "copy" : "copies"}` : "";
87
- return `${GREEN}\u2713${RESET} emitted ${files.length} file${files.length === 1 ? "" : "s"}${copySuffix} for ${DIM}${targets.join(", ")}${RESET}`;
89
+ const lines = [
90
+ `${GREEN}\u2713${RESET} emitted ${files.length} file${files.length === 1 ? "" : "s"}${copySuffix} for ${DIM}${targets.join(", ")}${RESET}`
91
+ ];
92
+ for (const path of maintained) lines.push(` ${DIM}updated ${path} to this CLI's version${RESET}`);
93
+ return lines.join("\n");
88
94
  }
89
95
  function renderFailure(errors) {
90
96
  const lines = [`${RED}\u2717${RESET} emit failed \u2014 nothing written`];
@@ -150,17 +156,48 @@ function findOrphans(repoRoot, computedFiles, sourcePaths) {
150
156
  }
151
157
  return orphans;
152
158
  }
159
+ function withholdKept(repoRoot, result, keep) {
160
+ if (keep === void 0) return { result, kept: [] };
161
+ const kept = [];
162
+ const files = result.files.filter((file) => {
163
+ const abs = join(repoRoot, file.path);
164
+ if (!existsSync(abs)) return true;
165
+ const existing = readFileSync(abs, "utf8");
166
+ if (existing === file.content || !keep({ kind: "file", path: file.path, existing, content: file.content })) return true;
167
+ kept.push(file.path);
168
+ return false;
169
+ });
170
+ const copies = result.copies.filter((copy) => {
171
+ const abs = join(repoRoot, copy.toPath);
172
+ if (!existsSync(abs)) return true;
173
+ const src = join(repoRoot, copy.fromSourcePath);
174
+ if (existsSync(src) && readFileSync(abs).equals(readFileSync(src))) return true;
175
+ if (!keep({ kind: "copy", path: copy.toPath, source: copy.fromSourcePath })) return true;
176
+ kept.push(copy.toPath);
177
+ return false;
178
+ });
179
+ return { result: { ...result, files, copies }, kept: kept.sort() };
180
+ }
153
181
  function runEmit(opts = {}) {
154
182
  const cwd = opts.cwd ?? process.cwd();
155
183
  if (!existsSync(cwd)) throw new EmitError(`no such directory: ${cwd}`);
156
184
  const targets = resolveTargetsFromConfig(cwd);
157
- const built = buildEmitInput(cwd, targets);
158
- const result = emitTree(built.input);
185
+ const maintained = maintainedFilesForExistingSkills(cwd, loadMaintainedFiles());
186
+ const built = buildEmitInput(cwd, targets, maintained);
187
+ const emitted = emitTree(built.input);
188
+ const { result, kept } = withholdKept(cwd, emitted, opts.keep);
159
189
  const hasErrors = result.errors.length > 0;
160
- if (opts.check) return runCheck(cwd, opts, targets, result, hasErrors, built.sourcePaths);
161
- const written = hasErrors ? { files: [], copies: [] } : writeOutputs(cwd, result);
190
+ if (opts.check) return runCheck(cwd, opts, targets, result, hasErrors, built.sourcePaths, maintained, kept);
191
+ const written = hasErrors ? { files: [], copies: [], maintained: [] } : writeOutputs(cwd, result, maintained);
162
192
  const exitCode = hasErrors ? 1 : 0;
163
- const output = opts.json ? JSON.stringify({ targets, files: written.files, copies: written.copies, warnings: result.warnings, errors: result.errors }) : hasErrors ? renderFailure(result.errors) : renderSuccess(targets, written.files, written.copies);
193
+ const output = opts.json ? JSON.stringify({
194
+ targets,
195
+ files: written.files,
196
+ copies: written.copies,
197
+ maintained: written.maintained,
198
+ warnings: result.warnings,
199
+ errors: result.errors
200
+ }) : hasErrors ? renderFailure(result.errors) : renderSuccess(targets, written.files, written.copies, written.maintained);
164
201
  if (!opts.quiet) {
165
202
  if (opts.json) {
166
203
  process.stdout.write(output + "\n");
@@ -172,11 +209,25 @@ function runEmit(opts = {}) {
172
209
  `);
173
210
  }
174
211
  }
175
- return { targets, files: written.files, copies: written.copies, warnings: result.warnings, errors: result.errors, output, exitCode };
212
+ return {
213
+ targets,
214
+ files: written.files,
215
+ copies: written.copies,
216
+ maintained: written.maintained,
217
+ kept,
218
+ warnings: result.warnings,
219
+ errors: result.errors,
220
+ output,
221
+ exitCode
222
+ };
176
223
  }
177
- function runCheck(cwd, opts, targets, result, hasErrors, sourcePaths) {
224
+ function runCheck(cwd, opts, targets, result, hasErrors, sourcePaths, maintained, kept) {
178
225
  const drift = [];
179
226
  if (!hasErrors) {
227
+ for (const file of maintained) {
228
+ const d = compareFile(cwd, file.path, file.content);
229
+ if (d) drift.push(d);
230
+ }
180
231
  const computedFiles = new Set(result.files.map((f) => f.path));
181
232
  for (const file of result.files) {
182
233
  const d = compareFile(cwd, file.path, file.content);
@@ -202,7 +253,7 @@ function runCheck(cwd, opts, targets, result, hasErrors, sourcePaths) {
202
253
  `);
203
254
  }
204
255
  }
205
- return { targets, files: [], copies: [], warnings: result.warnings, errors: result.errors, drift, output, exitCode };
256
+ return { targets, files: [], copies: [], maintained: [], kept, warnings: result.warnings, errors: result.errors, drift, output, exitCode };
206
257
  }
207
258
 
208
259
  export { EmitError, runEmit };
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { setCustomScrubbers, setLocalIdentity, setRepoRoot, resolveRepoIdentity, scrubDeep, downconvertSession, isHumanUserTurn, turnCountBound } from './chunk-G7FSV7JX.js';
4
+ import { execFileSync } from 'child_process';
5
+ import { hostname, userInfo } from 'os';
6
+
7
+ createRequire(import.meta.url);
8
+ function detectCi(env = process.env) {
9
+ return env.CI === "true" || env.CI === "1" || !!env.JENKINS_URL;
10
+ }
11
+ function gitGlobalConfig(key) {
12
+ try {
13
+ return execFileSync("git", ["config", "--global", key], { encoding: "utf8", timeout: 2e3 }).trim() || void 0;
14
+ } catch {
15
+ return void 0;
16
+ }
17
+ }
18
+ function installScrubContext(home, scrub) {
19
+ setCustomScrubbers(scrub);
20
+ setLocalIdentity({
21
+ name: gitGlobalConfig("user.name"),
22
+ email: gitGlobalConfig("user.email"),
23
+ username: (() => {
24
+ try {
25
+ return userInfo().username;
26
+ } catch {
27
+ return void 0;
28
+ }
29
+ })(),
30
+ hostname: (() => {
31
+ try {
32
+ return hostname();
33
+ } catch {
34
+ return void 0;
35
+ }
36
+ })(),
37
+ home
38
+ });
39
+ }
40
+ function applyContextProvenance(session, record) {
41
+ if (!record) return;
42
+ if (record.repository !== void 0) session.env.contextSourceRepo = record.repository;
43
+ if (record.ref !== void 0) session.env.contextSourceRef = record.ref;
44
+ if (record.sha !== null) session.env.contextSourceSha = record.sha;
45
+ session.env.contextSourceVerification = record.verification;
46
+ }
47
+ function prepareSessionForUpload(session, tier, envVars = process.env) {
48
+ setRepoRoot(resolveRepoIdentity(session.env.cwd).repoRoot);
49
+ scrubDeep(session);
50
+ session.workerKind ??= detectCi(envVars) ? "ci" : "seat";
51
+ return downconvertSession(session, tier);
52
+ }
53
+ var MAX_TOOL_IO_CHARS = 64e3;
54
+ function sliceToolIo(value) {
55
+ const text = typeof value === "string" ? value : JSON.stringify(value);
56
+ if (typeof text !== "string" || text.length <= MAX_TOOL_IO_CHARS) return value;
57
+ return `${text.slice(0, MAX_TOOL_IO_CHARS)}\u2026[sliced: ${text.length} chars exceeded the sync per-call cap]`;
58
+ }
59
+ function sliceTurnIo(turn) {
60
+ return {
61
+ ...turn,
62
+ toolCalls: (turn.toolCalls ?? []).map((call) => ({
63
+ ...call,
64
+ ...call.input !== void 0 ? { input: sliceToolIo(call.input) } : {},
65
+ ...call.output !== void 0 ? { output: sliceToolIo(call.output) } : {}
66
+ }))
67
+ };
68
+ }
69
+ function wholeSessionCounts(turns) {
70
+ const allCalls = turns.flatMap((t) => t.toolCalls ?? []);
71
+ return {
72
+ // An index BOUND (`1 + max(turn.index)`), not the shipped turn count —
73
+ // turn indexes are not guaranteed dense from zero, and a late artifact
74
+ // anchored at its real turn index must still validate against the
75
+ // summary row this produces (see turnCountBound).
76
+ turns: turnCountBound(turns),
77
+ toolCalls: allCalls.length,
78
+ errors: allCalls.filter((c) => c.status === "error").length,
79
+ userTurns: turns.filter(isHumanUserTurn).length
80
+ };
81
+ }
82
+
83
+ export { applyContextProvenance, detectCi, installScrubContext, prepareSessionForUpload, sliceTurnIo, wholeSessionCounts };
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { MIN_ENGINE_MAJOR } from './chunk-7A6RVXPZ.js';
4
+ import { firecrackerArch, MICROVM_ARCHES, guestAssetsPinned } from './chunk-ZNA27WEV.js';
5
+ import { spawnSync } from 'child_process';
6
+ import { existsSync, accessSync, constants, statSync, readFileSync } from 'fs';
7
+
8
+ createRequire(import.meta.url);
9
+ function groupName(gid) {
10
+ try {
11
+ for (const line of readFileSync("/etc/group", "utf8").split("\n")) {
12
+ const [name, , id] = line.split(":");
13
+ if (name !== void 0 && Number(id) === gid) return name;
14
+ }
15
+ } catch {
16
+ }
17
+ return String(gid);
18
+ }
19
+ function detectKvm(device) {
20
+ if (!existsSync(device)) return { state: "absent" };
21
+ try {
22
+ accessSync(device, constants.R_OK | constants.W_OK);
23
+ return { state: "usable" };
24
+ } catch {
25
+ return { state: "no-permission", group: groupName(statSync(device).gid) };
26
+ }
27
+ }
28
+ function readProcVersion() {
29
+ try {
30
+ return readFileSync("/proc/version", "utf8");
31
+ } catch {
32
+ return void 0;
33
+ }
34
+ }
35
+ function docker(args) {
36
+ const result = spawnSync("docker", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 15e3 });
37
+ const out = result.status === 0 ? result.stdout.trim() : "";
38
+ return out === "" ? void 0 : out;
39
+ }
40
+ function dockerFailure() {
41
+ const result = spawnSync("docker", ["version", "--format", "{{.Server.Version}}"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 });
42
+ if (result.error?.code === "ENOENT") return void 0;
43
+ return /permission denied/i.test(result.stderr ?? "") ? "permission" : "daemon";
44
+ }
45
+ function platformOf(probes) {
46
+ const platform = probes.platform ?? process.platform;
47
+ if (platform === "win32") return "windows";
48
+ if (platform === "darwin") return "darwin";
49
+ if (platform !== "linux") return "other";
50
+ return /microsoft/i.test((probes.procVersion ?? readProcVersion)() ?? "") ? "wsl2" : "linux";
51
+ }
52
+ function detectMachine(probes = {}) {
53
+ const platform = platformOf(probes);
54
+ const kvm = (probes.kvm ?? (() => detectKvm(probes.kvmDevice ?? "/dev/kvm")))();
55
+ const arch = firecrackerArch(probes.nodeArch ?? process.arch);
56
+ const version = (probes.dockerVersion ?? (() => docker(["version", "--format", "{{.Server.Version}}"])))();
57
+ if (version === void 0) {
58
+ const failure = (probes.dockerFailure ?? (probes.dockerVersion === void 0 ? dockerFailure : () => void 0))();
59
+ return { platform, docker: failure === void 0 ? { kind: "none" } : { kind: "unreachable", reason: failure }, kvm, arch };
60
+ }
61
+ const system = (probes.dockerOperatingSystem ?? (() => docker(["info", "--format", "{{.OperatingSystem}}"])))() ?? "";
62
+ return { platform, docker: { kind: /docker desktop/i.test(system) ? "desktop" : "engine", version }, kvm, arch };
63
+ }
64
+ function supportsContainerBuilds(machine) {
65
+ if (machine.platform !== "linux" && machine.platform !== "wsl2") return false;
66
+ return machine.docker.kind === "engine" && Number.parseInt(machine.docker.version ?? "", 10) >= MIN_ENGINE_MAJOR;
67
+ }
68
+ function supportsMicroVmBuilds(machine) {
69
+ return machine.platform === "linux" && machine.kvm?.state === "usable" && supportsContainerBuilds(machine) && microVmAssetsPinned(machine);
70
+ }
71
+ function microVmArchSupported(machine) {
72
+ return MICROVM_ARCHES.includes(machine.arch ?? "");
73
+ }
74
+ function microVmAssetsPinned(machine) {
75
+ return microVmArchSupported(machine) && guestAssetsPinned(machine.arch);
76
+ }
77
+ function reachDockerStep(machine) {
78
+ if (machine.docker.kind !== "unreachable") return void 0;
79
+ return machine.docker.reason === "permission" ? "give this user access to Docker Engine's socket (add it to the docker group, then log in again)" : "start Docker Engine's daemon (for example, sudo systemctl start docker)";
80
+ }
81
+ function describeMachineDocker(machine) {
82
+ const { kind, version, reason } = machine.docker;
83
+ const where = machine.platform === "windows" ? "Windows" : machine.platform === "darwin" ? "macOS" : machine.platform === "wsl2" ? "WSL2" : "Linux";
84
+ if (kind === "desktop") return `Docker Desktop ${version ?? ""} on ${where}, which cannot pass a Unix socket into a container`.replace(" ", " ");
85
+ if (kind === "none") return `no Docker on ${where}`;
86
+ if (kind === "unreachable") {
87
+ const problem = reason === "permission" ? "this user has no permission on its socket" : "its daemon is not running";
88
+ return `a docker command on ${where} that cannot reach Docker Engine (${problem})`;
89
+ }
90
+ if (machine.platform !== "linux" && machine.platform !== "wsl2") return `Docker Engine ${version ?? ""} on ${where}, which cannot isolate a build`.replace(" ", " ");
91
+ return `Docker Engine ${version ?? "of no known version"}, older than the ${MIN_ENGINE_MAJOR} that container builds need`;
92
+ }
93
+
94
+ export { describeMachineDocker, detectMachine, microVmArchSupported, microVmAssetsPinned, reachDockerStep, supportsContainerBuilds, supportsMicroVmBuilds };
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { mkdirSync, writeFileSync } from 'fs';
4
+ import { join, dirname } from 'path';
5
+
6
+ createRequire(import.meta.url);
7
+ var LAUNCH_AGENT_LABEL = "ai.outerlayer.runner-vm";
8
+ function launchAgentPath(home) {
9
+ return join(home, "Library", "LaunchAgents", `${LAUNCH_AGENT_LABEL}.plist`);
10
+ }
11
+ var escapeXml = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
12
+ function launchAgentPlist(limactl, instance) {
13
+ return `<?xml version="1.0" encoding="UTF-8"?>
14
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
15
+ <plist version="1.0">
16
+ <dict>
17
+ <key>Label</key>
18
+ <string>${LAUNCH_AGENT_LABEL}</string>
19
+ <key>ProgramArguments</key>
20
+ <array>
21
+ <string>${escapeXml(limactl)}</string>
22
+ <string>start</string>
23
+ <string>${escapeXml(instance)}</string>
24
+ </array>
25
+ <key>RunAtLoad</key>
26
+ <true/>
27
+ </dict>
28
+ </plist>
29
+ `;
30
+ }
31
+ function writeLaunchAgent(home, limactl, instance) {
32
+ const path = launchAgentPath(home);
33
+ mkdirSync(dirname(path), { recursive: true });
34
+ writeFileSync(path, launchAgentPlist(limactl, instance));
35
+ return path;
36
+ }
37
+
38
+ export { launchAgentPath, writeLaunchAgent };