@outerlayer/cli 0.17.0 → 0.19.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 (160) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +36 -2
  3. package/dist/agent-setup-TPR4BBFV.js +27 -0
  4. package/dist/{artifact-link-cmd-2F47LT7F.js → artifact-link-cmd-W7XF5BFB.js} +4 -3
  5. package/dist/{build-BG2YENGK.js → build-TQ6X5PYN.js} +5 -4
  6. package/dist/build-info.json +1 -1
  7. package/dist/{builder-2D3WSOOY.js → builder-C4QNTYMS.js} +5 -6
  8. package/dist/{check-5XE7XMC4.js → check-UWQAL2MN.js} +56 -52
  9. package/dist/{chunk-R7PF5OOU.js → chunk-3CWUOFV6.js} +21 -14
  10. package/dist/{chunk-4NPZVMJU.js → chunk-3UZTIRLF.js} +60 -4
  11. package/dist/chunk-452TM7J4.js +502 -0
  12. package/dist/{chunk-NNRTPG7X.js → chunk-5GHGAF6P.js} +4 -4
  13. package/dist/{chunk-WVUIRTWA.js → chunk-5MYFOW5V.js} +196 -37
  14. package/dist/{chunk-HGECYFEI.js → chunk-5TN7GLYX.js} +138 -109
  15. package/dist/{chunk-VQYBXPI7.js → chunk-6D7GT22T.js} +5 -5
  16. package/dist/{chunk-G5NPRL2K.js → chunk-6U3MV7TZ.js} +687 -76
  17. package/dist/{chunk-42ZI52JW.js → chunk-6X4SQ5MY.js} +4 -3
  18. package/dist/{chunk-MAK5LYRF.js → chunk-7KSSBQTS.js} +278 -535
  19. package/dist/{chunk-NWQTTSBB.js → chunk-7SJPQWBM.js} +41 -16
  20. package/dist/{chunk-J5M7BSJQ.js → chunk-7VUK5RFS.js} +23 -4
  21. package/dist/{chunk-OZGHL5Y5.js → chunk-7YP7T6GV.js} +2 -2
  22. package/dist/{chunk-TI7B7UEO.js → chunk-AMFWYESY.js} +1 -1
  23. package/dist/{chunk-YRYMNBGQ.js → chunk-ANPNT6PM.js} +2 -1
  24. package/dist/{chunk-4KSQKQXE.js → chunk-BM63HB2M.js} +20 -16
  25. package/dist/{chunk-4Q7OMKAT.js → chunk-BQXFBI7T.js} +81 -5
  26. package/dist/{chunk-ADJRGVZC.js → chunk-BWSUPRDK.js} +1 -1
  27. package/dist/{chunk-3NSNIIBE.js → chunk-CG36IONK.js} +18 -17
  28. package/dist/chunk-CJJDJ7AT.js +56 -0
  29. package/dist/chunk-CRB3POP4.js +34 -0
  30. package/dist/{chunk-LWSJQJC6.js → chunk-DADWEUH3.js} +7 -7
  31. package/dist/{chunk-KG5ECZGJ.js → chunk-DJ3ZUSC3.js} +11 -11
  32. package/dist/{chunk-F47JBIAW.js → chunk-DSF2FWAZ.js} +2 -1
  33. package/dist/{chunk-N5FOE5PS.js → chunk-ECODQ7II.js} +4 -33
  34. package/dist/{chunk-HHJDN6AU.js → chunk-ESVW4MJY.js} +1 -1
  35. package/dist/{chunk-YO4PSLVB.js → chunk-FVMYKL6L.js} +1 -1
  36. package/dist/chunk-G2YNJIGF.js +44 -0
  37. package/dist/{chunk-WUYTWNMA.js → chunk-G3IJ3N6F.js} +5 -3
  38. package/dist/{chunk-QKFB4K7X.js → chunk-GGOMT6KF.js} +1 -1
  39. package/dist/{chunk-N6LURUEF.js → chunk-GOSFARMS.js} +1 -1
  40. package/dist/{chunk-S3NYLI2P.js → chunk-HPJNLETG.js} +93 -38
  41. package/dist/{chunk-IP7BIBXJ.js → chunk-IF2OSI5T.js} +35 -1
  42. package/dist/{chunk-C5Y5ZJJZ.js → chunk-IXYZUUYQ.js} +5 -4
  43. package/dist/chunk-JBJTC3UU.js +35 -0
  44. package/dist/chunk-KYS3TQAG.js +296 -0
  45. package/dist/chunk-LLM3I5YU.js +89 -0
  46. package/dist/{chunk-YU3VKZ5F.js → chunk-LRG3ZMZD.js} +6 -7
  47. package/dist/{chunk-V7NIVOFD.js → chunk-M2ZWWTWZ.js} +1 -1
  48. package/dist/{chunk-ERI5GBNI.js → chunk-MLAAT4S6.js} +3 -3
  49. package/dist/{chunk-O765JR4W.js → chunk-MTP2QEVO.js} +1 -1
  50. package/dist/{chunk-ZM2IMMYN.js → chunk-N2PXGQRL.js} +9 -1
  51. package/dist/{chunk-BWQ66M6N.js → chunk-NFIR3GEE.js} +98 -24
  52. package/dist/{chunk-POJMEEJJ.js → chunk-NNLA7LLQ.js} +1 -1
  53. package/dist/{chunk-PLMTLGYH.js → chunk-NTRCK4NH.js} +5 -4
  54. package/dist/{chunk-22RMZBGW.js → chunk-P53VEAOF.js} +1 -1
  55. package/dist/{chunk-RLYZLUQ7.js → chunk-QEFKA2QD.js} +2 -2
  56. package/dist/{chunk-JXYVA45C.js → chunk-QNLX477Q.js} +1200 -322
  57. package/dist/{chunk-UGP76IZ5.js → chunk-S5PCA3SN.js} +1 -1
  58. package/dist/{chunk-RMWLBUCJ.js → chunk-SADUQSO4.js} +1 -1
  59. package/dist/{chunk-GQD22KKN.js → chunk-T2Q7ISPE.js} +2 -1
  60. package/dist/chunk-TUSYOL2O.js +113 -0
  61. package/dist/{chunk-NBGDBTO2.js → chunk-TWF3RICI.js} +9 -8
  62. package/dist/{chunk-ONKJUM6R.js → chunk-UVG35MWF.js} +1 -1
  63. package/dist/{chunk-KMDLFI4P.js → chunk-VMJPFQNE.js} +1 -1
  64. package/dist/chunk-VRRGENS4.js +144 -0
  65. package/dist/{chunk-PTW6W5NR.js → chunk-VYAL7PUE.js} +1 -1
  66. package/dist/{chunk-OHVJA253.js → chunk-WFSUUUAN.js} +47 -30
  67. package/dist/{chunk-RSNSZ2LJ.js → chunk-X5H2WYMB.js} +1 -1
  68. package/dist/{chunk-HYZRY4KZ.js → chunk-XNGMQ27R.js} +16 -6
  69. package/dist/chunk-XSZD4AZ6.js +490 -0
  70. package/dist/chunk-YQR6E6OV.js +109 -0
  71. package/dist/{chunk-XCK2YAY5.js → chunk-YVZJ6GKF.js} +3 -2
  72. package/dist/{chunk-2MNPHL23.js → chunk-ZHAUSEEN.js} +38 -15
  73. package/dist/claude-launcher-FPPHZPMR.js +115 -0
  74. package/dist/{cli-A76AXELQ.js → cli-HROW5T7X.js} +567 -285
  75. package/dist/{command-R2QCVX55.js → command-OEAV7BVX.js} +42 -40
  76. package/dist/{config-N3C6DFQA.js → config-CLSGABD5.js} +2 -3
  77. package/dist/{connect-cmd-JBKAQNSF.js → connect-cmd-UNYKGDAL.js} +10 -9
  78. package/dist/context-cleanup-HSVYDGMI.js +9 -0
  79. package/dist/control-plane-launch-NYVDJESK.js +216 -0
  80. package/dist/{dist-LKDWQWZ7.js → dist-WO7NOJNG.js} +2 -2
  81. package/dist/{docker-4J77Q2BF.js → docker-BFU3AQGY.js} +7 -8
  82. package/dist/{doctor-4TAIARIG.js → doctor-23BBJWBX.js} +48 -42
  83. package/dist/{emit-artifact-cmd-MPZVR56N.js → emit-artifact-cmd-O6SCM7TV.js} +27 -24
  84. package/dist/emit-cmd-7SP3I6UZ.js +14 -0
  85. package/dist/{emit-criteria-cmd-EFGYI23V.js → emit-criteria-cmd-LQNERZRV.js} +17 -16
  86. package/dist/{emit-finding-cmd-5GF3V4LR.js → emit-finding-cmd-AJPKHIOV.js} +18 -17
  87. package/dist/{emit-result-cmd-I4OADKMD.js → emit-result-cmd-RUICOKWQ.js} +22 -19
  88. package/dist/emit-step-cmd-347XNGPQ.js +26 -0
  89. package/dist/{exec-client-CQV746UF.js → exec-client-7UMESIE2.js} +3 -4
  90. package/dist/governing-source-V5EEGPTA.js +34 -0
  91. package/dist/guest-init-KPBQOFE5.js +9 -0
  92. package/dist/{hook-fast-R4FVSZ2H.js → hook-fast-YAZG5MDX.js} +5 -2
  93. package/dist/host-key-U4B5QETR.js +8 -0
  94. package/dist/{import-capture-cmd-EQ35EARA.js → import-capture-cmd-KKFFOGWP.js} +7 -5
  95. package/dist/{import-ruler-cmd-BGAZXCQM.js → import-ruler-cmd-5P6JOXQN.js} +5 -3
  96. package/dist/index.js +8 -4
  97. package/dist/{init-PWU5YVL7.js → init-LWHDMZQG.js} +40 -38
  98. package/dist/init-MOINY2DD.js +53 -0
  99. package/dist/{init-cmd-XAVO7HYZ.js → init-cmd-HSMRID6A.js} +75 -30
  100. package/dist/{init-update-CVCWD6VS.js → init-update-LWX5MPPR.js} +29 -29
  101. package/dist/{install-HDBIPHTX.js → install-4SEFIIW2.js} +4 -5
  102. package/dist/{install-cmd-5HYMXLEW.js → install-cmd-QDOTR4OM.js} +5 -4
  103. package/dist/{login-browser-QZCS3WW6.js → login-browser-DX35MARZ.js} +5 -4
  104. package/dist/{logout-cmd-QAUFDJ6J.js → logout-cmd-5V5MMBSY.js} +3 -2
  105. package/dist/{logs-H55NWW4H.js → logs-AMLKC3VP.js} +3 -1
  106. package/dist/loop-SYYPVFYW.js +77 -0
  107. package/dist/machine-6X63G2KH.js +48 -0
  108. package/dist/{mcp-install-cmd-FDTPQQS6.js → mcp-install-cmd-GGWB3JJ2.js} +3 -2
  109. package/dist/{mcp-serve-cmd-3KVYEUXG.js → mcp-serve-cmd-N3UTY6EE.js} +11 -9
  110. package/dist/paths-7XP63OAX.js +7 -0
  111. package/dist/pending-532ABZYC.js +8 -0
  112. package/dist/{pidfile-UZRH774M.js → pidfile-3DM4OBKO.js} +4 -2
  113. package/dist/{record-C43U4PKS.js → record-3WP6V2K5.js} +3 -2
  114. package/dist/{runner-env-EN7YE7QX.js → runner-env-FRZFFHDF.js} +5 -6
  115. package/dist/{runner-key-6SLBI7PP.js → runner-key-276SJU5R.js} +40 -38
  116. package/dist/session-sender-GGIY5OAX.js +36 -0
  117. package/dist/{pending-MXLSRYMO.js → shell-function-NRD4KVUK.js} +1 -2
  118. package/dist/spec-cmd-SIZBRN7D.js +115 -0
  119. package/dist/{starter-pack-KF66HGZR.js → starter-pack-6LQVLBIA.js} +7 -5
  120. package/dist/{status-7VNQN5PW.js → status-NAK6DYSF.js} +46 -42
  121. package/dist/{statusline-fast-SVTMBMD7.js → statusline-fast-KN3BX67H.js} +2 -1
  122. package/dist/sync-cmd-G7GF4UTW.js +35 -0
  123. package/dist/{watch-23HSAEI6.js → watch-EVM5GSMP.js} +22 -15
  124. package/dist/{work-claim-cmd-LKL3GHFQ.js → work-claim-cmd-3J3QW56U.js} +26 -29
  125. package/dist/work-cmd-5EPPD33Y.js +24 -0
  126. package/dist/{work-comment-cmd-AWHP56M2.js → work-comment-cmd-2ACOZQAE.js} +16 -15
  127. package/dist/{work-launch-R5HTAP6X.js → work-launch-LEQ2MPM3.js} +3 -1
  128. package/dist/work-logs-cmd-QDD2NMJL.js +60 -0
  129. package/dist/{work-open-pr-cmd-IOM4AVQQ.js → work-open-pr-cmd-XNCEI4ZM.js} +18 -17
  130. package/dist/work-pr-cmd-H4OQR26N.js +25 -0
  131. package/package.json +3 -2
  132. package/skill-pack/maintained/outerlayer-amend/SKILL.md +4 -2
  133. package/skill-pack/maintained/outerlayer-cli/SKILL.md +9 -2
  134. package/skill-pack/maintained/outerlayer-cli/references/find-out-why-a-build-ended.md +76 -0
  135. package/skill-pack/shipped.json +18 -2
  136. package/skill-pack/template/build/SKILL.md +23 -10
  137. package/skill-pack/template/build/references/agent-briefs.md +3 -1
  138. package/skill-pack/template/build/references/criteria-judge.md +27 -0
  139. package/skill-pack/template/build/references/release.md +22 -29
  140. package/skill-pack/template/spec/SKILL.md +35 -10
  141. package/skill-pack/validators/acceptance-criteria.yaml +25 -26
  142. package/dist/agent-setup-T624CWPD.js +0 -27
  143. package/dist/chunk-6Y5RUTHO.js +0 -39
  144. package/dist/chunk-BYT5O5RV.js +0 -61
  145. package/dist/chunk-WTE3MQML.js +0 -133
  146. package/dist/chunk-XCCLFVXM.js +0 -401
  147. package/dist/chunk-YZ5F4PIS.js +0 -797
  148. package/dist/context-adopt-NFHCLVUB.js +0 -108
  149. package/dist/context-materialize-IV2TUUFO.js +0 -22
  150. package/dist/emit-cmd-IJ3GVRFD.js +0 -12
  151. package/dist/guest-init-LJP5GPZF.js +0 -9
  152. package/dist/host-key-YMLXY6QB.js +0 -7
  153. package/dist/init-6XPDRDTC.js +0 -51
  154. package/dist/loop-FK3LPMMZ.js +0 -73
  155. package/dist/machine-SQXEBAM2.js +0 -46
  156. package/dist/paths-OYKMVYJP.js +0 -6
  157. package/dist/session-sender-FO3LCREV.js +0 -29
  158. package/dist/sync-cmd-GVPPFZ3G.js +0 -28
  159. package/dist/work-cmd-Z2WXLZPH.js +0 -23
  160. package/dist/work-pr-cmd-HNEUIEN7.js +0 -24
@@ -0,0 +1,296 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { launchRefusalForSession } from './chunk-ESVW4MJY.js';
4
+ import { autoConnect, detectActiveSession } from './chunk-IF2OSI5T.js';
5
+ import { acquireSpoolLockAt } from './chunk-G3IJ3N6F.js';
6
+ import { resolveRepoIdentity } from './chunk-QNLX477Q.js';
7
+ import { C } from './chunk-ANPNT6PM.js';
8
+ import { launchedItemForSession } from './chunk-T2Q7ISPE.js';
9
+ import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
10
+ import { defaultFetch } from './chunk-FVMYKL6L.js';
11
+ import { resolveGatewayCredentials, unauthorizedMessage } from './chunk-NTRCK4NH.js';
12
+ import { StepNameSchema, StepBodySchema } from './chunk-7VUK5RFS.js';
13
+ import { homeFromEnv } from './chunk-VNVZDWO3.js';
14
+ import { randomUUID } from 'crypto';
15
+ import { readFileSync, existsSync, mkdirSync, writeFileSync, renameSync, appendFileSync } from 'fs';
16
+ import { join, dirname } from 'path';
17
+
18
+ createRequire(import.meta.url);
19
+ var STEP_RETRY_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
20
+ var STEP_STATE_RESOLVED_CAP = 5e3;
21
+ function stepsSpoolPath(home = homeFromEnv()) {
22
+ return join(home, ".outerlayer", "spool", "steps.jsonl");
23
+ }
24
+ function stepsStatePath(home = homeFromEnv()) {
25
+ return join(home, ".outerlayer", "spool", "steps.state.json");
26
+ }
27
+ function stepsLockPath(home = homeFromEnv()) {
28
+ return join(home, ".outerlayer", "spool", "steps.lock");
29
+ }
30
+ function appendStepRecord(record, home = homeFromEnv()) {
31
+ const path = stepsSpoolPath(home);
32
+ mkdirSync(dirname(path), { recursive: true });
33
+ appendFileSync(path, JSON.stringify(record) + "\n");
34
+ }
35
+ function isRecord(value) {
36
+ if (value === null || typeof value !== "object") return false;
37
+ const r = value;
38
+ return typeof r.clientStepId === "string" && typeof r.name === "string" && typeof r.startedAt === "string" && typeof r.itemNumber === "number" && typeof r.appId === "string" && typeof r.cwd === "string" && typeof r.spooledAt === "string";
39
+ }
40
+ function readStepRecords(home = homeFromEnv()) {
41
+ const path = stepsSpoolPath(home);
42
+ if (!existsSync(path)) return { records: [], skipped: 0 };
43
+ const records = [];
44
+ let skipped = 0;
45
+ for (const line of readFileSync(path, "utf8").split("\n")) {
46
+ if (line.trim() === "") continue;
47
+ try {
48
+ const parsed = JSON.parse(line);
49
+ if (isRecord(parsed)) records.push(parsed);
50
+ else skipped += 1;
51
+ } catch {
52
+ skipped += 1;
53
+ }
54
+ }
55
+ return { records, skipped };
56
+ }
57
+ function readStepState(home = homeFromEnv()) {
58
+ const state = /* @__PURE__ */ new Map();
59
+ let parsed;
60
+ try {
61
+ parsed = JSON.parse(readFileSync(stepsStatePath(home), "utf8"));
62
+ } catch {
63
+ return state;
64
+ }
65
+ const entries = parsed?.records;
66
+ if (!Array.isArray(entries)) return state;
67
+ for (const entry of entries) {
68
+ const e = entry;
69
+ if (typeof e?.clientStepId !== "string") continue;
70
+ if (e.state === "pending" || e.state === "uploaded" || e.state === "dropped") state.set(e.clientStepId, e.state);
71
+ }
72
+ return state;
73
+ }
74
+ function writeStepState(state, home = homeFromEnv()) {
75
+ try {
76
+ const path = stepsStatePath(home);
77
+ mkdirSync(dirname(path), { recursive: true });
78
+ const pending = [];
79
+ const resolved = [];
80
+ for (const pair of state.entries()) (pair[1] === "pending" ? pending : resolved).push(pair);
81
+ const kept = resolved.length > STEP_STATE_RESOLVED_CAP ? resolved.slice(-STEP_STATE_RESOLVED_CAP) : resolved;
82
+ const records = [...pending, ...kept].map(([clientStepId, disposition]) => ({ clientStepId, state: disposition }));
83
+ const tmp = `${path}.tmp`;
84
+ writeFileSync(tmp, JSON.stringify({ version: 1, records }));
85
+ renameSync(tmp, path);
86
+ return true;
87
+ } catch {
88
+ return false;
89
+ }
90
+ }
91
+
92
+ // src/emit-step-cmd.ts
93
+ var EmitStepError = class extends Error {
94
+ };
95
+ function firstIssue(error) {
96
+ const issue = error.issues[0];
97
+ if (!issue) return "malformed";
98
+ const path = issue.path.join(".");
99
+ return path === "" || path === "name" ? issue.message : `${path}: ${issue.message}`;
100
+ }
101
+ function detectSessionId(opts) {
102
+ const identity = resolveRepoIdentity(opts.cwd);
103
+ return detectActiveSession({
104
+ env: opts.env,
105
+ home: opts.home,
106
+ cwds: identity.repoRoot !== void 0 ? [identity.repoRoot, opts.cwd] : [opts.cwd],
107
+ nowMs: opts.now().getTime()
108
+ });
109
+ }
110
+ function resolveItem(opts) {
111
+ if (opts.sessionId !== void 0) {
112
+ const refusal = launchRefusalForSession(opts.sessionId, opts.home);
113
+ if (refusal !== void 0) throw new EmitStepError(refusal);
114
+ }
115
+ if (opts.item !== void 0) {
116
+ const number = parseTrackerNumber(opts.item);
117
+ if (number === void 0) throw new EmitStepError(trackerNumberError("--item", opts.item));
118
+ return number;
119
+ }
120
+ const launched = opts.sessionId !== void 0 ? launchedItemForSession(opts.sessionId, opts.home) : void 0;
121
+ if (launched !== void 0) return launched;
122
+ throw new EmitStepError(
123
+ "no work item to report this step on. Pass --item <number>, or run inside a recorded session launched with OUTERLAYER_WORK, whose launch record names the item."
124
+ );
125
+ }
126
+ async function refusalReason(response) {
127
+ let text = "";
128
+ try {
129
+ text = await response.text();
130
+ } catch {
131
+ }
132
+ try {
133
+ const message = JSON.parse(text).error?.message;
134
+ if (typeof message === "string" && message !== "") return message;
135
+ } catch {
136
+ }
137
+ return text.replace(/\s+/g, " ").trim().slice(0, 160);
138
+ }
139
+ function stepUrl(base) {
140
+ return new URL("/v1/work-items/steps", base).toString();
141
+ }
142
+ function stepHeaders(apiKey, appId) {
143
+ return { "content-type": "application/json", authorization: `Bearer ${apiKey}`, "x-outerlayer-app-id": appId };
144
+ }
145
+ async function runEmitStep(opts) {
146
+ const cwd = opts.cwd ?? process.cwd();
147
+ const home = opts.home ?? homeFromEnv();
148
+ const env = opts.env ?? process.env;
149
+ const now = opts.now ?? (() => /* @__PURE__ */ new Date());
150
+ const name = StepNameSchema.safeParse(opts.name);
151
+ if (!name.success) throw new EmitStepError(`invalid step name: ${firstIssue(name.error)}`);
152
+ const sessionId = detectSessionId({ cwd, home, env, now });
153
+ const itemNumber = resolveItem({ item: opts.item, sessionId, home });
154
+ const parsed = StepBodySchema.safeParse({
155
+ clientStepId: randomUUID(),
156
+ name: opts.name,
157
+ item: { number: itemNumber },
158
+ startedAt: now().toISOString()
159
+ });
160
+ if (!parsed.success) throw new EmitStepError(`invalid step: ${firstIssue(parsed.error)}`);
161
+ const body = parsed.data;
162
+ const refusal = await autoConnect({ ...opts, cwd, home, env });
163
+ if (refusal !== void 0) throw new EmitStepError(refusal);
164
+ const resolved = resolveGatewayCredentials({ ...opts, cwd, home, env });
165
+ if (!resolved.ok) throw new EmitStepError(resolved.message);
166
+ const { url, credential: apiKey, appId } = resolved;
167
+ const fetchImpl = opts.fetchImpl ?? defaultFetch;
168
+ const endpoint = stepUrl(url);
169
+ const spool = (why) => {
170
+ appendStepRecord(
171
+ {
172
+ clientStepId: body.clientStepId,
173
+ name: body.name,
174
+ startedAt: body.startedAt,
175
+ itemNumber,
176
+ appId,
177
+ cwd,
178
+ spooledAt: now().toISOString()
179
+ },
180
+ home
181
+ );
182
+ const output2 = `${C.yellow}!${C.reset} step "${body.name}" queued (${why}); \`outerlayer sync\` will send it`;
183
+ if (!opts.quiet) process.stdout.write(output2 + "\n");
184
+ return { data: { clientStepId: body.clientStepId, queued: true }, output: output2, exitCode: 0, queued: true };
185
+ };
186
+ let response;
187
+ try {
188
+ response = await fetchImpl(endpoint, { method: "POST", headers: stepHeaders(apiKey, appId), body: JSON.stringify(body) });
189
+ } catch (err) {
190
+ return spool(`could not reach ${new URL(endpoint).host}: ${String(err)}`);
191
+ }
192
+ if (response.status >= 500) return spool(`the gateway answered ${response.status}`);
193
+ if (response.status === 401) throw new EmitStepError(unauthorizedMessage(apiKey));
194
+ if (!response.ok) {
195
+ const reason = await refusalReason(response);
196
+ throw new EmitStepError(`step refused (${response.status})${reason ? `: ${reason}` : ""}`);
197
+ }
198
+ let data = {};
199
+ try {
200
+ data = (await response.json()).data ?? {};
201
+ } catch {
202
+ }
203
+ const output = opts.json ? JSON.stringify(data) : `${C.green}\u2713${C.reset} step started: ${body.name} ${C.dim}\xB7${C.reset} item #${itemNumber}`;
204
+ if (!opts.quiet) process.stdout.write(output + "\n");
205
+ return { data, output, exitCode: 0, queued: false };
206
+ }
207
+ async function uploadSpooledSteps(opts) {
208
+ const { home } = opts;
209
+ const waiting = () => {
210
+ const state = readStepState(home);
211
+ return readStepRecords(home).records.filter((record) => {
212
+ const disposition = state.get(record.clientStepId);
213
+ return disposition !== "uploaded" && disposition !== "dropped";
214
+ });
215
+ };
216
+ if (waiting().length === 0) return void 0;
217
+ const lock = await acquireSpoolLockAt(stepsLockPath(home), { waitMs: opts.lockWaitMs });
218
+ if (!lock.ok) {
219
+ if (!opts.quiet) {
220
+ process.stderr.write(`${C.yellow}!${C.reset} steps not sent: ${lock.reason}; they stay queued for the next sync
221
+ `);
222
+ }
223
+ return { uploaded: 0, dropped: 0, pending: waiting().length };
224
+ }
225
+ try {
226
+ const fetchImpl = opts.fetchImpl ?? defaultFetch;
227
+ const nowMs = opts.nowMs ?? Date.now();
228
+ const state = readStepState(home);
229
+ const result = { uploaded: 0, dropped: 0, pending: 0 };
230
+ let stopped = false;
231
+ for (const record of readStepRecords(home).records) {
232
+ const saved = state.get(record.clientStepId);
233
+ if (saved === "uploaded" || saved === "dropped") continue;
234
+ const spooledMs = Date.parse(record.spooledAt);
235
+ if (Number.isFinite(spooledMs) && nowMs - spooledMs > STEP_RETRY_MAX_AGE_MS) {
236
+ state.set(record.clientStepId, "dropped");
237
+ result.dropped += 1;
238
+ continue;
239
+ }
240
+ const foreign = opts.kind === "key" && record.appId !== opts.appId || opts.onlyItem !== void 0 && record.itemNumber !== opts.onlyItem;
241
+ if (stopped || foreign) {
242
+ result.pending += 1;
243
+ continue;
244
+ }
245
+ const body = {
246
+ clientStepId: record.clientStepId,
247
+ name: record.name,
248
+ item: { number: record.itemNumber },
249
+ startedAt: record.startedAt
250
+ };
251
+ let response;
252
+ try {
253
+ response = await fetchImpl(stepUrl(opts.url), {
254
+ method: "POST",
255
+ headers: stepHeaders(opts.apiKey, opts.kind === "login" ? record.appId : opts.appId),
256
+ body: JSON.stringify(body)
257
+ });
258
+ } catch {
259
+ stopped = true;
260
+ result.pending += 1;
261
+ continue;
262
+ }
263
+ if (response.ok) {
264
+ state.set(record.clientStepId, "uploaded");
265
+ result.uploaded += 1;
266
+ } else if (response.status === 401) {
267
+ result.unauthorized = unauthorizedMessage(opts.apiKey);
268
+ stopped = true;
269
+ result.pending += 1;
270
+ } else if (response.status >= 500 || response.status === 408 || response.status === 429) {
271
+ result.pending += 1;
272
+ } else {
273
+ state.set(record.clientStepId, "dropped");
274
+ result.dropped += 1;
275
+ if (!opts.quiet) {
276
+ const reason = await refusalReason(response);
277
+ process.stderr.write(
278
+ `${C.yellow}!${C.reset} step "${record.name}" on item #${record.itemNumber} refused (${response.status}) and dropped${reason ? `: ${reason}` : ""}
279
+ `
280
+ );
281
+ }
282
+ }
283
+ }
284
+ if (!writeStepState(state, opts.home) && !opts.quiet) {
285
+ process.stderr.write(
286
+ `${C.yellow}!${C.reset} failed to save ${stepsStatePath(home)}; steps sent this run may be sent again, which is harmless
287
+ `
288
+ );
289
+ }
290
+ return result;
291
+ } finally {
292
+ lock.release();
293
+ }
294
+ }
295
+
296
+ export { EmitStepError, runEmitStep, uploadSpooledSteps };
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { rewriteControlPlanePaths, rewriteHookCommand } from './chunk-G2YNJIGF.js';
4
+ import { MAINTAINED_SKILLS } from './chunk-NNLA7LLQ.js';
5
+ import { existsSync, mkdirSync, copyFileSync, writeFileSync, readdirSync, lstatSync, readFileSync } from 'fs';
6
+ import { join, dirname } from 'path';
7
+
8
+ createRequire(import.meta.url);
9
+ function deliverySection(dir) {
10
+ return `This repository's control plane is not in the checkout. Its files are in \`${dir}\`. When an instruction or skill names a path under \`.outerlayer/\` or \`.claude/\`, read it under \`${dir}\`.`;
11
+ }
12
+ function isFile(path) {
13
+ try {
14
+ return lstatSync(path).isFile();
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+ function filesUnder(dir, base = dir) {
20
+ if (!existsSync(dir) || !lstatSync(dir).isDirectory()) return [];
21
+ const out = [];
22
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
23
+ const abs = join(dir, entry.name);
24
+ if (entry.isDirectory()) out.push(...filesUnder(abs, base));
25
+ else if (entry.isFile()) out.push(abs.slice(base.length + 1));
26
+ }
27
+ return out.sort();
28
+ }
29
+ function readText(path) {
30
+ if (!isFile(path)) return void 0;
31
+ const content = readFileSync(path);
32
+ return content.includes(0) ? void 0 : content.toString("utf8");
33
+ }
34
+ function deliveredSettings(raw, dir) {
35
+ if (raw === void 0) return "{}\n";
36
+ const rewrite = (value) => {
37
+ if (typeof value === "string") return rewriteHookCommand(value, dir);
38
+ if (Array.isArray(value)) return value.map(rewrite);
39
+ if (typeof value === "object" && value !== null) return Object.fromEntries(Object.entries(value).map(([key, inner]) => [key, rewrite(inner)]));
40
+ return value;
41
+ };
42
+ try {
43
+ return `${JSON.stringify(rewrite(JSON.parse(raw)), null, 2)}
44
+ `;
45
+ } catch {
46
+ return rewriteHookCommand(raw, dir);
47
+ }
48
+ }
49
+ function composeDelivery(input) {
50
+ const { tree, dir } = input;
51
+ const added = [];
52
+ const put = (rel, content) => {
53
+ mkdirSync(dirname(join(tree, rel)), { recursive: true });
54
+ writeFileSync(join(tree, rel), content);
55
+ added.push(rel);
56
+ };
57
+ for (const skill of MAINTAINED_SKILLS) {
58
+ for (const rel of filesUnder(join(tree, ".outerlayer/skills", skill))) {
59
+ const target = `.claude/skills/${skill}/${rel}`;
60
+ if (existsSync(join(tree, target))) continue;
61
+ mkdirSync(dirname(join(tree, target)), { recursive: true });
62
+ copyFileSync(join(tree, ".outerlayer/skills", skill, rel), join(tree, target));
63
+ added.push(target);
64
+ }
65
+ }
66
+ for (const rel of [...filesUnder(join(tree, ".claude/skills"), tree), ...filesUnder(join(tree, ".claude/agents"), tree)]) {
67
+ const text = readText(join(tree, rel));
68
+ if (text === void 0) continue;
69
+ const rewritten = rewriteControlPlanePaths(text, dir);
70
+ if (rewritten !== text) writeFileSync(join(tree, rel), rewritten);
71
+ }
72
+ const agents = readText(join(tree, "AGENTS.md"));
73
+ put("CLAUDE.md", agents === void 0 ? `${deliverySection(dir)}
74
+ ` : `${deliverySection(dir)}
75
+
76
+ ${rewriteControlPlanePaths(agents, dir)}`);
77
+ put("settings.json", deliveredSettings(readText(join(tree, ".claude/settings.json")), dir));
78
+ return added;
79
+ }
80
+ function sharedSkillNames(tree) {
81
+ const dir = join(tree, ".claude/skills");
82
+ if (!existsSync(dir)) return [];
83
+ return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
84
+ }
85
+ function skillConflictMessage(input) {
86
+ return `.claude/skills/${input.skill} in ${input.repository} has the same name as the shared skill ${input.skill} from ${input.controlPlane}`;
87
+ }
88
+
89
+ export { composeDelivery, sharedSkillNames, skillConflictMessage };
@@ -1,13 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { autoConnect } from './chunk-6Y5RUTHO.js';
4
- import { detectActiveSession } from './chunk-IP7BIBXJ.js';
5
- import { resolveRepoIdentity } from './chunk-JXYVA45C.js';
6
- import { C } from './chunk-YRYMNBGQ.js';
7
- import { parseWorkItemEnv } from './chunk-GQD22KKN.js';
3
+ import { autoConnect, detectActiveSession } from './chunk-IF2OSI5T.js';
4
+ import { resolveRepoIdentity } from './chunk-QNLX477Q.js';
5
+ import { C } from './chunk-ANPNT6PM.js';
6
+ import { parseWorkItemEnv } from './chunk-T2Q7ISPE.js';
8
7
  import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
9
- import { defaultFetch } from './chunk-YO4PSLVB.js';
10
- import { unauthorizedMessage, resolveGatewayCredentials } from './chunk-PLMTLGYH.js';
8
+ import { defaultFetch } from './chunk-FVMYKL6L.js';
9
+ import { unauthorizedMessage, resolveGatewayCredentials } from './chunk-NTRCK4NH.js';
11
10
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
12
11
 
13
12
  createRequire(import.meta.url);
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { hostKeyPath, runnerDir, RUNNER_DIR_MODE, RUNNER_FILE_MODE } from './chunk-ZM2IMMYN.js';
3
+ import { hostKeyPath, runnerDir, RUNNER_DIR_MODE, RUNNER_FILE_MODE } from './chunk-N2PXGQRL.js';
4
4
  import { generateKeyPairSync, createPrivateKey, createPublicKey, sign } from 'crypto';
5
5
  import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
6
6
 
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
3
  import { compareVersions } from './chunk-I2UOAV7X.js';
4
- import { launcherPath, writeLauncher, LauncherWriteError, pathNotes } from './chunk-PTW6W5NR.js';
5
- import { currentVersion, currentLink, isInLayout, packageRootIn, versionDir, switchCurrent, execStartLine } from './chunk-TI7B7UEO.js';
6
- import { RUNNER_DIR_MODE } from './chunk-ZM2IMMYN.js';
4
+ import { launcherPath, writeLauncher, LauncherWriteError, pathNotes } from './chunk-VYAL7PUE.js';
5
+ import { currentVersion, currentLink, isInLayout, packageRootIn, versionDir, switchCurrent, execStartLine } from './chunk-AMFWYESY.js';
6
+ import { RUNNER_DIR_MODE } from './chunk-N2PXGQRL.js';
7
7
  import { realpathSync, readFileSync, mkdirSync, rmSync, cpSync, existsSync } from 'fs';
8
8
  import { dirname, sep, join } from 'path';
9
9
 
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { RUNNER_DIR_MODE, RUNNER_FILE_MODE } from './chunk-ZM2IMMYN.js';
3
+ import { RUNNER_DIR_MODE, RUNNER_FILE_MODE } from './chunk-N2PXGQRL.js';
4
4
  import { readFileSync, existsSync, rmSync, mkdirSync, writeFileSync, renameSync } from 'fs';
5
5
  import { join } from 'path';
6
6
 
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
+ import { BUILD_HOME } from './chunk-BQXFBI7T.js';
3
4
  import { join, dirname } from 'path';
4
5
 
5
6
  createRequire(import.meta.url);
@@ -72,5 +73,12 @@ function diskTemplatesDir(runnerDirPath) {
72
73
  function vsockPath(buildDirPath) {
73
74
  return join(buildDirPath, "v.sock");
74
75
  }
76
+ var SHIM_DIR = `${BUILD_HOME}/.outerlayer/bin`;
77
+ function deliveryDir(repository, sha) {
78
+ if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository) || repository.split("/").some((part) => part === "." || part === "..") || !/^[0-9a-f]{7,64}$/.test(sha)) {
79
+ throw new Error(`cannot name a delivery directory for ${repository} at ${sha}`);
80
+ }
81
+ return `${BUILD_HOME}/.outerlayer/context/${repository}/${sha}`;
82
+ }
75
83
 
76
- export { RUNNER_DIR_MODE, RUNNER_FILE_MODE, attemptStamp, buildDir, contextHomeDir, diskTemplatesDir, ghConfigDir, hostKeyPath, imagesDir, jobDir, jobsDir, mirrorDir, pidFilePath, repoCacheDir, rootfsDir, rootfsPathFor, runnerDir, runnerDirOfBuild, spoolDir, stagedContextDir, vmAssetsDir, vsockPath };
84
+ export { RUNNER_DIR_MODE, RUNNER_FILE_MODE, SHIM_DIR, attemptStamp, buildDir, contextHomeDir, deliveryDir, diskTemplatesDir, ghConfigDir, hostKeyPath, imagesDir, jobDir, jobsDir, mirrorDir, pidFilePath, repoCacheDir, rootfsDir, rootfsPathFor, runnerDir, runnerDirOfBuild, spoolDir, stagedContextDir, vmAssetsDir, vsockPath };