@outerlayer/cli 0.1.1 → 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 (143) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/README.md +238 -77
  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-3YPKXE5L.js +1683 -0
  10. package/dist/{chunk-HNCCD2IX.js → chunk-5DLBAXY5.js} +209 -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-RCQXYLMO.js → chunk-DVDEBNQJ.js} +18 -18
  29. package/dist/{work-pr-cmd-GOYSPEN2.js → chunk-DVYHCMB2.js} +10 -25
  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-BQB3U2VD.js → chunk-G7FSV7JX.js} +5113 -3729
  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-3VQ4XXQA.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-WZBHBH3J.js +20 -0
  58. package/dist/{chunk-IWIUYLDR.js → chunk-XCCLFVXM.js} +118 -189
  59. package/dist/{context-materialize-J6CRQ7O7.js → chunk-XDDW4FRS.js} +134 -26
  60. package/dist/chunk-YYYXRJUW.js +101 -0
  61. package/dist/chunk-ZM2IMMYN.js +76 -0
  62. package/dist/chunk-ZMYPLFG3.js +71 -0
  63. package/dist/chunk-ZNA27WEV.js +470 -0
  64. package/dist/{cli-EFJIG2VT.js → cli-NHUXABYH.js} +597 -965
  65. package/dist/{paths-D2VGWWFI.js → cli-build-K56DK4DS.js} +1 -1
  66. package/dist/config-XVJYZ4IQ.js +9 -0
  67. package/dist/connect-cmd-OOG4TPU7.js +16 -0
  68. package/dist/context-adopt-UMA4O5NY.js +105 -0
  69. package/dist/context-materialize-G2FUFC72.js +19 -0
  70. package/dist/dist-RW4UPPC2.js +6 -0
  71. package/dist/docker-NEGDLU6D.js +7 -0
  72. package/dist/doctor-53F2PYY7.js +43 -0
  73. package/dist/doctor-UFVL4PZY.js +426 -0
  74. package/dist/{emit-artifact-cmd-KPPD3YLO.js → emit-artifact-cmd-KTYW2TN2.js} +25 -19
  75. package/dist/emit-cmd-DCERAU27.js +9 -0
  76. package/dist/emit-criteria-cmd-VAUUSAYD.js +166 -0
  77. package/dist/{emit-finding-cmd-VR5VEGXJ.js → emit-finding-cmd-UR4ID45Z.js} +35 -30
  78. package/dist/{emit-result-cmd-YOA5BJKL.js → emit-result-cmd-FDTUKGKE.js} +35 -61
  79. package/dist/exec-client-4XGXV3XN.js +8 -0
  80. package/dist/guest-init-GENRHNP7.js +8 -0
  81. package/dist/hook-fast-EYENPK6F.js +9 -0
  82. package/dist/{hook-wrap-fast-KXYNX3AD.js → hook-wrap-fast-NPLHJXFK.js} +2 -2
  83. package/dist/host-key-KDYZ5ECC.js +7 -0
  84. package/dist/import-capture-cmd-AVZ4VIVJ.js +68 -0
  85. package/dist/{import-ruler-cmd-7LH2QOMG.js → import-ruler-cmd-GUHL6IY5.js} +1 -1
  86. package/dist/index.js +4 -4
  87. package/dist/init-DXBCOAJK.js +36 -0
  88. package/dist/init-YEXK35CF.js +221 -0
  89. package/dist/init-cmd-NHR7PRSQ.js +135 -0
  90. package/dist/install-cmd-7WXOIFDO.js +55 -0
  91. package/dist/lima-XN65D7GN.js +55 -0
  92. package/dist/login-browser-7XGSV7MA.js +10 -0
  93. package/dist/{config-POF7DEQW.js → logout-cmd-QAUFDJ6J.js} +3 -1
  94. package/dist/{logs-TRNPQM42.js → logs-7RYUH5A6.js} +1 -1
  95. package/dist/loop-VZOLL4WQ.js +43 -0
  96. package/dist/machine-WSG52J75.js +35 -0
  97. package/dist/mcp-install-cmd-RLK4SO3N.js +9 -0
  98. package/dist/{mcp-serve-cmd-57EZZOTL.js → mcp-serve-cmd-JPTP5FMS.js} +20 -9
  99. package/dist/paths-OYKMVYJP.js +6 -0
  100. package/dist/{pidfile-PTW76F56.js → pidfile-UZRH774M.js} +2 -3
  101. package/dist/real-deps-SU24ZA2K.js +21 -0
  102. package/dist/relay-L76HDX72.js +46 -0
  103. package/dist/settings-J2652U5N.js +7 -0
  104. package/dist/starter-pack-LIZYMKYQ.js +10 -0
  105. package/dist/{status-JHVSZYC5.js → status-2UZKKNB7.js} +31 -12
  106. package/dist/{statusline-fast-3C5OXHDD.js → statusline-fast-SVTMBMD7.js} +3 -2
  107. package/dist/sync-cmd-VPPYCNNM.js +28 -0
  108. package/dist/version-BWM6VLDI.js +6 -0
  109. package/dist/{watch-NRLXJUST.js → watch-5C4ZBBGO.js} +20 -7
  110. package/dist/{work-claim-cmd-HXB7K27H.js → work-claim-cmd-LSO2B2EX.js} +34 -19
  111. package/dist/work-cmd-UOQO2AD4.js +17 -0
  112. package/dist/work-comment-cmd-WF3EOLAE.js +106 -0
  113. package/dist/{work-launch-YNNCKIH3.js → work-launch-YI4CJDAP.js} +2 -1
  114. package/dist/work-open-pr-cmd-4SSQZMYG.js +156 -0
  115. package/dist/work-pr-cmd-6AN6RRQJ.js +16 -0
  116. package/package.json +13 -3
  117. package/skill-pack/maintained/amend/SKILL.md +104 -0
  118. package/skill-pack/maintained/emitting-evidence/SKILL.md +99 -0
  119. package/skill-pack/maintained/emitting-evidence/references/agents-snippet.md +20 -0
  120. package/skill-pack/maintained/outerlayer/SKILL.md +55 -0
  121. package/skill-pack/maintained/reporting-findings/SKILL.md +148 -0
  122. package/skill-pack/template/build/SKILL.md +193 -0
  123. package/skill-pack/template/build/references/agent-briefs.md +243 -0
  124. package/skill-pack/template/build/references/criteria-judge.md +91 -0
  125. package/skill-pack/template/build/references/evidence.md +42 -0
  126. package/skill-pack/template/build/references/release.md +74 -0
  127. package/skill-pack/template/build/references/review-briefs.md +275 -0
  128. package/skill-pack/template/build/references/review-loop.md +158 -0
  129. package/skill-pack/template/build/scripts/record-criteria.mjs +235 -0
  130. package/skill-pack/template/spec/SKILL.md +84 -0
  131. package/skill-pack/template/writing-specs/SKILL.md +134 -0
  132. package/dist/chunk-JJP7YLMN.js +0 -25
  133. package/dist/chunk-KKEU6FFR.js +0 -589
  134. package/dist/chunk-OZ7C3XUE.js +0 -34
  135. package/dist/chunk-WQ6VGRGZ.js +0 -150
  136. package/dist/emit-commit-credit-cmd-656BRAQ4.js +0 -121
  137. package/dist/hook-fast-I2XTHDE6.js +0 -8
  138. package/dist/import-capture-cmd-EUMBGIV3.js +0 -176
  139. package/dist/init-PTBITAUO.js +0 -103
  140. package/dist/login-cmd-IRX6LZT7.js +0 -62
  141. package/dist/loop-BWE2BS6J.js +0 -646
  142. package/dist/sync-cmd-S7Q6DWU3.js +0 -15
  143. package/dist/work-cmd-ABP6XDBP.js +0 -16
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { installStarterPack } from './chunk-ZMYPLFG3.js';
4
+ import { runEmit } from './chunk-H5NCNVDA.js';
5
+ import { addStdioMcpEntryIfAbsent } from './chunk-MQ3IPHIZ.js';
6
+ import { C } from './chunk-YRYMNBGQ.js';
7
+ import { resolveGitCommonDir, defaultGitRunner } from './chunk-XDDW4FRS.js';
8
+ import { pinExists, readContextManifest } from './chunk-WIOZAJ2W.js';
9
+ import { loadTemplateFiles, loadMaintainedFiles, skillDir } from './chunk-WO2BXCTQ.js';
10
+ import { generatedHeaderSource } from './chunk-EMY4I27X.js';
11
+ import './chunk-ZJSNLVCE.js';
12
+ import './chunk-BCLJSQCV.js';
13
+ import './chunk-BOLTI6LR.js';
14
+ import './chunk-EABW6AJQ.js';
15
+ import './chunk-RA3O54FC.js';
16
+ import './chunk-M5POMKKW.js';
17
+ import './chunk-ZM2IMMYN.js';
18
+ import './chunk-XCCLFVXM.js';
19
+ import './chunk-BJ3KTMDH.js';
20
+ import './chunk-VNVZDWO3.js';
21
+ import './chunk-Z5GEFMRW.js';
22
+ import { execFileSync } from 'child_process';
23
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
24
+ import { join, dirname } from 'path';
25
+
26
+ createRequire(import.meta.url);
27
+ var CONFIG_PATH = ".outerlayer/config.json";
28
+ var MCP_OUTPUT_PATH = ".mcp.json";
29
+ var POLICY_PATH = ".outerlayer/policy.yaml";
30
+ var DEFAULT_CONFIG = `${JSON.stringify({ targets: ["claude-code"] }, null, 2)}
31
+ `;
32
+ var errorMessage = (err) => err instanceof Error ? err.message : String(err);
33
+ var skipped = (reason) => ({
34
+ outcome: "skipped",
35
+ reason,
36
+ text: `${C.dim}- Agent setup skipped: ${reason}${C.reset}
37
+ `,
38
+ paths: []
39
+ });
40
+ var GitLookupError = class extends Error {
41
+ };
42
+ function gitTopLevel(cwd) {
43
+ try {
44
+ const top = execFileSync("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
45
+ return top === "" ? void 0 : top;
46
+ } catch (err) {
47
+ const stderr = String(err.stderr ?? "");
48
+ if (/not a git repository/i.test(stderr)) return void 0;
49
+ throw new GitLookupError(`git could not report the repository root: ${(stderr.trim() || errorMessage(err)).split("\n")[0]}`);
50
+ }
51
+ }
52
+ function contextComesFromControlPlane(repoRoot) {
53
+ if (pinExists(repoRoot)) return true;
54
+ const gitCommonDir = resolveGitCommonDir(defaultGitRunner, repoRoot);
55
+ if (gitCommonDir === void 0) throw new GitLookupError("git could not report this repository's git directory");
56
+ return readContextManifest(gitCommonDir) !== void 0;
57
+ }
58
+ function mcpReplacementLosesNothing(existing, content) {
59
+ try {
60
+ const have = JSON.parse(existing);
61
+ const next = JSON.parse(content);
62
+ return Object.entries(have).every(([key, value]) => {
63
+ if (key !== "mcpServers") return JSON.stringify(next[key]) === JSON.stringify(value);
64
+ const nextServers = next.mcpServers ?? {};
65
+ return Object.entries(value ?? {}).every(([name, entry]) => JSON.stringify(nextServers[name]) === JSON.stringify(entry));
66
+ });
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+ function keepHandWritten(createdSources) {
72
+ return (output) => {
73
+ if (output.kind === "copy") return createdSources.has(output.source);
74
+ if (output.path.endsWith("mcp.json")) return !mcpReplacementLosesNothing(output.existing, output.content);
75
+ return generatedHeaderSource(output.existing) === null;
76
+ };
77
+ }
78
+ function runAgentSetup(opts) {
79
+ let repoRoot;
80
+ try {
81
+ repoRoot = gitTopLevel(opts.cwd);
82
+ if (repoRoot === void 0) return skipped("not a git repository");
83
+ if (contextComesFromControlPlane(repoRoot)) {
84
+ return skipped("this repository's context comes from a control plane and arrives at session start");
85
+ }
86
+ } catch (err) {
87
+ return failed(errorMessage(err), /* @__PURE__ */ new Set(), []);
88
+ }
89
+ const written = /* @__PURE__ */ new Set();
90
+ const kept = [];
91
+ const problems = [];
92
+ const keptOutputs = /* @__PURE__ */ new Set();
93
+ const createdSources = new Set(
94
+ [...loadTemplateFiles(), ...loadMaintainedFiles()].map((f) => f.path).filter((p) => !existsSync(join(repoRoot, p)))
95
+ );
96
+ const keep = keepHandWritten(createdSources);
97
+ try {
98
+ const pack = installStarterPack({ repoRoot });
99
+ for (const file of loadTemplateFiles().filter((f) => pack.copied.includes(f.skill))) written.add(file.path);
100
+ for (const path of pack.maintained) written.add(path);
101
+ for (const skill of pack.kept) kept.push(`${skillDir(skill)}/`);
102
+ if (pack.policy === "written") written.add(POLICY_PATH);
103
+ else kept.push(POLICY_PATH);
104
+ if (existsSync(join(repoRoot, CONFIG_PATH))) {
105
+ kept.push(CONFIG_PATH);
106
+ } else {
107
+ mkdirSync(dirname(join(repoRoot, CONFIG_PATH)), { recursive: true });
108
+ writeFileSync(join(repoRoot, CONFIG_PATH), DEFAULT_CONFIG);
109
+ written.add(CONFIG_PATH);
110
+ }
111
+ } catch (err) {
112
+ return failed(`could not install the skills: ${errorMessage(err)}`, written, kept);
113
+ }
114
+ try {
115
+ const mcp = addStdioMcpEntryIfAbsent(repoRoot);
116
+ if (mcp.added) written.add(mcp.path);
117
+ else kept.push(`${mcp.path} (already has an outerlayer server)`);
118
+ } catch (err) {
119
+ problems.push(errorMessage(err));
120
+ }
121
+ let pending;
122
+ try {
123
+ const check = runEmit({ cwd: repoRoot, check: true, quiet: true, keep });
124
+ pending = check.drift ?? [];
125
+ const emitted = runEmit({ cwd: repoRoot, quiet: true, keep });
126
+ for (const path of emitted.kept) keptOutputs.add(path);
127
+ if (emitted.exitCode !== 0) {
128
+ const detail = emitted.errors.map((e) => `${e.path}: ${e.message}`).join("; ");
129
+ problems.push(`context emit failed: ${detail}`);
130
+ } else {
131
+ for (const d of pending) if (d.kind !== "orphaned") written.add(d.path);
132
+ }
133
+ } catch (err) {
134
+ problems.push(`context emit failed: ${errorMessage(err)}`);
135
+ }
136
+ for (const path of [...keptOutputs].sort()) kept.push(`${path} (written by hand, not replaced)`);
137
+ const keptPaths = [...keptOutputs].sort();
138
+ if (problems.length > 0) {
139
+ for (const path of writtenBeforeFailure(repoRoot, pending, keep)) written.add(path);
140
+ return failed(problems.join("; "), written, kept, keptPaths);
141
+ }
142
+ const paths = [...written].sort();
143
+ return {
144
+ outcome: "done",
145
+ reason: paths.length === 0 ? "already set up" : `${paths.length} files written`,
146
+ text: renderDone(paths, kept, keptOutputs.has(MCP_OUTPUT_PATH)),
147
+ paths,
148
+ kept: keptPaths
149
+ };
150
+ }
151
+ function writtenBeforeFailure(repoRoot, pending, keep) {
152
+ if (pending === void 0 || pending.length === 0) return [];
153
+ try {
154
+ const now = new Set((runEmit({ cwd: repoRoot, check: true, quiet: true, keep }).drift ?? []).map((d) => d.path));
155
+ return pending.filter((d) => d.kind !== "orphaned" && !now.has(d.path)).map((d) => d.path);
156
+ } catch {
157
+ return [];
158
+ }
159
+ }
160
+ function failed(reason, written, kept, keptPaths = []) {
161
+ const paths = [...written].sort();
162
+ return {
163
+ outcome: "failed",
164
+ reason,
165
+ text: `${C.red}\u2717${C.reset} Agent setup: ${reason}
166
+ ${paths.length > 0 ? renderPaths(paths, kept) : ""}`,
167
+ paths,
168
+ kept: keptPaths
169
+ };
170
+ }
171
+ function renderPaths(paths, kept) {
172
+ const lines = [];
173
+ if (paths.length > 0) {
174
+ lines.push(" Written, uncommitted:");
175
+ for (const p of paths) lines.push(` ${p}`);
176
+ }
177
+ if (kept.length > 0) {
178
+ lines.push(" Kept as they were:");
179
+ for (const p of kept) lines.push(` ${C.dim}${p}${C.reset}`);
180
+ }
181
+ return `${lines.join("\n")}
182
+ `;
183
+ }
184
+ function renderDone(paths, kept, mcpKept) {
185
+ const mcpNote = mcpKept ? ` ${C.yellow}!${C.reset} The outerlayer entry was not added to .mcp.json, so its MCP server is not set up. Add it to .mcp.json by hand, or move the servers you wrote into .outerlayer/mcp.json and run \`outerlayer context emit\`.
186
+ ` : "";
187
+ const what = mcpKept ? "the OuterLayer skills" : "the OuterLayer skills and MCP server";
188
+ if (paths.length === 0) {
189
+ return `${C.green}\u2713${C.reset} Agent setup: already in place, nothing changed
190
+ ${renderPaths(paths, kept)}${mcpNote}`;
191
+ }
192
+ return `${C.green}\u2713${C.reset} Agent setup: Claude Code has ${what}
193
+ ` + renderPaths(paths, kept) + mcpNote + ` ${C.dim}\`context emit\` compiled every source under .outerlayer/ for the targets in ${CONFIG_PATH}, not only the skills.${C.reset}
194
+ `;
195
+ }
196
+
197
+ export { runAgentSetup };
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ export { buildRepositoryImage } from './chunk-ZNA27WEV.js';
4
+ import './chunk-N5FOE5PS.js';
5
+ export { RecipeError } from './chunk-QOZKPLVJ.js';
6
+ import './chunk-ZM2IMMYN.js';
7
+ import './chunk-Z5GEFMRW.js';
8
+
9
+ createRequire(import.meta.url);
@@ -1 +1 @@
1
- {"id":"2026-09-24T11:40:02.645Z+05fbecd","root":"/home/runner/_work/outerlayer/outerlayer","pkg":"outerlayer"}
1
+ {"id":"2026-10-02T18:11:11.100Z+54c322e","root":"/home/runner/_work/outerlayer/outerlayer","pkg":"outerlayer"}
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { probeCredentials } from './chunk-FS2VYGZT.js';
4
+ import './chunk-FAJSS2WN.js';
5
+ import './chunk-FV2CCGXK.js';
6
+ import './chunk-ABIWDUMS.js';
7
+ import './chunk-YYYXRJUW.js';
8
+ import './chunk-CBPPJSAR.js';
9
+ import { placeholderQueues, placeholderProblem, needsClaudeCredential, claudeCredentialCheck, claimPermissionProblem } from './chunk-7YIOOFVJ.js';
10
+ import './chunk-HE2D4EY5.js';
11
+ import './chunk-TBT347UY.js';
12
+ import './chunk-CBZB6XY6.js';
13
+ import './chunk-L2DBRKRA.js';
14
+ import './chunk-7A6RVXPZ.js';
15
+ import './chunk-ZNA27WEV.js';
16
+ import './chunk-N5FOE5PS.js';
17
+ import './chunk-OGFGZQA3.js';
18
+ import './chunk-774HEQL6.js';
19
+ import './chunk-QOZKPLVJ.js';
20
+ import './chunk-W4SQCOZU.js';
21
+ import './chunk-B2G7JHHB.js';
22
+ import './chunk-5DLBAXY5.js';
23
+ import './chunk-BFESLKPP.js';
24
+ import './chunk-6Y63SXFO.js';
25
+ import './chunk-G7FSV7JX.js';
26
+ import { loadRunnerConfig, RunnerConfigError, checkHooks } from './chunk-5Y3QQRUH.js';
27
+ import './chunk-F6GFZXZ3.js';
28
+ import './chunk-AUFG23AA.js';
29
+ import './chunk-WZBHBH3J.js';
30
+ import './chunk-XDDW4FRS.js';
31
+ import './chunk-WIOZAJ2W.js';
32
+ import './chunk-WO2BXCTQ.js';
33
+ import './chunk-EMY4I27X.js';
34
+ import './chunk-ZJSNLVCE.js';
35
+ import './chunk-BCLJSQCV.js';
36
+ import './chunk-BOLTI6LR.js';
37
+ import './chunk-EABW6AJQ.js';
38
+ import './chunk-RA3O54FC.js';
39
+ import './chunk-M5POMKKW.js';
40
+ import './chunk-ZM2IMMYN.js';
41
+ import './chunk-XCCLFVXM.js';
42
+ import './chunk-BJ3KTMDH.js';
43
+ import './chunk-VNVZDWO3.js';
44
+ import './chunk-Z5GEFMRW.js';
45
+
46
+ createRequire(import.meta.url);
47
+
48
+ // src/runner/check.ts
49
+ async function runRunnerCheck(configPath, deps = {}) {
50
+ let config;
51
+ try {
52
+ config = loadRunnerConfig(configPath);
53
+ } catch (err) {
54
+ return { ok: false, check: "config", message: err instanceof RunnerConfigError ? err.message : String(err) };
55
+ }
56
+ const hookProblem = checkHooks(config, deps.env ?? process.env);
57
+ if (hookProblem) return { ok: false, check: "hooks", message: `${configPath}: ${hookProblem}` };
58
+ const placeholders = placeholderQueues(config);
59
+ if (placeholders.length > 0) {
60
+ return { ok: false, check: "placeholders", message: `${configPath}: ${placeholderProblem(placeholders)}` };
61
+ }
62
+ if (needsClaudeCredential(config.hooks)) {
63
+ const credential = claudeCredentialCheck(deps.env ?? process.env);
64
+ if (credential.status === "fail") {
65
+ return { ok: false, check: "claude credential", message: `${credential.detail}. ${credential.fix}` };
66
+ }
67
+ }
68
+ const probe = await probeCredentials(config, deps.fetchImpl);
69
+ if (!probe.ok && probe.fatal) return { ok: false, check: "credentials", message: probe.reason };
70
+ const claimProblem = await claimPermissionProblem(config, deps.fetchImpl);
71
+ if (claimProblem.status === "lacks") {
72
+ return { ok: false, check: "claim permission", message: `${configPath}: ${claimProblem.problem}` };
73
+ }
74
+ const unverifiedReason = probe.ok ? claimProblem.reason : probe.reason;
75
+ const lines = [
76
+ `config ${configPath}`,
77
+ `host ${config.host}`,
78
+ `queues ${config.queues.join(", ")}`,
79
+ `concurrency ${config.concurrency}`,
80
+ `poll ${config.pollSeconds}s`,
81
+ `time limit ${config.timeLimitMinutes}m`,
82
+ `idle limit ${config.idleLimitMinutes}m`,
83
+ `auto update ${config.autoUpdate ? "on" : "off"}`,
84
+ ...config.queues.map((queue) => `command ${queue.padEnd(11)} ${config.commands[queue] ?? "(none)"}`),
85
+ `hook provision ${config.hooks.provision}`,
86
+ `hook cleanup ${config.hooks.cleanup}`,
87
+ `hook report ${config.hooks.report ?? "(none)"}`,
88
+ `repos include ${config.repos?.include?.join(", ") ?? "(all)"}`,
89
+ `repos exclude ${config.repos?.exclude?.join(", ") ?? "(none)"}`
90
+ ];
91
+ return { ok: true, lines, unverifiedReason, summary: `${config.url} \xB7 app ${config.appId}` };
92
+ }
93
+
94
+ export { runRunnerCheck };
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { dashboardBase, isRepositoryLinked, workPageUrl, notLinkedText } from './chunk-3TT7RQYB.js';
4
+ import { sanitizeTerminal } from './chunk-6Y63SXFO.js';
5
+ import { remoteLookupFailed } from './chunk-G7FSV7JX.js';
6
+ import { resolveGatewayCredentials } from './chunk-BOLTI6LR.js';
7
+ import { findGitDirs, readRepoConnection, writeRepoConnection } from './chunk-BJ3KTMDH.js';
8
+ import { createInterface } from 'readline/promises';
9
+
10
+ createRequire(import.meta.url);
11
+ var REQUEST_TIMEOUT_MS = 15e3;
12
+ var EXIT_NOT_LINKED = 2;
13
+ var REASON_TEXT = {
14
+ flag: "you passed --factory",
15
+ saved: "already connected",
16
+ linked: "the repository is linked to it",
17
+ only: "the only factory you can use",
18
+ prompt: "you chose it"
19
+ };
20
+ var slug = (f) => `${f.organization.name}/${f.name}`;
21
+ function result(code, outcome, reason, text) {
22
+ return { code, outcome, reason, text: text.endsWith("\n") ? text : `${text}
23
+ ` };
24
+ }
25
+ function realConnectDeps() {
26
+ return { fetchImpl: fetch, env: process.env, prompt: promptOnTerminal, now: () => /* @__PURE__ */ new Date() };
27
+ }
28
+ async function promptOnTerminal(question, io = { input: process.stdin, output: process.stderr }) {
29
+ const rl = createInterface(io);
30
+ try {
31
+ let text = `
32
+ ${question.message}
33
+ `;
34
+ question.options.forEach((o, i) => {
35
+ text += ` ${i + 1}) ${o.label}
36
+ `;
37
+ });
38
+ for (; ; ) {
39
+ const answer = (await rl.question(`${text}Choose 1-${question.options.length}: `)).trim();
40
+ if (answer === "") return void 0;
41
+ const picked = question.options[Number(answer) - 1];
42
+ if (picked !== void 0) return picked.value;
43
+ text = "";
44
+ }
45
+ } catch {
46
+ return void 0;
47
+ } finally {
48
+ rl.close();
49
+ }
50
+ }
51
+ function parseFactories(body) {
52
+ const rows = body?.data;
53
+ if (!Array.isArray(rows)) return void 0;
54
+ const out = [];
55
+ for (const row of rows) {
56
+ const org = row?.organization;
57
+ if (typeof row?.id !== "string" || typeof row.name !== "string" || typeof org?.id !== "string" || typeof org.name !== "string") return void 0;
58
+ out.push({
59
+ id: row.id,
60
+ name: sanitizeTerminal(row.name),
61
+ displayName: sanitizeTerminal(typeof row.displayName === "string" ? row.displayName : row.name),
62
+ organization: {
63
+ id: org.id,
64
+ name: sanitizeTerminal(org.name),
65
+ displayName: sanitizeTerminal(typeof org.displayName === "string" ? org.displayName : org.name)
66
+ },
67
+ linked: typeof row.linked === "boolean" ? row.linked : null
68
+ });
69
+ }
70
+ return out;
71
+ }
72
+ function listText(factories, repository) {
73
+ return factories.map((f) => ` ${slug(f)} ${f.displayName}${f.linked === true ? ` (linked to ${repository})` : ""}`).join("\n");
74
+ }
75
+ async function fetchFactories(deps, url, token, repository) {
76
+ let res;
77
+ try {
78
+ res = await deps.fetchImpl(`${url.replace(/\/+$/, "")}/v1/me/factories?${new URLSearchParams({ repository }).toString()}`, {
79
+ method: "GET",
80
+ headers: { authorization: `Bearer ${token}` },
81
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
82
+ });
83
+ } catch (err) {
84
+ const message = `could not reach ${url}: ${err instanceof Error ? err.message : String(err)}`;
85
+ return { ok: false, result: result(1, "failed", message, message) };
86
+ }
87
+ if (res.status === 401) {
88
+ const message = "the gateway refused the login. Run `outerlayer login` to sign in again.";
89
+ return { ok: false, result: result(1, "failed", message, message) };
90
+ }
91
+ if (!res.ok) {
92
+ const message = `${url} answered HTTP ${res.status} when listing your factories`;
93
+ return { ok: false, result: result(1, "failed", message, message) };
94
+ }
95
+ const factories = parseFactories(await res.json().catch(() => null));
96
+ if (factories === void 0) {
97
+ const message = `${url} answered, but the reply is not a list of factories. Check the gateway address with \`outerlayer doctor\`, then run \`outerlayer connect\` again.`;
98
+ return { ok: false, result: result(1, "failed", `${url} did not answer with a factory list`, message) };
99
+ }
100
+ return { ok: true, factories };
101
+ }
102
+ async function choose(flags, deps, factories, saved, repository) {
103
+ if (flags.factory !== void 0) {
104
+ const wanted = flags.factory.toLowerCase();
105
+ const named = factories.find((f) => f.id.toLowerCase() === wanted || slug(f).toLowerCase() === wanted);
106
+ if (named) return { factory: named, reason: "flag" };
107
+ const message = `no factory you can use is named "${flags.factory}". Choose one with --factory:
108
+ ${listText(factories, repository)}`;
109
+ return result(1, "failed", `--factory ${flags.factory} names no factory you can use`, message);
110
+ }
111
+ const remembered = saved && factories.find((f) => f.id === saved.appId);
112
+ if (remembered) return { factory: remembered, reason: "saved" };
113
+ const linked = factories.filter((f) => f.linked === true);
114
+ if (linked.length === 1) return { factory: linked[0], reason: "linked" };
115
+ if (factories.length === 1) return { factory: factories[0], reason: "only" };
116
+ if (!deps.isTerminal) {
117
+ const why = linked.length > 1 ? `${repository} is linked to more than one factory you can use` : `you can use more than one factory, and none is the only one ${repository} is linked to`;
118
+ const message = `${why}, and there is no terminal to ask on.
119
+ Run \`outerlayer connect --factory <org>/<factory>\` with one of:
120
+ ${listText(factories, repository)}`;
121
+ return result(1, "failed", "more than one factory fits; pass --factory", message);
122
+ }
123
+ let pool = factories;
124
+ const orgs = new Map(factories.map((f) => [f.organization.id, f.organization]));
125
+ if (orgs.size > 1) {
126
+ const org = await deps.prompt({
127
+ message: "Which organization?",
128
+ options: [...orgs.values()].map((o) => ({ label: `${o.displayName} (${o.name})`, value: o.id }))
129
+ });
130
+ pool = factories.filter((f) => f.organization.id === org);
131
+ }
132
+ const picked = pool.length === 0 ? void 0 : await deps.prompt({
133
+ message: `Which factory should ${repository} use?`,
134
+ options: pool.map((f) => ({
135
+ label: `${f.displayName} (${slug(f)})${f.linked === true ? ` - linked to ${repository}` : ""}`,
136
+ value: f.id
137
+ }))
138
+ });
139
+ const factory = pool.find((f) => f.id === picked);
140
+ if (!factory) return result(1, "failed", "no factory was chosen", "no factory was chosen. Run `outerlayer connect` again, or pass --factory <org>/<factory>.");
141
+ return { factory, reason: "prompt" };
142
+ }
143
+ async function runConnect(flags, deps) {
144
+ const creds = resolveGatewayCredentials({ home: deps.home, env: deps.env, cwd: deps.cwd });
145
+ if (creds.credential === void 0) {
146
+ const message = "no saved credential. Run `outerlayer login` to sign in, then run `outerlayer connect` again.";
147
+ return { ...result(1, "skipped", "no credential; run `outerlayer login` first", message), next: "Run `outerlayer login`, then `outerlayer init` again." };
148
+ }
149
+ if (creds.kind === "key") {
150
+ if (flags.factory !== void 0 && flags.factory !== creds.appId) {
151
+ const message2 = `a factory key decides the factory${creds.appId ? ` (${creds.appId})` : ""}, so --factory ${flags.factory} cannot apply. Remove --factory, or sign in with \`outerlayer login\` and unset the key.`;
152
+ return result(1, "failed", "--factory names a factory other than the key's", message2);
153
+ }
154
+ const message = `Your factory key decides the factory${creds.appId ? ` (${creds.appId})` : ""}, so there is nothing to connect. No file was changed.`;
155
+ return result(0, "done", "the factory key decides the factory", message);
156
+ }
157
+ if (findGitDirs(deps.cwd) === void 0) {
158
+ return result(1, "skipped", "not in a git repository", "connect chooses a factory for a repository, and this directory is not in a git repository. Run it inside one.");
159
+ }
160
+ const repository = deps.resolveRepository(deps.cwd);
161
+ if (repository === void 0 && remoteLookupFailed(deps.cwd)) {
162
+ const message = "git did not answer when asked for this repository's remote, so connect cannot tell which repository it is. Run `git remote -v` here, then run `outerlayer connect` again.";
163
+ return result(1, "failed", "git could not read this repository's remote", message);
164
+ }
165
+ if (repository === void 0) {
166
+ return result(1, "skipped", "this repository has no git remote", "this repository has no git remote, so connect cannot tell which repository it is. Add one (`git remote add origin <url>`), then run `outerlayer connect` again.");
167
+ }
168
+ if (creds.url === void 0) {
169
+ const message = "no gateway address is saved. Run `outerlayer login` to sign in.";
170
+ return result(1, "failed", message, message);
171
+ }
172
+ const listed = await fetchFactories(deps, creds.url, creds.credential, repository);
173
+ if (!listed.ok) return listed.result;
174
+ const { factories } = listed;
175
+ if (factories.length === 0) {
176
+ const message = `you can use no factory yet. Create one in the dashboard: ${dashboardBase(deps.home)}/orgs`;
177
+ return result(1, "failed", "no factory to choose from; create one in the dashboard", message);
178
+ }
179
+ const dirs = findGitDirs(deps.cwd);
180
+ const saved = readRepoConnection(deps.cwd);
181
+ const choice = await choose(flags, deps, factories, saved, repository);
182
+ if ("code" in choice) return choice;
183
+ const { factory, reason } = choice;
184
+ if (saved?.appId !== factory.id) {
185
+ try {
186
+ writeRepoConnection(dirs.gitCommonDir, {
187
+ appId: factory.id,
188
+ organization: { name: factory.organization.name, displayName: factory.organization.displayName },
189
+ factory: { name: factory.name, displayName: factory.displayName },
190
+ savedAt: deps.now().toISOString()
191
+ });
192
+ } catch (err) {
193
+ const message = `could not save the connection under ${dirs.gitCommonDir}: ${err instanceof Error ? err.message : String(err)}`;
194
+ return result(1, "failed", "could not save the connection", message);
195
+ }
196
+ }
197
+ const connected = `Connected ${repository} to factory "${factory.displayName}" in ${factory.organization.displayName} (${REASON_TEXT[reason]}).
198
+ `;
199
+ const why = `connected to ${slug(factory)}: ${REASON_TEXT[reason]}`;
200
+ const linked = await isRepositoryLinked(deps.fetchImpl, creds.url, creds.credential, factory.id, repository);
201
+ const chosen = { repository, factory: slug(factory) };
202
+ if (linked === false) {
203
+ const linkUrl = workPageUrl(deps.home, factory.organization.name, factory.name);
204
+ return { ...result(EXIT_NOT_LINKED, "done", why, connected + notLinkedText(factory.displayName, repository, linkUrl)), ...chosen, linked, linkUrl };
205
+ }
206
+ const changeIt = "Run `outerlayer connect --factory <org>/<factory>` to change it.";
207
+ const text = linked === null ? `${connected}Could not check whether ${sanitizeTerminal(repository)} is linked to ${factory.displayName}.
208
+ ${changeIt}` : connected + changeIt;
209
+ return { ...result(0, "done", why, text), ...chosen, linked };
210
+ }
211
+ function connectDocument(r) {
212
+ return {
213
+ status: r.outcome,
214
+ reason: r.reason,
215
+ ...r.repository === void 0 ? {} : { repository: r.repository, factory: r.factory },
216
+ ...r.linked === void 0 ? {} : { linked: r.linked },
217
+ ...r.linkUrl === void 0 ? {} : { linkUrl: r.linkUrl }
218
+ };
219
+ }
220
+ async function runConnectCommand(flags, deps, out) {
221
+ const r = await runConnect(flags, deps);
222
+ if (flags.json) {
223
+ out.stdout.write(`${JSON.stringify(connectDocument(r), null, 2)}
224
+ `);
225
+ out.stderr.write(r.outcome === "done" ? r.text : `\u2717 ${r.text}`);
226
+ return r.code;
227
+ }
228
+ (r.code === 0 ? out.stdout : out.stderr).write(r.outcome === "done" ? r.text : `\u2717 ${r.text}`);
229
+ return r.code;
230
+ }
231
+
232
+ export { promptOnTerminal, realConnectDeps, runConnect, runConnectCommand };
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { sanitizeTerminal } from './chunk-6Y63SXFO.js';
4
+ import { DEFAULT_DASHBOARD } from './chunk-USO2DKBD.js';
5
+ import { readCloudConfig } from './chunk-EABW6AJQ.js';
6
+
7
+ createRequire(import.meta.url);
8
+
9
+ // src/repository-link.ts
10
+ var LINKS_TIMEOUT_MS = 15e3;
11
+ var GITHUB_PREFIX = "github.com/";
12
+ function isLinkedRepository(local, linked) {
13
+ if (linked.provider !== "github" || typeof linked.repository !== "string") return false;
14
+ return local.toLowerCase() === `${GITHUB_PREFIX}${linked.repository.toLowerCase()}`;
15
+ }
16
+ function ownerAndName(repository) {
17
+ return repository.toLowerCase().startsWith(GITHUB_PREFIX) ? repository.slice(GITHUB_PREFIX.length) : repository;
18
+ }
19
+ async function isRepositoryLinked(fetchImpl, gatewayUrl, token, appId, repository) {
20
+ if (!repository.toLowerCase().startsWith(GITHUB_PREFIX)) return null;
21
+ let res;
22
+ try {
23
+ res = await fetchImpl(`${gatewayUrl.replace(/\/+$/, "")}/v1/apps/${encodeURIComponent(appId)}/git/links`, {
24
+ method: "GET",
25
+ headers: { authorization: `Bearer ${token}`, "x-outerlayer-app-id": appId },
26
+ signal: AbortSignal.timeout(LINKS_TIMEOUT_MS)
27
+ });
28
+ } catch {
29
+ return null;
30
+ }
31
+ if (!res.ok) return null;
32
+ const listed = (await res.json().catch(() => void 0))?.data;
33
+ if (!Array.isArray(listed)) return null;
34
+ return listed.some((entry) => isLinkedRepository(repository, entry ?? {}));
35
+ }
36
+ function dashboardBase(home) {
37
+ const saved = readCloudConfig(home).dashboard;
38
+ const base = typeof saved === "string" && saved.trim() !== "" ? saved : DEFAULT_DASHBOARD;
39
+ return base.replace(/\/+$/, "");
40
+ }
41
+ function workPageUrl(home, organization, factory) {
42
+ return `${dashboardBase(home)}/orgs/${encodeURIComponent(organization)}/factories/${encodeURIComponent(factory)}/work`;
43
+ }
44
+ function notLinkedText(factoryName, repository, url) {
45
+ const shown = sanitizeTerminal(repository);
46
+ const name = sanitizeTerminal(ownerAndName(repository));
47
+ return `${factoryName} has no link to ${shown} yet, so work from this repository cannot reach it.
48
+ Link it on the factory's Work page: ${url}
49
+ 1. Connect GitHub repository, and grant the App access to ${name}.
50
+ 2. Link repository, and pick ${name} and its default branch.
51
+ Then run \`outerlayer connect\` again.
52
+ `;
53
+ }
54
+
55
+ export { dashboardBase, isLinkedRepository, isRepositoryLinked, notLinkedText, workPageUrl };