@outerlayer/cli 0.5.1 → 0.7.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 (92) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +19 -0
  3. package/dist/{agent-setup-IFH3FB5X.js → agent-setup-TXLDY5RV.js} +7 -6
  4. package/dist/{build-PAEKVYVK.js → build-JF5UOWKJ.js} +2 -1
  5. package/dist/build-info.json +1 -1
  6. package/dist/{check-4Q33B5ZE.js → check-ASZL5BEO.js} +39 -23
  7. package/dist/chunk-2VO25HHO.js +75 -0
  8. package/dist/{chunk-XDDW4FRS.js → chunk-5FWNEQNZ.js} +3 -3
  9. package/dist/{chunk-CHLVJNTE.js → chunk-6IYLDODX.js} +18 -6
  10. package/dist/{chunk-W7UTFZDH.js → chunk-6QWV3YDJ.js} +2 -2
  11. package/dist/{chunk-EGABNHZG.js → chunk-6ZZHWY2Z.js} +149 -18
  12. package/dist/{lima-XN65D7GN.js → chunk-77MM5RYD.js} +0 -1
  13. package/dist/chunk-7FTNIAHS.js +211 -0
  14. package/dist/{chunk-EMY4I27X.js → chunk-7ZR5UPMB.js} +555 -5
  15. package/dist/{chunk-F4NU4H3V.js → chunk-AII7CKTH.js} +133 -35
  16. package/dist/{chunk-ZXBITXTS.js → chunk-AIW6GLET.js} +1 -1
  17. package/dist/{chunk-UWH5DSUP.js → chunk-AJKEHABI.js} +507 -90
  18. package/dist/{chunk-RTXKYLCA.js → chunk-AQ54MKSQ.js} +2 -2
  19. package/dist/{chunk-Z62HNKEJ.js → chunk-CEBIA5FD.js} +31 -5
  20. package/dist/{chunk-EX6M5JPR.js → chunk-D6FXM5OW.js} +6302 -28611
  21. package/dist/{chunk-RMAZ2NWA.js → chunk-DGAIVMMN.js} +23 -2
  22. package/dist/{chunk-3DD6AOWC.js → chunk-DTXOJDEH.js} +26 -2
  23. package/dist/{chunk-T52PKE2Y.js → chunk-E4PIFIC3.js} +244 -17
  24. package/dist/{chunk-H5NCNVDA.js → chunk-EV3M3EH7.js} +2 -2
  25. package/dist/chunk-FBTE2A2X.js +38 -0
  26. package/dist/{chunk-DVDEBNQJ.js → chunk-GQD22KKN.js} +17 -6
  27. package/dist/{chunk-BCJNHMZT.js → chunk-HHJDN6AU.js} +18 -3
  28. package/dist/chunk-HKMPVPO7.js +55 -0
  29. package/dist/{chunk-UFFXNXLQ.js → chunk-JPR2WHHF.js} +31 -29
  30. package/dist/{chunk-ZMYPLFG3.js → chunk-M3SQ4YGD.js} +1 -1
  31. package/dist/{chunk-H4CK2YK4.js → chunk-MQPRB5K7.js} +18 -10
  32. package/dist/{chunk-AUFG23AA.js → chunk-OD2VTIHN.js} +19 -47
  33. package/dist/chunk-OUUINEYB.js +27 -0
  34. package/dist/chunk-PUN7LMNS.js +39 -0
  35. package/dist/{install-cmd-7WXOIFDO.js → chunk-Q5G2ODUZ.js} +1 -2
  36. package/dist/{chunk-B2G7JHHB.js → chunk-R7STSBLC.js} +1 -1
  37. package/dist/{chunk-OR6AEEDO.js → chunk-SSU747O6.js} +85 -25
  38. package/dist/chunk-T6OIJQRT.js +31 -0
  39. package/dist/{chunk-YYYXRJUW.js → chunk-TI7B7UEO.js} +1 -1
  40. package/dist/{chunk-WO2BXCTQ.js → chunk-VXLJORHL.js} +1 -1
  41. package/dist/{chunk-CBPPJSAR.js → chunk-WVGSB3Z2.js} +2 -2
  42. package/dist/{chunk-JLDZTG3A.js → chunk-X2FSFU4C.js} +4 -1
  43. package/dist/chunk-YNJEOGDL.js +22434 -0
  44. package/dist/{chunk-W4SQCOZU.js → chunk-ZOAEPWGL.js} +1 -1
  45. package/dist/{chunk-WOBNRVQT.js → chunk-ZYHKJKSU.js} +1 -1
  46. package/dist/{chunk-WIOZAJ2W.js → chunk-ZZXLM64C.js} +1 -1
  47. package/dist/{cli-LHSUYYMI.js → cli-LEVCGAQV.js} +154 -79
  48. package/dist/command-FLSYCGIB.js +187 -0
  49. package/dist/config-I3TVFYLH.js +10 -0
  50. package/dist/{connect-cmd-OJBFHZWP.js → connect-cmd-MJMUONDY.js} +4 -4
  51. package/dist/{context-adopt-UMA4O5NY.js → context-adopt-CE4KDXS3.js} +5 -4
  52. package/dist/{context-materialize-G2FUFC72.js → context-materialize-QL33HDJN.js} +5 -4
  53. package/dist/{dist-WDV7U7UJ.js → dist-PXBSA7QX.js} +2 -1
  54. package/dist/{doctor-MOA3RIEP.js → doctor-FVDT3KJZ.js} +46 -19
  55. package/dist/{emit-artifact-cmd-FDWYKURA.js → emit-artifact-cmd-7RPMRSVP.js} +13 -7
  56. package/dist/{emit-cmd-DCERAU27.js → emit-cmd-2QRMYPYO.js} +4 -3
  57. package/dist/{emit-criteria-cmd-OCXHLK34.js → emit-criteria-cmd-MKI7WETO.js} +12 -3
  58. package/dist/{emit-finding-cmd-POW7PKIA.js → emit-finding-cmd-RMLGZASB.js} +14 -3
  59. package/dist/{emit-result-cmd-ID35SH53.js → emit-result-cmd-MTXAZ2GV.js} +15 -9
  60. package/dist/{exec-client-4XGXV3XN.js → exec-client-NPYNUAF3.js} +4 -3
  61. package/dist/{guest-init-GENRHNP7.js → guest-init-JWNNAH2H.js} +4 -3
  62. package/dist/{hook-fast-EYENPK6F.js → hook-fast-XBOZ3N6L.js} +1 -1
  63. package/dist/{import-capture-cmd-AVZ4VIVJ.js → import-capture-cmd-T4556ELW.js} +3 -2
  64. package/dist/{import-ruler-cmd-GUHL6IY5.js → import-ruler-cmd-SG3UVGI7.js} +2 -1
  65. package/dist/index.js +3 -2
  66. package/dist/{init-FYDJAKTM.js → init-BACXESVV.js} +21 -14
  67. package/dist/{init-2IPOIXJV.js → init-VLW7H6RW.js} +40 -62
  68. package/dist/{init-cmd-KLIDRVXQ.js → init-cmd-VJFB5L7X.js} +10 -9
  69. package/dist/inspect-XAUSB7L3.js +63 -0
  70. package/dist/install-NMAPLLBQ.js +14 -0
  71. package/dist/install-cmd-B5SEU3G7.js +8 -0
  72. package/dist/lima-7NPCNGWT.js +6 -0
  73. package/dist/{logs-7RYUH5A6.js → logs-H55NWW4H.js} +1 -0
  74. package/dist/loop-D3243JKT.js +64 -0
  75. package/dist/{machine-ECU3QCZD.js → machine-EB3GEVI4.js} +20 -13
  76. package/dist/session-sender-LMACEJJS.js +29 -0
  77. package/dist/{starter-pack-LIZYMKYQ.js → starter-pack-MUXMAU4S.js} +4 -3
  78. package/dist/{status-CLATHCTQ.js → status-F4THTJJK.js} +36 -16
  79. package/dist/{sync-cmd-SWQ5KLPL.js → sync-cmd-M6JA3OEE.js} +7 -7
  80. package/dist/{watch-3YW6Z3DQ.js → watch-NB5TGPEZ.js} +7 -7
  81. package/dist/{work-claim-cmd-3OPWA77E.js → work-claim-cmd-UHMWOS3G.js} +14 -7
  82. package/dist/{work-cmd-NUR4CAG6.js → work-cmd-6UMCFTAW.js} +9 -3
  83. package/dist/{work-comment-cmd-6TTTYGHM.js → work-comment-cmd-E4LEUIXZ.js} +9 -3
  84. package/dist/{work-launch-YI4CJDAP.js → work-launch-R5HTAP6X.js} +1 -1
  85. package/dist/{work-open-pr-cmd-KZ7QQFLD.js → work-open-pr-cmd-OPZWGSEQ.js} +10 -4
  86. package/dist/{work-pr-cmd-QNUBE6IQ.js → work-pr-cmd-IDIAFFRU.js} +9 -2
  87. package/package.json +2 -1
  88. package/skill-pack/template/build/scripts/record-criteria.mjs +2 -1
  89. package/dist/chunk-D2KWEAN6.js +0 -55
  90. package/dist/chunk-ZOC7JSYC.js +0 -83
  91. package/dist/config-HL2AIR3K.js +0 -9
  92. package/dist/loop-NEBUGD2Q.js +0 -45
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { runnerUnit, UNIT_MARKER } from './chunk-FBTE2A2X.js';
4
+ import { checkFit } from './chunk-OUUINEYB.js';
5
+ import { builtinKind } from './chunk-DTXOJDEH.js';
6
+ import { runnerDir } from './chunk-ZM2IMMYN.js';
7
+ import { dirname } from 'path';
8
+ import { spawnSync } from 'child_process';
9
+
10
+ createRequire(import.meta.url);
11
+ function withSystemd(conf) {
12
+ const lines = (conf ?? "").split("\n");
13
+ if (lines.at(-1) === "") lines.pop();
14
+ const boot = lines.findIndex((line) => line.trim() === "[boot]");
15
+ if (boot === -1) {
16
+ return { text: `${lines.length === 0 ? "" : `${lines.join("\n")}
17
+
18
+ `}[boot]
19
+ systemd=true
20
+ `, changed: true };
21
+ }
22
+ let end = lines.findIndex((line, at) => at > boot && /^\s*\[/.test(line));
23
+ if (end === -1) end = lines.length;
24
+ const key = lines.findIndex((line, at) => at > boot && at < end && /^\s*systemd\s*=/.test(line));
25
+ if (key !== -1) {
26
+ if (/^\s*systemd\s*=\s*true\s*$/i.test(lines[key])) return { text: `${lines.join("\n")}
27
+ `, changed: false };
28
+ lines[key] = "systemd=true";
29
+ } else {
30
+ lines.splice(boot + 1, 0, "systemd=true");
31
+ }
32
+ return { text: `${lines.join("\n")}
33
+ `, changed: true };
34
+ }
35
+ function taskNameFor(unitName) {
36
+ return unitName;
37
+ }
38
+ function taskCommand(distro) {
39
+ return `wsl.exe -d ${distro} --exec sleep infinity`;
40
+ }
41
+ function realTaskScheduler(run = defaultRun) {
42
+ const message = (result) => (result.stderr.trim() || result.stdout.trim()).slice(0, 300);
43
+ return {
44
+ available: () => run(["/?"]).status === 0,
45
+ create: (name, command) => {
46
+ const result = run(["/Create", "/F", "/TN", name, "/SC", "ONLOGON", "/TR", command]);
47
+ return result.status === 0 ? { ok: true } : { ok: false, message: message(result) };
48
+ },
49
+ exists: (name) => run(["/Query", "/TN", name]).status === 0,
50
+ remove: (name) => {
51
+ const result = run(["/Delete", "/F", "/TN", name]);
52
+ return result.status === 0 ? { ok: true } : { ok: false, message: message(result) };
53
+ }
54
+ };
55
+ }
56
+ function defaultRun(args) {
57
+ const result = spawnSync("schtasks.exe", args, { encoding: "utf8", timeout: 3e4 });
58
+ return { status: result.error === void 0 ? result.status : null, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
59
+ }
60
+
61
+ // src/runner/service/install.ts
62
+ var DEFAULT_UNIT_NAME = "outerlayer-runner";
63
+ function unitPathFor(unitName = DEFAULT_UNIT_NAME) {
64
+ return `/etc/systemd/system/${unitName}.service`;
65
+ }
66
+ var outcome = (ok, state, lines, unit) => ({ ok, state, lines, ...unit === void 0 ? {} : { unit } });
67
+ function asRoot(deps, lines, say, argv) {
68
+ say(`sudo systemctl ${argv.join(" ")}`);
69
+ const result = deps.systemd.systemctl(argv, { sudo: true });
70
+ if (result.status !== 0) lines.push(`sudo systemctl ${argv.join(" ")} failed with exit status ${result.status}`);
71
+ return result.status === 0;
72
+ }
73
+ function attempt(lines, step) {
74
+ try {
75
+ step();
76
+ return true;
77
+ } catch (err) {
78
+ lines.push(err instanceof Error ? err.message : String(err));
79
+ return false;
80
+ }
81
+ }
82
+ var OWN_HOOKS = "the hooks are the host's own executables, so the runner does not write a service for them: run `outerlayer runner install --service --print` to print the unit, and edit it for your hooks";
83
+ function installService(request, deps) {
84
+ const unitName = request.unitName ?? DEFAULT_UNIT_NAME;
85
+ const unitPath = unitPathFor(unitName);
86
+ const lines = [];
87
+ const say = deps.say ?? ((line) => void lines.push(line));
88
+ const kind = builtinKind(request.hooks.provision);
89
+ const own = kind === "own" || builtinKind(request.hooks.cleanup) === "own";
90
+ const unit = runnerUnit({
91
+ user: request.user,
92
+ configDir: dirname(request.configPath),
93
+ execStart: request.execStart,
94
+ timeLimitMinutes: request.timeLimitMinutes,
95
+ docker: kind === "container" || kind === "vm" || builtinKind(request.hooks.cleanup) === "container" || builtinKind(request.hooks.cleanup) === "vm"
96
+ });
97
+ if (request.print === true) return outcome(true, "printed", [], unit);
98
+ if (own) return outcome(false, "refused", [OWN_HOOKS]);
99
+ if (deps.platform === "darwin" || deps.platform === "windows") {
100
+ return outcome(false, "refused", [
101
+ deps.platform === "darwin" ? "the runner on a Mac lives in a Linux VM, and its service is set up there: run `outerlayer runner init --vm`" : "the runner on Windows lives in WSL2: run this from a WSL2 shell"
102
+ ]);
103
+ }
104
+ if (!deps.systemd.running()) {
105
+ if (deps.platform === "wsl2" && deps.wslConf !== void 0) {
106
+ const merged = withSystemd(deps.wslConf.read());
107
+ if (merged.changed) {
108
+ say("sudo tee /etc/wsl.conf");
109
+ const wslConf = deps.wslConf;
110
+ if (!attempt(lines, () => wslConf.write(merged.text))) return outcome(false, "refused", lines);
111
+ }
112
+ lines.push(
113
+ "systemd is not running in this WSL2 distribution. `systemd=true` is " + (merged.changed ? "now" : "already") + " in /etc/wsl.conf; run `wsl --shutdown` once from Windows, then run this again. No service or scheduled task is set up until then"
114
+ );
115
+ return outcome(true, "needs-wsl-shutdown", lines);
116
+ }
117
+ return outcome(false, "refused", ["systemd was not found (/run/systemd/system is missing), so no service is set up. `--print` prints the unit for another service manager"]);
118
+ }
119
+ if (kind === "container" || kind === "vm") {
120
+ const fit = checkFit({ concurrency: request.concurrency, buildMemory: request.buildMemory, usableBytes: deps.usableMemoryBytes });
121
+ if (!fit.fits) return outcome(false, "refused", [`${fit.message}. Nothing is set up: lower runner.concurrency or runner.build.memory, then run this again`]);
122
+ }
123
+ const existing = deps.systemd.read(unitPath);
124
+ const write = () => {
125
+ say(`sudo tee ${unitPath}`);
126
+ return attempt(lines, () => deps.systemd.write(unitPath, unit));
127
+ };
128
+ const running = () => deps.systemd.systemctl(["is-enabled", unitName]).status === 0 && deps.systemd.systemctl(["is-active", unitName]).status === 0;
129
+ const ensureTask = () => {
130
+ if (deps.platform === "wsl2") lines.push(...ensureLoginTask(deps, unitName));
131
+ };
132
+ if (existing === unit) {
133
+ if (!running() && !asRoot(deps, lines, say, ["enable", "--now", unitName])) return outcome(false, "refused", lines);
134
+ lines.push(`the service ${unitName} is current: the unit already matches the config, so nothing was changed`);
135
+ ensureTask();
136
+ return outcome(true, "current", lines);
137
+ }
138
+ if (existing === void 0) {
139
+ if (!write()) return outcome(false, "refused", lines);
140
+ if (!asRoot(deps, lines, say, ["daemon-reload"]) || !asRoot(deps, lines, say, ["enable", "--now", unitName])) {
141
+ say(`sudo rm ${unitPath}`);
142
+ if (attempt(lines, () => deps.systemd.remove(unitPath))) deps.systemd.systemctl(["daemon-reload"], { sudo: true });
143
+ return outcome(false, "refused", lines);
144
+ }
145
+ lines.push(`installed and started the service ${unitName}: the runner starts at boot and after a crash`);
146
+ ensureTask();
147
+ return outcome(true, "installed", lines);
148
+ }
149
+ if (!existing.startsWith(UNIT_MARKER) && request.replace !== true) {
150
+ lines.push(`${unitPath} differs from what this command writes, and it was not written by it, so it was left alone. Pass --replace to replace it, or \`--print\` to compare`);
151
+ return outcome(false, "differs", lines);
152
+ }
153
+ if (!write()) return outcome(false, "refused", lines);
154
+ if (!asRoot(deps, lines, say, ["daemon-reload"])) return outcome(false, "refused", lines);
155
+ if ((!existing.startsWith(UNIT_MARKER) || !running()) && !asRoot(deps, lines, say, ["enable", "--now", unitName])) return outcome(false, "refused", lines);
156
+ ensureTask();
157
+ if (deps.buildsRunning(runnerDir(request.configPath))) {
158
+ lines.push(`rewrote ${unitPath}; a build is running, so the service was not restarted and the new unit applies at the next restart`);
159
+ return outcome(true, "pending-restart", lines);
160
+ }
161
+ if (!asRoot(deps, lines, say, ["restart", unitName])) return outcome(false, "refused", lines);
162
+ lines.push(`rewrote ${unitPath} and restarted the service ${unitName}`);
163
+ return outcome(true, "updated", lines);
164
+ }
165
+ function ensureLoginTask(deps, unitName) {
166
+ const tasks = deps.tasks;
167
+ const name = taskNameFor(unitName);
168
+ const command = taskCommand(deps.distro ?? "Ubuntu");
169
+ const manual = `from Windows, run: schtasks /Create /F /TN ${name} /SC ONLOGON /TR "${command}"`;
170
+ if (tasks === void 0 || !tasks.available()) {
171
+ return [`Windows interop is off, so the scheduled task that starts WSL2 at login was not made: ${manual}`];
172
+ }
173
+ if (tasks.exists(name)) return [`the scheduled task ${name} already starts WSL2 at login`];
174
+ const created = tasks.create(name, command);
175
+ return created.ok ? [`made the scheduled task ${name}: it starts WSL2 at login and keeps it running, so the service runs from login`] : [`the scheduled task ${name} was not made (${created.message ?? "schtasks.exe failed"}): ${manual}`];
176
+ }
177
+ function uninstallService(request, deps) {
178
+ const unitName = request.unitName ?? DEFAULT_UNIT_NAME;
179
+ const unitPath = unitPathFor(unitName);
180
+ const lines = [];
181
+ const say = deps.say ?? ((line) => void lines.push(line));
182
+ const taskName = taskNameFor(unitName);
183
+ const removeTask = () => {
184
+ if (deps.platform !== "wsl2" || deps.tasks === void 0 || !deps.tasks.available() || !deps.tasks.exists(taskName)) return;
185
+ const removed = deps.tasks.remove(taskName);
186
+ lines.push(removed.ok ? `removed the scheduled task ${taskName}` : `the scheduled task ${taskName} was not removed (${removed.message ?? "schtasks.exe failed"}): from Windows, run: schtasks /Delete /F /TN ${taskName}`);
187
+ };
188
+ const existing = deps.systemd.running() ? deps.systemd.read(unitPath) : void 0;
189
+ if (existing === void 0) {
190
+ removeTask();
191
+ lines.push(`no service ${unitName} is installed, so there is nothing to remove`);
192
+ return outcome(true, "absent", lines);
193
+ }
194
+ if (!existing.startsWith(UNIT_MARKER)) {
195
+ lines.push(`${unitPath} was not written by \`runner install --service\`, so it was left alone`);
196
+ return outcome(false, "differs", lines);
197
+ }
198
+ if (!asRoot(deps, lines, say, ["disable", "--now", unitName])) return outcome(false, "refused", lines);
199
+ say(`sudo rm ${unitPath}`);
200
+ if (!attempt(lines, () => deps.systemd.remove(unitPath))) return outcome(false, "refused", lines);
201
+ if (deps.systemd.read(unitPath) !== void 0) {
202
+ lines.push(`${unitPath} is still there after the remove, so the service ${unitName} was not removed`);
203
+ return outcome(false, "refused", lines);
204
+ }
205
+ if (!asRoot(deps, lines, say, ["daemon-reload"])) return outcome(false, "refused", lines);
206
+ removeTask();
207
+ lines.push(`removed the service ${unitName}`);
208
+ return outcome(true, "removed", lines);
209
+ }
210
+
211
+ export { DEFAULT_UNIT_NAME, installService, realTaskScheduler, uninstallService, unitPathFor };