@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
@@ -0,0 +1,534 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { existsSync, unlinkSync, chmodSync } from 'fs';
4
+ import { request as request$1, createServer } from 'http';
5
+ import { request } from 'https';
6
+ import { createServer as createServer$1 } from 'net';
7
+ import { join } from 'path';
8
+
9
+ createRequire(import.meta.url);
10
+
11
+ // src/runner/builtin-destinations.ts
12
+ var GATEWAY_DESTINATION = "gateway";
13
+ var CLAUDE_DESTINATION = "claude";
14
+ var BUILT_IN_NAMES = [GATEWAY_DESTINATION, CLAUDE_DESTINATION];
15
+ var GATEWAY_PORT = 3126;
16
+ var CLAUDE_PORT = 3127;
17
+ var GATEWAY_SOCKET = "gateway.sock";
18
+ var CLAUDE_SOCKET = "claude.sock";
19
+ var CLAUDE_UPSTREAM = "https://api.anthropic.com/";
20
+ var AGENT_CREDENTIAL_MISSING = "agent_credential_missing";
21
+ var OAUTH_PLACEHOLDER = "sk-ant-oat01-placeholder-not-a-real-token";
22
+ var API_KEY_PLACEHOLDER = "sk-ant-api03-placeholder-not-a-real-key";
23
+ var ITEM_KEY_PLACEHOLDER = "olitem_placeholder-not-a-real-key";
24
+ var ITEM_KEY_VARIABLE = "OUTERLAYER_ITEM_KEY";
25
+ var CLAUDE_CREDENTIAL_VARIABLES = ["CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"];
26
+ function claudeCredential(hostEnv) {
27
+ const held = (name) => hostEnv[name] !== void 0 && hostEnv[name] !== "";
28
+ if (held("CLAUDE_CODE_OAUTH_TOKEN")) return { variable: "CLAUDE_CODE_OAUTH_TOKEN", placeholder: OAUTH_PLACEHOLDER };
29
+ if (held("ANTHROPIC_API_KEY")) return { variable: "ANTHROPIC_API_KEY", placeholder: API_KEY_PLACEHOLDER };
30
+ return void 0;
31
+ }
32
+ function gatewayUpstream(gatewayUrl) {
33
+ return `${new URL(gatewayUrl).origin}/`;
34
+ }
35
+ function builtInDestinations(input) {
36
+ const credential = claudeCredential(input.hostEnv);
37
+ const apiKeyMode = credential?.variable === "ANTHROPIC_API_KEY";
38
+ return [
39
+ {
40
+ destination: {
41
+ name: GATEWAY_DESTINATION,
42
+ upstream: gatewayUpstream(input.gatewayUrl),
43
+ header: { name: "Authorization", value: `Bearer \${${ITEM_KEY_VARIABLE}}`, variables: [ITEM_KEY_VARIABLE] },
44
+ env: {}
45
+ },
46
+ hostEnv: input.itemKey === void 0 ? {} : { [ITEM_KEY_VARIABLE]: input.itemKey },
47
+ drop: [],
48
+ socket: GATEWAY_SOCKET
49
+ },
50
+ {
51
+ destination: {
52
+ name: CLAUDE_DESTINATION,
53
+ upstream: input.claudeUpstream ?? CLAUDE_UPSTREAM,
54
+ header: apiKeyMode ? { name: "x-api-key", value: "${ANTHROPIC_API_KEY}", variables: ["ANTHROPIC_API_KEY"] } : { name: "Authorization", value: "Bearer ${CLAUDE_CODE_OAUTH_TOKEN}", variables: ["CLAUDE_CODE_OAUTH_TOKEN"] },
55
+ env: {}
56
+ },
57
+ hostEnv: input.hostEnv,
58
+ // A build's own key in either header would sit beside the runner's, and the upstream takes one of them.
59
+ drop: ["x-api-key", "authorization"],
60
+ socket: CLAUDE_SOCKET
61
+ }
62
+ ];
63
+ }
64
+ function builtInEnvironment(localFor, credential) {
65
+ return {
66
+ OUTERLAYER_URL: localFor(GATEWAY_DESTINATION),
67
+ OUTERLAYER_API_KEY: ITEM_KEY_PLACEHOLDER,
68
+ ANTHROPIC_BASE_URL: localFor(CLAUDE_DESTINATION),
69
+ ...credential === void 0 ? {} : { [credential.variable]: credential.placeholder },
70
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
71
+ };
72
+ }
73
+
74
+ // src/runner/destinations.ts
75
+ var MAX_RELEASED_DESTINATIONS = 20;
76
+ var MAX_DESTINATIONS = MAX_RELEASED_DESTINATIONS - BUILT_IN_NAMES.length;
77
+ var DESTINATION_BASE_PORT = 3129;
78
+ var DESTINATION_NAME = /^[a-z0-9][a-z0-9-]{0,62}$/;
79
+ var VARIABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,254}$/;
80
+ var HEADER_NAME = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
81
+ var HEADER_VARIABLE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
82
+ var DESTINATION_KEYS = ["name", "upstream", "header", "env"];
83
+ var MAX_SOCKET_PATH_BYTES = 100;
84
+ var UPSTREAM_IDLE_MS = 6e5;
85
+ function headerVariables(destinations) {
86
+ return [...new Set((destinations ?? []).flatMap((d) => d.header.variables))];
87
+ }
88
+ function resolveDestinations(given, variables, isReserved, fail) {
89
+ if (!Array.isArray(given)) fail("runner.build.destinations", "must be a list of destinations");
90
+ if (given.length > MAX_DESTINATIONS) {
91
+ fail(
92
+ "runner.build.destinations",
93
+ `lists ${given.length} destinations; a release records at most ${MAX_RELEASED_DESTINATIONS}, and ${BUILT_IN_NAMES.length} of those are the built-in ${BUILT_IN_NAMES.join(" and ")} destinations, so a host lists at most ${MAX_DESTINATIONS}`
94
+ );
95
+ }
96
+ const resolved = [];
97
+ const names = /* @__PURE__ */ new Set();
98
+ const envNames = /* @__PURE__ */ new Set();
99
+ for (const [index, entry] of given.entries()) {
100
+ const at = `runner.build.destinations[${index}]`;
101
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) fail(at, "must be an object");
102
+ const record = entry;
103
+ for (const key of Object.keys(record)) {
104
+ if (!DESTINATION_KEYS.includes(key)) {
105
+ fail(`${at}.${key}`, `is not a key a destination accepts; the keys are ${DESTINATION_KEYS.join(", ")}`);
106
+ }
107
+ }
108
+ const name = record.name;
109
+ if (typeof name !== "string" || !DESTINATION_NAME.test(name)) {
110
+ fail(`${at}.name`, "must be 1 to 63 lower-case letters, digits or hyphens, starting with a letter or digit");
111
+ }
112
+ if (BUILT_IN_NAMES.includes(name)) fail(`${at}.name`, `is ${name}, a destination the runner serves itself`);
113
+ if (names.has(name)) fail(`${at}.name`, `repeats the destination ${name}`);
114
+ names.add(name);
115
+ const upstream = parseUpstream(record.upstream, `${at}.upstream`, fail);
116
+ const header = parseHeader(record.header, `${at}.header`, fail);
117
+ for (const variable of header.variables) {
118
+ if (variables.includes(variable)) {
119
+ fail(`${at}.header`, `names ${variable}, which build.variables also passes into the build; a secret a build can read is not a destination's`);
120
+ }
121
+ }
122
+ const env = parseEnv(record.env, `${at}.env`, isReserved, fail);
123
+ for (const variable of Object.keys(env)) {
124
+ if (envNames.has(variable)) fail(`${at}.env`, `sets ${variable}, which another destination sets too`);
125
+ envNames.add(variable);
126
+ if (variables.includes(variable)) fail(`${at}.env`, `sets ${variable}, which build.variables also passes into the build`);
127
+ }
128
+ resolved.push({ name, upstream, header, env });
129
+ }
130
+ for (const [index, destination] of resolved.entries()) {
131
+ for (const variable of destination.header.variables) {
132
+ if (envNames.has(variable)) {
133
+ fail(`runner.build.destinations[${index}].env`, `sets ${variable}, which a destination's header reads as a secret`);
134
+ }
135
+ }
136
+ }
137
+ return resolved;
138
+ }
139
+ function parseUpstream(value, key, fail) {
140
+ if (typeof value !== "string") fail(key, "must be an https:// address");
141
+ let url;
142
+ try {
143
+ url = new URL(value);
144
+ } catch {
145
+ fail(key, `${JSON.stringify(value)} is not an https:// address`);
146
+ }
147
+ if (url.protocol !== "https:") fail(key, `${JSON.stringify(value)} is not an https:// address: a destination's secret is only ever sent over TLS`);
148
+ if (url.username !== "" || url.password !== "") fail(key, "must not carry a login; the credential goes in the header");
149
+ if (url.search !== "" || url.hash !== "") fail(key, "must not carry a query or a fragment");
150
+ return url.href;
151
+ }
152
+ function parseHeader(value, key, fail) {
153
+ if (typeof value !== "string") fail(key, 'must be a string such as "Authorization: Bearer ${NPM_TOKEN}"');
154
+ const colon = value.indexOf(":");
155
+ if (colon === -1) fail(key, 'must be "Name: value", with a colon');
156
+ const name = value.slice(0, colon).trim();
157
+ const text = value.slice(colon + 1).trim();
158
+ if (!HEADER_NAME.test(name)) fail(key, `names the header ${JSON.stringify(name)}, which is not a header name`);
159
+ if (text === "") fail(key, "has an empty value");
160
+ if (/[\r\n\0]/.test(text)) fail(key, "has a value with a line break in it");
161
+ const variables = [...new Set([...text.matchAll(HEADER_VARIABLE)].map((m) => m[1]))];
162
+ return { name, value: text, variables };
163
+ }
164
+ function parseEnv(value, key, isReserved, fail) {
165
+ if (value === void 0) return {};
166
+ if (typeof value !== "object" || value === null || Array.isArray(value)) fail(key, "must be an object of variable names and values");
167
+ const env = {};
168
+ for (const [name, text] of Object.entries(value)) {
169
+ if (!VARIABLE_NAME.test(name)) fail(key, `names ${JSON.stringify(name)}, which is not a variable name`);
170
+ if (isReserved(name)) fail(key, `names ${name}, which the runner sets itself`);
171
+ if (typeof text !== "string") fail(key, `gives ${name} a value that is not a string`);
172
+ env[name] = text;
173
+ }
174
+ return env;
175
+ }
176
+ function destinationEnvironment(destinations, localFor) {
177
+ const env = {};
178
+ for (const [index, destination] of (destinations ?? []).entries()) {
179
+ for (const [name, value] of Object.entries(destination.env)) env[name] = value.split("{local}").join(localFor(index));
180
+ }
181
+ return env;
182
+ }
183
+ function containerLocalAddress(index) {
184
+ return `http://127.0.0.1:${DESTINATION_BASE_PORT + index}/`;
185
+ }
186
+ function destinationSocketPath(buildDir, index) {
187
+ return join(buildDir, `dest-${index}.sock`);
188
+ }
189
+ function respond(res, status, body) {
190
+ if (res.headersSent) {
191
+ res.destroy();
192
+ return;
193
+ }
194
+ res.writeHead(status, { "Content-Type": "text/plain", "Content-Length": Buffer.byteLength(body), Connection: "close" });
195
+ res.end(body);
196
+ }
197
+ function expandHeader(header, env) {
198
+ for (const name of header.variables) {
199
+ if (env[name] === void 0 || env[name] === "") return { missing: name };
200
+ }
201
+ return { value: header.value.replace(HEADER_VARIABLE, (_match, name) => env[name]) };
202
+ }
203
+ async function startDestination(input) {
204
+ const { destination, hostEnv } = input;
205
+ const upstream = new URL(destination.upstream);
206
+ const prefix = upstream.pathname.replace(/\/+$/, "");
207
+ const secure = upstream.protocol === "https:";
208
+ const send = input.request ?? (secure ? request : request$1);
209
+ const headerName = destination.header.name.toLowerCase();
210
+ const dropped = /* @__PURE__ */ new Set(["host", "authorization", "proxy-authorization", "cookie", "connection", "proxy-connection", "keep-alive", "transfer-encoding", "upgrade", ...input.drop ?? []]);
211
+ const handle = (req, res) => {
212
+ const target = req.url ?? "";
213
+ if (!target.startsWith("/")) {
214
+ respond(res, 400, "send an origin-form request path\n");
215
+ return;
216
+ }
217
+ const expanded = expandHeader(destination.header, hostEnv);
218
+ if ("missing" in expanded) {
219
+ respond(res, 502, `the destination ${destination.name} cannot be reached: ${expanded.missing} is not set in the runner's environment
220
+ `);
221
+ return;
222
+ }
223
+ const headers = {};
224
+ for (const [name, value] of Object.entries(req.headers)) {
225
+ if (!dropped.has(name)) headers[name] = value;
226
+ }
227
+ headers.host = upstream.host;
228
+ headers[headerName] = expanded.value;
229
+ let forwarded;
230
+ try {
231
+ forwarded = send(
232
+ {
233
+ protocol: upstream.protocol,
234
+ hostname: upstream.hostname,
235
+ port: upstream.port === "" ? secure ? 443 : 80 : Number(upstream.port),
236
+ method: req.method,
237
+ path: `${prefix}${target}`,
238
+ headers,
239
+ ...input.ca === void 0 ? {} : { ca: input.ca }
240
+ },
241
+ (answer) => {
242
+ res.writeHead(answer.statusCode ?? 502, answer.headers);
243
+ answer.on("error", () => res.destroy());
244
+ answer.on("close", () => {
245
+ if (!answer.complete) res.destroy();
246
+ });
247
+ answer.pipe(res);
248
+ }
249
+ );
250
+ } catch {
251
+ respond(res, 502, `the destination ${destination.name} cannot be reached: its header value was refused
252
+ `);
253
+ return;
254
+ }
255
+ input.onUsed?.(destination.name);
256
+ forwarded.setTimeout(UPSTREAM_IDLE_MS, () => forwarded.destroy());
257
+ res.on("close", () => forwarded.destroy());
258
+ forwarded.on("error", () => respond(res, 502, `the upstream of the destination ${destination.name} could not be reached
259
+ `));
260
+ req.pipe(forwarded);
261
+ };
262
+ const sockets = /* @__PURE__ */ new Set();
263
+ const server = createServer(handle);
264
+ server.on("connection", (socket) => {
265
+ sockets.add(socket);
266
+ socket.on("close", () => sockets.delete(socket));
267
+ });
268
+ const listeners = [server];
269
+ const served = {};
270
+ if (input.socketPath !== void 0) {
271
+ if (Buffer.byteLength(input.socketPath) > MAX_SOCKET_PATH_BYTES) {
272
+ throw new Error(`${input.socketPath} is too long for a Unix socket; keep the runner's config file nearer the root of the filesystem`);
273
+ }
274
+ if (existsSync(input.socketPath)) unlinkSync(input.socketPath);
275
+ await new Promise((resolve, reject) => {
276
+ server.once("error", reject);
277
+ server.listen(input.socketPath, () => resolve());
278
+ });
279
+ chmodSync(input.socketPath, 438);
280
+ served.socketPath = input.socketPath;
281
+ }
282
+ if (input.port !== void 0) {
283
+ const tcp = createServer$1((socket) => server.emit("connection", socket));
284
+ await new Promise((resolve, reject) => {
285
+ tcp.once("error", reject);
286
+ tcp.listen(input.port, "127.0.0.1", () => resolve());
287
+ });
288
+ listeners.push(tcp);
289
+ const address = tcp.address();
290
+ served.port = address.port;
291
+ }
292
+ const close = async () => {
293
+ for (const socket of sockets) socket.destroy();
294
+ await Promise.all(listeners.map((listener) => new Promise((resolve) => listener.close(() => resolve()))));
295
+ };
296
+ return { ...served, close };
297
+ }
298
+ async function startBuildServices(input) {
299
+ const tunnel = await input.startTunnel(join(input.buildDir, "tunnel.sock"), {
300
+ ...input.tunnelOptions,
301
+ ...input.allowHosts === void 0 ? {} : { allowHosts: input.allowHosts },
302
+ recordPath: join(input.jobDir, "tunnel.json")
303
+ });
304
+ const servers = [];
305
+ const closeAll = async () => {
306
+ await Promise.all(servers.map((s) => s.close()));
307
+ await tunnel.close();
308
+ };
309
+ try {
310
+ let proxyPort;
311
+ if (input.proxy === true) proxyPort = (await tunnel.listenTcp({ host: "127.0.0.1", port: input.ports?.proxy ?? 0 })).port;
312
+ const destinationPorts = [];
313
+ const builtInPorts = {};
314
+ for (const [index, destination] of input.destinations.entries()) {
315
+ const server = await startDestination({
316
+ destination,
317
+ ...input.serve === "tcp" ? {} : { socketPath: destinationSocketPath(input.buildDir, index) },
318
+ ...input.serve === "socket" ? {} : { port: input.ports?.destinations?.[index] ?? 0 },
319
+ hostEnv: input.hostEnv,
320
+ onUsed: (name) => tunnel.destinationUsed(name),
321
+ ...input.destinationOptions
322
+ });
323
+ servers.push(server);
324
+ if (server.port !== void 0) destinationPorts.push(server.port);
325
+ }
326
+ const builtIns = input.builtIns === void 0 ? [] : builtInDestinations({ gatewayUrl: input.builtIns.gatewayUrl, itemKey: input.builtIns.itemKey, hostEnv: input.hostEnv, claudeUpstream: input.builtIns.claudeUpstream });
327
+ for (const builtIn of builtIns) {
328
+ const server = await startDestination({
329
+ destination: builtIn.destination,
330
+ ...input.serve === "tcp" ? {} : { socketPath: join(input.buildDir, builtIn.socket) },
331
+ ...input.serve === "socket" ? {} : { port: input.ports?.builtIns?.[builtIn.destination.name] ?? 0 },
332
+ hostEnv: builtIn.hostEnv,
333
+ drop: builtIn.drop,
334
+ onUsed: (name) => tunnel.destinationUsed(name),
335
+ ...input.destinationOptions
336
+ });
337
+ servers.push(server);
338
+ if (server.port !== void 0) builtInPorts[builtIn.destination.name] = server.port;
339
+ }
340
+ return { ...tunnel, proxyPort, destinationPorts, builtInPorts, close: closeAll };
341
+ } catch (err) {
342
+ await closeAll();
343
+ throw err;
344
+ }
345
+ }
346
+
347
+ // src/runner/commit-identity.ts
348
+ var NOREPLY_SUFFIX = "@users.noreply.github.com";
349
+ var MAX_LENGTH = 200;
350
+ function parseIdentity(value) {
351
+ if (typeof value !== "object" || value === null) return void 0;
352
+ const { name, email } = value;
353
+ if (typeof name !== "string" || typeof email !== "string") return void 0;
354
+ if (name.trim() === "" || name.length > MAX_LENGTH || /[\r\n\0<>]/.test(name)) return void 0;
355
+ if (email.length > MAX_LENGTH || !email.endsWith(NOREPLY_SUFFIX) || /[\s\0<>]/.test(email) || email.length === NOREPLY_SUFFIX.length) return void 0;
356
+ return { name, email };
357
+ }
358
+ function parseClaimIdentity(data) {
359
+ const author = parseIdentity(data.commitAuthor);
360
+ if (author === void 0) return void 0;
361
+ const coAuthor = parseIdentity(data.coAuthor);
362
+ return coAuthor === void 0 ? { author } : { author, coAuthor };
363
+ }
364
+ function authorEnvironment(identity) {
365
+ if (identity === void 0) return {};
366
+ return {
367
+ GIT_AUTHOR_NAME: identity.name,
368
+ GIT_AUTHOR_EMAIL: identity.email,
369
+ GIT_COMMITTER_NAME: identity.name,
370
+ GIT_COMMITTER_EMAIL: identity.email
371
+ };
372
+ }
373
+ function configCount(env) {
374
+ const raw = String(env.GIT_CONFIG_COUNT);
375
+ return /^[0-9]+$/.test(raw) ? Number(raw) : 0;
376
+ }
377
+ function withHooksPath(env, dir) {
378
+ const at = configCount(env);
379
+ return {
380
+ ...env,
381
+ GIT_CONFIG_COUNT: String(at + 1),
382
+ [`GIT_CONFIG_KEY_${at}`]: "core.hooksPath",
383
+ [`GIT_CONFIG_VALUE_${at}`]: dir
384
+ };
385
+ }
386
+ function commitIdentityRecord(identity) {
387
+ return {
388
+ author: identity === void 0 ? "host" : "app",
389
+ coAuthor: identity?.coAuthor !== void 0 && identity.hooksPath !== void 0 ? "credited" : "none"
390
+ };
391
+ }
392
+
393
+ // src/runner/container/environment.ts
394
+ var RELAY_PORT = 3128;
395
+ var JOB_MOUNT = "/outerlayer/job";
396
+ var CHECKOUT_DIR = "/build/work/checkout";
397
+ var MAX_HOSTS = 200;
398
+ function tokenVariables() {
399
+ return ["GH_TOKEN", "GITHUB_TOKEN", "GH_ENTERPRISE_TOKEN", "GITHUB_ENTERPRISE_TOKEN"];
400
+ }
401
+ function githubVariables() {
402
+ return /* @__PURE__ */ new Set([...tokenVariables(), "GH_CONFIG_DIR", "GH_REPO", "GH_NO_UPDATE_NOTIFIER"]);
403
+ }
404
+ var PROXY_URL = `http://127.0.0.1:${RELAY_PORT}`;
405
+ var NO_LOCAL = "localhost,127.0.0.1";
406
+ function proxyEnvironment(proxyUrl, alsoNoProxy) {
407
+ const NO_PROXY = alsoNoProxy === void 0 || NO_LOCAL.split(",").includes(alsoNoProxy) ? NO_LOCAL : `${NO_LOCAL},${alsoNoProxy}`;
408
+ return {
409
+ HTTPS_PROXY: proxyUrl,
410
+ HTTP_PROXY: proxyUrl,
411
+ // curl and git read the lowercase forms.
412
+ https_proxy: proxyUrl,
413
+ http_proxy: proxyUrl,
414
+ NO_PROXY,
415
+ no_proxy: NO_PROXY,
416
+ NODE_USE_ENV_PROXY: "1",
417
+ // Yarn 2 and later ignore the variables above and read only their own.
418
+ // Without these, `yarn install` resolves registry names itself, which a
419
+ // build with no network of its own cannot do.
420
+ YARN_HTTPS_PROXY: proxyUrl,
421
+ YARN_HTTP_PROXY: proxyUrl
422
+ };
423
+ }
424
+ function runnerLiterals(repository) {
425
+ return {
426
+ OUTERLAYER_JOB_DIR: JOB_MOUNT,
427
+ HOME: "/build/home",
428
+ TMPDIR: "/tmp",
429
+ ...proxyEnvironment(PROXY_URL),
430
+ GIT_TERMINAL_PROMPT: "0",
431
+ GH_CONFIG_DIR: `${JOB_MOUNT}/gh`,
432
+ GH_NO_UPDATE_NOTIFIER: "1",
433
+ ...repository === void 0 ? {} : { GH_REPO: repository }
434
+ };
435
+ }
436
+ function containerBuiltInAddress(name) {
437
+ return `http://127.0.0.1:${name === GATEWAY_DESTINATION ? GATEWAY_PORT : CLAUDE_PORT}`;
438
+ }
439
+ function reservedNames() {
440
+ return /* @__PURE__ */ new Set([
441
+ ...Object.keys(runnerLiterals("owner/name")),
442
+ ...Object.keys(builtInEnvironment(containerBuiltInAddress, void 0)),
443
+ "PATH",
444
+ ...tokenVariables()
445
+ ]);
446
+ }
447
+ function isReservedVariable(name) {
448
+ return name.startsWith("OUTERLAYER_") || reservedNames().has(name);
449
+ }
450
+ function isRefusedInConfig(name) {
451
+ return isReservedVariable(name) && !githubVariables().has(name);
452
+ }
453
+ function buildEnvironment(input) {
454
+ const authored = authorEnvironment(input.identity?.author);
455
+ const recipe = {};
456
+ for (const [name, value] of Object.entries(input.containerEnv)) {
457
+ if (!isReservedVariable(name) && !(name in authored)) recipe[name] = value;
458
+ }
459
+ const builtIns = input.builtIns === true;
460
+ const passed = {
461
+ ...builtIns ? {} : { OUTERLAYER_URL: input.config.url },
462
+ ...builtIns || input.itemKey === void 0 ? {} : { OUTERLAYER_API_KEY: input.itemKey },
463
+ OUTERLAYER_APP_ID: input.config.appId,
464
+ OUTERLAYER_WORK: String(input.item),
465
+ ...input.branch === void 0 ? {} : { OUTERLAYER_BRANCH: input.branch }
466
+ };
467
+ const variables = [];
468
+ const skippedTokenVariables = [];
469
+ for (const name of input.config.build?.variables ?? []) {
470
+ if (tokenVariables().includes(name)) {
471
+ skippedTokenVariables.push(name);
472
+ continue;
473
+ }
474
+ const value = input.hostEnv[name];
475
+ if (value === void 0 || isReservedVariable(name) || name in authored) continue;
476
+ if (builtIns && CLAUDE_CREDENTIAL_VARIABLES.includes(name)) continue;
477
+ passed[name] = value;
478
+ variables.push(name);
479
+ }
480
+ const destinations = destinationEnvironment(input.config.build?.destinations, containerLocalAddress);
481
+ const builtInLiterals = builtIns ? builtInEnvironment(containerBuiltInAddress, claudeCredential(input.hostEnv)) : {};
482
+ const hooksPath = input.identity?.hooksPath;
483
+ const hooks = {};
484
+ if (hooksPath !== void 0) {
485
+ const extended = withHooksPath({ ...recipe, ...passed }, hooksPath);
486
+ const at = Number(extended.GIT_CONFIG_COUNT) - 1;
487
+ Object.assign(hooks, {
488
+ GIT_CONFIG_COUNT: extended.GIT_CONFIG_COUNT,
489
+ [`GIT_CONFIG_KEY_${at}`]: "core.hooksPath",
490
+ [`GIT_CONFIG_VALUE_${at}`]: hooksPath
491
+ });
492
+ delete passed.GIT_CONFIG_COUNT;
493
+ }
494
+ return {
495
+ literals: { ...recipe, ...destinations, ...runnerLiterals(input.repository), ...builtInLiterals, ...authored, ...hooks },
496
+ passed,
497
+ variables,
498
+ skippedTokenVariables
499
+ };
500
+ }
501
+ function parseIsolation(value) {
502
+ return value === "container" || value === "vm" ? value : "shared-user";
503
+ }
504
+ function capHosts(hosts, alsoReached = 0) {
505
+ const distinct = [...new Set(hosts)];
506
+ const hostsReached = distinct.slice(0, MAX_HOSTS);
507
+ return { hostsReached, hostsReachedMore: distinct.length - hostsReached.length + alsoReached };
508
+ }
509
+ function sharedUserEnvironment(cliVersion) {
510
+ return { isolation: "shared-user", cliVersion };
511
+ }
512
+ var MAX_TEXT = 255;
513
+ var MAX_IGNORED = 500;
514
+ function sendableIgnored(list) {
515
+ if (list === void 0) return void 0;
516
+ const fits = (text) => text.length >= 1 && text.length <= MAX_TEXT;
517
+ return list.filter((entry) => fits(entry.property) && fits(entry.source)).slice(0, MAX_IGNORED);
518
+ }
519
+ function withTunnelRecord(environment, record, onlyWhenUsed = false) {
520
+ const ignoredProperties = sendableIgnored(environment.ignoredProperties);
521
+ const base = ignoredProperties === void 0 ? environment : { ...environment, ignoredProperties };
522
+ if (record === void 0) return base;
523
+ const used = record.hosts.length + record.hostsMore + record.refusals.length + record.refusalsMore + (record.destinations?.length ?? 0) > 0;
524
+ if (onlyWhenUsed && !used) return base;
525
+ return {
526
+ ...base,
527
+ ...capHosts(record.hosts, record.hostsMore),
528
+ refusals: record.refusals.slice(0, MAX_HOSTS),
529
+ refusalsMore: Math.max(0, record.refusals.length - MAX_HOSTS) + record.refusalsMore,
530
+ ...record.destinations === void 0 || record.destinations.length === 0 ? {} : { destinations: record.destinations }
531
+ };
532
+ }
533
+
534
+ export { AGENT_CREDENTIAL_MISSING, BUILT_IN_NAMES, CHECKOUT_DIR, CLAUDE_CREDENTIAL_VARIABLES, CLAUDE_PORT, CLAUDE_SOCKET, DESTINATION_BASE_PORT, GATEWAY_PORT, GATEWAY_SOCKET, JOB_MOUNT, RELAY_PORT, authorEnvironment, buildEnvironment, builtInEnvironment, claudeCredential, commitIdentityRecord, containerLocalAddress, destinationEnvironment, headerVariables, isRefusedInConfig, isReservedVariable, parseClaimIdentity, parseIsolation, proxyEnvironment, resolveDestinations, sharedUserEnvironment, startBuildServices, withHooksPath, withTunnelRecord };
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { RELAY_PORT, GATEWAY_SOCKET, GATEWAY_PORT, CLAUDE_SOCKET, CLAUDE_PORT, DESTINATION_BASE_PORT } from './chunk-AUFG23AA.js';
4
+ import { symlinkSync, unlinkSync } from 'fs';
5
+ import { join } from 'path';
6
+
7
+ createRequire(import.meta.url);
8
+ var EXEC_PORT = 1024;
9
+ var GUEST_CID = 3;
10
+ var MAX_SOCKET_PATH = 107;
11
+ function vsockLinks(input) {
12
+ return [
13
+ { port: RELAY_PORT, socket: "tunnel.sock" },
14
+ ...Array.from({ length: input.destinations }, (_unused, index) => ({ port: DESTINATION_BASE_PORT + index, socket: `dest-${index}.sock` })),
15
+ ...input.builtIns ? [
16
+ { port: GATEWAY_PORT, socket: GATEWAY_SOCKET },
17
+ { port: CLAUDE_PORT, socket: CLAUDE_SOCKET }
18
+ ] : []
19
+ ];
20
+ }
21
+ function linkVsockSockets(input) {
22
+ for (const { port, socket } of input.links) {
23
+ const link = `${input.vsockPath}_${port}`;
24
+ removeLink(link);
25
+ symlinkSync(join(input.buildDir, socket), link);
26
+ }
27
+ }
28
+ function removeLink(link) {
29
+ try {
30
+ unlinkSync(link);
31
+ } catch (err) {
32
+ if (err.code !== "ENOENT") throw err;
33
+ }
34
+ }
35
+ var LONGEST_SUFFIX = "/v.sock_65535";
36
+ function socketPathProblem(buildDir) {
37
+ const longest = Buffer.byteLength(`${buildDir}${LONGEST_SUFFIX}`);
38
+ if (longest <= MAX_SOCKET_PATH) return void 0;
39
+ return `the build directory ${buildDir} is too deep for the microVM's sockets: a socket path holds at most ${MAX_SOCKET_PATH} bytes and the longest here is ${longest}; move the runner's config file closer to the root`;
40
+ }
41
+
42
+ export { EXEC_PORT, GUEST_CID, linkVsockSockets, socketPathProblem, vsockLinks };
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { readLaunchSpoolState, launchesPath } from './chunk-DVDEBNQJ.js';
4
+
5
+ createRequire(import.meta.url);
6
+
7
+ // src/launch-filter.ts
8
+ function launchRefusalForSession(sessionId, home) {
9
+ const spool = readLaunchSpoolState(home);
10
+ if (spool.ids.has(sessionId)) return void 0;
11
+ if (spool.unreadable !== void 0) {
12
+ return `the launch spool at ${launchesPath(home)} could not be read (${spool.unreadable}), so this session's launch could not be checked and nothing it produces may leave the machine. Fix the file's permissions and re-run.`;
13
+ }
14
+ if (spool.malformedLines !== void 0) {
15
+ return `the launch spool at ${launchesPath(home)} has ${spool.malformedLines} damaged line(s), so this session's launch could not be checked and nothing it produces may leave the machine.`;
16
+ }
17
+ return `this session was not launched with OUTERLAYER_WORK naming a work item, so nothing it produces may leave the machine. Start the session with OUTERLAYER_WORK set (for example OUTERLAYER_WORK=42), or run this from CI.`;
18
+ }
19
+ var LaunchFilter = class {
20
+ constructor(launchedSessionIds) {
21
+ this.launchedSessionIds = launchedSessionIds;
22
+ /** Sessions dropped for carrying no launch record — for the sync summary. */
23
+ this.skippedTotal = 0;
24
+ }
25
+ /** True when the session should sync. Records skips for reporting. */
26
+ allows(session) {
27
+ const key = session.subagent?.parentSessionId ?? session.id;
28
+ const allowed = typeof key === "string" && this.launchedSessionIds.has(key);
29
+ if (!allowed) this.skippedTotal += 1;
30
+ return allowed;
31
+ }
32
+ };
33
+
34
+ export { LaunchFilter, launchRefusalForSession };
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { readCloudConfig, cloudConfigPath } from './chunk-EABW6AJQ.js';
4
+ import { signRunnerRequest, signerFor } from './chunk-M5POMKKW.js';
5
+ import { homeFromEnv } from './chunk-VNVZDWO3.js';
6
+
7
+ createRequire(import.meta.url);
8
+
9
+ // src/signing-fetch.ts
10
+ function bodyBytes(body) {
11
+ if (body === void 0 || body === null) return new Uint8Array(0);
12
+ if (typeof body === "string") return new TextEncoder().encode(body);
13
+ if (body instanceof Uint8Array) return body;
14
+ if (body instanceof ArrayBuffer) return new Uint8Array(body);
15
+ return void 0;
16
+ }
17
+ async function savedKeySigner(headers, home) {
18
+ const authorization = headers.get("authorization");
19
+ if (authorization === null || !authorization.startsWith("Bearer ")) return void 0;
20
+ if (headers.has("signature")) return void 0;
21
+ if (readCloudConfig(home).apiKey !== authorization.slice("Bearer ".length)) return void 0;
22
+ try {
23
+ return await signerFor(cloudConfigPath(home));
24
+ } catch {
25
+ return void 0;
26
+ }
27
+ }
28
+ function signedBy(base, signerOf) {
29
+ return (async (input, init) => {
30
+ if (input instanceof Request) return base(input, init);
31
+ const headers = new Headers(init?.headers);
32
+ const signer = await signerOf(headers);
33
+ const body = bodyBytes(init?.body);
34
+ if (signer === void 0 || body === void 0) return base(input, init);
35
+ const signature = await signRunnerRequest({ method: init?.method ?? "GET", url: input.toString(), body, signer });
36
+ for (const [name, value] of Object.entries(signature)) headers.set(name, value);
37
+ return base(input, { ...init, headers });
38
+ });
39
+ }
40
+ function signingFetch(base, home = () => homeFromEnv()) {
41
+ return signedBy(base, (headers) => savedKeySigner(headers, home()));
42
+ }
43
+ function signingFetchFor(base, configPath, apiKey) {
44
+ return signedBy(base, async (headers) => {
45
+ const authorization = headers.get("authorization");
46
+ if (authorization !== `Bearer ${apiKey}` || headers.has("signature")) return void 0;
47
+ try {
48
+ return await signerFor(configPath);
49
+ } catch {
50
+ return void 0;
51
+ }
52
+ });
53
+ }
54
+ var defaultFetch = signingFetch((input, init) => globalThis.fetch(input, init));
55
+
56
+ export { defaultFetch, signingFetchFor };