@outerlayer/cli 0.16.1 → 0.18.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 (153) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +26 -11
  3. package/dist/agent-setup-RBTEACYU.js +28 -0
  4. package/dist/artifact-link-cmd-AVM2EKTV.js +25 -0
  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-DUI2ZH2H.js} +5 -6
  8. package/dist/{check-CNTMSGOG.js → check-5C4YDZYV.js} +53 -50
  9. package/dist/{chunk-R7PF5OOU.js → chunk-3CWUOFV6.js} +21 -14
  10. package/dist/{chunk-R4ZQQ7U2.js → chunk-3OFZM4X6.js} +551 -72
  11. package/dist/{chunk-2MNPHL23.js → chunk-47TTLEDX.js} +6 -5
  12. package/dist/{chunk-PLMTLGYH.js → chunk-52NC2FIU.js} +4 -3
  13. package/dist/chunk-6CHIYEZZ.js +564 -0
  14. package/dist/{chunk-TI7B7UEO.js → chunk-AMFWYESY.js} +1 -1
  15. package/dist/{chunk-YRYMNBGQ.js → chunk-ANPNT6PM.js} +2 -1
  16. package/dist/{chunk-JJEGKEFV.js → chunk-B3UKB46N.js} +6 -6
  17. package/dist/{chunk-JXYVA45C.js → chunk-BMYGAGBJ.js} +1050 -304
  18. package/dist/{chunk-4Q7OMKAT.js → chunk-BQXFBI7T.js} +81 -5
  19. package/dist/chunk-CRB3POP4.js +34 -0
  20. package/dist/{chunk-F47JBIAW.js → chunk-DSF2FWAZ.js} +2 -1
  21. package/dist/{chunk-N5FOE5PS.js → chunk-ECODQ7II.js} +4 -33
  22. package/dist/{chunk-NBGDBTO2.js → chunk-ESC6UR76.js} +6 -5
  23. package/dist/{chunk-HHJDN6AU.js → chunk-ESVW4MJY.js} +1 -1
  24. package/dist/{chunk-42ZI52JW.js → chunk-F4XSVQJ4.js} +4 -3
  25. package/dist/{chunk-FWX735W7.js → chunk-FUB7LRUE.js} +7 -7
  26. package/dist/chunk-G2YNJIGF.js +44 -0
  27. package/dist/{chunk-QKFB4K7X.js → chunk-GGOMT6KF.js} +1 -1
  28. package/dist/{chunk-CA3XMYFN.js → chunk-GOBOJMOJ.js} +6 -6
  29. package/dist/{chunk-N6LURUEF.js → chunk-GOSFARMS.js} +1 -1
  30. package/dist/chunk-JBJTC3UU.js +35 -0
  31. package/dist/{chunk-U3HXXRTY.js → chunk-JF42URPU.js} +16 -15
  32. package/dist/{chunk-ADJRGVZC.js → chunk-KDVIU23F.js} +1 -1
  33. package/dist/{chunk-6QRMLNH3.js → chunk-KK4VBOH6.js} +93 -37
  34. package/dist/{chunk-KMDLFI4P.js → chunk-L2USZT6T.js} +1 -1
  35. package/dist/{chunk-WTE3MQML.js → chunk-LXAY4A2G.js} +3 -12
  36. package/dist/{chunk-V7NIVOFD.js → chunk-M2ZWWTWZ.js} +1 -1
  37. package/dist/{chunk-ERI5GBNI.js → chunk-MLAAT4S6.js} +3 -3
  38. package/dist/{chunk-POJMEEJJ.js → chunk-MNTSH4CL.js} +1 -1
  39. package/dist/{chunk-WUYTWNMA.js → chunk-MQJSXHV7.js} +1 -1
  40. package/dist/{chunk-O765JR4W.js → chunk-MTP2QEVO.js} +1 -1
  41. package/dist/{chunk-ZM2IMMYN.js → chunk-N2PXGQRL.js} +9 -1
  42. package/dist/{chunk-J5M7BSJQ.js → chunk-N3CUDFCW.js} +9 -3
  43. package/dist/{chunk-4KSQKQXE.js → chunk-NACDBXUG.js} +19 -15
  44. package/dist/{chunk-RSPXEVDX.js → chunk-NL4XPC7W.js} +1 -1
  45. package/dist/{chunk-EHAAPRWY.js → chunk-QQM2UJ6T.js} +31 -26
  46. package/dist/{chunk-5FEK6PNE.js → chunk-RAZTWYZN.js} +10 -9
  47. package/dist/{chunk-4NPZVMJU.js → chunk-S26M3YBJ.js} +60 -4
  48. package/dist/{chunk-UGP76IZ5.js → chunk-S5PCA3SN.js} +1 -1
  49. package/dist/{chunk-YDSMROTB.js → chunk-S76AYB4M.js} +25 -20
  50. package/dist/{chunk-RMWLBUCJ.js → chunk-SADUQSO4.js} +1 -1
  51. package/dist/{chunk-OZGHL5Y5.js → chunk-SWCTGCSC.js} +2 -2
  52. package/dist/{chunk-GQD22KKN.js → chunk-T2Q7ISPE.js} +2 -1
  53. package/dist/{chunk-C5Y5ZJJZ.js → chunk-T4Y6SCG3.js} +5 -4
  54. package/dist/{chunk-6JNURGOA.js → chunk-ULSIJGLS.js} +45 -13
  55. package/dist/{chunk-ONRCEE6D.js → chunk-UYOYTKE2.js} +4 -4
  56. package/dist/{chunk-PTW6W5NR.js → chunk-VYAL7PUE.js} +1 -1
  57. package/dist/{chunk-JEHRUE3F.js → chunk-W5VPCONO.js} +166 -196
  58. package/dist/{chunk-NPQWKGUA.js → chunk-WBT5X3NH.js} +5 -4
  59. package/dist/{chunk-RLYZLUQ7.js → chunk-WGAUY5LE.js} +2 -2
  60. package/dist/{chunk-RSNSZ2LJ.js → chunk-X5H2WYMB.js} +1 -1
  61. package/dist/{chunk-OHVJA253.js → chunk-X5N6KC6V.js} +47 -29
  62. package/dist/{chunk-22RMZBGW.js → chunk-XGP3VYOG.js} +1 -1
  63. package/dist/{chunk-HYZRY4KZ.js → chunk-XNGMQ27R.js} +16 -6
  64. package/dist/{chunk-W3EKCZSQ.js → chunk-Y5GE76XC.js} +180 -53
  65. package/dist/{chunk-EWORLJMO.js → chunk-Y5MXET4M.js} +277 -526
  66. package/dist/{chunk-XCK2YAY5.js → chunk-YVZJ6GKF.js} +3 -2
  67. package/dist/{chunk-YO4PSLVB.js → chunk-ZRL6HKBY.js} +1 -1
  68. package/dist/claude-launcher-YVXI76NX.js +102 -0
  69. package/dist/{cli-AEWH62UE.js → cli-C77H54BV.js} +489 -174
  70. package/dist/{command-HHPBR6CS.js → command-H5CILO4S.js} +40 -37
  71. package/dist/{config-N3C6DFQA.js → config-FXDG4NPJ.js} +2 -3
  72. package/dist/connect-cmd-ZW5FV3ZW.js +17 -0
  73. package/dist/{context-adopt-VMMGFRUZ.js → context-adopt-O5OFXMKI.js} +13 -12
  74. package/dist/context-materialize-Y5IJ4MQT.js +23 -0
  75. package/dist/{dist-LKDWQWZ7.js → dist-QTX4RSCL.js} +2 -2
  76. package/dist/{docker-4J77Q2BF.js → docker-RC3PRX3Q.js} +7 -8
  77. package/dist/{doctor-N6Y7HKK3.js → doctor-2NQQML6P.js} +47 -39
  78. package/dist/{emit-artifact-cmd-H5MH3AOP.js → emit-artifact-cmd-7MP63KXU.js} +25 -22
  79. package/dist/emit-cmd-PPN4QQF2.js +14 -0
  80. package/dist/{emit-criteria-cmd-HBP6XS5C.js → emit-criteria-cmd-3RXPDLNK.js} +16 -14
  81. package/dist/{emit-finding-cmd-ZXDRL6PG.js → emit-finding-cmd-OL7JB6H3.js} +18 -16
  82. package/dist/{emit-result-cmd-RFKALS5Z.js → emit-result-cmd-UZVMOCF4.js} +20 -17
  83. package/dist/{exec-client-CQV746UF.js → exec-client-7UMESIE2.js} +3 -4
  84. package/dist/governing-source-BUU4IABS.js +35 -0
  85. package/dist/guest-init-KPBQOFE5.js +9 -0
  86. package/dist/hidden-prompt-UXBFMQC5.js +68 -0
  87. package/dist/{hook-fast-D7HV4IXG.js → hook-fast-W5MKHMJT.js} +4 -1
  88. package/dist/host-key-U4B5QETR.js +8 -0
  89. package/dist/{import-capture-cmd-EQ35EARA.js → import-capture-cmd-ZO6KGHI2.js} +7 -5
  90. package/dist/{import-ruler-cmd-BGAZXCQM.js → import-ruler-cmd-KJMWL5V6.js} +5 -3
  91. package/dist/index.js +8 -4
  92. package/dist/init-VWXIZEAN.js +54 -0
  93. package/dist/{init-FBCA4DRA.js → init-WNXXYMO7.js} +80 -53
  94. package/dist/{init-cmd-CAG572UE.js → init-cmd-WS6TIRYT.js} +28 -24
  95. package/dist/{init-update-VDFF4K3B.js → init-update-E3J6TVIN.js} +27 -26
  96. package/dist/{install-HDBIPHTX.js → install-CBWIEF3R.js} +4 -5
  97. package/dist/{install-cmd-5HYMXLEW.js → install-cmd-QDOTR4OM.js} +5 -4
  98. package/dist/{login-browser-QZCS3WW6.js → login-browser-O63QZN7D.js} +4 -3
  99. package/dist/{logout-cmd-QAUFDJ6J.js → logout-cmd-5V5MMBSY.js} +3 -2
  100. package/dist/{logs-H55NWW4H.js → logs-AMLKC3VP.js} +3 -1
  101. package/dist/loop-UGI2BJOT.js +76 -0
  102. package/dist/machine-JOW4RNZ7.js +49 -0
  103. package/dist/{mcp-install-cmd-FDTPQQS6.js → mcp-install-cmd-GGWB3JJ2.js} +3 -2
  104. package/dist/{mcp-serve-cmd-3KVYEUXG.js → mcp-serve-cmd-DOTY5FKY.js} +10 -8
  105. package/dist/paths-7XP63OAX.js +7 -0
  106. package/dist/pending-532ABZYC.js +8 -0
  107. package/dist/{pidfile-UZRH774M.js → pidfile-3DM4OBKO.js} +4 -2
  108. package/dist/{record-C43U4PKS.js → record-3WP6V2K5.js} +3 -2
  109. package/dist/{runner-env-EN7YE7QX.js → runner-env-4NB2GA7O.js} +5 -6
  110. package/dist/runner-key-TC3NFWFV.js +177 -0
  111. package/dist/{session-sender-24L5EW25.js → session-sender-NNVPSPY7.js} +17 -14
  112. package/dist/spec-cmd-OWVTBB4J.js +116 -0
  113. package/dist/{starter-pack-KF66HGZR.js → starter-pack-OPWUDOWX.js} +7 -5
  114. package/dist/{status-XHSGEYO5.js → status-TKCLLKFG.js} +42 -39
  115. package/dist/{statusline-fast-SVTMBMD7.js → statusline-fast-KN3BX67H.js} +2 -1
  116. package/dist/{sync-cmd-5G5EIEHD.js → sync-cmd-Q6VYNPWG.js} +16 -13
  117. package/dist/{watch-UTOXO4SE.js → watch-ARAGADTF.js} +16 -13
  118. package/dist/{work-claim-cmd-RKHYFE5Y.js → work-claim-cmd-34FERLYX.js} +25 -27
  119. package/dist/work-cmd-7S5B2H2P.js +25 -0
  120. package/dist/{work-comment-cmd-A55CUSYE.js → work-comment-cmd-SZJSPM5S.js} +15 -13
  121. package/dist/{work-launch-R5HTAP6X.js → work-launch-LEQ2MPM3.js} +3 -1
  122. package/dist/{work-open-pr-cmd-E6O53IJD.js → work-open-pr-cmd-RPXKCBWS.js} +17 -15
  123. package/dist/work-pr-cmd-5LGANL32.js +26 -0
  124. package/package.json +3 -2
  125. package/skill-pack/maintained/outerlayer-amend/SKILL.md +6 -2
  126. package/skill-pack/maintained/outerlayer-cli/SKILL.md +2 -0
  127. package/skill-pack/maintained/outerlayer-evidence/SKILL.md +4 -2
  128. package/skill-pack/maintained/outerlayer-findings/SKILL.md +2 -0
  129. package/skill-pack/shipped.json +27 -5
  130. package/skill-pack/template/build/SKILL.md +25 -10
  131. package/skill-pack/template/build/references/agent-briefs.md +3 -1
  132. package/skill-pack/template/build/references/criteria-judge.md +27 -0
  133. package/skill-pack/template/build/references/release.md +27 -34
  134. package/skill-pack/template/build/scripts/criterion-citations.mjs +8 -3
  135. package/skill-pack/template/build/scripts/record-criteria.mjs +8 -2
  136. package/skill-pack/template/spec/SKILL.md +37 -10
  137. package/skill-pack/validators/acceptance-criteria.yaml +25 -25
  138. package/skill-pack/validators/code-review.yaml +6 -5
  139. package/dist/agent-setup-K3ABU5NL.js +0 -27
  140. package/dist/chunk-BYT5O5RV.js +0 -61
  141. package/dist/connect-cmd-JUAW6MU4.js +0 -16
  142. package/dist/context-materialize-2WXTS4CW.js +0 -22
  143. package/dist/emit-cmd-IJ3GVRFD.js +0 -12
  144. package/dist/guest-init-LJP5GPZF.js +0 -9
  145. package/dist/host-key-YMLXY6QB.js +0 -7
  146. package/dist/init-MYX7YMIH.js +0 -51
  147. package/dist/loop-75J4YXLZ.js +0 -73
  148. package/dist/machine-A6ZCTUGS.js +0 -46
  149. package/dist/paths-OYKMVYJP.js +0 -6
  150. package/dist/pending-MXLSRYMO.js +0 -7
  151. package/dist/runner-key-W25ISHI3.js +0 -155
  152. package/dist/work-cmd-DJQJEZVB.js +0 -23
  153. package/dist/work-pr-cmd-L77K32WK.js +0 -24
@@ -1,36 +1,40 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { gatherDoctorChecks, doctorExitCode, summarizeChecks, renderDoctorText, recordWrapped, recordUnwrapped, recordRemoval, readManifest, recordInstall, recordStatusline } from './chunk-W3EKCZSQ.js';
4
- import './chunk-NBGDBTO2.js';
5
- import './chunk-6QRMLNH3.js';
6
- import './chunk-NPQWKGUA.js';
3
+ import { gatherDoctorChecks, doctorExitCode, summarizeChecks, renderDoctorText, recordWrapped, recordUnwrapped, recordRemoval, readManifest, recordInstall, recordStatusline } from './chunk-Y5GE76XC.js';
4
+ import './chunk-ESC6UR76.js';
5
+ import { verifiedTrackerNames, TRACKERS } from './chunk-KK4VBOH6.js';
6
+ import './chunk-WBT5X3NH.js';
7
7
  import './chunk-IP7BIBXJ.js';
8
- import './chunk-F47JBIAW.js';
8
+ import './chunk-S76AYB4M.js';
9
+ import './chunk-MQJSXHV7.js';
10
+ import './chunk-DSF2FWAZ.js';
9
11
  import './chunk-BKO6JEZI.js';
10
- import './chunk-XCK2YAY5.js';
11
- import './chunk-42ZI52JW.js';
12
- import './chunk-YDSMROTB.js';
13
- import './chunk-WUYTWNMA.js';
14
- import './chunk-JXYVA45C.js';
15
- import './chunk-4KSQKQXE.js';
16
- import './chunk-N6LURUEF.js';
17
- import { C, NO_COLOR_FLAG, warnDeprecated, stripNoColor, API_KEY_FLAG_DEPRECATION } from './chunk-YRYMNBGQ.js';
12
+ import './chunk-BMYGAGBJ.js';
13
+ import './chunk-YVZJ6GKF.js';
14
+ import './chunk-F4XSVQJ4.js';
15
+ import './chunk-NACDBXUG.js';
16
+ import './chunk-GOSFARMS.js';
17
+ import { C, NO_COLOR_FLAG, warnDeprecated, stripNoColor, API_KEY_FLAG_DEPRECATION } from './chunk-ANPNT6PM.js';
18
18
  import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
19
19
  import { settingsPath, readSettings, SettingsParseError, listHookCandidates, wrapHooks, writeSettings, unwrapHooks, REGISTERED_EVENTS, cliCommand, removeHooks, removeStatusline, cliBinResolvable, mergeHooks, repointWrappedHooks, AUTO_WRAP_EVENTS, mergeStatusline } from './chunk-5QRP3MVS.js';
20
- import { pinExists } from './chunk-WTE3MQML.js';
21
- import './chunk-C5Y5ZJJZ.js';
22
- import './chunk-YO4PSLVB.js';
23
- import './chunk-POJMEEJJ.js';
24
- import './chunk-OHVJA253.js';
25
- import './chunk-PLMTLGYH.js';
26
- import './chunk-J5M7BSJQ.js';
20
+ import { pinExists } from './chunk-LXAY4A2G.js';
21
+ import './chunk-T4Y6SCG3.js';
22
+ import './chunk-ZRL6HKBY.js';
23
+ import './chunk-52NC2FIU.js';
24
+ import './chunk-MNTSH4CL.js';
25
+ import './chunk-X5N6KC6V.js';
26
+ import './chunk-N3CUDFCW.js';
27
27
  import './chunk-EABW6AJQ.js';
28
28
  import { resolveVersion } from './chunk-RA3O54FC.js';
29
- import './chunk-V7NIVOFD.js';
30
- import './chunk-ZM2IMMYN.js';
31
- import { hookFiredMarkerPath, installedCliDistDir } from './chunk-6JNURGOA.js';
29
+ import './chunk-M2ZWWTWZ.js';
30
+ import './chunk-N2PXGQRL.js';
31
+ import './chunk-BQXFBI7T.js';
32
+ import { hookFiredMarkerPath, installedCliDistDir } from './chunk-ULSIJGLS.js';
33
+ import './chunk-G2YNJIGF.js';
32
34
  import { managedHookScripts, removeJournalHooks, installPreCommitGuardHook, removePreCommitGuardHook } from './chunk-XCCLFVXM.js';
35
+ import './chunk-ZJSNLVCE.js';
33
36
  import './chunk-BJ3KTMDH.js';
37
+ import { cliInvocation, packageRunnerOf } from './chunk-CRB3POP4.js';
34
38
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
35
39
  import './chunk-Z5GEFMRW.js';
36
40
  import { EventEmitter } from 'events';
@@ -3400,7 +3404,7 @@ function useColor() {
3400
3404
  // ../../node_modules/commander/index.js
3401
3405
  new Command();
3402
3406
  function isEphemeralBin(bin) {
3403
- return bin.replace(/\\/g, "/").split("/").some((p) => p === "_npx" || p === "dlx" || p.startsWith("dlx-") || p.startsWith("bunx-"));
3407
+ return packageRunnerOf(bin) !== void 0;
3404
3408
  }
3405
3409
  function initHookBin(opts) {
3406
3410
  const { runningBin } = opts;
@@ -3610,11 +3614,12 @@ function orgRolloutSnippet(cliBin) {
3610
3614
  "managed-settings.json:",
3611
3615
  managed,
3612
3616
  "",
3613
- "Each developer still runs `outerlayer login` once to attribute sessions."
3617
+ `Each developer still runs \`${cliInvocation()} login\` once to attribute sessions.`
3614
3618
  ].join("\n");
3615
3619
  }
3616
3620
 
3617
3621
  // src/cli.ts
3622
+ var TRACKER_OPTION_FLAG = `--tracker <${verifiedTrackerNames(TRACKERS).join("|")}>`;
3618
3623
  function resolveCliBin() {
3619
3624
  try {
3620
3625
  return fileURLToPath(new URL("./index.js", import.meta.url));
@@ -3632,7 +3637,7 @@ function printLauncher(result, summary) {
3632
3637
  const line = summary(result.launcher);
3633
3638
  if (line !== void 0) process.stdout.write(`${C.green}\u2713${C.reset} ${line}
3634
3639
  `);
3635
- for (const note of result.launcher.notes) process.stdout.write(`${C.yellow}${note}${C.reset}
3640
+ for (const note of result.launcher.notes) process.stdout.write(`${C.yellow}!${C.reset} ${note}
3636
3641
  `);
3637
3642
  }
3638
3643
  function writeJson(value) {
@@ -3640,13 +3645,13 @@ function writeJson(value) {
3640
3645
  `);
3641
3646
  }
3642
3647
  function apiKeyOption() {
3643
- return new Option("--api-key <key>", "deprecated: use `outerlayer login` or OUTERLAYER_API_KEY").hideHelp();
3648
+ return new Option("--api-key <key>", `deprecated: use \`${cliInvocation()} login\` or OUTERLAYER_API_KEY`).hideHelp();
3644
3649
  }
3645
3650
  function warnIfApiKeyFlag(opts) {
3646
3651
  if (opts.apiKey !== void 0) warnDeprecated(API_KEY_FLAG_DEPRECATION);
3647
3652
  }
3648
3653
  async function installStarterPackInRepo() {
3649
- const { installStarterPack, formatStarterPackOutput } = await import('./starter-pack-KF66HGZR.js');
3654
+ const { installStarterPack, formatStarterPackOutput } = await import('./starter-pack-OPWUDOWX.js');
3650
3655
  const cwd = process.cwd();
3651
3656
  let repoRoot = cwd;
3652
3657
  try {
@@ -3687,7 +3692,7 @@ ${C.dim} backup: ${result.backupPath}${C.reset}` : ""}
3687
3692
  }
3688
3693
  if (result.wrapped.length > 0) {
3689
3694
  lines.push(
3690
- `${C.green}\u2713${C.reset} Auto-wrapped ${result.wrapped.length} hook(s) for execution evidence (adds one spawn per firing \u2014 see ${C.yellow}outerlayer hooks unwrap${C.reset} to undo):
3695
+ `${C.green}\u2713${C.reset} Auto-wrapped ${result.wrapped.length} hook(s) for execution evidence (adds one spawn per firing \u2014 see ${C.yellow}${cliInvocation()} hooks unwrap${C.reset} to undo):
3691
3696
  `
3692
3697
  );
3693
3698
  for (const w of result.wrapped) {
@@ -3729,9 +3734,12 @@ cannot be disabled.
3729
3734
  (opts.next === false ? "\n" : `
3730
3735
  Next: launch a session naming its work item, then sync it:
3731
3736
  ${C.yellow}OUTERLAYER_WORK=42 claude${C.reset}
3732
- ${C.yellow}outerlayer sync${C.reset}
3737
+ ${C.yellow}${cliInvocation()} sync${C.reset}
3733
3738
 
3734
- `) + (opts.connected ? "This repository is connected to a factory, so launched sessions sync\nautomatically in the background after each agent turn and at the end\nof the session, at most once every five minutes.\n" : "Once you have run outerlayer login and outerlayer init in this repository,\nlaunched sessions sync automatically in the background after each agent\nturn and at the end of the session, at most once every five minutes.\n")
3739
+ `) + (opts.connected ? "This repository is connected to a factory, so launched sessions sync\nautomatically in the background after each agent turn and at the end\nof the session, at most once every five minutes.\n" : `Once you have run ${cliInvocation()} login and ${cliInvocation()} init in this repository,
3740
+ launched sessions sync automatically in the background after each agent
3741
+ turn and at the end of the session, at most once every five minutes.
3742
+ `)
3735
3743
  );
3736
3744
  return lines.join("");
3737
3745
  }
@@ -3748,7 +3756,7 @@ function formatGuardInstallOutput(guard) {
3748
3756
  return {
3749
3757
  lines: [
3750
3758
  `${C.yellow}!${C.reset} a pre-commit hook we did not write already exists (${guard.path}) \u2014 left untouched.`,
3751
- ` Append the guard script yourself: ${C.yellow}outerlayer hooks install-git --print${C.reset}`,
3759
+ ` Append the guard script yourself: ${C.yellow}${cliInvocation()} hooks install-git --print${C.reset}`,
3752
3760
  ` Place it before any exit in the existing hook \u2014 code after an early exit never runs.`
3753
3761
  ],
3754
3762
  exitCode: 1
@@ -3760,7 +3768,7 @@ function formatGuardInstallOutput(guard) {
3760
3768
  return {
3761
3769
  lines: [
3762
3770
  `${C.yellow}!${C.reset} this repo manages hooks in its working tree (core.hooksPath) \u2014 nothing written.`,
3763
- ` Add the script to your hook manager instead: ${C.yellow}outerlayer hooks install-git --print${C.reset}`
3771
+ ` Add the script to your hook manager instead: ${C.yellow}${cliInvocation()} hooks install-git --print${C.reset}`
3764
3772
  ],
3765
3773
  exitCode: 1
3766
3774
  };
@@ -3775,7 +3783,7 @@ async function installHooks(opts, resolvedBin, connected, starterTemplate, withN
3775
3783
  cliBin = initHookBin({ home: homeFromEnv(), runningBin: resolvedBin, remove: opts.remove === true });
3776
3784
  } catch (err) {
3777
3785
  const text = `could not copy the CLI to ~/.outerlayer/cli: ${err instanceof Error ? err.message : String(err)}
3778
- Hooks were not changed. Fix the permissions on ~/.outerlayer, then re-run ${C.yellow}outerlayer init${C.reset}.`;
3786
+ Hooks were not changed. Fix the permissions on ~/.outerlayer, then re-run ${C.yellow}${cliInvocation()} init${C.reset}.`;
3779
3787
  return { ok: false, text, detail: "could not copy the CLI to ~/.outerlayer/cli" };
3780
3788
  }
3781
3789
  try {
@@ -3789,7 +3797,7 @@ async function installHooks(opts, resolvedBin, connected, starterTemplate, withN
3789
3797
  });
3790
3798
  if (result.cliBinUnresolved) {
3791
3799
  const text = `the resolved CLI path (${cliBin}) does not exist \u2014 refusing to write hook entries pointing at it.
3792
- Reinstall OuterLayer, then re-run ${C.yellow}outerlayer init${C.reset}.`;
3800
+ Reinstall OuterLayer, then re-run ${C.yellow}${cliInvocation()} init${C.reset}.`;
3793
3801
  return { ok: false, text, detail: "the CLI path does not exist" };
3794
3802
  }
3795
3803
  const starter = starterTemplate === void 0 ? null : await installStarterPackInRepo();
@@ -3806,8 +3814,8 @@ ${starter.output}`),
3806
3814
  }
3807
3815
  }
3808
3816
  async function realConnectDepsForCwd() {
3809
- const { realConnectDeps } = await import('./connect-cmd-JUAW6MU4.js');
3810
- const { resolveRepoIdentity } = await import('./dist-LKDWQWZ7.js');
3817
+ const { realConnectDeps } = await import('./connect-cmd-ZW5FV3ZW.js');
3818
+ const { resolveRepoIdentity } = await import('./dist-QTX4RSCL.js');
3811
3819
  return {
3812
3820
  ...realConnectDeps(),
3813
3821
  home: homeFromEnv(),
@@ -3822,13 +3830,53 @@ function invokedCommandToken(argv) {
3822
3830
  }
3823
3831
  function buildProgram(argv = process.argv) {
3824
3832
  const program2 = new Command();
3825
- program2.name("outerlayer").description("OuterLayer \u2014 see what your coding agents actually do").enablePositionalOptions().version(resolveVersion()).option(NO_COLOR_FLAG, "disable color (NO_COLOR=1 does the same)").addHelpText("after", SUPPORT_TEXT);
3826
- program2.command("init").description("Connect this repository, install hooks and agent skills, run doctor").option("--user", "install into user settings [default]").option("--project", "install into project settings (./.claude/settings.json)").option("--org", "print a managed-settings.json snippet for MDM/GPO rollout").option("--remove", "uninstall OuterLayer hooks").option("--local", "install the hooks only: no connect step and no network call").option("--factory <org/factory>", "the factory to connect this repository to (see `outerlayer connect`)").option("--gitignore", "also add .outerlayer/ to .gitignore (project scope)").option("--template <name>", "install the starter skills and a policy file (only: default)").option("--update", "update the files init wrote that you have not edited, and write missing ones").option("--dry-run", "with --update: print the plan and write nothing").option("--no-wrap-hooks", "leave PreToolUse/PostToolUse hooks unwrapped").option("--statusline", "install the status-line segment [default]").option("--no-statusline", "leave the statusLine slot untouched").option("--json", "machine-readable JSON output").addHelpText(
3833
+ program2.name(cliInvocation()).description("OuterLayer \u2014 see what your coding agents actually do").enablePositionalOptions().version(resolveVersion()).option(NO_COLOR_FLAG, "disable color (NO_COLOR=1 does the same)").addHelpText("after", SUPPORT_TEXT);
3834
+ program2.command("init").description("Connect this repository, install hooks and agent skills, run doctor").option("--user", "install into user settings [default]").option("--project", "install into project settings (./.claude/settings.json)").option("--org", "print a managed-settings.json snippet for MDM/GPO rollout").option("--remove", "uninstall OuterLayer hooks").option("--local", "install the hooks only: no connect step and no network call").option("--factory <org/factory>", `the factory to connect this repository to (see \`${cliInvocation()} connect\`)`).option("--gitignore", "also add .outerlayer/ to .gitignore (project scope)").option("--template <name>", "install the starter skills and a policy file (only: default)").option("--update", "update the files init wrote that you have not edited, and write missing ones").option("--dry-run", "with --update: print the plan and write nothing").option("--no-wrap-hooks", "leave PreToolUse/PostToolUse hooks unwrapped").option("--statusline", "install the status-line segment [default]").option("--no-statusline", "leave the statusLine slot untouched").option("--json", "machine-readable JSON output").addHelpText(
3827
3835
  "after",
3828
- "\nIn a repository, init runs four steps: connect the repository to a\nfactory (`outerlayer connect`), install the hooks, set up the coding agent,\nand run `outerlayer doctor`. It never signs in and asks nothing. With no\nlogin it skips the connect step and says to run `outerlayer login`. With\n--json it prints one document naming the outcome of each step: done,\nskipped or failed. The exit code is 1 when a step failed.\n\nThe agent setup step installs the starter skills and the `outerlayer` skill\ninto .outerlayer/skills/, writes .outerlayer/config.json for Claude Code when\nthere is none, runs `outerlayer context emit` so the skills appear in\n.claude/skills/, and adds the outerlayer and outerlayer-docs MCP servers to\n.mcp.json. Nothing you wrote is changed: an existing config, policy, edited\nskill, MCP entry or root CLAUDE.md and AGENTS.md is left as it is, and a\nhand-written file the emit would replace is kept and listed. The files are\nleft uncommitted.\nThe step is skipped outside a git repository and in a repository whose\ncontext comes from a control plane, where it arrives at session start.\n\n--local installs only the hooks: no connect step, no agent setup and no\nnetwork call. Add `--template default` to also write the starter skills and\na policy file into .outerlayer/ without compiling them; `outerlayer context\nemit` does that.\n\nIdempotent: settings are backed up before any write, and a second run\nchanges nothing. Wrapping adds one spawn per hook firing; undo it with\n`outerlayer hooks unwrap`. An occupied status-line slot is wrapped, never\nreplaced.\n\nThree of the starter skills are copied once and are yours to edit. The\nothers are rewritten from this CLI on every `context emit`.\n\n--update brings the files init wrote once up to this CLI's version: the\ntemplate skills, the policy, the two starter checks and the two MCP\nentries. A file still equal to a version an earlier release wrote is\nreplaced, and a missing one is written. A file you edited is never\ntouched. It then runs `outerlayer context emit` and leaves the changes\nuncommitted. --dry-run prints the plan, with a command to compare each\nedited file, and writes nothing. `outerlayer doctor` reports the same\nstates under Starter files.\n"
3836
+ `
3837
+ In a repository, init runs four steps: connect the repository to a
3838
+ factory (\`${cliInvocation()} connect\`), install the hooks, set up the coding agent,
3839
+ and run \`${cliInvocation()} doctor\`. It never signs in and asks nothing. With no
3840
+ login it skips the connect step and says to run \`${cliInvocation()} login\`. With
3841
+ --json it prints one document naming the outcome of each step: done,
3842
+ skipped or failed. The exit code is 1 when a step failed.
3843
+
3844
+ The agent setup step installs the starter skills and the \`outerlayer\` skill
3845
+ into .outerlayer/skills/, writes .outerlayer/config.json for Claude Code when
3846
+ there is none, runs \`${cliInvocation()} context emit\` so the skills appear in
3847
+ .claude/skills/, and adds the outerlayer and outerlayer-docs MCP servers to
3848
+ .mcp.json. Nothing you wrote is changed: an existing config, policy, edited
3849
+ skill, MCP entry or root CLAUDE.md and AGENTS.md is left as it is, and a
3850
+ hand-written file the emit would replace is kept and listed. The files are
3851
+ left uncommitted.
3852
+ The step is skipped outside a git repository and in a repository whose
3853
+ context comes from a control plane, where it arrives at session start.
3854
+
3855
+ --local installs only the hooks: no connect step, no agent setup and no
3856
+ network call. Add \`--template default\` to also write the starter skills and
3857
+ a policy file into .outerlayer/ without compiling them; \`${cliInvocation()} context
3858
+ emit\` does that.
3859
+
3860
+ Idempotent: settings are backed up before any write, and a second run
3861
+ changes nothing. Wrapping adds one spawn per hook firing; undo it with
3862
+ \`${cliInvocation()} hooks unwrap\`. An occupied status-line slot is wrapped, never
3863
+ replaced.
3864
+
3865
+ Three of the starter skills are copied once and are yours to edit. The
3866
+ others are rewritten from this CLI on every \`context emit\`.
3867
+
3868
+ --update brings the files init wrote once up to this CLI's version: the
3869
+ template skills, the policy, the two starter checks and the two MCP
3870
+ entries. A file still equal to a version an earlier release wrote is
3871
+ replaced, and a missing one is written. A file you edited is never
3872
+ touched. It then runs \`${cliInvocation()} context emit\` and leaves the changes
3873
+ uncommitted. --dry-run prints the plan, with a command to compare each
3874
+ edited file, and writes nothing. \`${cliInvocation()} doctor\` reports the same
3875
+ states under Starter files.
3876
+ `
3829
3877
  ).action(async (opts) => {
3830
3878
  if (opts.update || opts.dryRun) {
3831
- const { runInitUpdate, formatInitUpdate, updateFlagProblem } = await import('./init-update-VDFF4K3B.js');
3879
+ const { runInitUpdate, formatInitUpdate, updateFlagProblem } = await import('./init-update-E3J6TVIN.js');
3832
3880
  const problem = updateFlagProblem(opts);
3833
3881
  if (problem !== void 0) {
3834
3882
  fail(problem);
@@ -3842,7 +3890,7 @@ function buildProgram(argv = process.argv) {
3842
3890
  }
3843
3891
  let starterTemplate;
3844
3892
  if (opts.template !== void 0) {
3845
- const { assertKnownTemplate, StarterPackError } = await import('./starter-pack-KF66HGZR.js');
3893
+ const { assertKnownTemplate, StarterPackError } = await import('./starter-pack-OPWUDOWX.js');
3846
3894
  try {
3847
3895
  assertKnownTemplate(opts.template);
3848
3896
  } catch (err) {
@@ -3873,7 +3921,7 @@ function buildProgram(argv = process.argv) {
3873
3921
  else process.stdout.write(outcome.text);
3874
3922
  return;
3875
3923
  }
3876
- const { runInitCommand } = await import('./init-cmd-CAG572UE.js');
3924
+ const { runInitCommand } = await import('./init-cmd-WS6TIRYT.js');
3877
3925
  const code = await runInitCommand(
3878
3926
  { json: opts.json, factory: opts.factory },
3879
3927
  {
@@ -3885,7 +3933,7 @@ function buildProgram(argv = process.argv) {
3885
3933
  return { ok, text, detail };
3886
3934
  },
3887
3935
  runAgentSetup: async () => {
3888
- const { runAgentSetup } = await import('./agent-setup-K3ABU5NL.js');
3936
+ const { runAgentSetup } = await import('./agent-setup-RBTEACYU.js');
3889
3937
  return runAgentSetup({ cwd: process.cwd() });
3890
3938
  },
3891
3939
  runDoctorChecks: gatherDoctorChecks
@@ -3895,9 +3943,29 @@ function buildProgram(argv = process.argv) {
3895
3943
  });
3896
3944
  program2.command("connect").description("Choose the factory this repository's work goes to").option("--factory <org/factory>", "the factory to use: <org>/<factory> as in its dashboard address, or a factory id").option("--json", "write one JSON document to stdout; the text goes to stderr").addHelpText(
3897
3945
  "after",
3898
- "\nRun it in a repository, after `outerlayer login`. With a login, connect lists\nthe factories you can use and takes the first rule that leaves one answer:\n 1. --factory <org>/<factory>, or a factory id\n 2. the factory this repository is already connected to\n 3. the one factory this repository is linked to\n 4. the one factory you can use\n 5. on a terminal, ask; with no terminal, exit 1 and list each --factory value\n\nIt says what it chose and why. The choice is saved in this repository's git\ndirectory (outerlayer/connection.json), shared by every worktree of the\nclone and never committed. A factory key already belongs to one factory, so\nconnect changes nothing for it. Connect never links a repository.\n\nAfter it saves the choice, connect checks that this repository is linked to\nthe factory. If it is not, connect says so, prints the factory's Work page\nlink where you link it, and exits 2. The choice is saved either way. Run\nconnect again once the repository is linked. With --json, the document has\nlinked (true, false, or null when it could not be checked) and, when false,\nlinkUrl.\n"
3946
+ `
3947
+ Run it in a repository, after \`${cliInvocation()} login\`. With a login, connect lists
3948
+ the factories you can use and takes the first rule that leaves one answer:
3949
+ 1. --factory <org>/<factory>, or a factory id
3950
+ 2. the factory this repository is already connected to
3951
+ 3. the one factory this repository is linked to
3952
+ 4. the one factory you can use
3953
+ 5. on a terminal, ask; with no terminal, exit 1 and list each --factory value
3954
+
3955
+ It says what it chose and why. The choice is saved in this repository's git
3956
+ directory (outerlayer/connection.json), shared by every worktree of the
3957
+ clone and never committed. A factory key already belongs to one factory, so
3958
+ connect changes nothing for it. Connect never links a repository.
3959
+
3960
+ After it saves the choice, connect checks that this repository is linked to
3961
+ the factory. If it is not, connect says so, prints the factory's Work page
3962
+ link where you link it, and exits 2. The choice is saved either way. Run
3963
+ connect again once the repository is linked. With --json, the document has
3964
+ linked (true, false, or null when it could not be checked) and, when false,
3965
+ linkUrl.
3966
+ `
3899
3967
  ).action(async (opts) => {
3900
- const { runConnectCommand } = await import('./connect-cmd-JUAW6MU4.js');
3968
+ const { runConnectCommand } = await import('./connect-cmd-ZW5FV3ZW.js');
3901
3969
  const code = await runConnectCommand({ factory: opts.factory, json: opts.json }, await realConnectDepsForCwd(), {
3902
3970
  stdout: process.stdout,
3903
3971
  stderr: process.stderr
@@ -3915,9 +3983,28 @@ function buildProgram(argv = process.argv) {
3915
3983
  });
3916
3984
  program2.command("login").description("Sign in to your account by browser approval, or save a piped factory key").option("--dashboard <address>", "the dashboard to sign in on (default: https://app.outerlayer.ai)").option("--no-browser", "print the approve link without opening a browser").option("--check <id>", "check a login that is still waiting for approval, once").option("--url <url>", "gateway base URL for a piped key (browser login gets it from the dashboard)").option("--app-id <id>", "factory id a piped key is bound to (Settings \u2192 General)").option("--no-input", "never wait on a person; fail if the key is not on stdin").option("--json", "machine-readable JSON output").addHelpText(
3917
3985
  "after",
3918
- '\nWith no key on stdin, login prints an approve link and a code. A signed-in\nperson approves it on the dashboard. The login is a token for that person\'s\naccount, made for this machine, saved here and never printed. It picks no\nfactory: run `outerlayer connect` for that. `outerlayer logout` revokes it.\n\nWhen stdout is not a terminal, login does not wait. It prints one JSON\ndocument with the link, the code and a `login --check <id>` command, and\nexits 75. Run the check until it stops exiting 75: 0 is signed in, 1 is\ndenied or expired. A dropped connection or a 5xx also exits 75.\n\nA factory key piped on stdin is saved as it always was, never as a flag and\nnever printed back. A factory key always wins over the login, so CI and\nrunners keep acting as their key.\n\n echo "$OUTERLAYER_KEY" | outerlayer login --url <gateway url> --app-id <factory id>\n\nEvery later command reads the saved file, so none of them needs a key on\nits command line. A saved key is checked for real on the first `sync`.\n'
3986
+ `
3987
+ With no key on stdin, login prints an approve link and a code. A signed-in
3988
+ person approves it on the dashboard. The login is a token for that person's
3989
+ account, made for this machine, saved here and never printed. It picks no
3990
+ factory: run \`${cliInvocation()} connect\` for that. \`${cliInvocation()} logout\` revokes it.
3991
+
3992
+ When stdout is not a terminal, login does not wait. It prints one JSON
3993
+ document with the link, the code and a \`login --check <id>\` command, and
3994
+ exits 75. Run the check until it stops exiting 75: 0 is signed in, 1 is
3995
+ denied or expired. A dropped connection or a 5xx also exits 75.
3996
+
3997
+ A factory key piped on stdin is saved as it always was, never as a flag and
3998
+ never printed back. A factory key always wins over the login, so CI and
3999
+ runners keep acting as their key.
4000
+
4001
+ echo "$OUTERLAYER_KEY" | ${cliInvocation()} login --url <gateway url> --app-id <factory id>
4002
+
4003
+ Every later command reads the saved file, so none of them needs a key on
4004
+ its command line. A saved key is checked for real on the first \`sync\`.
4005
+ `
3919
4006
  ).action(async (opts) => {
3920
- const { runLoginCommand, realLoginDeps } = await import('./login-browser-QZCS3WW6.js');
4007
+ const { runLoginCommand, realLoginDeps } = await import('./login-browser-O63QZN7D.js');
3921
4008
  const code = await runLoginCommand(
3922
4009
  {
3923
4010
  browser: opts.browser !== false,
@@ -3934,18 +4021,30 @@ function buildProgram(argv = process.argv) {
3934
4021
  });
3935
4022
  program2.command("logout").description("Revoke this machine's login and remove it").addHelpText(
3936
4023
  "after",
3937
- "\nRevokes the token `outerlayer login` saved, then deletes it from the config\nfile. A factory key and the chosen factory are left alone. When the\ndashboard cannot be reached the local copy is still deleted and the exit\ncode is 1: revoke the token on /profile/security.\n"
4024
+ `
4025
+ Revokes the token \`${cliInvocation()} login\` saved, then deletes it from the config
4026
+ file. A factory key and the chosen factory are left alone. When the
4027
+ dashboard cannot be reached the local copy is still deleted and the exit
4028
+ code is 1: revoke the token on /profile/security.
4029
+ `
3938
4030
  ).action(async () => {
3939
- const { runLogoutCommand, realLogoutDeps } = await import('./logout-cmd-QAUFDJ6J.js');
4031
+ const { runLogoutCommand, realLogoutDeps } = await import('./logout-cmd-5V5MMBSY.js');
3940
4032
  const code = await runLogoutCommand(realLogoutDeps());
3941
4033
  if (code !== 0) process.exitCode = code;
3942
4034
  });
3943
- program2.command("sync").description("Send local coding-agent sessions to OuterLayer cloud").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").option("--tier <tier>", "capture tier: metrics | redacted | full (default: full)").option("--dry-run", "print what would leave this machine, send nothing").option("--all", "ignore the sync checkpoint and re-send everything").option("--limit <n>", "only the N most recent sessions", (v) => parseInt(v, 10)).option("--json", "machine-readable JSON output").option("--quiet", "no output; exit code only").option("--root <dir>", "Claude Code transcript root (default ~/.claude/projects)").option("--codex-root <dir>", "Codex sessions root (default ~/.codex/sessions)").option("--cursor-root <dir>", "Cursor chats root (default ~/.cursor/chats)").addHelpText(
4035
+ program2.command("sync").description("Send local coding-agent sessions to OuterLayer cloud").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").option("--tier <tier>", "capture tier: metrics | redacted | full (default: full)").option("--dry-run", "print what would leave this machine, send nothing").option("--all", "ignore the sync checkpoint and re-send everything").option("--limit <n>", "only the N most recent sessions", (v) => parseInt(v, 10)).option("--json", "machine-readable JSON output").option("--quiet", "no output; exit code only").option("--link <artifactId>", "print the dashboard link of an artifact already uploaded; sends nothing").option("--root <dir>", "Claude Code transcript root (default ~/.claude/projects)").option("--codex-root <dir>", "Codex sessions root (default ~/.codex/sessions)").option("--cursor-root <dir>", "Cursor chats root (default ~/.cursor/chats)").addHelpText(
3944
4036
  "after",
3945
4037
  "\nIncremental: only sessions newer than the last checkpoint ship. The tier\nis applied on this machine before anything leaves it; `--dry-run` prints\nexactly what would. The hook-triggered background sync runs `--quiet`.\nExit 2 means the server rejected at least one session.\n"
3946
4038
  ).action(async (opts) => {
3947
4039
  warnIfApiKeyFlag(opts);
3948
- const { runSync, SyncConfigError, SyncTransportError } = await import('./sync-cmd-5G5EIEHD.js');
4040
+ if (opts.link !== void 0) {
4041
+ const { lookupArtifactLink } = await import('./artifact-link-cmd-AVM2EKTV.js');
4042
+ const found = lookupArtifactLink(opts.link);
4043
+ if (found.ok) process.stdout.write(found.url + "\n");
4044
+ else fail(found.reason);
4045
+ return;
4046
+ }
4047
+ const { runSync, SyncConfigError, SyncTransportError } = await import('./sync-cmd-Q6VYNPWG.js');
3949
4048
  try {
3950
4049
  const result = await runSync({
3951
4050
  url: opts.url,
@@ -3970,11 +4069,13 @@ function buildProgram(argv = process.argv) {
3970
4069
  throw err;
3971
4070
  }
3972
4071
  });
3973
- program2.command("daemon").alias("watch").description("Run the copy-out daemon that mirrors transcripts before they are deleted").option("--once", "do a single mirror sweep and exit").addHelpText("after", "\n`outerlayer watch` is the former name and still works, with a warning.\n").action(async (opts) => {
4072
+ program2.command("daemon").alias("watch").description("Run the copy-out daemon that mirrors transcripts before they are deleted").option("--once", "do a single mirror sweep and exit").addHelpText("after", `
4073
+ \`${cliInvocation()} watch\` is the former name and still works, with a warning.
4074
+ `).action(async (opts) => {
3974
4075
  if (invokedCommandToken(argv) === "watch") {
3975
- warnDeprecated("`outerlayer watch` is now `outerlayer daemon`; the old name will be removed in a future release.");
4076
+ warnDeprecated(`\`${cliInvocation()} watch\` is now \`${cliInvocation()} daemon\`; the old name will be removed in a future release.`);
3976
4077
  }
3977
- const { runWatch } = await import('./watch-UTOXO4SE.js');
4078
+ const { runWatch } = await import('./watch-ARAGADTF.js');
3978
4079
  await runWatch({ once: opts.once });
3979
4080
  });
3980
4081
  program2.command("hook <event>", { hidden: true }).description("capture-hook fast path, installed by `init`").action(() => {
@@ -3983,6 +4084,8 @@ function buildProgram(argv = process.argv) {
3983
4084
  });
3984
4085
  program2.command("hook-wrap", { hidden: true }).description("runs a hook wrapped by `hooks wrap`, recording exec evidence").option("--id <base64>", "the original hook command, base64-encoded").option("--capture-output", "tee stdout/stderr into a bounded tail").action(() => {
3985
4086
  });
4087
+ program2.command("claude [args...]", { hidden: true }).description("start Claude Code with the repository's control plane attached").allowUnknownOption().helpOption(false).action(() => {
4088
+ });
3986
4089
  program2.command("broker-relay", { hidden: true }).description("relay a build's loopback connections to the runner's tunnel and destinations").requiredOption("--port <port>", "the loopback port to listen on").requiredOption("--socket <path>", "the runner's tunnel socket").option("--forward <port=socket>", "also relay a loopback port to a destination's socket; repeatable", (value, previous) => [...previous, value], []).action(async (opts) => {
3987
4090
  const { startRelay } = await import('./relay-L76HDX72.js');
3988
4091
  const port = Number(opts.port);
@@ -4003,7 +4106,7 @@ function buildProgram(argv = process.argv) {
4003
4106
  await startRelay({ port, socketPath: opts.socket, forwards });
4004
4107
  });
4005
4108
  program2.command("vm-exec", { hidden: true }).description("run a command in a microVM build's guest through its exec agent").requiredOption("--socket <path>", "the Unix socket Firecracker serves the build's vsock device on").option("--user <user>", "the user to run as", "").option("--cwd <dir>", "the working directory", "/").option("--env <NAME=value>", "an environment value the command line carries; repeatable", (value, previous) => [...previous, value], []).option("--pass <NAME>", "an environment variable whose value is read from this process; repeatable", (value, previous) => [...previous, value], []).argument("<argv...>", "the command and its arguments").action(async (argv2, opts) => {
4006
- const { runVmExec } = await import('./exec-client-CQV746UF.js');
4109
+ const { runVmExec } = await import('./exec-client-7UMESIE2.js');
4007
4110
  const env = {};
4008
4111
  for (const entry of opts.env) {
4009
4112
  const eq = entry.indexOf("=");
@@ -4028,7 +4131,7 @@ function buildProgram(argv = process.argv) {
4028
4131
  }
4029
4132
  });
4030
4133
  program2.command("session-sender", { hidden: true }).description("stream a runner build's sessions to its work item while the build runs").action(async () => {
4031
- const { runSessionSender } = await import('./session-sender-24L5EW25.js');
4134
+ const { runSessionSender } = await import('./session-sender-NNVPSPY7.js');
4032
4135
  try {
4033
4136
  await runSessionSender();
4034
4137
  } catch (err) {
@@ -4036,7 +4139,7 @@ function buildProgram(argv = process.argv) {
4036
4139
  }
4037
4140
  });
4038
4141
  program2.command("vm-init", { hidden: true }).description("lay out a microVM build's guest; the guest helper runs it").action(async () => {
4039
- const { realGuestHost, runGuestInit } = await import('./guest-init-LJP5GPZF.js');
4142
+ const { realGuestHost, runGuestInit } = await import('./guest-init-KPBQOFE5.js');
4040
4143
  const { readFileSync: readFileSync3 } = await import('fs');
4041
4144
  try {
4042
4145
  runGuestInit(realGuestHost(), readFileSync3("/proc/cmdline", "utf8"));
@@ -4050,6 +4153,7 @@ function buildProgram(argv = process.argv) {
4050
4153
  registerMcpCommands(program2);
4051
4154
  registerContextCommands(program2);
4052
4155
  registerWorkCommands(program2);
4156
+ registerSpecCommands(program2);
4053
4157
  registerRunnerCommands(program2);
4054
4158
  return program2;
4055
4159
  }
@@ -4063,7 +4167,7 @@ async function runCli(processArgv) {
4063
4167
  await buildProgram(argv).parseAsync(argv);
4064
4168
  }
4065
4169
  async function compileContext(opts) {
4066
- const { runEmit, EmitError } = await import('./emit-cmd-IJ3GVRFD.js');
4170
+ const { runEmit, EmitError } = await import('./emit-cmd-PPN4QQF2.js');
4067
4171
  try {
4068
4172
  const result = runEmit({ cwd: opts.dir, check: opts.check, json: opts.json });
4069
4173
  if (result.exitCode !== 0) process.exitCode = result.exitCode;
@@ -4078,15 +4182,39 @@ async function compileContext(opts) {
4078
4182
  function registerEmitCommands(program2) {
4079
4183
  const emitCmd = program2.command("emit").enablePositionalOptions().description("Record evidence for a work item: a named check result").argument("[name]", "the check name a validator declares (e.g. smoke.pass)").option("--link <url>", "proof link, usually the CI run URL").option("--body <text>", "one sentence recording your judgment").option("--body-file <path>", "read the sentence from a file; - reads standard input").option("--result <pass|fail>", "check outcome (default: pass)").option("--item <number>", "work item to record on directly, by its number in this factory").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addOption(new Option("--check", "deprecated: use `context emit --check`").hideHelp()).addOption(new Option("--dir <path>", "deprecated: use `context emit --dir`").hideHelp()).addHelpText(
4080
4184
  "after",
4081
- "\n outerlayer emit smoke.pass --item 412 --link https://ci.example.com/run/42\n outerlayer emit code-review --result fail --item 412 --body \"The button should be red.\"\n\nRecords that a named check passed or failed, anchored to a work item.\n--item is always required, in or out of a recorded session \u2014 there is no\nsession fallback. A check that ran carries the run URL as proof (--link);\na judgment you are making yourself carries one sentence (--body, or\n--body-file to read it from a file or standard input). A fail needs at\nleast one of the two. Run from inside a session, the check is stored as\nthe session's; a session cannot record over a person's fail. With\nnothing to attach it to, the command refuses.\n\nWho recorded a check is decided by your API key, never by what you send:\na key bound to your membership records it as yours, and nothing automated\ncan then clear it.\n\nA person's pass or fail on one criterion's proof is recorded on that\ncriterion's thread instead \u2014 see `outerlayer work comment`.\n\nSubcommands: `emit artifact <file>` for screenshots, recordings, reports and\nlogs; `emit finding` / `emit findings <file>` to record a problem in the factory;\n`emit criteria <file>` to record a work item's acceptance criteria.\nCompiling .outerlayer/ moved to `outerlayer context emit`.\n"
4185
+ `
4186
+ ${cliInvocation()} emit smoke.pass --item 412 --link https://ci.example.com/run/42
4187
+ ${cliInvocation()} emit code-review --result fail --item 412 --body "The button should be red."
4188
+
4189
+ Records that a named check passed or failed, anchored to a work item.
4190
+ --item is always required, in or out of a recorded session \u2014 there is no
4191
+ session fallback. A check that ran carries the run URL as proof (--link);
4192
+ a judgment you are making yourself carries one sentence (--body, or
4193
+ --body-file to read it from a file or standard input). A fail needs at
4194
+ least one of the two. Run from inside a session, the check is stored as
4195
+ the session's; a session cannot record over a person's fail. With
4196
+ nothing to attach it to, the command refuses.
4197
+
4198
+ Who recorded a check is decided by your API key, never by what you send:
4199
+ a key bound to your membership records it as yours, and nothing automated
4200
+ can then clear it.
4201
+
4202
+ A person's pass or fail on one criterion's proof is recorded on that
4203
+ criterion's thread instead \u2014 see \`${cliInvocation()} work comment\`.
4204
+
4205
+ Subcommands: \`emit artifact <file>\` for screenshots, recordings, reports and
4206
+ logs; \`emit finding\` / \`emit findings <file>\` to record a problem in the factory;
4207
+ \`emit criteria <file>\` to record a work item's acceptance criteria.
4208
+ Compiling .outerlayer/ moved to \`${cliInvocation()} context emit\`.
4209
+ `
4082
4210
  ).action(async (name, opts) => {
4083
4211
  if (name === void 0) {
4084
- warnDeprecated("`outerlayer emit` with no name now lives at `outerlayer context emit`; the bare form will be removed in a future release.");
4212
+ warnDeprecated(`\`${cliInvocation()} emit\` with no name now lives at \`${cliInvocation()} context emit\`; the bare form will be removed in a future release.`);
4085
4213
  await compileContext({ dir: opts.dir, check: opts.check, json: opts.json });
4086
4214
  return;
4087
4215
  }
4088
4216
  warnIfApiKeyFlag(opts);
4089
- const { runEmitResult, EmitResultError } = await import('./emit-result-cmd-RFKALS5Z.js');
4217
+ const { runEmitResult, EmitResultError } = await import('./emit-result-cmd-UZVMOCF4.js');
4090
4218
  try {
4091
4219
  const result = await runEmitResult({
4092
4220
  name,
@@ -4116,10 +4244,10 @@ function registerEmitCommands(program2) {
4116
4244
  ).option("--replaces <ids>", "artifact id(s) this one corrects; comma-separate 2-20 to retire them all").option("--pr <number>", "pull request number to anchor to").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addHelpText(
4117
4245
  "after",
4118
4246
  `
4119
- outerlayer emit artifact shot.png --caption "the dot row, current dot marked" --for LOGIN-01
4247
+ ${cliInvocation()} emit artifact shot.png --caption "the dot row, current dot marked" --for LOGIN-01
4120
4248
 
4121
4249
  Inside a recorded Claude Code session the artifact spools locally and ships,
4122
- bound to that session, on the next \`outerlayer sync\` \u2014 but only if that
4250
+ bound to that session, on the next \`${cliInvocation()} sync\` \u2014 but only if that
4123
4251
  session was launched with OUTERLAYER_WORK; without it nothing the session
4124
4252
  produces may leave the machine, and the command refuses rather than queue an
4125
4253
  artifact that can never ship. Otherwise it uploads immediately, anchored to a
@@ -4137,7 +4265,7 @@ locally so it never uploads. One correction can retire several duplicates.
4137
4265
  `
4138
4266
  ).action(async (file, opts) => {
4139
4267
  warnIfApiKeyFlag(opts);
4140
- const { runEmitArtifact, EmitArtifactError } = await import('./emit-artifact-cmd-H5MH3AOP.js');
4268
+ const { runEmitArtifact, EmitArtifactError } = await import('./emit-artifact-cmd-7MP63KXU.js');
4141
4269
  let pr;
4142
4270
  if (opts.pr !== void 0) {
4143
4271
  pr = parseTrackerNumber(opts.pr);
@@ -4169,10 +4297,35 @@ locally so it never uploads. One correction can retire several duplicates.
4169
4297
  });
4170
4298
  emitCmd.command("finding").description("Record one finding: a problem you hit in the factory").requiredOption("--id <id>", "client id \u2014 re-emitting the same id replaces it").option("--category <context|flaky-test|tooling|environment|defect|other>", "what kind of problem this is").requiredOption("--title <text>", "one sentence").requiredOption("--file <path>", "the file the problem is in, or the instruction's own path").option("--line <number>", "line number in --file").option("--rule-path <path>", "the instruction's own path \u2014 required for a context finding").option("--rule-line <number>", "line number in --rule-path").option("--rule-quote <text>", "the instruction's quoted sentence \u2014 required unless --rule-relation is missing").option("--rule-relation <wrong|broken|missing>", "how the instruction fails \u2014 required for a context finding").requiredOption("--where <label>", 'where this was recorded, e.g. "build session"').option("--detail <text>", "what happened, a few paragraphs").option("--cause <text>", 'what is actually wrong, or "unknown"').option("--suggestion <text>", "what would help").option("--command <text>", "the command that misbehaved").option("--error <text>", "the first error line").option("--test <name>", "the test name, for a flaky test").option("--symbol <name>", "the function the problem is in, for a defect").option("--started-at <event>", "event id where you hit the problem (default: first output showing --error)").option("--ended-at <event>", "event id where you got past it (default: the newest event)").option("--untimed", "record no time, only when the problem cost none in this session").option("--find <text>", "list this session's events that contain the text, and record nothing").option("--problem <id>", "a problem this finding reports; repeat for each one (needs --link-reason)", (value, previous = []) => [...previous, value]).option("--link-reason <text>", "why the finding reports each --problem").option("--new", "record a new problem instead of linking an existing one").option("--trace-id <id>", "the session's trace id, when there is one").option("--turn <number>", "turn index within the session").option("--round <number>", "round number, when there is one").addOption(new Option("--subject <value>").hideHelp()).addOption(new Option("--kind <value>").hideHelp()).addOption(new Option("--severity <value>").hideHelp()).addOption(new Option("--verdict <value>").hideHelp()).addOption(new Option("--fixed").hideHelp()).addOption(new Option("--source <value>").hideHelp()).option("--head-sha <sha>", "the commit this batch was recorded against").option("--context-source-sha <sha>", "the context-source commit this batch was recorded against").option("--item <number>", "work item to record on directly, by its number in this factory").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addHelpText(
4171
4299
  "after",
4172
- '\n outerlayer emit finding --id gate-skip --category tooling \\\n --title "The pre-push gate prints green when a step was skipped" \\\n --file scripts/git/pre-push-checks.mjs --where "build session"\n\nRecords one problem you hit in the factory: an instruction that is wrong or\nmissing (context), a flaky test, a misbehaving script (tooling), a local\nsetup trap (environment), a bug outside your own change (defect), or\nanything else. A problem in your own change is fixed, not recorded.\nA context finding needs --rule-path and --rule-relation, and --rule-quote\nunless the relation is missing. Add --cause, --detail and --suggestion, and\n--command, --error (first error line), --test or --symbol when there is one.\n\nEvery finding joins a factory-level problem. When one of its keys matches a\nknown problem it joins that problem. Otherwise nothing is stored: the command\nprints the closest problems and exits with code 3. Run it again with\n--problem <id> --link-reason <why> (repeat --problem for each problem the\nfinding reports), or with --new if none of them is the same problem.\nA finding records the time it cost, read from two events of your session or\nits subagents. The start is --started-at, else the first tool output that\nshows --error. The end is --ended-at, else the newest event. When the session\nhas events and no start is found, nothing is stored: the command lists the\nrecent events with their ids. --find <text> searches all of them.\n--item names the work item; without it, inside a recorded session, the\nitem that session was launched for is used automatically.\n'
4300
+ `
4301
+ ${cliInvocation()} emit finding --id gate-skip --category tooling \\
4302
+ --title "The pre-push gate prints green when a step was skipped" \\
4303
+ --file scripts/git/pre-push-checks.mjs --where "build session"
4304
+
4305
+ Records one problem you hit in the factory: an instruction that is wrong or
4306
+ missing (context), a flaky test, a misbehaving script (tooling), a local
4307
+ setup trap (environment), a bug outside your own change (defect), or
4308
+ anything else. A problem in your own change is fixed, not recorded.
4309
+ A context finding needs --rule-path and --rule-relation, and --rule-quote
4310
+ unless the relation is missing. Add --cause, --detail and --suggestion, and
4311
+ --command, --error (first error line), --test or --symbol when there is one.
4312
+
4313
+ Every finding joins a factory-level problem. When one of its keys matches a
4314
+ known problem it joins that problem. Otherwise nothing is stored: the command
4315
+ prints the closest problems and exits with code 3. Run it again with
4316
+ --problem <id> --link-reason <why> (repeat --problem for each problem the
4317
+ finding reports), or with --new if none of them is the same problem.
4318
+ A finding records the time it cost, read from two events of your session or
4319
+ its subagents. The start is --started-at, else the first tool output that
4320
+ shows --error. The end is --ended-at, else the newest event. When the session
4321
+ has events and no start is found, nothing is stored: the command lists the
4322
+ recent events with their ids. --find <text> searches all of them.
4323
+ --item names the work item; without it, inside a recorded session, the
4324
+ item that session was launched for is used automatically.
4325
+ `
4173
4326
  ).action(async (opts) => {
4174
4327
  warnIfApiKeyFlag(opts);
4175
- const { runEmitFinding, refuseRemovedFindingFlags, EmitFindingError } = await import('./emit-finding-cmd-ZXDRL6PG.js');
4328
+ const { runEmitFinding, refuseRemovedFindingFlags, EmitFindingError } = await import('./emit-finding-cmd-OL7JB6H3.js');
4176
4329
  try {
4177
4330
  refuseRemovedFindingFlags(opts);
4178
4331
  const result = await runEmitFinding({
@@ -4222,10 +4375,25 @@ locally so it never uploads. One correction can retire several duplicates.
4222
4375
  });
4223
4376
  emitCmd.command("findings <file>").description("Record a batch of findings from a FindingBatch JSON file (- for standard input)").option("--item <number>", "work item to record on directly, by its number in this factory").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addHelpText(
4224
4377
  "after",
4225
- '\n outerlayer emit findings review.json --item 412\n\nRecords a whole batch at once \u2014 a FindingBatch JSON file (schemaVersion 3,\noptional headSha / contextSourceSha, and 1-500 findings), validated against\nthe same contract `emit finding` uses before anything is sent. Re-emitting\nan id already on the item replaces that row rather than adding a second one.\nFindings that need a choice between candidate problems are listed and not\nstored; the rest are stored, and the exit code is 3.\nEach finding records its time as `emit finding` does: "startedAt" is an event\nid, else the first tool output that shows its "error"; "endedAt" defaults to\nthe newest event. A finding with neither, in a session that has events, stops\nthe whole batch. "untimed": true records one with no time on purpose.\n--item names the work item; without it, inside a recorded session, the item\nthat session was launched for is used automatically.\n'
4378
+ `
4379
+ ${cliInvocation()} emit findings review.json --item 412
4380
+
4381
+ Records a whole batch at once \u2014 a FindingBatch JSON file (schemaVersion 3,
4382
+ optional headSha / contextSourceSha, and 1-500 findings), validated against
4383
+ the same contract \`emit finding\` uses before anything is sent. Re-emitting
4384
+ an id already on the item replaces that row rather than adding a second one.
4385
+ Findings that need a choice between candidate problems are listed and not
4386
+ stored; the rest are stored, and the exit code is 3.
4387
+ Each finding records its time as \`emit finding\` does: "startedAt" is an event
4388
+ id, else the first tool output that shows its "error"; "endedAt" defaults to
4389
+ the newest event. A finding with neither, in a session that has events, stops
4390
+ the whole batch. "untimed": true records one with no time on purpose.
4391
+ --item names the work item; without it, inside a recorded session, the item
4392
+ that session was launched for is used automatically.
4393
+ `
4226
4394
  ).action(async (file, opts) => {
4227
4395
  warnIfApiKeyFlag(opts);
4228
- const { runEmitFindings, EmitFindingError } = await import('./emit-finding-cmd-ZXDRL6PG.js');
4396
+ const { runEmitFindings, EmitFindingError } = await import('./emit-finding-cmd-OL7JB6H3.js');
4229
4397
  try {
4230
4398
  const result = await runEmitFindings({
4231
4399
  file,
@@ -4247,7 +4415,7 @@ locally so it never uploads. One correction can retire several duplicates.
4247
4415
  emitCmd.command("criteria <file>").description("Record a work item's acceptance criteria from a JSON file (- for stdin)").option("--item <number>", "work item to record on directly, by its number in this factory").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addHelpText(
4248
4416
  "after",
4249
4417
  `
4250
- outerlayer emit criteria criteria.json --item 412
4418
+ ${cliInvocation()} emit criteria criteria.json --item 412
4251
4419
 
4252
4420
  Records the item's acceptance criteria as one list. The file is JSON:
4253
4421
  { "criteria": [ { "id": "AC-1", "text": "Given ...", "proof": null },
@@ -4265,7 +4433,7 @@ session was launched for is used automatically.
4265
4433
  `
4266
4434
  ).action(async (file, opts) => {
4267
4435
  warnIfApiKeyFlag(opts);
4268
- const { runEmitCriteria, EmitCriteriaError } = await import('./emit-criteria-cmd-HBP6XS5C.js');
4436
+ const { runEmitCriteria, EmitCriteriaError } = await import('./emit-criteria-cmd-3RXPDLNK.js');
4269
4437
  try {
4270
4438
  const result = await runEmitCriteria({
4271
4439
  file,
@@ -4285,14 +4453,56 @@ session was launched for is used automatically.
4285
4453
  }
4286
4454
  });
4287
4455
  }
4456
+ function registerSpecCommands(program2) {
4457
+ const specCmd = program2.command("spec").description("Find, file and edit the issue a spec lives in, in your factory's tracker");
4458
+ const credentialOptions = (command) => command.option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)");
4459
+ const run = async (opts, action) => {
4460
+ warnIfApiKeyFlag(opts);
4461
+ const { WorkCommandError } = await import('./work-cmd-7S5B2H2P.js');
4462
+ try {
4463
+ await action(await import('./spec-cmd-OWVTBB4J.js'));
4464
+ } catch (err) {
4465
+ if (err instanceof WorkCommandError) {
4466
+ fail(err.message);
4467
+ return;
4468
+ }
4469
+ throw err;
4470
+ }
4471
+ };
4472
+ credentialOptions(
4473
+ specCmd.command("target").description("Say where a spec would be filed, and whether this factory can file there").option("--tracker <name>", "one connected tracker, when the factory has more than one")
4474
+ ).action((opts) => run(opts, (spec) => spec.runSpecTarget(opts)));
4475
+ credentialOptions(
4476
+ specCmd.command("search").description("Find open and closed issues that match a query, before filing a duplicate").argument("<query>", "words from the title or body").option("--tracker <name>", "one connected tracker, when the factory has more than one")
4477
+ ).action((query, opts) => run(opts, (spec) => spec.runSpecSearch({ ...opts, query })));
4478
+ credentialOptions(
4479
+ specCmd.command("file").description("File a spec as an issue and print its key, URL and the command that builds it").requiredOption("--title <text>", "the issue title").option("--body-file <path>", "the spec as Markdown; - reads standard input").option("--repo <owner/name>", "the repository to file in (GitHub), or the one the work lands in").option("--tracker <name>", "the tracker, when the factory files in more than one").option("--scope <key>", "the Linear team or Jira project, when the connection covers several")
4480
+ ).action((opts) => run(opts, (spec) => spec.runSpecFile(opts)));
4481
+ credentialOptions(
4482
+ specCmd.command("edit").description("Replace the body of an issue that holds a spec").argument("<key>", "the issue key, such as acme/api#12 or ENG-142").option("--body-file <path>", "the new body as Markdown; - reads standard input").option("--updated-at <timestamp>", "the updatedAt you read; the edit is refused if the issue has changed since")
4483
+ ).action((key, opts) => run(opts, (spec) => spec.runSpecEdit({ ...opts, key })));
4484
+ }
4288
4485
  function registerWorkCommands(program2) {
4289
4486
  const workCmd = program2.command("work").description("Start, remove, or read what the factory is working on (the Work page)");
4290
- workCmd.command("build").description("Start work on an item: a host builds it, or with --local you do").option("--issue <number>", "issue number, or a Linear/Jira key like ENG-123 (adds it to Work first)").option("--tracker <linear|jira>", "tracker for a keyed --issue \u2014 required if more than one is connected").option("--item <number>", "the item's own number inside this factory \u2014 must already exist").option("--local", "you build it in a session you start; no host takes it").option("--repo <owner/name>", "repository \u2014 the checkout's remote for a number, never guessed for a key").option("--note <text>", "a short note stored with the request").option("--session-id <id>", "session id to record as-is, skipping detection").option("--retry <count>", "retries after a gateway answer worth trying again").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addHelpText(
4487
+ workCmd.command("build").description("Start work on an item: a host builds it, or with --local you do").option("--issue <number>", "issue number, or a Linear/Jira key like ENG-123 (adds it to Work first)").option(TRACKER_OPTION_FLAG, "tracker for a keyed --issue \u2014 required if more than one is connected").option("--item <number>", "the item's own number inside this factory \u2014 must already exist").option("--local", "you build it in a session you start; no host takes it").option("--repo <owner/name>", "repository \u2014 the checkout's remote for a number, never guessed for a key").option("--note <text>", "a short note stored with the request").option("--session-id <id>", "session id to record as-is, skipping detection").option("--retry <count>", "retries after a gateway answer worth trying again").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addHelpText(
4291
4488
  "after",
4292
- "\n outerlayer work build --issue 42 # a host builds it\n outerlayer work build --item 7 # retry an item already on Work\n outerlayer work build --issue 42 --local # you build it\n outerlayer work build --issue ENG-123 --tracker linear # a Linear issue\n\nWithout --local, records a build request: a host takes the item when it\nhas a free slot. It never links the calling session and never takes a\nlease, even run from inside one.\n\nWith --local, puts the item on Work with no build request, so no host\ntakes it, and prints the line that starts your session on it. That\nsession links itself and holds the item's lease while it works.\n"
4489
+ `
4490
+ ${cliInvocation()} work build --issue 42 # a host builds it
4491
+ ${cliInvocation()} work build --item 7 # retry an item already on Work
4492
+ ${cliInvocation()} work build --issue 42 --local # you build it
4493
+ ${cliInvocation()} work build --issue ENG-123 --tracker linear # a Linear issue
4494
+
4495
+ Without --local, records a build request: a host takes the item when it
4496
+ has a free slot. It never links the calling session and never takes a
4497
+ lease, even run from inside one.
4498
+
4499
+ With --local, puts the item on Work with no build request, so no host
4500
+ takes it, and prints the line that starts your session on it. That
4501
+ session links itself and holds the item's lease while it works.
4502
+ `
4293
4503
  ).action(async (opts) => {
4294
4504
  warnIfApiKeyFlag(opts);
4295
- const { runWorkBuild, WorkCommandError } = await import('./work-cmd-DJQJEZVB.js');
4505
+ const { runWorkBuild, WorkCommandError } = await import('./work-cmd-7S5B2H2P.js');
4296
4506
  try {
4297
4507
  await runWorkBuild(opts);
4298
4508
  } catch (err) {
@@ -4305,10 +4515,18 @@ function registerWorkCommands(program2) {
4305
4515
  });
4306
4516
  workCmd.command("link-session").description("Link the calling session to a work item that already exists").argument("<number>", "the item's number inside this factory").option("--session-id <id>", "session id to record as-is, skipping detection (used by the SessionStart hook)").option("--retry <count>", "resend after a failure that may pass, with backoff").option("--no-lease", "link only \u2014 ask for no session lease (used by a host's own job)").option("--ended", "release this session's lease instead of taking or renewing one").addOption(new Option("--log-failures", "append a failure to the hook error log (used by hooks)").hideHelp()).option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addHelpText(
4307
4517
  "after",
4308
- "\n outerlayer work link-session 42\n\nNames an item that already exists on the calling session \u2014 it never\ncreates one. Create one with `outerlayer work build --local`. Linking\nthe same session to the same item twice is a no-op. Also takes or\nrenews that session's own 20-minute lease, unless --no-lease or\n--ended is given, or another worker already holds a live one.\n"
4518
+ `
4519
+ ${cliInvocation()} work link-session 42
4520
+
4521
+ Names an item that already exists on the calling session \u2014 it never
4522
+ creates one. Create one with \`${cliInvocation()} work build --local\`. Linking
4523
+ the same session to the same item twice is a no-op. Also takes or
4524
+ renews that session's own 20-minute lease, unless --no-lease or
4525
+ --ended is given, or another worker already holds a live one.
4526
+ `
4309
4527
  ).action(async (number, opts) => {
4310
4528
  warnIfApiKeyFlag(opts);
4311
- const { runWorkLinkSession, WorkCommandError } = await import('./work-cmd-DJQJEZVB.js');
4529
+ const { runWorkLinkSession, WorkCommandError } = await import('./work-cmd-7S5B2H2P.js');
4312
4530
  try {
4313
4531
  await runWorkLinkSession({ ...opts, number, noLease: opts.lease === false, logFailures: opts.logFailures === true });
4314
4532
  } catch (err) {
@@ -4322,7 +4540,7 @@ function registerWorkCommands(program2) {
4322
4540
  workCmd.command("comment").description("Post a comment on a work item's general thread or one of its criterion threads").requiredOption("--item <number>", "the item's number inside this factory").option("--criterion <id>", "the criterion thread to post on (default: the general thread)").option("--body <text>", "the comment's text").option("--artifact <id>", "artifact id \u2014 required with --attach, naming the criterion's new proof").option("--pass", "record a pass (a person's verdict; refused from an agent session or a key)").option("--fail", "record a fail (a person's verdict; refused from an agent session or a key)").option("--attach", "make --artifact the named criterion's proof").option("--ready", "hand the thread back to a person (refused from a person)").option("--session-id <id>", "session id to record as-is, skipping detection (used by the SessionStart hook)").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addHelpText(
4323
4541
  "after",
4324
4542
  `
4325
- outerlayer work comment --item 7 --criterion AC-9-01 --body "fixed" --artifact 3f2a1c9d --attach
4543
+ ${cliInvocation()} work comment --item 7 --criterion AC-9-01 --body "fixed" --artifact 3f2a1c9d --attach
4326
4544
 
4327
4545
  Posts one comment. Omitting --criterion posts on the general thread;
4328
4546
  naming one posts on that criterion's own thread. --pass/--fail are a
@@ -4333,8 +4551,8 @@ most one action flag may be given.
4333
4551
  `
4334
4552
  ).action(async (opts) => {
4335
4553
  warnIfApiKeyFlag(opts);
4336
- const { runWorkComment, WorkCommentCommandError } = await import('./work-comment-cmd-A55CUSYE.js');
4337
- const { WorkCommandError } = await import('./work-cmd-DJQJEZVB.js');
4554
+ const { runWorkComment, WorkCommentCommandError } = await import('./work-comment-cmd-SZJSPM5S.js');
4555
+ const { WorkCommandError } = await import('./work-cmd-7S5B2H2P.js');
4338
4556
  try {
4339
4557
  await runWorkComment(opts);
4340
4558
  } catch (err) {
@@ -4347,11 +4565,17 @@ most one action flag may be given.
4347
4565
  });
4348
4566
  workCmd.command("threads").description("Read a work item's general thread and its criterion threads").requiredOption("--item <number>", "the item's number inside this factory").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addHelpText(
4349
4567
  "after",
4350
- "\n outerlayer work threads --item 7\n\nPrints the item's general thread, then one thread per acceptance\ncriterion that carries proof or a comment, each with who is waiting\non whom next.\n"
4568
+ `
4569
+ ${cliInvocation()} work threads --item 7
4570
+
4571
+ Prints the item's general thread, then one thread per acceptance
4572
+ criterion that carries proof or a comment, each with who is waiting
4573
+ on whom next.
4574
+ `
4351
4575
  ).action(async (opts) => {
4352
4576
  warnIfApiKeyFlag(opts);
4353
- const { runWorkThreads, WorkCommentCommandError } = await import('./work-comment-cmd-A55CUSYE.js');
4354
- const { WorkCommandError } = await import('./work-cmd-DJQJEZVB.js');
4577
+ const { runWorkThreads, WorkCommentCommandError } = await import('./work-comment-cmd-SZJSPM5S.js');
4578
+ const { WorkCommandError } = await import('./work-cmd-7S5B2H2P.js');
4355
4579
  try {
4356
4580
  await runWorkThreads(opts);
4357
4581
  } catch (err) {
@@ -4362,10 +4586,10 @@ most one action flag may be given.
4362
4586
  throw err;
4363
4587
  }
4364
4588
  });
4365
- workCmd.command("remove").description("Withdraw a source's addition \u2014 never deletes the item").option("--item <number>", "the item's number inside this factory").option("--issue <number>", "issue number (default: named by --item instead)").option("--repo <owner/name>", "repository (default: the current checkout's remote)").requiredOption("--reason <text>", "why this source is no longer working on it").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addHelpText(
4589
+ workCmd.command("remove").description("Withdraw a source's addition \u2014 never deletes the item").option("--item <number>", "the item's number inside this factory").option("--issue <number>", "issue number or Linear/Jira key (default: named by --item instead)").option(TRACKER_OPTION_FLAG, "tracker for a keyed --issue, when both hold the key").option("--repo <owner/name>", "repository for an issue number (default: the checkout's remote)").requiredOption("--reason <text>", "why this source is no longer working on it").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addHelpText(
4366
4590
  "after",
4367
4591
  `
4368
- outerlayer work remove --item 42 --reason "done"
4592
+ ${cliInvocation()} work remove --item 42 --reason "done"
4369
4593
 
4370
4594
  Withdraws the caller's own addition, recording the reason. Never
4371
4595
  deletes anything \u2014 the item is removed from Work only once no
@@ -4373,7 +4597,7 @@ live addition remains on it.
4373
4597
  `
4374
4598
  ).action(async (opts) => {
4375
4599
  warnIfApiKeyFlag(opts);
4376
- const { runWorkRemove, WorkCommandError } = await import('./work-cmd-DJQJEZVB.js');
4600
+ const { runWorkRemove, WorkCommandError } = await import('./work-cmd-7S5B2H2P.js');
4377
4601
  try {
4378
4602
  await runWorkRemove(opts);
4379
4603
  } catch (err) {
@@ -4384,9 +4608,9 @@ live addition remains on it.
4384
4608
  throw err;
4385
4609
  }
4386
4610
  });
4387
- workCmd.command("status").description("Show one item's stage, gate ledger, and additions").option("--item <number>", "the item's number inside this factory").option("--issue <number>", "issue number").option("--repo <owner/name>", "repository (default: the current checkout's remote)").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").action(async (opts) => {
4611
+ workCmd.command("status").description("Show one item's stage, gate ledger, and additions").option("--item <number>", "the item's number inside this factory").option("--issue <number>", "issue number, or a Linear/Jira key like ENG-123").option(TRACKER_OPTION_FLAG, "tracker for a keyed --issue, when both hold the key").option("--repo <owner/name>", "repository for an issue number (default: the checkout's remote)").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").action(async (opts) => {
4388
4612
  warnIfApiKeyFlag(opts);
4389
- const { runWorkStatus, WorkCommandError } = await import('./work-cmd-DJQJEZVB.js');
4613
+ const { runWorkStatus, WorkCommandError } = await import('./work-cmd-7S5B2H2P.js');
4390
4614
  try {
4391
4615
  await runWorkStatus(opts);
4392
4616
  } catch (err) {
@@ -4399,7 +4623,7 @@ live addition remains on it.
4399
4623
  });
4400
4624
  workCmd.command("list").description("List live work items for this factory").option("--stage <spec|build|review>", "filter by stage").option("--section <attention|waiting|flowing>", "filter by Queue section").option("--repo <owner/name>", "repository (default: the current checkout's remote)").option("--unclaimed", "only items with no live lease").option("--claimed", "only items with a live lease, held by a host or a local session").option("--startable", "only items a host can start: a build request, no open PR, no live lease").option("--needs <amend>", "only items with a thread waiting on an agent").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").action(async (opts) => {
4401
4625
  warnIfApiKeyFlag(opts);
4402
- const { runWorkList, WorkCommandError } = await import('./work-cmd-DJQJEZVB.js');
4626
+ const { runWorkList, WorkCommandError } = await import('./work-cmd-7S5B2H2P.js');
4403
4627
  try {
4404
4628
  await runWorkList(opts);
4405
4629
  } catch (err) {
@@ -4410,12 +4634,18 @@ live addition remains on it.
4410
4634
  throw err;
4411
4635
  }
4412
4636
  });
4413
- workCmd.command("claim").description("Claim a work item under a lease this API key holds before working on it").option("--item <number>", "the item's number inside this factory").option("--issue <number>", "issue number (default: named by --item instead)").option("--repo <owner/name>", "repository (default: the current checkout's remote)").requiredOption("--kind <implement|amend>", "what this host is doing with the item").option("--host <name>", "the claiming host (default: this machine's hostname)").option("--seconds <n>", "lease length in seconds, clamped to 900 (default: 900)").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addHelpText(
4637
+ workCmd.command("claim").description("Claim a work item under a lease this API key holds before working on it").option("--item <number>", "the item's number inside this factory").option("--issue <number>", "issue number or Linear/Jira key (default: named by --item instead)").option(TRACKER_OPTION_FLAG, "tracker for a keyed --issue, when both hold the key").option("--repo <owner/name>", "repository for an issue number (default: the checkout's remote)").requiredOption("--kind <implement|amend>", "what this host is doing with the item").option("--host <name>", "the claiming host (default: this machine's hostname)").option("--seconds <n>", "lease length in seconds, clamped to 900 (default: 900)").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addHelpText(
4414
4638
  "after",
4415
- "\n outerlayer work claim --item 42 --kind implement\n\nRecords a lease held by the API key that sends it. A live lease held\nby a different key is refused; claiming again with the same key extends\nit. --host is a label only.\n"
4639
+ `
4640
+ ${cliInvocation()} work claim --item 42 --kind implement
4641
+
4642
+ Records a lease held by the API key that sends it. A live lease held
4643
+ by a different key is refused; claiming again with the same key extends
4644
+ it. --host is a label only.
4645
+ `
4416
4646
  ).action(async (opts) => {
4417
4647
  warnIfApiKeyFlag(opts);
4418
- const { runWorkClaim, WorkCommandError } = await import('./work-claim-cmd-RKHYFE5Y.js');
4648
+ const { runWorkClaim, WorkCommandError } = await import('./work-claim-cmd-34FERLYX.js');
4419
4649
  try {
4420
4650
  await runWorkClaim(opts);
4421
4651
  } catch (err) {
@@ -4426,12 +4656,17 @@ live addition remains on it.
4426
4656
  throw err;
4427
4657
  }
4428
4658
  });
4429
- workCmd.command("renew").description("Extend the lease this API key holds on a work item").option("--item <number>", "the item's number inside this factory").option("--issue <number>", "issue number (default: named by --item instead)").option("--repo <owner/name>", "repository (default: the current checkout's remote)").option("--host <name>", "the claiming host (default: this machine's hostname)").option("--seconds <n>", "lease length in seconds, clamped to 900 (default: 900)").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addHelpText(
4659
+ workCmd.command("renew").description("Extend the lease this API key holds on a work item").option("--item <number>", "the item's number inside this factory").option("--issue <number>", "issue number or Linear/Jira key (default: named by --item instead)").option(TRACKER_OPTION_FLAG, "tracker for a keyed --issue, when both hold the key").option("--repo <owner/name>", "repository for an issue number (default: the checkout's remote)").option("--host <name>", "the claiming host (default: this machine's hostname)").option("--seconds <n>", "lease length in seconds, clamped to 900 (default: 900)").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addHelpText(
4430
4660
  "after",
4431
- "\n outerlayer work renew --item 42\n\nExtends the live lease this API key holds. Refused with a 410 if the\nlease has expired, or a 403 if a different key holds it.\n"
4661
+ `
4662
+ ${cliInvocation()} work renew --item 42
4663
+
4664
+ Extends the live lease this API key holds. Refused with a 410 if the
4665
+ lease has expired, or a 403 if a different key holds it.
4666
+ `
4432
4667
  ).action(async (opts) => {
4433
4668
  warnIfApiKeyFlag(opts);
4434
- const { runWorkRenew, WorkCommandError } = await import('./work-claim-cmd-RKHYFE5Y.js');
4669
+ const { runWorkRenew, WorkCommandError } = await import('./work-claim-cmd-34FERLYX.js');
4435
4670
  try {
4436
4671
  await runWorkRenew(opts);
4437
4672
  } catch (err) {
@@ -4442,15 +4677,29 @@ live addition remains on it.
4442
4677
  throw err;
4443
4678
  }
4444
4679
  });
4445
- workCmd.command("release").description("Release the lease this API key holds on a work item, or this session's own lease").option("--item <number>", "the item's number inside this factory").option("--issue <number>", "issue number (default: named by --item instead)").option("--repo <owner/name>", "repository (default: the current checkout's remote)").option("--host <name>", "the claiming host (default: this machine's hostname); releases a host's lease").option("--session-id <id>", "the session whose lease to release, skipping detection").option(
4680
+ workCmd.command("release").description("Release the lease this API key holds on a work item, or this session's own lease").option("--item <number>", "the item's number inside this factory").option("--issue <number>", "issue number or Linear/Jira key (default: named by --item instead)").option(TRACKER_OPTION_FLAG, "tracker for a keyed --issue, when both hold the key").option("--repo <owner/name>", "repository for an issue number (default: the checkout's remote)").option("--host <name>", "the claiming host (default: this machine's hostname); releases a host's lease").option("--session-id <id>", "the session whose lease to release, skipping detection").option(
4446
4681
  "--outcome <ok|incomplete|failed|timed_out|idle|lease_lost|interrupted|stopped>",
4447
4682
  "how a host's attempt ended (default: none)"
4448
4683
  ).option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addHelpText(
4449
4684
  "after",
4450
- "\n outerlayer work release --item 42 --outcome ok\n outerlayer work release --item 42\n\nMarks the lease this API key holds released, optionally with an\noutcome. A lease held by a different key is refused. Releasing an\nalready-released lease is a no-op that returns the recorded release\ntime and outcome.\n\nRun inside a session with no --host, it releases that session's own\nlease instead, so the item's review opens without ending the\nsession. It never releases another session's lease. --outcome\napplies to a host's lease only. A later tool call in the session\ntakes the lease again.\n"
4685
+ `
4686
+ ${cliInvocation()} work release --item 42 --outcome ok
4687
+ ${cliInvocation()} work release --item 42
4688
+
4689
+ Marks the lease this API key holds released, optionally with an
4690
+ outcome. A lease held by a different key is refused. Releasing an
4691
+ already-released lease is a no-op that returns the recorded release
4692
+ time and outcome.
4693
+
4694
+ Run inside a session with no --host, it releases that session's own
4695
+ lease instead, so the item's review opens without ending the
4696
+ session. It never releases another session's lease. --outcome
4697
+ applies to a host's lease only. A later tool call in the session
4698
+ takes the lease again.
4699
+ `
4451
4700
  ).action(async (opts) => {
4452
4701
  warnIfApiKeyFlag(opts);
4453
- const { runWorkRelease, WorkCommandError } = await import('./work-claim-cmd-RKHYFE5Y.js');
4702
+ const { runWorkRelease, WorkCommandError } = await import('./work-claim-cmd-34FERLYX.js');
4454
4703
  try {
4455
4704
  await runWorkRelease(opts);
4456
4705
  } catch (err) {
@@ -4463,10 +4712,22 @@ live addition remains on it.
4463
4712
  });
4464
4713
  workCmd.command("pr <number>").description("Declare a pull request onto the launched work item, or withdraw it").option("--repo <owner/name>", "repository (default: the current checkout's remote)").option("--session-id <id>", "session id to record as-is, skipping detection").option("--withdraw", "remove the declaration instead of making one (needs --item)").option("--item <number>", "with --withdraw, the item to remove the declaration from").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addHelpText(
4465
4714
  "after",
4466
- "\n outerlayer work pr 481\n outerlayer work pr 481 --withdraw --item 250\n\nRun inside a session already on a work item. Records that pull\nrequest 481 in the checkout's repository belongs to the item named by\nOUTERLAYER_WORK, or refuses when the session is not on it. The same\nfact a `pr-link` in the session's own transcript records on sync.\nIdempotent: declaring the same pull request twice is a no-op.\n\n--withdraw removes one item's declaration of the pull request. It\nneeds your own login; an agent session or a factory key is refused.\n"
4715
+ `
4716
+ ${cliInvocation()} work pr 481
4717
+ ${cliInvocation()} work pr 481 --withdraw --item 250
4718
+
4719
+ Run inside a session already on a work item. Records that pull
4720
+ request 481 in the checkout's repository belongs to the item named by
4721
+ OUTERLAYER_WORK, or refuses when the session is not on it. The same
4722
+ fact a \`pr-link\` in the session's own transcript records on sync.
4723
+ Idempotent: declaring the same pull request twice is a no-op.
4724
+
4725
+ --withdraw removes one item's declaration of the pull request. It
4726
+ needs your own login; an agent session or a factory key is refused.
4727
+ `
4467
4728
  ).action(async (number, opts) => {
4468
4729
  warnIfApiKeyFlag(opts);
4469
- const { runWorkPr, WorkPrCommandError } = await import('./work-pr-cmd-L77K32WK.js');
4730
+ const { runWorkPr, WorkPrCommandError } = await import('./work-pr-cmd-5LGANL32.js');
4470
4731
  try {
4471
4732
  await runWorkPr({ ...opts, number });
4472
4733
  } catch (err) {
@@ -4479,11 +4740,21 @@ live addition remains on it.
4479
4740
  });
4480
4741
  workCmd.command("open-pr").description("Open, or edit, the pull request for the work item this session is on").requiredOption("--title <title>", "the pull request's title").requiredOption("--body-file <path>", "a file holding the pull request's description").option("--repository <owner/name>", "repository to open the pull request in (default: the primary)").option("--repo <owner/name>", "same as --repository, on the gh path").option("--session-id <id>", "session id to record as-is, on the gh path, skipping detection").option("--json", "machine-readable JSON output").option("--url <url>", "cloud base URL (or OUTERLAYER_URL / config)").addOption(apiKeyOption()).option("--app-id <id>", "factory id the key is bound to (or OUTERLAYER_APP_ID / config)").addHelpText(
4481
4742
  "after",
4482
- '\n outerlayer work open-pr --title "Add the thing" --body-file pr.md\n outerlayer work open-pr --repository acme/web --title "Add the thing" --body-file pr.md\n\nIn a host build, which holds an item key, the gateway opens the pull\nrequest as the factory\'s GitHub App, from the branch the claim named,\nor edits the one already open on that branch. No gh command runs.\nIn your own session it runs `gh pr create` (or `gh pr edit`) under your\nlogin, then declares the pull request on the item, as `work pr` does.\nRun it again after a change to the title or description.\n'
4743
+ `
4744
+ ${cliInvocation()} work open-pr --title "Add the thing" --body-file pr.md
4745
+ ${cliInvocation()} work open-pr --repository acme/web --title "Add the thing" --body-file pr.md
4746
+
4747
+ In a host build, which holds an item key, the gateway opens the pull
4748
+ request as the factory's GitHub App, from the branch the claim named,
4749
+ or edits the one already open on that branch. No gh command runs.
4750
+ In your own session it runs \`gh pr create\` (or \`gh pr edit\`) under your
4751
+ login, then declares the pull request on the item, as \`work pr\` does.
4752
+ Run it again after a change to the title or description.
4753
+ `
4483
4754
  ).action(async (opts) => {
4484
4755
  warnIfApiKeyFlag(opts);
4485
- const { runWorkOpenPr, WorkOpenPrCommandError } = await import('./work-open-pr-cmd-E6O53IJD.js');
4486
- const { WorkCommandError } = await import('./work-cmd-DJQJEZVB.js');
4756
+ const { runWorkOpenPr, WorkOpenPrCommandError } = await import('./work-open-pr-cmd-RPXKCBWS.js');
4757
+ const { WorkCommandError } = await import('./work-cmd-7S5B2H2P.js');
4487
4758
  try {
4488
4759
  await runWorkOpenPr(opts);
4489
4760
  } catch (err) {
@@ -4499,9 +4770,9 @@ function registerRunnerCommands(program2) {
4499
4770
  const runnerCmd = program2.command("runner").description("Take work from the implement and amend queues on this machine");
4500
4771
  const configOption = "the cloud config file to run against (default: ~/.outerlayer/config.json)";
4501
4772
  runnerCmd.command("start").description("Run the loop: claim work, run it, report, clean up, repeat").option("--config <path>", configOption).action(async (opts) => {
4502
- const { defaultRunnerConfigPath, RunnerConfigError } = await import('./config-N3C6DFQA.js');
4503
- const { start, RunnerStartError } = await import('./loop-75J4YXLZ.js');
4504
- const { RunnerAlreadyRunningError } = await import('./pidfile-UZRH774M.js');
4773
+ const { defaultRunnerConfigPath, RunnerConfigError } = await import('./config-FXDG4NPJ.js');
4774
+ const { start, RunnerStartError } = await import('./loop-UGI2BJOT.js');
4775
+ const { RunnerAlreadyRunningError } = await import('./pidfile-3DM4OBKO.js');
4505
4776
  const configPath = opts.config ?? defaultRunnerConfigPath();
4506
4777
  try {
4507
4778
  const { realUpdateDeps } = await import('./real-deps-KNNBQC75.js');
@@ -4515,8 +4786,8 @@ function registerRunnerCommands(program2) {
4515
4786
  }
4516
4787
  });
4517
4788
  runnerCmd.command("check").description("Validate the config, the hooks and the key, and print the effective settings").option("--config <path>", configOption).action(async (opts) => {
4518
- const { defaultRunnerConfigPath } = await import('./config-N3C6DFQA.js');
4519
- const { runRunnerCheck } = await import('./check-CNTMSGOG.js');
4789
+ const { defaultRunnerConfigPath } = await import('./config-FXDG4NPJ.js');
4790
+ const { runRunnerCheck } = await import('./check-5C4YDZYV.js');
4520
4791
  const configPath = opts.config ?? defaultRunnerConfigPath();
4521
4792
  const result = await runRunnerCheck(configPath);
4522
4793
  if (!result.ok) {
@@ -4532,8 +4803,8 @@ function registerRunnerCommands(program2) {
4532
4803
  `);
4533
4804
  });
4534
4805
  runnerCmd.command("install").description("Put this CLI in the runner's install layout; --service adds its service").option("--config <path>", configOption).option("--service", "set up the service that keeps the runner running").option("--print", "with --service, print the unit and set up nothing").option("--user <account>", "with --service, the account the service runs as").option("--replace", "replace a unit or launcher file this command did not write").option("--allow-downgrade", "install this CLI even when it is older than the version the runner runs").option("--unit-name <name>", "with --service, the unit's name, to run a second runner beside the first").action(async (opts) => {
4535
- const { defaultRunnerConfigPath, RunnerConfigError } = await import('./config-N3C6DFQA.js');
4536
- const { runRunnerInstall, RunnerInstallError, launcherSummary, restartNote } = await import('./install-cmd-5HYMXLEW.js');
4806
+ const { defaultRunnerConfigPath, RunnerConfigError } = await import('./config-FXDG4NPJ.js');
4807
+ const { runRunnerInstall, RunnerInstallError, launcherSummary, restartNote } = await import('./install-cmd-QDOTR4OM.js');
4537
4808
  const configPath = opts.config ?? defaultRunnerConfigPath();
4538
4809
  if (opts.service !== true) {
4539
4810
  for (const flag of ["print", "user", "unitName"]) {
@@ -4545,7 +4816,7 @@ function registerRunnerCommands(program2) {
4545
4816
  }
4546
4817
  try {
4547
4818
  if (opts.service === true) {
4548
- const { installServiceCommand } = await import('./command-HHPBR6CS.js');
4819
+ const { installServiceCommand } = await import('./command-H5CILO4S.js');
4549
4820
  const result2 = await installServiceCommand({
4550
4821
  configPath,
4551
4822
  print: opts.print,
@@ -4583,7 +4854,7 @@ function registerRunnerCommands(program2) {
4583
4854
  printLauncher(result, launcherSummary);
4584
4855
  process.stdout.write(
4585
4856
  `
4586
- To keep it running, run ${C.yellow}outerlayer runner install --service${C.reset}. To start the runner from the install layout yourself, use this line, then restart the service:
4857
+ To keep it running, run ${C.yellow}${cliInvocation()} runner install --service${C.reset}. To start the runner from the install layout yourself, use this line, then restart the service:
4587
4858
  ${C.yellow}${result.execStart}${C.reset}
4588
4859
  `
4589
4860
  );
@@ -4597,26 +4868,43 @@ To keep it running, run ${C.yellow}outerlayer runner install --service${C.reset}
4597
4868
  });
4598
4869
  runnerCmd.command("uninstall").description("Stop the runner's service and remove it (needs --service)").option("--config <path>", configOption).option("--service", "remove the service, and the launch agent or scheduled task").option("--unit-name <name>", "the unit's name, when it is not the default").action(async (opts) => {
4599
4870
  if (opts.service !== true) {
4600
- fail("pass --service: `outerlayer runner uninstall --service` removes the runner's service.");
4871
+ fail(`pass --service: \`${cliInvocation()} runner uninstall --service\` removes the runner's service.`);
4601
4872
  return;
4602
4873
  }
4603
- const { defaultRunnerConfigPath } = await import('./config-N3C6DFQA.js');
4604
- const { uninstallServiceCommand } = await import('./command-HHPBR6CS.js');
4874
+ const { defaultRunnerConfigPath } = await import('./config-FXDG4NPJ.js');
4875
+ const { uninstallServiceCommand } = await import('./command-H5CILO4S.js');
4605
4876
  const result = await uninstallServiceCommand({ configPath: opts.config ?? defaultRunnerConfigPath(), unitName: opts.unitName, home: homeFromEnv() });
4606
4877
  if (result.ok) for (const line of result.lines) process.stdout.write(`${C.green}\u2713${C.reset} ${line}
4607
4878
  `);
4608
4879
  else fail(result.lines.length > 0 ? result.lines.join("\n ") : "the service was not removed");
4609
4880
  });
4610
- runnerCmd.command("init").description("Write a runner block that fits this machine; on macOS, offers a Linux VM").option("--config <path>", configOption).option("--vm", "on macOS, run the runner in a Linux VM (Lima), with container builds").option("--no-vm", "on macOS, skip the VM and run builds as unisolated processes").option("--allow-process-builds", "where builds cannot be isolated, run them as processes anyway").addOption(new Option("--config-stdin", "the VM-side hand-off: read the whole config as JSON on stdin").hideHelp()).option("--vm-cli-package <path>", "with --vm, install this packed CLI tarball in the VM").option("--unit-name <name>", "the service unit's name, to run a second runner beside the first").option("--agent <name>", "write commands that start this agent (claude); its credential comes on stdin").option("--app-id <id>", "factory id to create the runner key on, when it comes from your login").option("--key-name <name>", "name of the runner key created from your login (default: machine name)").addHelpText(
4881
+ runnerCmd.command("init").description("Write a runner block that fits this machine; on macOS, offers a Linux VM").option("--config <path>", configOption).option("--vm", "on macOS, run the runner in a Linux VM (Lima), with container builds").option("--no-vm", "on macOS, skip the VM and run builds as unisolated processes").option("--allow-process-builds", "where builds cannot be isolated, run them as processes anyway").addOption(new Option("--config-stdin", "the VM-side hand-off: read the whole config as JSON on stdin").hideHelp()).option("--vm-cli-package <path>", "with --vm, install this packed CLI tarball in the VM").option("--unit-name <name>", "the service unit's name, to run a second runner beside the first").option("--agent <name>", "write commands that start this agent (claude); its credential comes on stdin").option("--factory <org/factory>", "factory to create the runner key on, as <org>/<factory>").option("--app-id <id>", "factory id, instead of --factory (Settings \u2192 General)").addHelpText(
4611
4882
  "after",
4612
- "\nWith no apiKey in the config file, and a login saved by `outerlayer login`,\nrunner init creates the runner key itself, with the Runner preset, on the\nfactory --app-id names (or OUTERLAYER_APP_ID, or this repository's\nconnection). Your login needs factory_keys.insert on that factory.\n\nWith --agent claude, the queue commands start Claude Code instead of the\nplaceholder, and Claude's credential is read from stdin: a token from\n`claude setup-token` (sk-ant-oat...) or an API key (sk-ant-api...). It is\nnever printed, and is stored in the runner.env beside the config with mode\n600. Other lines in the file are kept. With nothing piped, a credential\nalready in runner.env is used.\n\n printf '%s' \"$CLAUDE_CODE_OAUTH_TOKEN\" | outerlayer runner init --agent claude\n"
4883
+ `
4884
+ With no apiKey in the config file, and a login saved by \`${cliInvocation()} login\`,
4885
+ runner init names the factory the runner is for: --factory, then --app-id,
4886
+ OUTERLAYER_APP_ID, or this repository's connection. With none of those, a
4887
+ login that can use one factory uses it, and one that can use several lists
4888
+ them. It then prints the factory's API keys page, where you create a key with
4889
+ the Runner preset, and asks you to paste it. Nothing is shown as you paste.
4890
+ With no terminal to ask on, it stops and says what to put in the config file.
4891
+
4892
+ With --agent claude, the queue commands start Claude Code instead of the
4893
+ placeholder, and Claude's credential is read from stdin: a token from
4894
+ \`claude setup-token\` (sk-ant-oat...) or an API key (sk-ant-api...). It is
4895
+ never printed, and is stored in the runner.env beside the config with mode
4896
+ 600. Other lines in the file are kept. With nothing piped, a credential
4897
+ already in runner.env is used.
4898
+
4899
+ printf '%s' "$CLAUDE_CODE_OAUTH_TOKEN" | ${cliInvocation()} runner init --agent claude --factory <org>/<factory>
4900
+ `
4613
4901
  ).action(async (opts) => {
4614
- const { defaultRunnerConfigPath, RunnerConfigError } = await import('./config-N3C6DFQA.js');
4615
- const { prepareRunnerInit, runRunnerInit, RunnerInitError, writeRunnerInit } = await import('./init-MYX7YMIH.js');
4616
- const { createRunnerKey, runnerKeyLogin } = await import('./runner-key-W25ISHI3.js');
4617
- const { detectMachine } = await import('./machine-A6ZCTUGS.js');
4618
- const { chooseVm, runVmInit, VmInitError } = await import('./init-FBCA4DRA.js');
4619
- const { AGENT_COMMANDS, credentialFromStdin, isAgentName, restartForCredential, RunnerEnvError } = await import('./runner-env-EN7YE7QX.js');
4902
+ const { defaultRunnerConfigPath, RunnerConfigError } = await import('./config-FXDG4NPJ.js');
4903
+ const { prepareRunnerInit, runRunnerInit, RunnerInitError, writeRunnerInit } = await import('./init-VWXIZEAN.js');
4904
+ const { obtainRunnerKey, runnerKeyLogin } = await import('./runner-key-TC3NFWFV.js');
4905
+ const { detectMachine } = await import('./machine-JOW4RNZ7.js');
4906
+ const { chooseVm, runVmInit, VmInitError } = await import('./init-WNXXYMO7.js');
4907
+ const { AGENT_COMMANDS, credentialFromStdin, isAgentName, restartForCredential, RunnerEnvError } = await import('./runner-env-4NB2GA7O.js');
4620
4908
  const configPath = opts.config ?? defaultRunnerConfigPath();
4621
4909
  if (opts.agent !== void 0 && !isAgentName(opts.agent)) {
4622
4910
  fail(`--agent ${opts.agent}: runner init writes commands for ${Object.keys(AGENT_COMMANDS).join(", ")}. For another agent, leave --agent out and edit runner.commands.`);
@@ -4630,7 +4918,7 @@ To keep it running, run ${C.yellow}outerlayer runner install --service${C.reset}
4630
4918
  try {
4631
4919
  let credential;
4632
4920
  if (agent !== void 0) {
4633
- const { readPipedKey } = await import('./login-browser-QZCS3WW6.js');
4921
+ const { readPipedKey } = await import('./login-browser-O63QZN7D.js');
4634
4922
  credential = credentialFromStdin(
4635
4923
  await readPipedKey(process.stdin, {
4636
4924
  onWaiting: () => process.stderr.write(`runner init is waiting for Claude's credential on stdin; close stdin to use the one already in runner.env
@@ -4640,14 +4928,14 @@ To keep it running, run ${C.yellow}outerlayer runner install --service${C.reset}
4640
4928
  }
4641
4929
  const machine = detectMachine();
4642
4930
  if (opts.configStdin === true) {
4643
- if (opts.appId !== void 0 || opts.keyName !== void 0) {
4644
- fail("the VM-side hand-off carries its runner key in its config on stdin; pass no --app-id or --key-name with it.");
4931
+ if (opts.appId !== void 0 || opts.factory !== void 0) {
4932
+ fail("the VM-side hand-off carries its runner key in its config on stdin; pass no --app-id or --factory with it.");
4645
4933
  return;
4646
4934
  }
4647
4935
  const chunks = [];
4648
4936
  for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
4649
4937
  const result2 = runRunnerInit(configPath, { machine, configFromStdin: Buffer.concat(chunks).toString("utf8"), requireContainer: true });
4650
- const { ensureHostKey: ensureHostKey2 } = await import('./host-key-YMLXY6QB.js');
4938
+ const { ensureHostKey: ensureHostKey2 } = await import('./host-key-U4B5QETR.js');
4651
4939
  const hostKey2 = await ensureHostKey2(configPath);
4652
4940
  process.stdout.write(`host key ${hostKey2.fingerprint}
4653
4941
  hooks: ${result2.hooks}
@@ -4656,14 +4944,13 @@ hooks: ${result2.hooks}
4656
4944
  `);
4657
4945
  return;
4658
4946
  }
4659
- const login = runnerKeyLogin(configPath, { appId: opts.appId });
4660
- if (login === void 0 && opts.keyName !== void 0) {
4661
- fail(
4662
- `--key-name names the runner key runner init creates from your login, and it creates none here: ${configPath} already has an apiKey, or no login is saved. Run \`outerlayer login\` first, or leave --key-name out.`
4663
- );
4664
- return;
4665
- }
4666
- const makeKey = login === void 0 ? void 0 : () => createRunnerKey(login, { name: opts.keyName });
4947
+ const login = await runnerKeyLogin(configPath, { appId: opts.appId, factory: opts.factory });
4948
+ if (login?.note !== void 0) process.stdout.write(`${login.note}
4949
+ `);
4950
+ const { terminalPrompt } = await import('./hidden-prompt-UXBFMQC5.js');
4951
+ const readKey = login === void 0 ? void 0 : terminalPrompt();
4952
+ if (login !== void 0 && readKey === void 0) await obtainRunnerKey(login, { configPath, readKey });
4953
+ const makeKey = login === void 0 ? void 0 : () => obtainRunnerKey(login, { configPath, readKey });
4667
4954
  let declinedVm = opts.vm === false;
4668
4955
  const useVm = await chooseVm({
4669
4956
  platform: machine.platform,
@@ -4698,6 +4985,8 @@ hooks: ${result2.hooks}
4698
4985
  `)
4699
4986
  });
4700
4987
  for (const line of result2.lines) process.stdout.write(`${C.green}\u2713${C.reset} ${line}
4988
+ `);
4989
+ for (const line of result2.warnings) process.stdout.write(`${C.yellow}!${C.reset} ${line}
4701
4990
  `);
4702
4991
  return;
4703
4992
  }
@@ -4705,10 +4994,10 @@ hooks: ${result2.hooks}
4705
4994
  const prepared = prepareRunnerInit(configPath, { machine, allowProcess, agent, credential, keyFromLogin: makeKey !== void 0 });
4706
4995
  const created = makeKey === void 0 ? void 0 : await makeKey();
4707
4996
  const result = writeRunnerInit(prepared, created);
4708
- const { ensureHostKey } = await import('./host-key-YMLXY6QB.js');
4997
+ const { ensureHostKey } = await import('./host-key-U4B5QETR.js');
4709
4998
  const hostKey = await ensureHostKey(configPath);
4710
4999
  if (created !== void 0) {
4711
- process.stdout.write(`${C.green}\u2713${C.reset} created the runner key ${created.name} from your login, with the Runner preset, and saved it in ${result.configPath}
5000
+ process.stdout.write(`${C.green}\u2713${C.reset} saved the runner key in ${result.configPath}
4712
5001
  `);
4713
5002
  }
4714
5003
  process.stdout.write(`${C.green}\u2713${C.reset} wrote a runner block into ${result.configPath}
@@ -4730,10 +5019,10 @@ ${C.yellow}${result.note}${C.reset}
4730
5019
  `);
4731
5020
  if (machine.platform === "darwin" && opts.vm === void 0) {
4732
5021
  process.stdout.write(`
4733
- To isolate builds in containers instead, run ${C.yellow}outerlayer runner init --vm${C.reset}.
5022
+ To isolate builds in containers instead, run ${C.yellow}${cliInvocation()} runner init --vm${C.reset}.
4734
5023
  `);
4735
5024
  }
4736
- const { runRunnerInstall, RunnerInstallError, RunnerDowngradeError, launcherSummary } = await import('./install-cmd-5HYMXLEW.js');
5025
+ const { runRunnerInstall, RunnerInstallError, RunnerDowngradeError, launcherSummary } = await import('./install-cmd-QDOTR4OM.js');
4737
5026
  let installedLayout = false;
4738
5027
  let noService = "it starts the runner from the install layout, which needs a CLI installed with npm";
4739
5028
  try {
@@ -4765,7 +5054,7 @@ ${C.yellow}The runner will not update itself: ${installErr.message}${C.reset}
4765
5054
  let serviceUp = false;
4766
5055
  let restart;
4767
5056
  if (installedLayout) {
4768
- const { installServiceCommand } = await import('./command-HHPBR6CS.js');
5057
+ const { installServiceCommand } = await import('./command-H5CILO4S.js');
4769
5058
  const service = await installServiceCommand({
4770
5059
  configPath,
4771
5060
  entryPath: process.argv[1] ?? "",
@@ -4787,14 +5076,14 @@ ${C.yellow}The runner will not update itself: ${installErr.message}${C.reset}
4787
5076
  if (restart !== void 0) process.stdout.write(`
4788
5077
  ${C.yellow}${restart}${C.reset}
4789
5078
  `);
4790
- const steps = agent === void 0 ? [`Edit the command lines in ${result.configPath} to start your agent.`, `${C.yellow}outerlayer runner check${C.reset}`] : [`${C.yellow}outerlayer runner check${C.reset}`];
4791
- if (!serviceUp) steps.push(`${C.yellow}outerlayer runner start${C.reset}, or ${C.yellow}outerlayer runner install --service${C.reset} to keep it running.`);
5079
+ const steps = agent === void 0 ? [`Edit the command lines in ${result.configPath} to start your agent.`, `${C.yellow}${cliInvocation()} runner check${C.reset}`] : [`${C.yellow}${cliInvocation()} runner check${C.reset}`];
5080
+ if (!serviceUp) steps.push(`${C.yellow}${cliInvocation()} runner start${C.reset}, or ${C.yellow}${cliInvocation()} runner install --service${C.reset} to keep it running.`);
4792
5081
  process.stdout.write(
4793
5082
  `
4794
5083
  Next:
4795
5084
  ` + steps.map((step, i) => ` ${i + 1}. ${step}
4796
- `).join("") + (serviceUp && agent === void 0 ? ` The runner service claims nothing until step 1 is done and a Claude credential is in ${join(dirname(configPath), "runner.env")}; ${C.yellow}outerlayer runner status${C.reset} shows what it is doing.
4797
- ` : serviceUp ? ` The runner service is running; ${C.yellow}outerlayer runner status${C.reset} shows what it is doing.
5085
+ `).join("") + (serviceUp && agent === void 0 ? ` The runner service claims nothing until step 1 is done and a Claude credential is in ${join(dirname(configPath), "runner.env")}; ${C.yellow}${cliInvocation()} runner status${C.reset} shows what it is doing.
5086
+ ` : serviceUp ? ` The runner service is running; ${C.yellow}${cliInvocation()} runner status${C.reset} shows what it is doing.
4798
5087
  ` : "")
4799
5088
  );
4800
5089
  } catch (err) {
@@ -4805,29 +5094,42 @@ Next:
4805
5094
  throw err;
4806
5095
  }
4807
5096
  });
4808
- runnerCmd.command("image").description("Build a repository's image from its default branch's devcontainer file").requiredOption("--repository <owner/name>", "the repository to build the image for").option("--remote <url>", "where to fetch from (default: the repository's GitHub URL)").option("--config <path>", configOption).option("--json", "machine-readable JSON output").addHelpText(
5097
+ runnerCmd.command("image").description("Build a repository's image from its governing repository's devcontainer").requiredOption("--repository <owner/name>", "the repository to build the image for").option("--remote <url>", "where to fetch from (default: the repository's GitHub URL)").option("--config <path>", configOption).option("--json", "machine-readable JSON output").addHelpText(
4809
5098
  "after",
4810
- "\nFetches the head of the default branch into the runner's cache, reads its\n.devcontainer/devcontainer.json, and builds the image with the devcontainer CLI. Settings\nthat reach the host or widen the container's privileges are ignored and listed. The\nimage holds this CLI and a pinned Claude Code. Nothing runs inside the image.\n"
5099
+ "\nA repository governed by a control plane builds the control plane's image, at the commit\nthe gateway has stored for it. A repository that governs itself fetches the head of its\ndefault branch into the runner's cache. Either way it reads .devcontainer/devcontainer.json\nand builds the image with the devcontainer CLI. Settings\nthat reach the host or widen the container's privileges are ignored and listed. The\nimage holds this CLI and a pinned Claude Code. Nothing runs inside the image.\n"
4811
5100
  ).action(async (opts) => {
4812
- const { defaultRunnerConfigPath } = await import('./config-N3C6DFQA.js');
4813
- const { buildRepositoryImage, RecipeError } = await import('./build-BG2YENGK.js');
5101
+ const { defaultRunnerConfigPath } = await import('./config-FXDG4NPJ.js');
5102
+ const { buildRepositoryImage, RecipeError } = await import('./build-TQ6X5PYN.js');
4814
5103
  const { currentCliBuild } = await import('./cli-build-K56DK4DS.js');
4815
- const { createDockerHost } = await import('./docker-4J77Q2BF.js');
4816
- const { builderSpec } = await import('./builder-2D3WSOOY.js');
4817
- const { loadRunnerConfig } = await import('./config-N3C6DFQA.js');
5104
+ const { createDockerHost } = await import('./docker-RC3PRX3Q.js');
5105
+ const { builderSpec } = await import('./builder-DUI2ZH2H.js');
5106
+ const { loadRunnerConfig } = await import('./config-FXDG4NPJ.js');
4818
5107
  const configPath = opts.config ?? defaultRunnerConfigPath();
4819
- const memory = (() => {
5108
+ const loaded = (() => {
4820
5109
  try {
4821
- return loadRunnerConfig(configPath).build?.memory ?? "8g";
5110
+ return loadRunnerConfig(configPath);
4822
5111
  } catch {
4823
- return "8g";
5112
+ return void 0;
4824
5113
  }
4825
5114
  })();
5115
+ const memory = loaded?.build?.memory ?? "8g";
4826
5116
  try {
5117
+ const { imageTargetFor } = await import('./governing-source-BUU4IABS.js');
5118
+ const target = await imageTargetFor({ config: loaded, repository: opts.repository, fetchImpl: fetch });
5119
+ if (!target.ok) {
5120
+ if (opts.json) {
5121
+ writeJson({ error: { code: "governing_source_unavailable", message: target.reason } });
5122
+ process.exitCode = 1;
5123
+ return;
5124
+ }
5125
+ fail(target.reason);
5126
+ return;
5127
+ }
4827
5128
  const result = await buildRepositoryImage(
4828
5129
  {
4829
- repository: opts.repository,
4830
- remote: opts.remote,
5130
+ repository: target.repository,
5131
+ ...target.pinned === void 0 ? {} : { pinned: target.pinned },
5132
+ remote: target.pinned === void 0 ? opts.remote : void 0,
4831
5133
  configPath,
4832
5134
  cli: currentCliBuild(),
4833
5135
  log: (line) => {
@@ -4875,22 +5177,22 @@ Next:
4875
5177
  }
4876
5178
  });
4877
5179
  runnerCmd.command("stop").description("Take no more work; wait for running jobs to finish, then exit").option("--config <path>", "the cloud config file naming the runner to stop").option("--now", "end running jobs immediately, with outcome stopped").action(async (opts) => {
4878
- const { defaultRunnerConfigPath } = await import('./config-N3C6DFQA.js');
4879
- const { requestStop } = await import('./loop-75J4YXLZ.js');
5180
+ const { defaultRunnerConfigPath } = await import('./config-FXDG4NPJ.js');
5181
+ const { requestStop } = await import('./loop-UGI2BJOT.js');
4880
5182
  const configPath = opts.config ?? defaultRunnerConfigPath();
4881
5183
  await requestStop(configPath, Boolean(opts.now));
4882
5184
  });
4883
5185
  runnerCmd.command("status").description("List running jobs; with --recent, finished ones too").option("--config <path>", "the cloud config file naming the runner to inspect").option("--recent", "also list finished jobs, newest first, capped at 20").option("--json", "machine-readable JSON output").action(async (opts) => {
4884
- const { defaultRunnerConfigPath, loadRunnerConfig } = await import('./config-N3C6DFQA.js');
4885
- const { jobsDir, runnerDir } = await import('./paths-OYKMVYJP.js');
4886
- const { listJobFiles, runnerProcessState } = await import('./loop-75J4YXLZ.js');
4887
- const { renderStatus } = await import('./status-XHSGEYO5.js');
4888
- const { readHousekeepingRecord } = await import('./record-C43U4PKS.js');
5186
+ const { defaultRunnerConfigPath, loadRunnerConfig } = await import('./config-FXDG4NPJ.js');
5187
+ const { jobsDir, runnerDir } = await import('./paths-7XP63OAX.js');
5188
+ const { listJobFiles, runnerProcessState } = await import('./loop-UGI2BJOT.js');
5189
+ const { renderStatus } = await import('./status-TKCLLKFG.js');
5190
+ const { readHousekeepingRecord } = await import('./record-3WP6V2K5.js');
4889
5191
  const path2 = await import('path');
4890
5192
  const configPath = opts.config ?? defaultRunnerConfigPath();
4891
5193
  const all = listJobFiles(configPath);
4892
5194
  const housekeeping = readHousekeepingRecord(runnerDir(configPath));
4893
- const { readPendingUpdate } = await import('./pending-MXLSRYMO.js');
5195
+ const { readPendingUpdate } = await import('./pending-532ABZYC.js');
4894
5196
  const pendingUpdate = readPendingUpdate(runnerDir(configPath));
4895
5197
  let host = "unknown";
4896
5198
  let concurrency = 1;
@@ -4904,7 +5206,7 @@ Next:
4904
5206
  const service = await (async () => {
4905
5207
  if (process.platform !== "linux" || !existsSync("/run/systemd/system")) return void 0;
4906
5208
  const { inspectService, realInspectProbes } = await import('./inspect-6UYAAXH7.js');
4907
- const { DEFAULT_UNIT_NAME } = await import('./install-HDBIPHTX.js');
5209
+ const { DEFAULT_UNIT_NAME } = await import('./install-CBWIEF3R.js');
4908
5210
  const facts = inspectService(DEFAULT_UNIT_NAME, path2.dirname(configPath), realInspectProbes());
4909
5211
  return facts === void 0 ? void 0 : { unit: facts.unit, running: facts.mainPid !== void 0, delegated: facts.delegate };
4910
5212
  })();
@@ -4939,9 +5241,9 @@ Next:
4939
5241
  );
4940
5242
  });
4941
5243
  runnerCmd.command("logs").argument("<item>", "the work item's number in the factory").description("Print the newest attempt's log for one item").option("--config <path>", "the cloud config file naming the runner to inspect").option("-f, --follow", "keep printing as the log grows").action(async (itemArg, opts) => {
4942
- const { defaultRunnerConfigPath } = await import('./config-N3C6DFQA.js');
4943
- const { listJobFiles } = await import('./loop-75J4YXLZ.js');
4944
- const { newestAttempt, logPathFor, tail, RunnerLogsError } = await import('./logs-H55NWW4H.js');
5244
+ const { defaultRunnerConfigPath } = await import('./config-FXDG4NPJ.js');
5245
+ const { listJobFiles } = await import('./loop-UGI2BJOT.js');
5246
+ const { newestAttempt, logPathFor, tail, RunnerLogsError } = await import('./logs-AMLKC3VP.js');
4945
5247
  const configPath = opts.config ?? defaultRunnerConfigPath();
4946
5248
  const item = parseTrackerNumber(itemArg);
4947
5249
  if (item === void 0) {
@@ -5162,9 +5464,22 @@ function registerMcpCommands(program2) {
5162
5464
  const mcpCmd = program2.command("mcp").description("Configure MCP clients against the OuterLayer gateway");
5163
5465
  mcpCmd.command("install").description("Write or update the OuterLayer entry in .mcp.json or .outerlayer/mcp.json").option("--transport <stdio|http>", "how the client connects (default: stdio)").option("--command <exe>", "stdio only: executable the client spawns (default: outerlayer)").option("--url <url>", "gateway MCP endpoint (defaults to the gateway you logged in to)").option("--name <name>", 'mcpServers key to write under (default: "outerlayer")').option("--dir <path>", "repo root to write into (default: cwd)").option("--app-id <uuid>", "self-host only: factory id sent as X-Outerlayer-App-Id").option("--json", "machine-readable JSON output").addHelpText(
5164
5466
  "after",
5165
- "\nThe API key is never a flag and never written to .mcp.json as a literal value.\n stdio (default): the client spawns `outerlayer mcp serve`, which reads the key\n from ~/.outerlayer/config.json (or OUTERLAYER_API_KEY) on every connect, so a\n rotated key is picked up without rewriting the entry.\n http: the entry carries the ${OUTERLAYER_API_KEY} placeholder, which the client\n resolves from the environment it was launched with.\n\n--command: use an absolute or ${HOME}-relative path when the CLI is not on PATH.\n--url: with stdio the endpoint resolves from OUTERLAYER_URL / the config file at\nconnect time; for self-host pass your gateway-node origin + /v1/mcp.\nSelf-host deployments also need --app-id: without a key service to resolve the\ncaller from the bearer token, the gateway answers 401 without that header.\n"
5467
+ `
5468
+ The API key is never a flag and never written to .mcp.json as a literal value.
5469
+ stdio (default): the client spawns \`${cliInvocation()} mcp serve\`, which reads the key
5470
+ from ~/.outerlayer/config.json (or OUTERLAYER_API_KEY) on every connect, so a
5471
+ rotated key is picked up without rewriting the entry.
5472
+ http: the entry carries the \${OUTERLAYER_API_KEY} placeholder, which the client
5473
+ resolves from the environment it was launched with.
5474
+
5475
+ --command: use an absolute or \${HOME}-relative path when the CLI is not on PATH.
5476
+ --url: with stdio the endpoint resolves from OUTERLAYER_URL / the config file at
5477
+ connect time; for self-host pass your gateway-node origin + /v1/mcp.
5478
+ Self-host deployments also need --app-id: without a key service to resolve the
5479
+ caller from the bearer token, the gateway answers 401 without that header.
5480
+ `
5166
5481
  ).action(async (opts) => {
5167
- const { runMcpInstall, handleMcpInstallError, McpInstallError } = await import('./mcp-install-cmd-FDTPQQS6.js');
5482
+ const { runMcpInstall, handleMcpInstallError, McpInstallError } = await import('./mcp-install-cmd-GGWB3JJ2.js');
5168
5483
  try {
5169
5484
  if (opts.transport !== void 0 && opts.transport !== "stdio" && opts.transport !== "http") {
5170
5485
  throw new McpInstallError(`--transport must be "stdio" or "http", got "${opts.transport}"`);
@@ -5186,7 +5501,7 @@ function registerMcpCommands(program2) {
5186
5501
  "after",
5187
5502
  "\nWhat the stdio .mcp.json entry spawns. Reads newline-delimited JSON-RPC 2.0 on\nstdin and writes responses on stdout. The API key comes from OUTERLAYER_API_KEY\nor ~/.outerlayer/config.json at start-up; with neither the command exits 1 and\nprints why, which the MCP client shows as the connect error.\n"
5188
5503
  ).action(async (opts) => {
5189
- const { runMcpServe, handleMcpServeError } = await import('./mcp-serve-cmd-3KVYEUXG.js');
5504
+ const { runMcpServe, handleMcpServeError } = await import('./mcp-serve-cmd-DOTY5FKY.js');
5190
5505
  try {
5191
5506
  await runMcpServe({ url: opts.url, appId: opts.appId });
5192
5507
  } catch (err) {
@@ -5197,7 +5512,7 @@ function registerMcpCommands(program2) {
5197
5512
  function registerImportCommands(program2) {
5198
5513
  const importCmd = program2.command("import").description("Import an existing config format into .outerlayer/");
5199
5514
  importCmd.command("ruler").description("Port a .ruler/ tree into the equivalent .outerlayer/ tree").option("--dir <path>", "repo root to read from (default: cwd)").option("--json", "machine-readable JSON output").addHelpText("after", "\nRuler: https://github.com/intellectronica/ruler. Mostly a rename.\n").action(async (opts) => {
5200
- const { runImportRuler, ImportRulerError } = await import('./import-ruler-cmd-BGAZXCQM.js');
5515
+ const { runImportRuler, ImportRulerError } = await import('./import-ruler-cmd-KJMWL5V6.js');
5201
5516
  try {
5202
5517
  const result = runImportRuler({ cwd: opts.dir, json: opts.json });
5203
5518
  if (result.exitCode !== 0) process.exitCode = result.exitCode;
@@ -5213,7 +5528,7 @@ function registerImportCommands(program2) {
5213
5528
  "after",
5214
5529
  "\nInstalls the four maintained skills (outerlayer-evidence, outerlayer-amend,\nouterlayer-findings, outerlayer-cli) and an AGENTS.md snippet teaching agents to run\n`emit artifact`. A skill that differs from this CLI's bundle is rewritten to it, and the output says so.\n"
5215
5530
  ).action(async (opts) => {
5216
- const { runImportCapture, ImportCaptureError } = await import('./import-capture-cmd-EQ35EARA.js');
5531
+ const { runImportCapture, ImportCaptureError } = await import('./import-capture-cmd-ZO6KGHI2.js');
5217
5532
  try {
5218
5533
  runImportCapture({ cwd: opts.dir, json: opts.json });
5219
5534
  } catch (err) {
@@ -5256,7 +5571,7 @@ function registerContextCommands(program2) {
5256
5571
  await compileContext({ dir: opts.dir, check: opts.check, json: opts.json });
5257
5572
  });
5258
5573
  contextCmd.command("adopt <stagedDir>", { hidden: true }).description("Copy context the runner staged into this checkout (container builds)").option("--json", "machine-readable JSON output").action(async (stagedDir) => {
5259
- const { runContextAdopt } = await import('./context-adopt-VMMGFRUZ.js');
5574
+ const { runContextAdopt } = await import('./context-adopt-O5OFXMKI.js');
5260
5575
  const { exitCode, stdout } = runContextAdopt(stagedDir, process.cwd());
5261
5576
  process.stdout.write(stdout);
5262
5577
  process.exitCode = exitCode;
@@ -5265,7 +5580,7 @@ function registerContextCommands(program2) {
5265
5580
  "after",
5266
5581
  "\nThe same flow SessionStart runs automatically, exposed for a manual retry or a\nfirst check outside a session. Writes AGENTS.md, .claude/**, and .outerlayer/**.\n"
5267
5582
  ).action(async (opts) => {
5268
- const { materializeContext } = await import('./context-materialize-2WXTS4CW.js');
5583
+ const { materializeContext } = await import('./context-materialize-Y5IJ4MQT.js');
5269
5584
  const result = await materializeContext({ cwd: process.cwd(), sessionId: `manual-${Date.now()}` });
5270
5585
  const output = formatContextMaterializeOutput(result);
5271
5586
  if (output.exitCode !== void 0) process.exitCode = output.exitCode;