elanous 0.2.4 → 0.2.5

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 (412) hide show
  1. package/README.md +3 -3
  2. package/apps/pwa/out/404/index.html +1 -1
  3. package/apps/pwa/out/404.html +1 -1
  4. package/apps/pwa/out/_next/static/chunks/06ef2eba-d1b99ac6d2ea663c.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1413-7179a49332db5a80.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/150-25c277137a9638ab.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1640-bc99ec23bbe29d12.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/{2415.ed6428ff334c7ce2.js → 1751.50b96ea9c37b49b6.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/2300-de059d0ab2a16630.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2684-05b0a81e75298c16.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/3026-2ed7e317d9b02102.js +25 -0
  12. package/apps/pwa/out/_next/static/chunks/3611-77496ada38cd6358.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/3717-68957c4dc9ceec90.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/3929-46ba9af6f9d895cc.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/4015-62c5a3744698a8da.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/4217-3c4317a7905f9eeb.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/4240-3230f230fae2d7d5.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/4271-e53e075ebd48593b.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/4377.3b125fef79576dfd.js +5 -0
  20. package/apps/pwa/out/_next/static/chunks/4440-18bb160d95819f4e.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4853-35fdbe6bdbcdc0fc.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → 4daa2798-1363d725c79f3593.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/5007-6a3b30e23ff68a6e.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/5244-12b424c42070f5ff.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/{5791-bb82743fa6299562.js → 5348-bbf838e119661eee.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/5391-8793f88b03b30161.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/5611-df00af3e4e4a61bb.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/5890-dca2321773be829a.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/6123-37c9dc57ddaa7ab0.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/6588-b7975304a302ab91.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/6604-c03961962b6273b6.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/7109-cf8c87e968e83a65.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7178-30f77877525a2efb.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/7262-871563fc9663730b.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/7294-f0eeebb92aa93459.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7429-881af44deed6958f.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/7527.943753b6ed829f09.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/764-a01e858525a190c0.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/7868-e35d5f7985ecd3f1.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8320-51484af7b01fdfa5.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 8617.465ca22a319c95b3.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8918-482b30ce2a8ebf4c.js} +3 -3
  43. package/apps/pwa/out/_next/static/chunks/9131-52f5fad51cceca04.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9238.2cd5f47f4bf7a2c3.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 9704-3221c102623c8d73.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-70c404c7bb6d7f0f.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/approvals/page-3ffd80166b1050aa.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f89e9a303515a033.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/board/page-4f403cd060442c95.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-9fa879ebf5959096.js → page-0ac5d053f0444d21.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/bots/page-4bcc63e272ae8205.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/chat/page-880734bd07d62882.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/control/{page-4472ddf1e2458e69.js → page-72bd384898d88955.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1018bbc9586ecae5.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/editor/page-de86f8c38076dd7b.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/healthz/route-70c404c7bb6d7f0f.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/intake/page-cd6447196d8bafe6.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/layout-e733630c47fa102b.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/live/page-d1c1a8357b161875.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/market/page-60c514128e81d787.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8b637b7e325e13c1.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/morning/{page-112f7e477bda77fd.js → page-bccce0babceae30f.js} +1 -1
  63. package/apps/pwa/out/_next/static/chunks/app/not-found-63301d11dbe73836.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/observatory/page-a8cf0dc472060db2.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/page-02fba54881ccfa49.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d5e8e90f0038f8de.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-b1ef6824f494466a.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/sessions/page-d589bb0f233b1009.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c9bbaebef4cda059.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/settings/page-3639ce7916b92fed.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7450ae7478955653.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/setup/layout-70c404c7bb6d7f0f.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/setup/page-b41ca4abfd618452.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/share/page-b333dd79ebee3fa9.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/showroom/page-8be1227492793655.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/tasks/page-6e03e9371b552e0d.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/term/page-911411e9fca18d03.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/trace/page-792170e649b37088.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/vault/page-c40463466937bc91.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-509c798b80384536.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/workflows/page-1cac2aa21d77c417.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9626799d9b4a2cd1.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-84cd419494e16b0f.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → f6f3850d-7f324c6566002113.js} +1 -1
  85. package/apps/pwa/out/_next/static/chunks/framework-361487d2da603917.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/main-app-607f5beb94cb2e07.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/main-d0222d219e29dddf.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/pages/_app-a26980a31126decc.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/pages/_error-edb2b37decf23e4d.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/{webpack-e5bea36f183b25e6.js → webpack-2bf59c720fe9542c.js} +1 -1
  91. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +1 -0
  92. package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → ktMqfrBbf8BMGac0jqLrb}/_buildManifest.js +1 -1
  93. package/apps/pwa/out/approvals/index.html +1 -1
  94. package/apps/pwa/out/approvals/index.txt +29 -27
  95. package/apps/pwa/out/autopilot/index.html +1 -1
  96. package/apps/pwa/out/autopilot/index.txt +29 -27
  97. package/apps/pwa/out/board/index.html +1 -1
  98. package/apps/pwa/out/board/index.txt +29 -27
  99. package/apps/pwa/out/botlab/index.html +1 -1
  100. package/apps/pwa/out/botlab/index.txt +26 -24
  101. package/apps/pwa/out/bots/index.html +1 -1
  102. package/apps/pwa/out/bots/index.txt +26 -24
  103. package/apps/pwa/out/chat/index.html +1 -1
  104. package/apps/pwa/out/chat/index.txt +26 -24
  105. package/apps/pwa/out/control/index.html +1 -1
  106. package/apps/pwa/out/control/index.txt +29 -27
  107. package/apps/pwa/out/design-check/index.html +1 -1
  108. package/apps/pwa/out/design-check/index.txt +29 -27
  109. package/apps/pwa/out/editor/index.html +1 -1
  110. package/apps/pwa/out/editor/index.txt +29 -27
  111. package/apps/pwa/out/healthz +1 -1
  112. package/apps/pwa/out/icons/elanous-16.png +0 -0
  113. package/apps/pwa/out/icons/elanous-180.png +0 -0
  114. package/apps/pwa/out/icons/elanous-192.png +0 -0
  115. package/apps/pwa/out/icons/elanous-32.png +0 -0
  116. package/apps/pwa/out/icons/elanous-512.png +0 -0
  117. package/apps/pwa/out/index.html +1 -1
  118. package/apps/pwa/out/index.txt +26 -24
  119. package/apps/pwa/out/intake/index.html +1 -1
  120. package/apps/pwa/out/intake/index.txt +29 -27
  121. package/apps/pwa/out/live/index.html +1 -1
  122. package/apps/pwa/out/live/index.txt +29 -27
  123. package/apps/pwa/out/manifest.webmanifest +5 -2
  124. package/apps/pwa/out/market/index.html +1 -1
  125. package/apps/pwa/out/market/index.txt +26 -24
  126. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  127. package/apps/pwa/out/missions/placeholder/index.txt +26 -24
  128. package/apps/pwa/out/morning/index.html +1 -1
  129. package/apps/pwa/out/morning/index.txt +29 -27
  130. package/apps/pwa/out/observatory/index.html +1 -1
  131. package/apps/pwa/out/observatory/index.txt +29 -27
  132. package/apps/pwa/out/reflection/index.html +1 -1
  133. package/apps/pwa/out/reflection/index.txt +27 -25
  134. package/apps/pwa/out/scheduler/index.html +1 -1
  135. package/apps/pwa/out/scheduler/index.txt +29 -27
  136. package/apps/pwa/out/sessions/index.html +1 -1
  137. package/apps/pwa/out/sessions/index.txt +26 -24
  138. package/apps/pwa/out/settings/devices/index.html +1 -1
  139. package/apps/pwa/out/settings/devices/index.txt +29 -27
  140. package/apps/pwa/out/settings/index.html +1 -1
  141. package/apps/pwa/out/settings/index.txt +29 -27
  142. package/apps/pwa/out/setup/done/index.html +1 -1
  143. package/apps/pwa/out/setup/done/index.txt +29 -27
  144. package/apps/pwa/out/setup/index.html +1 -1
  145. package/apps/pwa/out/setup/index.txt +29 -27
  146. package/apps/pwa/out/share/index.html +1 -1
  147. package/apps/pwa/out/share/index.txt +29 -27
  148. package/apps/pwa/out/showroom/index.html +1 -1
  149. package/apps/pwa/out/showroom/index.txt +26 -24
  150. package/apps/pwa/out/sw-strategies.js +44 -0
  151. package/apps/pwa/out/sw.js +6 -44
  152. package/apps/pwa/out/tasks/index.html +1 -1
  153. package/apps/pwa/out/tasks/index.txt +29 -27
  154. package/apps/pwa/out/term/index.html +1 -1
  155. package/apps/pwa/out/term/index.txt +29 -27
  156. package/apps/pwa/out/trace/index.html +1 -1
  157. package/apps/pwa/out/trace/index.txt +29 -27
  158. package/apps/pwa/out/vault/index.html +1 -1
  159. package/apps/pwa/out/vault/index.txt +29 -27
  160. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  161. package/apps/pwa/out/workflows/chat-ui/index.txt +29 -27
  162. package/apps/pwa/out/workflows/index.html +1 -1
  163. package/apps/pwa/out/workflows/index.txt +29 -27
  164. package/apps/pwa/out/workspace/index.html +1 -1
  165. package/apps/pwa/out/workspace/index.txt +29 -27
  166. package/apps/pwa/out/worktrees/index.html +1 -1
  167. package/apps/pwa/out/worktrees/index.txt +29 -27
  168. package/catalog/resources.yaml +6 -6
  169. package/docs/goal-context/steward-directives-2026-09-28.md +35 -37
  170. package/graphs/landing/landing-heal.yaml +1 -1
  171. package/graphs/release/recipes.yaml +8 -1
  172. package/graphs/release/release-loop.yaml +6 -1
  173. package/graphs/video/README.md +1 -0
  174. package/graphs/video/explainer-line.yaml +58 -0
  175. package/package.json +3 -1
  176. package/plugins/job-coach/.codex-plugin/plugin.json +18 -1
  177. package/plugins/job-coach/README.md +12 -0
  178. package/plugins/job-coach/connectors/ncs/server.ts +4 -3
  179. package/plugins/job-coach/graphs/gap-judge.ts +44 -0
  180. package/plugins/job-coach/graphs/recipes.yaml +27 -0
  181. package/plugins/job-coach/graphs/report-enterprise.yaml +28 -0
  182. package/plugins/job-coach/graphs/run-step.ts +211 -17
  183. package/plugins/job-coach/plugin.json +16 -3
  184. package/plugins/job-coach/skills/enterprise-ai-fit/SKILL.md +8 -0
  185. package/plugins/job-coach/skills/enterprise-competency/SKILL.md +8 -0
  186. package/plugins/job-coach/skills/enterprise-needs/SKILL.md +8 -0
  187. package/plugins/job-coach/skills/enterprise-roadmap/SKILL.md +8 -0
  188. package/plugins/job-coach/skills/enterprise-task-analysis/SKILL.md +8 -0
  189. package/plugins/job-coach/skills/interview-to-profile/SKILL.md +18 -1
  190. package/scripts/backup/elanous-backup.sh +4 -1
  191. package/scripts/build-docs-site.ts +4 -3
  192. package/scripts/coord-channel-watch.sh +23 -4
  193. package/scripts/coord-post.sh +49 -12
  194. package/scripts/coord-tracks.json +5 -5
  195. package/scripts/eval-bot-vs-grok.ts +79 -0
  196. package/scripts/eval-grok-live-baseline.ts +36 -0
  197. package/scripts/fixtures/front-route-corpus.sample.json +17 -0
  198. package/scripts/fleet/node-setup.sh +46 -7
  199. package/scripts/landing-heal/must-fix-match.ts +26 -0
  200. package/scripts/landing-heal/run-mission.ts +114 -20
  201. package/scripts/lib/front-route-measurement.ts +131 -0
  202. package/scripts/lib/tui-sim-keys.ts +42 -0
  203. package/scripts/measure-front-route-classifier.ts +56 -0
  204. package/scripts/media/ocr-leak-scan.sh +21 -0
  205. package/scripts/public-export.ts +2 -3
  206. package/scripts/public-import-pr.ts +80 -6
  207. package/scripts/publish-internal-dist.ts +2 -3
  208. package/scripts/red-sweep-diff.ts +1 -1
  209. package/scripts/release-loop/auto-approve-node.ts +72 -0
  210. package/scripts/release-loop/gate-node.ts +332 -44
  211. package/scripts/release-loop/publish-node.ts +7 -3
  212. package/scripts/release-loop/release-docs.ts +20 -13
  213. package/scripts/release-loop/test-durations.ts +63 -0
  214. package/scripts/release-loop/upgrade-node.ts +3 -2
  215. package/scripts/test-audit-coverage-per-file.sh +16 -0
  216. package/scripts/test-audit-meaningless.ts +88 -0
  217. package/scripts/test-audit-overlap-and-weak.ts +73 -0
  218. package/scripts/test-deterministic.ts +27 -5
  219. package/scripts/tui-sim.ts +59 -14
  220. package/scripts/ux/bump-installed.sh +20 -0
  221. package/scripts/ux/lib/cdp.ts +72 -0
  222. package/scripts/ux/record-page.ts +50 -0
  223. package/skills/explainer-video/SKILL.md +99 -0
  224. package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -0
  225. package/skills/explainer-video/engine/build.mjs +431 -0
  226. package/skills/explainer-video/engine/vo.ts +37 -0
  227. package/skills/explainer-video/examples/elanous-0.2.5/script.json +155 -0
  228. package/src/agent/nested-agent-env.ts +1 -0
  229. package/src/agent-mission/driver.ts +95 -16
  230. package/src/agent-mission/mission-cli.ts +6 -0
  231. package/src/agent-mission/resource-ladder.ts +73 -25
  232. package/src/auth/acp-token.ts +16 -0
  233. package/src/auth/github-app-token.ts +112 -0
  234. package/src/autopilot/gh-rest-fallback.ts +177 -0
  235. package/src/autopilot/pr-manager.ts +51 -4
  236. package/src/boot/acp-server.ts +2 -16
  237. package/src/boot/daemon-tools/web-search.ts +2 -1
  238. package/src/boot/ws-bridge.ts +11 -0
  239. package/src/chat/index.ts +37 -8
  240. package/src/chat/log-scroll-key.ts +12 -0
  241. package/src/chat/submit-trace.ts +30 -0
  242. package/src/cli/agent-cli.ts +7 -2
  243. package/src/cli/decisions-cli.ts +94 -0
  244. package/src/cli/doctor-cli.ts +40 -54
  245. package/src/cli/doctor-readiness.ts +16 -24
  246. package/src/cli/intake-cli.ts +1 -1
  247. package/src/cli/mcp-call.ts +9 -0
  248. package/src/cli/mcp-gateway-cli.ts +7 -3
  249. package/src/cli/plugin-cli.ts +109 -11
  250. package/src/cli/pr-lineage.ts +2 -1
  251. package/src/cli/pwa-start.ts +3 -0
  252. package/src/cli/release-cli.ts +94 -7
  253. package/src/cli/self-update.ts +7 -4
  254. package/src/cli/start-cli.ts +33 -14
  255. package/src/dashboard/acp-boot.ts +3 -0
  256. package/src/dashboard/index.ts +62 -13
  257. package/src/dashboard/input/no-provider-reply.ts +13 -0
  258. package/src/dashboard/slash-runtime/unknown-slash-reply.ts +78 -0
  259. package/src/dashboard/stale-install-check.ts +77 -0
  260. package/src/decisions/decision-ledger.ts +404 -0
  261. package/src/domains/outbound-alert.ts +8 -2
  262. package/src/domains/telegram-kind-route.ts +21 -0
  263. package/src/execution-loop/dispatch-task.ts +139 -0
  264. package/src/execution-loop/gate-decision-summary.ts +29 -0
  265. package/src/execution-loop/gates.ts +197 -0
  266. package/src/graph-kinds/installed-plugin-nodes.ts +95 -0
  267. package/src/graph-kinds/plugin-nodes.ts +10 -2
  268. package/src/graph-runner/runner.ts +35 -5
  269. package/src/grounding/token.ts +12 -6
  270. package/src/harness/harness-cli-command.ts +68 -9
  271. package/src/harness/harness-pod-dispatch.ts +8 -2
  272. package/src/harness/harness-stop.ts +147 -18
  273. package/src/harness/self-send-decide.ts +26 -17
  274. package/src/harness/self-send-target.ts +22 -1
  275. package/src/harness/worktree-branch-prefix.ts +4 -3
  276. package/src/index.ts +56 -13
  277. package/src/instance/resolve.ts +6 -0
  278. package/src/intake-plane/collect-telegram-saved.ts +5 -0
  279. package/src/intake-plane/digest.ts +11 -2
  280. package/src/live/detail-switch.ts +8 -3
  281. package/src/llm/usable-llm.ts +32 -0
  282. package/src/loops/registry.ts +27 -5
  283. package/src/market/publish.ts +59 -3
  284. package/src/native-tool-catalog.ts +2 -2
  285. package/src/nexus/api/graph-approvals.ts +125 -0
  286. package/src/nexus/api/http-server.ts +60 -6
  287. package/src/nexus/api/log-fabric.ts +10 -1
  288. package/src/nexus/api/mcp-oauth-hint.ts +10 -9
  289. package/src/nexus/api/merge-approvals.ts +2 -1
  290. package/src/nexus/api/meta-api.ts +14 -0
  291. package/src/nexus/api/plugins-credentials-api.ts +47 -0
  292. package/src/nexus/api/plugins-market.ts +87 -3
  293. package/src/nexus/api/pod-credential-api.ts +68 -14
  294. package/src/nexus/api/public-routes.ts +7 -0
  295. package/src/nexus/api/trace.ts +57 -6
  296. package/src/nexus/boot/register-mcp-clients.ts +99 -14
  297. package/src/nexus/index.ts +33 -32
  298. package/src/nexus/outbound/router.ts +1 -1
  299. package/src/onboarding.ts +25 -2
  300. package/src/plugins/core/host.ts +14 -0
  301. package/src/plugins/install/market-fetch.ts +4 -3
  302. package/src/plugins/install/plugin-credentials.ts +106 -0
  303. package/src/plugins/install/plugin-install.ts +163 -32
  304. package/src/prompt-library/universal-preamble.ts +8 -2
  305. package/src/release-loop/checklist.ts +191 -0
  306. package/src/release-loop/release-note.ts +6 -3
  307. package/src/self-dev/dev-cli.ts +40 -20
  308. package/src/self-dev/dev-pipeline.ts +77 -1
  309. package/src/self-implement/gate-baseline.ts +3 -1
  310. package/src/self-implement/goal-author.ts +18 -1
  311. package/src/self-implement/host-regate.ts +6 -2
  312. package/src/self-implement/orchestrator.ts +7 -2
  313. package/src/self-implement/seams.ts +4 -2
  314. package/src/skills/tools/dev-harness.ts +42 -0
  315. package/src/steward/steward-cards.ts +45 -0
  316. package/src/steward/triage.ts +50 -14
  317. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +61 -3
  318. package/src/task-orchestrator/surfaces/pod-command-job.ts +27 -6
  319. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +39 -9
  320. package/src/task-orchestrator/surfaces/pod-source-bundle.ts +16 -1
  321. package/src/task-orchestrator/surfaces/pod-source-receive.ts +51 -13
  322. package/src/task-orchestrator/surfaces/self-implement-pod.ts +172 -28
  323. package/src/telegram-report.ts +12 -6
  324. package/src/tui-client/acp-transport-ws-client.ts +72 -5
  325. package/src/tui.ts +3 -2
  326. package/src/user-config.ts +28 -2
  327. package/src/util/spawn-sync-output.ts +15 -0
  328. package/src/version/code-revision.ts +1 -1
  329. package/src/version/packed-revision.json +1 -1
  330. package/src/web-search/free.ts +77 -0
  331. package/src/web-search/index.ts +2 -0
  332. package/src/workflow-runtime/deps-bridge.ts +4 -3
  333. package/src/workflow-runtime/plugin-kind-node.ts +32 -4
  334. package/src/workflow-runtime/types.ts +2 -1
  335. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/1191-7b13a24117a689fa.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/2375-3fcae5186c64fe6b.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +0 -25
  346. package/apps/pwa/out/_next/static/chunks/319-d37df1b6e0cbeff4.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
  348. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/4836-3c93795ab834381e.js +0 -1
  352. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
  353. package/apps/pwa/out/_next/static/chunks/6090-1bd6077591603947.js +0 -1
  354. package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +0 -1
  355. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
  356. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
  357. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
  358. package/apps/pwa/out/_next/static/chunks/7457-f5cb1cc8db9f7453.js +0 -1
  359. package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +0 -1
  360. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
  361. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
  362. package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +0 -1
  363. package/apps/pwa/out/_next/static/chunks/8142-011e1f474efb8b33.js +0 -1
  364. package/apps/pwa/out/_next/static/chunks/8519-0059d82bdb4812bf.js +0 -1
  365. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
  366. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
  367. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
  368. package/apps/pwa/out/_next/static/chunks/9740-52467f244755917f.js +0 -1
  369. package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +0 -1
  370. package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/app/approvals/page-21c3261af6ab377c.js +0 -1
  373. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-1991445b8ea86274.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/app/board/page-4ebfaee2369fe248.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/app/bots/page-ca7c3cc305c3285a.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/app/chat/page-9951930add3d2051.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/app/design-check/page-4de08d4b766f134b.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/app/editor/page-7a975e2b9db1dfb7.js +0 -1
  379. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/app/intake/page-49270714ef35bb96.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/app/layout-33fbdf353e2a5982.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/app/live/page-259f122e2dd64317.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/app/market/page-65b82726753658e1.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-4b8df3748dc06285.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/app/not-found-542a5c0006da637c.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/app/observatory/page-63ffb347831262ea.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/app/page-98e4e359d97263b4.js +0 -1
  388. package/apps/pwa/out/_next/static/chunks/app/reflection/page-97e24ac675ce576e.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-ac6391f26391d89d.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9f45c9d45ffc43bd.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-36995b9cb5d98218.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/app/settings/page-0c795b577988d63f.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b4311e2f71c6735a.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/app/setup/page-eb2381074df20dba.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/app/share/page-b01336e64eba01ae.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7af7f21c1f2858ef.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/app/tasks/page-a98ecd364586c89b.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/app/term/page-57f0502dfbfa089b.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/app/trace/page-3467d2e1abd8a14a.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/app/vault/page-7842ca23d53ba682.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-ae9ac882912b0198.js +0 -1
  403. package/apps/pwa/out/_next/static/chunks/app/workflows/page-3a0d72e3f783bcef.js +0 -1
  404. package/apps/pwa/out/_next/static/chunks/app/workspace/page-f3b39fe8358c2a97.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-5a22af3181ac2afb.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/main-app-1457627bb302a22c.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
  411. package/apps/pwa/out/_next/static/css/e66b7f5efc8df3f3.css +0 -1
  412. /package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → ktMqfrBbf8BMGac0jqLrb}/_ssgManifest.js +0 -0
@@ -8,7 +8,7 @@ import { fileURLToPath } from 'node:url';
8
8
  import type { Command } from 'commander';
9
9
  import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
10
10
  import { discoverChromeBinary } from '../browser-cdp/client.js';
11
- import { listProviders } from '../oauth/store.js';
11
+ import { resolveUsableLlm, type UsableLlm } from '../llm/usable-llm.js';
12
12
  import { detectLocale } from '../expression/locale.js';
13
13
  import { findRetiredConfigKeysInFile, getUserConfig, type RetiredConfigKey, type UserConfig } from '../user-config.js';
14
14
  import { codeRevision } from '../version/code-revision.js';
@@ -142,7 +142,8 @@ export interface DoctorOptions {
142
142
  */
143
143
  readiness?: ReadinessDeps;
144
144
  checkReadiness?: (deps: ReadinessDeps) => ReadinessReport;
145
- /** Read-only auth-store provider names. Values are never requested. */
145
+ resolveLlm?: () => UsableLlm;
146
+ /** Legacy read-only seam; route selection no longer uses provider-name presence. */
146
147
  listAuthProviders?: () => readonly string[];
147
148
  /** Read-only `codeRevision()`. */
148
149
  codeRevision?: () => string | undefined;
@@ -187,15 +188,6 @@ export interface DoctorCliDeps extends DoctorOptions, Pick<DoctorFixDeps, 'home'
187
188
  const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
188
189
  const tavilyNames = new Set(['TAVILY_API_KEY', 'TAVILY_KEY']);
189
190
 
190
- function codexLoginPresent(providers: readonly string[]): boolean {
191
- return providers.some((name) => name === 'openai-codex' || name.startsWith('openai-codex:'));
192
- }
193
-
194
- /** Presence only. Token strings from the auth store never leave this function. */
195
- function defaultListAuthProviders(): readonly string[] {
196
- return listProviders();
197
- }
198
-
199
191
  function defaultCodeRevision(): string | undefined {
200
192
  try {
201
193
  return codeRevision();
@@ -294,7 +286,6 @@ interface ReadinessLookup {
294
286
  platform: NodeJS.Platform;
295
287
  commandExists: (name: string) => boolean;
296
288
  getConfig: () => UserConfig;
297
- listAuthProviders: () => readonly string[];
298
289
  codeRevision: () => string | undefined;
299
290
  fetchHealth: () => { daemonSha?: string } | null;
300
291
  readInstallPrefix: () => string | null | undefined;
@@ -306,8 +297,6 @@ interface ReadinessLookup {
306
297
  /** 저장소가 시험한 bun 판(`.bun-version`) · 지금 도는 bun 판(시험 seam). */
307
298
  bunPin?: () => string | null;
308
299
  bunVersion?: () => string | null;
309
- /** LLM 키가 하나라도 풀렸나(자격 보고서에서 · 없으면 못 쟀다). */
310
- llmKeyResolved?: boolean;
311
300
  /** `/etc/os-release` 본문(시험 seam) — 못 읽으면 null. */
312
301
  readOsRelease?: () => string | null;
313
302
  /** 빌드 도구 탐침(시험 seam). */
@@ -371,19 +360,6 @@ function resolveReadinessDeps(lookup: ReadinessLookup): ReadinessDeps {
371
360
  if (provider !== null && provider !== 'auto' && !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(provider)) {
372
361
  provider = null;
373
362
  }
374
- let codexLogin: boolean | null = null;
375
- let anyLogin: boolean | null = null;
376
- try {
377
- const providers = lookup.listAuthProviders();
378
- codexLogin = codexLoginPresent(providers);
379
- anyLogin = providers.length > 0;
380
- } catch {
381
- codexLogin = null;
382
- }
383
- // 로그인이 있거나 LLM 키가 풀렸으면 true · 둘 다 «아니라고 잰» 경우만 false · 하나라도 못 쟀으면 null.
384
- const llmCredentialAvailable = anyLogin === true || lookup.llmKeyResolved === true
385
- ? true
386
- : anyLogin === false && lookup.llmKeyResolved === false ? false : null;
387
363
  const pathEntries = (lookup.env.PATH ?? '').split(lookup.pathDelimiter).filter((entry) => entry.length > 0);
388
364
  let ghOnPath: boolean | null = null;
389
365
  let ghAuthStatus: number | null = null;
@@ -466,8 +442,6 @@ function resolveReadinessDeps(lookup: ReadinessLookup): ReadinessDeps {
466
442
  }
467
443
  return {
468
444
  provider,
469
- codexLogin,
470
- llmCredentialAvailable,
471
445
  ghOnPath,
472
446
  ghAuthStatus,
473
447
  ghVersion,
@@ -1045,8 +1019,7 @@ export function runDoctor(options: DoctorOptions = {}): DoctorReport {
1045
1019
  pathDelimiter,
1046
1020
  platform,
1047
1021
  commandExists,
1048
- getConfig,
1049
- listAuthProviders: options.listAuthProviders ?? defaultListAuthProviders,
1022
+ getConfig: () => resolutionOptions.userConfig,
1050
1023
  codeRevision: options.codeRevision ?? defaultCodeRevision,
1051
1024
  fetchHealth: options.fetchHealth ?? (() => defaultFetchHealth(options.resolveDaemonEndpoint ?? resolveDaemonEndpoint)),
1052
1025
  readInstallPrefix: options.readInstallPrefix ?? (() => defaultReadInstallPrefix(root, exists)),
@@ -1054,12 +1027,17 @@ export function runDoctor(options: DoctorOptions = {}): DoctorReport {
1054
1027
  ghAuthStatus: options.ghAuthStatus ?? (() => defaultGhAuthStatus(commandExists)),
1055
1028
  ghVersion: options.ghVersion ?? (() => defaultGhVersion(commandExists)),
1056
1029
  tmpdirSameFsAsBunCache: options.tmpdirSameFsAsBunCache ?? (() => defaultTmpdirSameFsAsBunCache(env)),
1057
- llmKeyResolved: llmKeyResolvedFrom(credentials),
1058
1030
  nodePty: nodePtyStatus(externalCommands.commands),
1059
1031
  ...(options.probeBuildToolchain ? { probeBuildToolchain: options.probeBuildToolchain } : {}),
1060
1032
  ...(options.checkPythonEnv ? { checkPythonEnv: options.checkPythonEnv } : {}),
1061
1033
  ...(options.probeHostEnvironment ? { probeHostEnvironment: options.probeHostEnvironment } : {}),
1062
1034
  });
1035
+ // An injected readiness snapshot stays as-is. For a real machine, measure the
1036
+ // same runtime route as `start` rather than inferring usability from key names.
1037
+ if (options.readiness === undefined && readiness.provider !== null) {
1038
+ try { readiness.usableLlm = options.resolveLlm ? options.resolveLlm() : resolveUsableLlm({ config: resolutionOptions.userConfig }); }
1039
+ catch { readiness.usableLlm = null; }
1040
+ }
1063
1041
  return {
1064
1042
  ok: true,
1065
1043
  credentials,
@@ -1083,15 +1061,14 @@ function formatReadiness(readiness: ReadinessReport | undefined): string[] {
1083
1061
  if (readiness === undefined) return [];
1084
1062
  const todo = readiness.items.filter((entry) => entry.status === 'manual' || entry.status === 'fixable');
1085
1063
  return [
1086
- '준비 상태:',
1064
+ 'Readiness:',
1087
1065
  ...readiness.items.map((entry) => {
1088
1066
  const remedy = entry.remedy === undefined ? '' : ` — ${entry.remedy}`;
1089
1067
  return ` ${entry.id}: ${entry.status} — ${entry.evidence}${remedy}`;
1090
1068
  }),
1091
1069
  todo.length === 0
1092
- ? '할 일 없음.'
1093
- // 처방은 줄마다 이미 있다 — 「fixable」이 곧 `doctor --fix` 는 아니다(install-path 의 처방은 export 한 줄).
1094
- : `할 일 ${todo.length}개: ${todo.map((entry) => entry.id).join(', ')} — 처방은 위 각 줄 끝`,
1070
+ ? 'Nothing to do.'
1071
+ : `To do (${todo.length}): ${todo.map((entry) => entry.id).join(', ')} — each line above ends with its fix`,
1095
1072
  ];
1096
1073
  }
1097
1074
 
@@ -1105,14 +1082,21 @@ function humanFreeFallback(value: string): string {
1105
1082
  .replace(/\s{2,}/g, ' ').trim().replace(/[—–·;,\s]+$/, '');
1106
1083
  }
1107
1084
 
1085
+ export function humanBreaks(value: string): string | undefined {
1086
+ if (value.trim().toLowerCase() === 'unmeasured'
1087
+ || /\b\d+ test (failures|errors)\b|\bmeasured\b|\bpostmortem\b|\bquota\b/i.test(value)
1088
+ || /[가-힣ㄱ-ㅎㅏ-ㅣ]/.test(value)) return undefined;
1089
+ return humanFreeFallback(value) || undefined;
1090
+ }
1091
+
1108
1092
  function formatCapabilitySummary(summary: DoctorCapabilitySummary): string[] {
1109
1093
  const unavailable = summary.unavailable.map((capability) =>
1110
1094
  ` ${capability.name} — unlock with ${capability.credential}${capability.freeFallback !== undefined && humanFreeFallback(capability.freeFallback) ? `; free alternative: ${humanFreeFallback(capability.freeFallback)}` : ''}`,
1111
1095
  );
1112
1096
  return [
1113
- '할 수 있는 일:',
1097
+ 'Available:',
1114
1098
  ...(summary.available.length === 0 ? [' None.'] : summary.available.map((capability) => ` ${capability.name} — unlocked by ${capability.credential}`)),
1115
- '못 하는 일:',
1099
+ 'Unavailable:',
1116
1100
  ...(unavailable.length === 0 ? [' None.'] : unavailable),
1117
1101
  'Unknown by credential:',
1118
1102
  ...(summary.unknownCredentials.length === 0 ? [' Empty.'] : summary.unknownCredentials.map((credential) => ` ${credential}`)),
@@ -1129,8 +1113,8 @@ export function formatDoctorReport(report: DoctorReport, options: { credentials?
1129
1113
  const satisfied = report.credentials.filter((credential) => !credential.resolved && credential.satisfiedBy !== undefined).length;
1130
1114
  return [
1131
1115
  ...formatReadiness(report.readiness),
1132
- ...(requiredMissing.length ? [`⛔ 필수 명령 없음: ${requiredMissing.join(', ')}`] : []),
1133
- `자격증명: ${resolved}/${report.credentials.length} resolved${satisfied ? ` · ${satisfied} satisfied by alternative names` : ''} · details: elanous doctor --credentials`,
1116
+ ...(requiredMissing.length ? [`⛔ Required commands missing: ${requiredMissing.join(', ')}`] : []),
1117
+ `Credentials: ${resolved}/${report.credentials.length} resolved${satisfied ? ` · ${satisfied} satisfied by alternative names` : ''} · details: elanous doctor --credentials`,
1134
1118
  ...(options.credentials ? report.credentials.map((credential) => [
1135
1119
  `${credential.name}: ${credential.resolved ? 'resolved' : 'unresolved'} (${credential.source}) — ${credential.note}`,
1136
1120
  ...(credential.satisfiedBy !== undefined ? [` Satisfied by: ${credential.satisfiedBy.name} (${credential.satisfiedBy.source})`] : []),
@@ -1138,14 +1122,15 @@ export function formatDoctorReport(report: DoctorReport, options: { credentials?
1138
1122
  ...(credential.freeFallback !== undefined ? [` Free fallback${credential.freeFallbackMode === 'auto' || credential.freeFallbackMode === 'manual' || credential.freeFallbackMode === 'none' ? ` [${credential.freeFallbackMode}]` : ''}: ${humanFreeFallback(credential.freeFallback)}`] : []),
1139
1123
  ].join('\n')) : []),
1140
1124
  ...(report.catalogMetadataUnavailable ? ['Catalog metadata unavailable: could not read catalog/resources.yaml.'] : []),
1141
- ...(report.retiredConfigKeys ?? []).map(({ path, reason }) => `더는 안 쓰는 설정 키: ${path} — ${reason}`),
1125
+ ...(report.retiredConfigKeys ?? []).map(({ path, reason }) => `Retired config key: ${path} — ${reason}`),
1142
1126
  'External commands:',
1143
1127
  ...report.externalCommands.map((command) => {
1144
1128
  const breaks = command.status === 'broken' ? command.breaks_broken ?? command.breaks : command.breaks;
1129
+ const visibleBreaks = breaks === undefined ? undefined : humanBreaks(breaks);
1145
1130
  const fix = command.status === 'broken' ? command.fix_broken ?? command.fix : command.fix;
1146
1131
  return [
1147
1132
  `${command.name}: ${command.status === 'skipped' ? `skipped (${command.platform}-only)` : `${command.status} (${command.tier})`}${command.detail === undefined ? '' : ` — ${command.detail}`}`,
1148
- ...((command.status === 'missing' || command.status === 'broken') && breaks !== undefined ? [` Breaks: ${breaks}`] : []),
1133
+ ...((command.status === 'missing' || command.status === 'broken') && visibleBreaks !== undefined ? [` Breaks: ${visibleBreaks}`] : []),
1149
1134
  ...((command.status === 'missing' || command.status === 'broken') && fix !== undefined ? [` Fix: ${fix}`] : []),
1150
1135
  ].join('\n');
1151
1136
  }),
@@ -1260,13 +1245,12 @@ export function registerDoctorCommand(program: Command, deps: DoctorCliDeps = {}
1260
1245
  home: deps.home,
1261
1246
  cacheDir: deps.cacheDir,
1262
1247
  readdir: deps.readdir,
1263
- readiness: deps.readiness ?? resolveReadinessDeps({
1248
+ readiness: deps.readiness ?? { ...resolveReadinessDeps({
1264
1249
  env: deps.env ?? process.env,
1265
1250
  pathDelimiter: deps.pathDelimiter ?? delimiter,
1266
1251
  platform: deps.platform ?? process.platform,
1267
1252
  commandExists: deps.commandExists ?? ((name) => (deps.env ?? process.env).PATH?.split(deps.pathDelimiter ?? delimiter).some((dir) => existsSync(join(dir, name))) ?? false),
1268
- getConfig: deps.getUserConfig ?? getUserConfig,
1269
- listAuthProviders: deps.listAuthProviders ?? defaultListAuthProviders,
1253
+ getConfig: () => deps.userConfig ?? (deps.getUserConfig ?? getUserConfig)(),
1270
1254
  codeRevision: deps.codeRevision ?? defaultCodeRevision,
1271
1255
  fetchHealth: deps.fetchHealth ?? (() => defaultFetchHealth(deps.resolveDaemonEndpoint ?? resolveDaemonEndpoint)),
1272
1256
  readInstallPrefix: deps.readInstallPrefix ?? (() => defaultReadInstallPrefix(deps.repositoryRoot ?? repositoryRoot, deps.exists ?? existsSync)),
@@ -1274,13 +1258,15 @@ export function registerDoctorCommand(program: Command, deps: DoctorCliDeps = {}
1274
1258
  // gh 판은 수리 계획에 쓰인다(낡은 gh → 정적 gh) — 🩸 09-25: 여기서 안 재서 계획은 보고서에만 뜨고 적용이 안 됐다.
1275
1259
  ghVersion: deps.ghVersion ?? (() => defaultGhVersion((name) => (deps.env ?? process.env).PATH?.split(deps.pathDelimiter ?? delimiter).some((dir) => existsSync(join(dir, name))) ?? false)),
1276
1260
  tmpdirSameFsAsBunCache: deps.tmpdirSameFsAsBunCache ?? (() => defaultTmpdirSameFsAsBunCache(deps.env ?? process.env)),
1277
- // 보고서와 같은 자로 — 로그인도 LLM 키도 없으면 계획에 «사람 한 줄»(로그인)이 실린다(🅢 #20263 ③).
1278
- ...(source.ok ? { llmKeyResolved: llmKeyResolvedFrom(source.credentials), nodePty: nodePtyStatus(source.externalCommands) } : {}),
1261
+ ...(source.ok ? { nodePty: nodePtyStatus(source.externalCommands) } : {}),
1279
1262
  // L0 칸(substrate·docker·kubernetes·memory)은 «정보성·선택 기능»이라 수리 계획에 싣지 않는다 —
1280
1263
  // `--sudo` 가 `sudo systemctl start docker` 를 «설치 줄»로 치지 않게 하고, docker·kubectl 탐침(최대 수 초)을 두 번 돌리지 않는다.
1281
1264
  // 보고서(`runDoctor`)에는 그대로 나온다.
1282
1265
  probeHostEnvironment: () => null,
1283
- }),
1266
+ }), usableLlm: (() => {
1267
+ try { return deps.resolveLlm ? deps.resolveLlm() : resolveUsableLlm({ config: deps.userConfig ?? (deps.getUserConfig ?? getUserConfig)() }); }
1268
+ catch { return null; }
1269
+ })() },
1284
1270
  exists: deps.exists,
1285
1271
  readFile: deps.readFile,
1286
1272
  writeFile: deps.writeFile,
@@ -1374,16 +1360,16 @@ export function registerDoctorCommand(program: Command, deps: DoctorCliDeps = {}
1374
1360
  else out.log([
1375
1361
  formatDoctorReport(currentReport, { credentials: opts.credentials }),
1376
1362
  ...(advice ? [formatDoctorAdvice(advice)] : []),
1377
- '수정 계획:',
1363
+ 'Fix plan:',
1378
1364
  ...plan.items.map((item) => ` ${item.id}: ${item.status} — ${item.path} — ${item.action}${item.reason ? ` — ${item.reason}` : ''}`),
1379
1365
  ...plan.manual.map((item) => ` ${item.id}: manual${item.remedy ? ` — ${item.remedy}` : ''}`),
1380
1366
  ...manualCommandFixes(currentReport).map((line) => ` ${line}`),
1381
- ...(gitInstall ? [`git 설치: ${gitInstall.detail}${gitPlan?.note ? ` — ${gitPlan.note}` : ''}`] : []),
1367
+ ...(gitInstall ? [`Git installation: ${gitInstall.detail}${gitPlan?.note ? ` — ${gitPlan.note}` : ''}`] : []),
1382
1368
  ...(sudoResult ? [sudoResult.sudoAvailable
1383
- ? `sudo 설치: ${sudoResult.runs.length ? sudoResult.runs.map((entry) => `${entry.result} — ${entry.command}${entry.detail ? ` — ${entry.detail}` : ''}`).join(' · ') : '칠 줄 없음'}`
1384
- : 'sudo 설치: 건너뜀 — 이 기계는 sudo 에 암호가 필요하다(sudo -n true 실패). 위 «manual» 줄을 직접 치세요.'] : []),
1385
- ...(results ? ['적용 결과:', ...results.items.map((item) => ` ${item.id}: ${item.result} — ${item.path}${item.reason ? ` — ${item.reason}` : ''}`)] : ['적용하려면 --fix --yes']),
1386
- ...(restartResult ? [`서비스 재시작: ${restartResult.result} — ${restartResult.reason}`] : []),
1369
+ ? `Sudo installation: ${sudoResult.runs.length ? sudoResult.runs.map((entry) => `${entry.result} — ${entry.command}${entry.detail ? ` — ${entry.detail}` : ''}`).join(' · ') : '칠 줄 없음'}`
1370
+ : 'Sudo installation: 건너뜀 — 이 기계는 sudo 에 암호가 필요하다(sudo -n true 실패). 위 «manual» 줄을 직접 치세요.'] : []),
1371
+ ...(results ? ['Applied:', ...results.items.map((item) => ` ${item.id}: ${item.result} — ${item.path}${item.reason ? ` — ${item.reason}` : ''}`)] : ['적용하려면 --fix --yes']),
1372
+ ...(restartResult ? [`Service restart: ${restartResult.result} — ${restartResult.reason}`] : []),
1387
1373
  ].join('\n'));
1388
1374
  const exitCode = Math.max(results?.exitCode ?? 0, sudoResult?.exitCode ?? 0, restartResult?.result === 'failed' ? 1 : 0, currentReport.requiredMissing?.length ? 1 : 0, gitInstall?.ran && !gitInstall.ok ? 1 : 0);
1389
1375
  if (results || exitCode) setExitCode(exitCode);
@@ -1,4 +1,5 @@
1
1
  import { remediesFor, type DistroFamily } from './doctor-distro.js';
2
+ import type { UsableLlm } from '../llm/usable-llm.js';
2
3
  import { providerSecretNames } from '../nexus/install/launchd.js';
3
4
  import { win32 } from 'node:path';
4
5
  /**
@@ -42,13 +43,8 @@ export interface ReadinessDeps {
42
43
  * must pass `null` rather than omit it).
43
44
  */
44
45
  provider?: string | null;
45
- /**
46
- * Codex subscription login is present. `null` means not measured.
47
- * The token itself is never passed in.
48
- */
49
- codexLogin?: boolean | null;
50
- /** LLM 을 부를 자격이 «하나라도» 있나(어떤 로그인이든 · LLM 키가 풀렸든). 없으면(undefined/null) 못 쟀다. */
51
- llmCredentialAvailable?: boolean | null;
46
+ /** Selected runtime route, already resolved with the runtime auto selector. null = unmeasured. */
47
+ usableLlm?: UsableLlm | null;
52
48
  /** `gh` is on PATH. `null` means not measured. */
53
49
  ghOnPath?: boolean | null;
54
50
  /** `gh --version` 의 판 · null/undefined = 못 쟀다. */
@@ -210,23 +206,19 @@ function providerDecision(deps: ReadinessDeps): ReadinessItem {
210
206
  return item('provider-decision', 'unknown', 'llm.provider was not measured');
211
207
  }
212
208
  const provider = deps.provider.trim() || 'auto';
213
- if (provider !== 'auto') {
214
- // 아는 provider 이름만 보여 준다 — 설정 칸에 자격 같은 값이 들어와 있어도 보고에 싣지 않는다.
215
- const shown = KNOWN_PROVIDERS.has(provider) ? provider : '(set · value not shown)';
216
- return item('provider-decision', 'ok', `llm.provider=${shown}`);
217
- }
218
- if (deps.codexLogin === undefined || deps.codexLogin === null) {
219
- return item('provider-decision', 'unknown', 'llm.provider=auto but codex login was not measured');
220
- }
221
- if (deps.codexLogin === true) {
222
- // 2026-09-24 결정(`#20142`) — auto 는 codex 구독을 먼저 쓰고 계정 회전·llm.fallbackChain 이 뒤를 잇는다.
223
- return item('provider-decision', 'ok', 'llm.provider=auto uses the codex login first; account rotation and llm.fallbackChain apply');
224
- }
225
- // 🩸 2026-09-24 빈 VM(🅢 #20263): 로그인도 키도 없는데 여기서 `ok` 가 나왔다(거짓 초록) — auto 가 고를 것이 없다.
226
- if (deps.llmCredentialAvailable === false) {
227
- return item('provider-decision', 'manual', 'llm.provider=auto but no LLM login or key was found — LLM commands will fail', 'elanous login openai-codex');
228
- }
229
- return item('provider-decision', 'ok', 'llm.provider=auto and no codex login');
209
+ const shown = KNOWN_PROVIDERS.has(provider) ? provider : '(set · value not shown)';
210
+ if (deps.usableLlm == null) return item('provider-decision', 'unknown', `llm.provider=${shown} route was not measured`);
211
+ const { usable, provider: selected, via, why } = deps.usableLlm;
212
+ if (!usable) {
213
+ const reason = why === 'no usable LLM route selected' ? why : 'route unavailable';
214
+ const remedy = provider === 'auto' || provider === 'openai-codex' ? 'elanous login openai-codex'
215
+ : provider === 'openai' ? 'Set OPENAI_API_KEY or llm.apiKey for openai'
216
+ : provider === 'local' ? 'Set llm.baseUrl or LOCAL_LLM_URL for local'
217
+ : KNOWN_PROVIDERS.has(provider) ? `Configure credentials for llm.provider=${provider}` : undefined;
218
+ return item('provider-decision', 'manual', `llm.provider=${shown} selected no usable LLM (via none) — ${reason}`, remedy);
219
+ }
220
+ const selectedName = selected && KNOWN_PROVIDERS.has(selected) ? selected : 'selected provider';
221
+ return item('provider-decision', 'ok', `llm.provider=${shown} → ${via} ${selectedName}`);
230
222
  }
231
223
 
232
224
  /** Debian 계열 apt 의 gh 는 GH_MIN_VERSION 미만이다(📏 09-25: Debian 12 = 2.23 · Ubuntu 24.04 = 2.45) — apt 로 깔면 곧바로
@@ -282,7 +282,7 @@ export function registerIntakeCommands(program: Command): void {
282
282
  const { sendTelegramReport } = await import('../telegram-report.js');
283
283
  const { getUserConfig } = await import('../user-config.js');
284
284
  const text = renderDigestTelegram(d, { ...(opts.vault ? { vaultRoot: opts.vault } : {}), ...(opts.note ? { notePath: opts.note } : {}) });
285
- const sent = await sendTelegramReport(getUserConfig(), text, { markdown: true });
285
+ const sent = await sendTelegramReport(getUserConfig(), text, { markdown: true, kind: 'intake' });
286
286
  debug.log('intake.digest', 'telegram', { day, absorbed: d.absorbed.length, goals: d.goals.length, sent });
287
287
  console.log(sent ? `텔레그램 보고 채널로 보냈다 (${day} · 흡수 ${d.absorbed.length} · 골 후보 ${d.goals.length})` : '텔레그램 보고 채널 설정이 없다(telegram.reportChannel) — 보내지 않음');
288
288
  if (!sent) process.exitCode = 3;
@@ -1,6 +1,7 @@
1
1
  import { resolveDaemonEndpoint, type ResolveDaemonEndpointOpts } from '../nexus/daemon-endpoint.js';
2
2
  import { RemotesStore } from './remotes.js';
3
3
  import { bookmarkAttachDefaults } from './remote-resolve.js';
4
+ import { getNodeKind } from '../graph-kinds/registry.js';
4
5
 
5
6
  export type McpCliClassification =
6
7
  | 'ok'
@@ -84,7 +85,15 @@ export async function callDaemonMcpTool(opts: {
84
85
  arguments: Record<string, unknown>;
85
86
  endpoint?: string | ResolvedEndpoint;
86
87
  fetchFn?: McpCallOpts['fetchFn'];
88
+ pluginKind?: string;
87
89
  }): Promise<unknown> {
90
+ if (opts.pluginKind) {
91
+ const entry = getNodeKind('workflow', opts.pluginKind);
92
+ if (!entry || entry.core || !entry.plugin || !entry.run || !('mcp' in entry.run)
93
+ || opts.tool !== `${entry.run.mcp.server}.${entry.run.mcp.tool}`) {
94
+ throw new McpCliFailure('mcp-usage-error', 'invalid plugin MCP kind');
95
+ }
96
+ }
88
97
  const endpoint = typeof opts.endpoint === 'string' ? { url: opts.endpoint } : opts.endpoint;
89
98
  const resolved = endpoint ? { ok: true as const, value: endpoint } : resolveMcpEndpoint({});
90
99
  if (!resolved.ok) throw new McpCliFailure(resolved.classification, resolved.message);
@@ -6,6 +6,7 @@ import { getElanousConfigDir } from '../elanous-config-dir.js';
6
6
  import { effectiveInstanceRoot } from '../instance/resolve.js';
7
7
  import { issueMcpPat, listMcpPats, revokeMcpPat } from '../mcp-gateway/pat-store.js';
8
8
  import { startMcpGateway } from '../mcp-gateway/gateway.js';
9
+ import { resolveDaemonEndpoint } from '../nexus/daemon-endpoint.js';
9
10
 
10
11
  function privateTokenFile(path: string): string {
11
12
  const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
@@ -47,12 +48,15 @@ export function registerMcpGatewayCommands(mcp: Command): void {
47
48
  .description('Owner-PAT authenticated public MCP relay to the scoped NEXUS endpoint')
48
49
  .option('--host <host>', 'Bind host', '127.0.0.1')
49
50
  .option('--port <port>', 'Bind port', '31482')
50
- .requiredOption('--nexus-url <url>', 'NEXUS base URL')
51
+ .option('--nexus-url <url>', 'NEXUS base URL. Default: the current daemon (elanous nexus show).')
51
52
  .option('--public-url <url>', 'Public HTTPS origin used in protected resource metadata')
52
53
  .option('--nexus-token-file <path>', 'Private file holding the mcp-public NEXUS token; use - for stdin')
53
- .action(safeAction(async (opts: { host: string; port: string; nexusUrl: string; publicUrl?: string; nexusTokenFile?: string }) => {
54
+ .action(safeAction(async (opts: { host: string; port: string; nexusUrl?: string; publicUrl?: string; nexusTokenFile?: string }) => {
54
55
  const port = Number(opts.port);
55
56
  if (!Number.isSafeInteger(port) || port < 1 || port > 65535) throw new Error('invalid gateway port');
57
+ // 데몬 주소는 짐작하지 않는다 — 통합관제(daemon-endpoint)에 묻고, 모르면 멈춘다(대표 09-28).
58
+ const nexusUrl = opts.nexusUrl ?? resolveDaemonEndpoint()?.baseUrl;
59
+ if (!nexusUrl) throw new Error('daemon address unknown — pass --nexus-url or start the daemon (elanous nexus show)');
56
60
  if (!opts.publicUrl) throw new Error('--public-url <url> required for protected-resource metadata');
57
61
  if (!opts.nexusTokenFile) throw new Error('--nexus-token-file <path|-> required (no token argv)');
58
62
  if (opts.nexusTokenFile === '-' && process.stdin.isTTY) throw new Error('pipe the nexus token on stdin');
@@ -66,7 +70,7 @@ export function registerMcpGatewayCommands(mcp: Command): void {
66
70
  if (!stat.isFile() || (stat.mode & 0o077) !== 0) throw new Error('PAT store must be a private regular file (0600)');
67
71
  } finally { closeSync(fd); }
68
72
  listMcpPats(patRoot);
69
- const server = startMcpGateway({ host: opts.host, port, nexusUrl: opts.nexusUrl, nexusToken, patRoot, publicUrl: opts.publicUrl });
73
+ const server = startMcpGateway({ host: opts.host, port, nexusUrl, nexusToken, patRoot, publicUrl: opts.publicUrl });
70
74
  process.stderr.write(`MCP gateway listening on ${server.hostname}:${server.port}\n`);
71
75
  }));
72
76
 
@@ -5,10 +5,12 @@ import { join } from 'node:path';
5
5
  import { existsSync, readFileSync } from 'node:fs';
6
6
  import { elanousStateRoot } from '../autopilot/state-paths.js';
7
7
  import { userConfigPath } from '../user-config.js';
8
- import { debug } from '../debug/log.js';
8
+ import { debug, redactSecretText } from '../debug/log.js';
9
+ import { addMarket, listMarkets, MarketFetchError, updateMarket } from '../plugins/install/market-fetch.js';
9
10
  import { emitDecision } from '../live/detail-switch.js';
10
11
  import { OFFICIAL_INDEX_KEYS } from '../market/official-keys.js';
11
12
  import { installPlugin, listInstalledPlugins, PluginInstallError, removePlugin, type InstallEvent } from '../plugins/install/plugin-install.js';
13
+ import { credentialStatus, setPluginCredentials } from '../plugins/install/plugin-credentials.js';
12
14
 
13
15
  function keys(): ReadonlyArray<{ keyId: string; publicKey: string }> {
14
16
  const configPath = userConfigPath();
@@ -32,43 +34,99 @@ async function askConsent(capabilities: string[], json: boolean): Promise<boolea
32
34
  }
33
35
  }
34
36
 
35
- function outputEvent(event: InstallEvent, json: boolean): void {
37
+ function outputEvent(event: InstallEvent, json: boolean, nodeCount: { value: number }): void {
36
38
  // Connector field names are useful; values and secret-field metadata are not needed here.
37
39
  const safe = event.event === 'credentials'
38
40
  ? { event: 'credentials', connectors: event.connectors.map(({ id, fields }) => ({ id, fields: fields.map(({ name }) => name) })) }
39
41
  : event;
40
42
  debug.log('plugin.cli', event.event, { event: event.event });
43
+ if (event.event === 'registered') nodeCount.value = event.nodes.length;
41
44
  if (json) stdout.write(JSON.stringify(safe) + '\n');
42
45
  else if (event.event === 'credentials') {
43
46
  if (event.connectors.length) console.log(`Connectors: ${event.connectors.map(connector => connector.id).join(', ')} — configure via elanous connector settings (no secrets requested here)`);
44
- } else if (event.event === 'done') console.log(`Installed ${event.plugin}@${event.version}`);
47
+ } else if (event.event === 'done') console.log(`Installed ${event.plugin}@${event.version} (${nodeCount.value} nodes)`);
45
48
  else if (event.event === 'consent' && event.required) console.log(`Permissions: ${event.capabilities.join(', ')}`);
46
49
  }
47
50
 
51
+ /** Only our own install/market errors carry a cause (fixed wording that may name the user's own spec); URLs and secrets are masked; any other error stays generic. */
52
+ function safeCause(error: unknown): string {
53
+ if (!(error instanceof PluginInstallError || error instanceof MarketFetchError)) return 'unexpected error';
54
+ return redactSecretText(error.message.replace(/https?:\/\/[^\s"'<>]+/gi, '[redacted URL]'));
55
+ }
56
+
48
57
  export function registerPluginCommands(program: Command): void {
49
58
  const plugin = program.command('plugin').description('Install and manage Elanous plugins');
50
59
  plugin.command('add <spec>').description('Install a local path, pinned git plugin or signed market plugin')
51
60
  .option('--yes', 'Accept requested capabilities')
52
61
  .option('--json', 'NDJSON progress events')
53
62
  .option('--allow-unsigned', 'Allow unsigned marketplace indices')
54
- .action(async (spec: string, opts: { yes?: boolean; json?: boolean; allowUnsigned?: boolean }) => {
63
+ .option('--refresh', 'Refresh the signed marketplace index before installing')
64
+ .action(async (spec: string, opts: { yes?: boolean; json?: boolean; allowUnsigned?: boolean; refresh?: boolean }) => {
55
65
  try {
56
66
  const root = elanousStateRoot();
67
+ const nodeCount = { value: 0 };
57
68
  await installPlugin(spec, {
58
69
  root, marketDir: join(root, 'plugins', 'markets'), trustedKeys: keys(),
59
- yes: opts.yes, allowUnsigned: opts.allowUnsigned,
60
- onEvent: event => outputEvent(event, !!opts.json),
70
+ yes: opts.yes, allowUnsigned: opts.allowUnsigned, refresh: opts.refresh,
71
+ onEvent: event => outputEvent(event, !!opts.json, nodeCount),
61
72
  consent: capabilities => askConsent(capabilities, !!opts.json),
62
73
  });
63
74
  emitDecision({ kind: 'ROUTE', what: 'plugin add', reason: 'plugin installed', purpose: 'make plugin available', target: 'plugin host' });
64
75
  } catch (error) {
65
76
  const reason = error instanceof PluginInstallError ? error.reason : 'io';
66
- // Installer errors may embed an arbitrary source path or git URL; never echo them or config secrets.
67
77
  const detail = reason === 'consent-denied' ? 'plugin capabilities require consent' : `plugin installation ${reason} error`;
68
- debug.log('plugin.cli', 'failed', { reason }, { level: 'error' });
78
+ const cause = safeCause(error);
79
+ debug.log('plugin.cli', 'failed', { reason, cause }, { level: 'error' });
69
80
  emitDecision({ kind: 'ESCALATE', what: 'plugin add', reason, purpose: 'report installation failure', target: 'user' });
70
- if (opts.json) stdout.write(JSON.stringify({ event: 'failed', reason, detail }) + '\n');
71
- else console.error(`plugin add failed: ${detail}`);
81
+ if (opts.json) stdout.write(JSON.stringify({ event: 'failed', reason, detail, ...(reason === 'consent-denied' ? {} : { cause }) }) + '\n');
82
+ else console.error(`plugin add failed: ${detail}${reason === 'consent-denied' ? '' : `: ${cause}`}`);
83
+ process.exitCode = 1;
84
+ }
85
+ });
86
+ const market = plugin.command('market').description('Manage signed plugin marketplaces');
87
+ market.command('add <name> <url>').description('Register a marketplace URL')
88
+ .action((name: string, url: string) => {
89
+ try {
90
+ const added = addMarket(name, url);
91
+ debug.log('plugin.cli', 'market.add', { name: added.name });
92
+ console.log(`Added market ${added.name} (${added.url})`);
93
+ } catch (error) {
94
+ console.error(`plugin market add failed: ${safeCause(error)}`);
95
+ process.exitCode = 1;
96
+ }
97
+ });
98
+ market.command('list').description('List configured marketplaces').option('--json', 'JSON array')
99
+ .action((opts: { json?: boolean }) => {
100
+ try {
101
+ const markets = listMarkets();
102
+ debug.log('plugin.cli', 'market.list', { count: markets.length });
103
+ if (opts.json) stdout.write(JSON.stringify(markets) + '\n');
104
+ else for (const entry of markets) console.log(`${entry.name} (${entry.url})`);
105
+ } catch (error) {
106
+ if (opts.json) stdout.write(JSON.stringify({ event: 'failed', cause: safeCause(error) }) + '\n');
107
+ else console.error(`plugin market list failed: ${safeCause(error)}`);
108
+ process.exitCode = 1;
109
+ }
110
+ });
111
+ market.command('update [name]').description('Refresh signed marketplace indices')
112
+ .action(async (name?: string) => {
113
+ try {
114
+ const markets = listMarkets();
115
+ const selected = name ? markets.filter(entry => entry.name === name) : markets;
116
+ if (!selected.length) throw new Error('market not configured');
117
+ const root = elanousStateRoot();
118
+ for (const entry of selected) {
119
+ try {
120
+ const result = await updateMarket(entry.name, { root, marketDir: join(root, 'plugins', 'markets') });
121
+ debug.log('plugin.cli', 'market.update', { name: result.market.name, sequence: result.index.sequence });
122
+ console.log(`Updated market ${result.market.name} (sequence ${result.index.sequence})`);
123
+ } catch (error) {
124
+ console.error(`plugin market update ${entry.name} failed: ${safeCause(error)}`);
125
+ process.exitCode = 1;
126
+ }
127
+ }
128
+ } catch (error) {
129
+ console.error(`plugin market update failed: ${safeCause(error)}`);
72
130
  process.exitCode = 1;
73
131
  }
74
132
  });
@@ -83,12 +141,52 @@ export function registerPluginCommands(program: Command): void {
83
141
  const installed = listInstalledPlugins(root).filter(item => registered.has(`${item.market}/${item.name}/${item.version}`));
84
142
  debug.log('plugin.cli', 'list', { count: installed.length });
85
143
  if (opts.json) stdout.write(JSON.stringify(installed) + '\n');
86
- else for (const item of installed) console.log(`${item.name}@${item.version} (${item.market})`);
144
+ else for (const item of installed) console.log(`${item.name}@${item.version} (${item.market})${item.installedAt ? ` — installed ${item.installedAt}` : ''}`);
87
145
  } catch {
88
146
  console.error('plugin list failed: invalid plugin ledger');
89
147
  process.exitCode = 1;
90
148
  }
91
149
  });
150
+ plugin.command('credentials <plugin>').description('Show or update installed plugin credential status')
151
+ .option('--set <NAME=VALUE>', 'Set a declared field', (value: string, values: string[]) => [...values, value], [] as string[])
152
+ .option('--unset <NAME>', 'Remove a declared field', (value: string, values: string[]) => [...values, value], [] as string[])
153
+ .option('--stdin <NAME>', 'Read one credential value from standard input')
154
+ .option('--json', 'JSON status')
155
+ .action(async (name: string, opts: { set: string[]; unset: string[]; stdin?: string; json?: boolean }) => {
156
+ try {
157
+ const root = elanousStateRoot();
158
+ const fields: Record<string, string | null> = Object.create(null);
159
+ for (const assignment of opts.set) {
160
+ const equal = assignment.indexOf('=');
161
+ if (equal < 1) throw new Error('invalid credential assignment');
162
+ fields[assignment.slice(0, equal)] = assignment.slice(equal + 1);
163
+ }
164
+ for (const field of opts.unset) fields[field] = null;
165
+ if (opts.stdin !== undefined) {
166
+ let input = '';
167
+ for await (const chunk of stdin) {
168
+ input += chunk.toString();
169
+ if (input.includes('\n')) break;
170
+ }
171
+ if (!input) throw new Error('credential stdin is empty');
172
+ fields[opts.stdin] = input.split(/\r?\n/, 1)[0]!;
173
+ }
174
+ if (Object.keys(fields).length > 0) {
175
+ setPluginCredentials(name, fields, root);
176
+ debug.log('plugin.credentials', 'set', { plugin: name, fields: Object.keys(fields), count: Object.keys(fields).length });
177
+ }
178
+ const status = credentialStatus(name, root);
179
+ if (opts.json) stdout.write(JSON.stringify(status) + '\n');
180
+ else for (const field of status.fields) console.log(`${field.name}\t${field.env}\t${field.set ? 'set' : 'unset'}`);
181
+ // The web app's PUT reloads MCP clients itself; a CLI write reaches an already-running daemon only after a reload.
182
+ // stderr in both modes, so `--json` stdout stays one JSON line.
183
+ if (Object.keys(fields).length > 0) console.error('A running daemon applies this to plugin MCP servers after `elanous mcp reload` (the web app does it for you).');
184
+ } catch {
185
+ if (opts.json) stdout.write(JSON.stringify({ error: 'plugin credentials failed' }) + '\n');
186
+ else console.error('plugin credentials failed');
187
+ process.exitCode = 1;
188
+ }
189
+ });
92
190
  plugin.command('remove <name>').description('Remove installed plugin versions')
93
191
  .action((name: string) => {
94
192
  try {
@@ -1,5 +1,6 @@
1
1
  const SELF_IMPL_PREFIX = 'self-impl/';
2
2
  const LINEAGE_HASH_SUFFIX = /-[0-9a-f]{7,8}$/i;
3
+ const RUN_SUFFIX = /-r[a-z0-9]{6}$/i;
3
4
  /**
4
5
  * Formal new-generation segment after `self-impl/`: dash-delimited `-goalid-<id>-`.
5
6
  * ⛔ A substring like `mygoalid-foo` is not that segment — it has no leading `-`/`^` boundary.
@@ -9,7 +10,7 @@ const GOAL_ID_SEGMENT = /(?:^|-)goalid-([^/-]+)-/;
9
10
  /** Lineage slug of a harness branch: `self-impl/<slug>-<7–8 hex>`, or null when the name is not that shape. */
10
11
  export function branchLineageSlug(branch: string): string | null {
11
12
  if (!branch.startsWith(SELF_IMPL_PREFIX)) return null;
12
- const rest = branch.slice(SELF_IMPL_PREFIX.length);
13
+ const rest = branch.slice(SELF_IMPL_PREFIX.length).replace(RUN_SUFFIX, '');
13
14
  const hash = LINEAGE_HASH_SUFFIX.exec(rest);
14
15
  return hash ? rest.slice(0, rest.length - hash[0].length) : rest;
15
16
  }
@@ -126,6 +126,8 @@ export interface PwaStartOpts {
126
126
  * MCP-client boot is skipped (PR1 2026-05-13). Default undefined =
127
127
  * leave the decision to the daemon-side `mcp.enabled` config. */
128
128
  mcpEnabled?: boolean;
129
+ /** Forward an explicit --no-auth opt-out to the detached Nexus child. */
130
+ noAuth?: boolean;
129
131
  out?: { log: (s: string) => void; error: (s: string) => void };
130
132
  // ─── Test seams ─────────────────────────────────────────────
131
133
  bgLaunchFn?: (opts: {
@@ -237,6 +239,7 @@ function collectForwardArgs(opts: PwaStartOpts, httpPort: number): string[] {
237
239
  // those gates only matter on the parent (build runs before fork).
238
240
  if (opts.watch && (opts.mode ?? 'static') === 'static') args.push('--watch');
239
241
  if (opts.mcpEnabled === false) args.push('--no-mcp');
242
+ if (opts.noAuth === true) args.push('--no-auth');
240
243
  return args;
241
244
  }
242
245