elanous 0.2.6 → 0.2.7

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 (450) hide show
  1. package/.env.example +10 -7
  2. package/README.md +4 -0
  3. package/apps/pwa/out/404/index.html +1 -1
  4. package/apps/pwa/out/404.html +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/{3995-bee512a7063036ce.js → 2375-cb6debea56cce4fb.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +25 -0
  16. package/apps/pwa/out/_next/static/chunks/{8d0d9faf-97887ff99713b84e.js → 2ea96958-e8c7e112de50b067.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/{5329.8b7e4bca684b6eec.js → 3403.4bc97b4066945a94.js} +2 -2
  18. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/{7633.0654fe6ea259c1ee.js → 4323.c5dfb6f503304775.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/4836-2eba6079f31b88d4.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/{2984-977cfa339fcd65a5.js → 5080-1613752891fcadb4.js} +3 -3
  24. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/{763-7d0e1913b5074e98.js → 5791-bb82743fa6299562.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/{4240-7153881b0b377306.js → 6062-73e68343a9814f6a.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/{91ff6dda-3b33d42d20657429.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8519-f6b5d8a5833a7603.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8894-e92491a164192cc1.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9740-67051141115ce775.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d5ebde04844689e6.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-10ed24f413ed66e9.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/board/page-211810b6c93ab66a.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/botlab/page-624f7a2f89b60c8f.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/bots/page-5a5fb385a4df0ffe.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/chat/page-80497e93a878ddc5.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/consult/page-7457f52683562161.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/control/page-cee86394de407aa6.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/design-check/page-4ff8edab44196b8c.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/editor/page-92e4f44428d5e290.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/exec/page-b7dcf5d48a854b76.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/field/page-51480757e7d68b92.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/intake/page-2a05dfe80c8bdf16.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/layout-d9979633aa82f8e6.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/live/page-30533117beb53625.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/market/page-f7857826c2f97ad7.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-07267f1391b96aba.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/morning/page-55d50e9b0006a91d.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/not-found-9699b1786be6171d.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/observatory/page-43dd772aee8af75f.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/page-a7e37af7d0399cb8.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/reflection/page-973ab7af59c718b7.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a9f8d3865b91e436.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/sessions/page-15e5a15db84c56aa.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a2e572e4511f2a16.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/settings/page-dd107bf1c7d52a90.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d42b99cdfb9f27c6.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/setup/page-2764ff40dad5a995.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/share/page-c5f1152db116ff96.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/showroom/page-f933bca81ea38a6f.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/tasks/page-c1c95739c0bff3b8.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/term/page-a7aaffcf771662d8.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/trace/page-71d18eec33b9c667.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/vault/page-56adbe1a1f1379bd.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-2c4e59079ee357ac.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a81f113f3ef89d72.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5d422f38c052967e.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-fc27b00e20733146.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/{webpack-37b8bf78a3c6eec5.js → webpack-e5bea36f183b25e6.js} +1 -1
  95. package/apps/pwa/out/_next/static/css/f537bdb19c69306a.css +1 -0
  96. package/apps/pwa/out/_next/static/keajW7DDyghEs1y8oJe6T/_buildManifest.js +1 -0
  97. package/apps/pwa/out/approvals/index.html +1 -1
  98. package/apps/pwa/out/approvals/index.txt +22 -22
  99. package/apps/pwa/out/autopilot/index.html +1 -1
  100. package/apps/pwa/out/autopilot/index.txt +22 -22
  101. package/apps/pwa/out/board/index.html +1 -1
  102. package/apps/pwa/out/board/index.txt +22 -22
  103. package/apps/pwa/out/botlab/index.html +1 -1
  104. package/apps/pwa/out/botlab/index.txt +21 -21
  105. package/apps/pwa/out/bots/index.html +1 -1
  106. package/apps/pwa/out/bots/index.txt +21 -21
  107. package/apps/pwa/out/chat/index.html +1 -1
  108. package/apps/pwa/out/chat/index.txt +21 -21
  109. package/apps/pwa/out/consult/index.html +1 -0
  110. package/apps/pwa/out/consult/index.txt +32 -0
  111. package/apps/pwa/out/control/index.html +1 -1
  112. package/apps/pwa/out/control/index.txt +22 -22
  113. package/apps/pwa/out/design-check/index.html +1 -1
  114. package/apps/pwa/out/design-check/index.txt +22 -22
  115. package/apps/pwa/out/editor/index.html +1 -1
  116. package/apps/pwa/out/editor/index.txt +22 -22
  117. package/apps/pwa/out/exec/index.html +1 -0
  118. package/apps/pwa/out/exec/index.txt +32 -0
  119. package/apps/pwa/out/field/index.html +1 -0
  120. package/apps/pwa/out/field/index.txt +32 -0
  121. package/apps/pwa/out/healthz +1 -1
  122. package/apps/pwa/out/index.html +1 -1
  123. package/apps/pwa/out/index.txt +21 -21
  124. package/apps/pwa/out/intake/index.html +1 -1
  125. package/apps/pwa/out/intake/index.txt +22 -22
  126. package/apps/pwa/out/live/index.html +1 -1
  127. package/apps/pwa/out/live/index.txt +22 -22
  128. package/apps/pwa/out/market/index.html +1 -1
  129. package/apps/pwa/out/market/index.txt +21 -21
  130. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  131. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  132. package/apps/pwa/out/morning/index.html +1 -1
  133. package/apps/pwa/out/morning/index.txt +22 -22
  134. package/apps/pwa/out/observatory/index.html +1 -1
  135. package/apps/pwa/out/observatory/index.txt +22 -22
  136. package/apps/pwa/out/reflection/index.html +1 -1
  137. package/apps/pwa/out/reflection/index.txt +22 -22
  138. package/apps/pwa/out/scheduler/index.html +1 -1
  139. package/apps/pwa/out/scheduler/index.txt +22 -22
  140. package/apps/pwa/out/sessions/index.html +1 -1
  141. package/apps/pwa/out/sessions/index.txt +21 -21
  142. package/apps/pwa/out/settings/devices/index.html +1 -1
  143. package/apps/pwa/out/settings/devices/index.txt +22 -22
  144. package/apps/pwa/out/settings/index.html +1 -1
  145. package/apps/pwa/out/settings/index.txt +22 -22
  146. package/apps/pwa/out/setup/done/index.html +1 -1
  147. package/apps/pwa/out/setup/done/index.txt +22 -22
  148. package/apps/pwa/out/setup/index.html +1 -1
  149. package/apps/pwa/out/setup/index.txt +22 -22
  150. package/apps/pwa/out/share/index.html +1 -1
  151. package/apps/pwa/out/share/index.txt +22 -22
  152. package/apps/pwa/out/showroom/index.html +1 -1
  153. package/apps/pwa/out/showroom/index.txt +21 -21
  154. package/apps/pwa/out/tasks/index.html +1 -1
  155. package/apps/pwa/out/tasks/index.txt +22 -22
  156. package/apps/pwa/out/term/index.html +1 -1
  157. package/apps/pwa/out/term/index.txt +22 -22
  158. package/apps/pwa/out/trace/index.html +1 -1
  159. package/apps/pwa/out/trace/index.txt +22 -22
  160. package/apps/pwa/out/vault/index.html +1 -1
  161. package/apps/pwa/out/vault/index.txt +22 -22
  162. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  163. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  164. package/apps/pwa/out/workflows/index.html +1 -1
  165. package/apps/pwa/out/workflows/index.txt +22 -22
  166. package/apps/pwa/out/workspace/index.html +1 -1
  167. package/apps/pwa/out/workspace/index.txt +22 -22
  168. package/apps/pwa/out/worktrees/index.html +1 -1
  169. package/apps/pwa/out/worktrees/index.txt +22 -22
  170. package/bin/elanous.cjs +107 -0
  171. package/graphs/field/field-feed.yaml +24 -0
  172. package/graphs/field/recipes.yaml +12 -0
  173. package/graphs/landing/landing-heal.yaml +3 -1
  174. package/graphs/landing/recipes.yaml +3 -0
  175. package/graphs/release/recipes.yaml +10 -0
  176. package/graphs/release/release-loop.yaml +10 -3
  177. package/package.json +5 -5
  178. package/plugins/card-followup/README.md +13 -4
  179. package/plugins/card-followup/graphs/card-followup.yaml +3 -1
  180. package/plugins/card-followup/graphs/recipes.yaml +5 -1
  181. package/plugins/card-followup/graphs/run-step.ts +122 -18
  182. package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
  183. package/plugins/card-followup/plugin.json +1 -1
  184. package/plugins/doc-draft/README.md +13 -0
  185. package/plugins/doc-draft/examples/brief-sample.json +8 -0
  186. package/plugins/doc-draft/graphs/doc-draft.yaml +14 -0
  187. package/plugins/doc-draft/graphs/recipes.yaml +9 -0
  188. package/plugins/doc-draft/graphs/run-step.ts +113 -0
  189. package/plugins/doc-draft/plugin.json +24 -0
  190. package/plugins/geo-check/README.md +3 -1
  191. package/plugins/geo-check/graphs/run-step.ts +24 -3
  192. package/plugins/geo-check/plugin.json +1 -1
  193. package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
  194. package/plugins/job-coach/README.md +1 -1
  195. package/plugins/job-coach/graphs/run-step.ts +2 -2
  196. package/plugins/job-coach/plugin.json +1 -1
  197. package/plugins/job-coach/plugin.ts +1 -1
  198. package/scripts/affinity/endcard.sh +3 -2
  199. package/scripts/affinity/reframe.sh +3 -1
  200. package/scripts/ci-public-export-leak-gate.ts +48 -0
  201. package/scripts/codex-quota-alert.ts +122 -258
  202. package/scripts/coord-tracks.json +4 -4
  203. package/scripts/field-feed/deliver-node.ts +52 -0
  204. package/scripts/field-feed/feed-node.ts +67 -0
  205. package/scripts/field-feed/lib.ts +162 -0
  206. package/scripts/field-feed/reel-node.ts +24 -0
  207. package/scripts/field-feed/start.ts +62 -0
  208. package/scripts/fixtures/vflow-sample.ndjson +6 -0
  209. package/scripts/install.sh +92 -13
  210. package/scripts/landing-heal/clean-checkout.ts +114 -0
  211. package/scripts/landing-heal/run-mission.ts +38 -3
  212. package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
  213. package/scripts/public-export.ts +8 -4
  214. package/scripts/release-loop/baseline-from-logs.ts +92 -0
  215. package/scripts/release-loop/env-known-failures.ts +31 -0
  216. package/scripts/release-loop/gate-node.ts +81 -19
  217. package/scripts/release-loop/known-issues-node.ts +159 -0
  218. package/scripts/release-loop/npm-publish-node.ts +118 -0
  219. package/scripts/release-loop/ops-upgrade-node.ts +87 -0
  220. package/scripts/release-loop/release-readiness.ts +58 -0
  221. package/scripts/release-loop/unattended-release.ts +93 -0
  222. package/scripts/ux/measure-first-text.ts +102 -0
  223. package/scripts/vflow-collect.ts +20 -7
  224. package/scripts/vflow-search.ts +73 -0
  225. package/scripts/video-free-line-test-walker.ts +36 -0
  226. package/scripts/video-pipeline.ts +2 -2
  227. package/skills/explainer-video/SKILL.md +12 -9
  228. package/skills/explainer-video/engine/build.mjs +1 -1
  229. package/skills/explainer-video/engine/reel.mjs +138 -31
  230. package/skills/explainer-video/engine/reel.sh +2 -1
  231. package/skills/explainer-video/examples/elanous-0.2.5/script.json +1 -1
  232. package/skills/grill-me/SKILL.md +1 -1
  233. package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +1 -1
  234. package/src/a2a/server.ts +2 -0
  235. package/src/acp/elanous-aware-peers.ts +30 -0
  236. package/src/acp/server.ts +91 -15
  237. package/src/agent/monad-agent-turn.ts +25 -6
  238. package/src/agent/shared-app-tools.ts +5 -3
  239. package/src/agent-substrate/review-intent.ts +6 -2
  240. package/src/agent-substrate/shard-boundary-intent.ts +70 -0
  241. package/src/auth/github-app-token.ts +4 -1
  242. package/src/autopilot/track-agent-shadow.ts +151 -0
  243. package/src/autopilot/track-agent.ts +23 -3
  244. package/src/boot/acp-server-help.ts +16 -0
  245. package/src/boot/daemon-prompt-turn.ts +31 -10
  246. package/src/boot/daemon-tools/index.ts +7 -1
  247. package/src/budget/codex-credit-pace.ts +84 -0
  248. package/src/budget/codex-credit-plan.ts +154 -0
  249. package/src/budget/codex-reset-credit-state.ts +2 -0
  250. package/src/chat/chat-cli.ts +4 -1
  251. package/src/chat/short-question.ts +40 -0
  252. package/src/cli/agent-cli.ts +24 -15
  253. package/src/cli/ask-bare.ts +42 -0
  254. package/src/cli/cli-user-error.ts +25 -0
  255. package/src/cli/codex-credit-plan-view.ts +25 -0
  256. package/src/cli/dependence-gauge.ts +237 -0
  257. package/src/cli/doctor-cli.ts +5 -0
  258. package/src/cli/doctor-modes.ts +48 -0
  259. package/src/cli/intake-cli.ts +12 -2
  260. package/src/cli/intake-source-cli.ts +58 -0
  261. package/src/cli/machine-cli.ts +23 -0
  262. package/src/cli/msg-cli.ts +137 -0
  263. package/src/cli/plugin-cli.ts +25 -0
  264. package/src/cli/pr-cli.ts +36 -0
  265. package/src/cli/provider-cli.ts +5 -0
  266. package/src/cli/release-cli.ts +42 -1
  267. package/src/cli/release-run-if-ready.ts +48 -0
  268. package/src/cli/setup-cli.ts +27 -6
  269. package/src/cli/setup-recommend.ts +8 -0
  270. package/src/cli/usage-cli.ts +11 -1
  271. package/src/codex/setup.ts +27 -8
  272. package/src/dashboard/index.ts +1 -1
  273. package/src/discord.ts +56 -6
  274. package/src/domains/core-tools.ts +37 -14
  275. package/src/domains/outbound-alert.ts +50 -11
  276. package/src/exec-requests/planner.ts +140 -0
  277. package/src/exec-requests/runner.ts +338 -0
  278. package/src/exec-requests/store.ts +76 -0
  279. package/src/field/field-media.ts +8 -3
  280. package/src/field/field-reel.ts +196 -0
  281. package/src/grok/credential.ts +7 -5
  282. package/src/harness/aside-run-status.ts +33 -0
  283. package/src/harness/browser-verify.ts +48 -17
  284. package/src/harness/harness-cli-command.ts +25 -3
  285. package/src/harness/image-load-state.ts +21 -0
  286. package/src/harness/process-garbage.ts +45 -0
  287. package/src/index.ts +86 -14
  288. package/src/instance/leader.ts +13 -1
  289. package/src/intake-plane/collect-github.ts +5 -3
  290. package/src/intake-plane/collect-telegram-saved.ts +15 -3
  291. package/src/intake-plane/discord-seat-work.ts +56 -0
  292. package/src/intake-plane/intake-sources.ts +202 -0
  293. package/src/intake-plane/items.ts +28 -12
  294. package/src/intake-plane/seat-doc-route.ts +207 -0
  295. package/src/intake-plane/submit-intake-work.ts +9 -1
  296. package/src/intake-plane/telegram-seat-work.ts +72 -0
  297. package/src/llm/model-defaults.ts +24 -1
  298. package/src/llm.ts +5 -4
  299. package/src/machines/machine-ledger.ts +127 -0
  300. package/src/market/pack-internal-refs.ts +28 -0
  301. package/src/market/publish.ts +31 -4
  302. package/src/maturity/feature-maturity.ts +65 -0
  303. package/src/model-tier/turn-tier.ts +24 -0
  304. package/src/msg/msg-store.ts +149 -0
  305. package/src/native-tool-catalog.ts +2 -1
  306. package/src/nexus/api/chat-fast-path-config.ts +28 -0
  307. package/src/nexus/api/consult-requests.ts +117 -0
  308. package/src/nexus/api/discord-trigger-bot.ts +3 -0
  309. package/src/nexus/api/exec-requests.ts +51 -0
  310. package/src/nexus/api/field-uploads.ts +40 -3
  311. package/src/nexus/api/graph-approvals-feed.ts +140 -0
  312. package/src/nexus/api/graph-approvals.ts +71 -4
  313. package/src/nexus/api/http-server.ts +61 -7
  314. package/src/nexus/api/seat-requests.ts +254 -0
  315. package/src/nexus/api/trace-run-scan.ts +37 -0
  316. package/src/nexus/api/trace.ts +19 -3
  317. package/src/nexus/index.ts +3 -0
  318. package/src/nexus/setup-status.ts +4 -5
  319. package/src/notify/llm-budget-alert.ts +16 -10
  320. package/src/oauth/codex-account-rotation.ts +26 -4
  321. package/src/oauth/codex-account-store.ts +20 -2
  322. package/src/oauth/codex-browser-login.ts +173 -0
  323. package/src/oauth/codex.ts +4 -1
  324. package/src/oauth/fallback-chain.ts +5 -5
  325. package/src/oauth/store.ts +42 -5
  326. package/src/onboarding/continue-here.ts +29 -0
  327. package/src/onboarding/entry-hints.ts +8 -0
  328. package/src/onboarding/full-screen-io.ts +35 -3
  329. package/src/onboarding/help/skills.md +1 -1
  330. package/src/onboarding/non-interactive.ts +1 -0
  331. package/src/onboarding/subscription-hint.ts +27 -0
  332. package/src/onboarding/web-first.ts +83 -0
  333. package/src/onboarding.ts +230 -23
  334. package/src/plugins/maker/node-install.ts +92 -0
  335. package/src/plugins/maker/node-maker.ts +139 -0
  336. package/src/plugins/maker/plugin-maker.ts +19 -7
  337. package/src/policy/llm-policy.ts +268 -0
  338. package/src/prompt-library/session-guidance.ts +7 -1
  339. package/src/prompt-library/universal-preamble.ts +2 -2
  340. package/src/relay/relay-cli.ts +20 -0
  341. package/src/relay/relay-server.ts +220 -0
  342. package/src/release-loop/checklist.ts +9 -8
  343. package/src/self-dev/decompose.ts +1 -0
  344. package/src/self-dev/dev-pipeline.ts +6 -3
  345. package/src/self-dev/run-record-mirror.ts +44 -0
  346. package/src/self-dev/run-store.ts +12 -3
  347. package/src/self-implement/harness-policy-gates.ts +4 -1
  348. package/src/self-implement/report-origin.ts +12 -1
  349. package/src/session/chat.ts +17 -5
  350. package/src/skills/shared-agent-skill-roots.ts +25 -0
  351. package/src/steward/failure-streak.ts +78 -0
  352. package/src/steward/steward-cards.ts +24 -0
  353. package/src/steward/triage-budget.ts +127 -0
  354. package/src/steward/triage-plan.ts +75 -0
  355. package/src/steward/triage.ts +244 -42
  356. package/src/task-cards/card-store.ts +1 -1
  357. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
  358. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +68 -0
  359. package/src/task-orchestrator/surfaces/self-implement-pod.ts +78 -4
  360. package/src/telegram.ts +38 -2
  361. package/src/tool-runtime/elanous-skills-list-runtime.ts +3 -3
  362. package/src/tool-runtime/self-cognition-runtimes.ts +160 -2
  363. package/src/user-config.ts +80 -4
  364. package/src/version/packed-revision.json +1 -1
  365. package/src/video-pipeline/recipes/character.ts +42 -5
  366. package/src/video-pipeline/recipes/ffmpeg.ts +1 -1
  367. package/src/video-pipeline/recipes/free-line.ts +16 -3
  368. package/src/video-pipeline/recipes/remote.ts +7 -1
  369. package/src/video-pipeline/vflow-reference.ts +109 -0
  370. package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +0 -1
  373. package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/3190-e9282753e7ebde85.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +0 -25
  379. package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +0 -1
  388. package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +0 -1
  403. package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +0 -1
  404. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +0 -1
  448. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
  449. package/apps/pwa/out/_next/static/qMI0jnlZaBjjm4aIDYNGM/_buildManifest.js +0 -1
  450. /package/apps/pwa/out/_next/static/{qMI0jnlZaBjjm4aIDYNGM → keajW7DDyghEs1y8oJe6T}/_ssgManifest.js +0 -0
package/src/onboarding.ts CHANGED
@@ -25,6 +25,9 @@
25
25
  // back-fill merge — so tests don't need to stub config paths either.
26
26
 
27
27
  import { existsSync, statSync } from 'node:fs';
28
+ import { delimiter, join } from 'node:path';
29
+ import { spawnSync } from 'node:child_process';
30
+ import { isHeadlessEnv, spawnCodexLogin, type CodexLoginResult } from './acp/codex-auth.js';
28
31
  import { getRunContext, type RunContext } from './agent/run-context.js';
29
32
  import * as readline from 'node:readline/promises';
30
33
  import { stdin as input, stdout as output } from 'node:process';
@@ -40,6 +43,7 @@ import {
40
43
  import { CODEX_DEFAULT_MODEL } from './llm.js';
41
44
  import { suggestSetupModel } from './model-tier/index.js';
42
45
  import { loginWithCodex, CODEX_DEVICE_LOGIN_URL } from './oauth/codex.js';
46
+ import { loginWithCodexBrowser } from './oauth/codex-browser-login.js';
43
47
  import { loadTokens } from './oauth/store.js';
44
48
  import { TelegramBot } from './telegram.js';
45
49
  import { DiscordBot } from './discord.js';
@@ -54,6 +58,10 @@ import type {
54
58
  import { showStepOr, chooseFrom } from './onboarding/io-extended.js';
55
59
  import { askWithHelp } from './onboarding/wire-helpers.js';
56
60
  import { stepTransition } from './onboarding/transition.js';
61
+ import { unattendedSetupHint } from './onboarding/entry-hints.js';
62
+ import { continueHereLines } from './onboarding/continue-here.js';
63
+ import type { NexusShowResult } from './cli/nexus-show.js';
64
+ import type { IssuedTempToken } from './auth/temp-tokens.js';
57
65
  import {
58
66
  askValidated,
59
67
  validateIntList,
@@ -151,6 +159,8 @@ export interface WizardIO {
151
159
  * and `ask` otherwise. */
152
160
  askSecret?(prompt: string, field?: string): Promise<string>;
153
161
  print(text: string): void;
162
+ /** Synthetic, unattended IO (never issue or display a connection token). */
163
+ nonInteractive?: boolean;
154
164
  /** Called once after a successful config save. */
155
165
  complete?(): void;
156
166
  close(): void;
@@ -325,6 +335,104 @@ export interface LocalProbeDeps {
325
335
 
326
336
  export interface GrokOnboardingDeps {
327
337
  resolveCredential?: () => GrokCredential | null;
338
+ /** Subscription detection for step 1 (OB1) — tests inject; default = `detectProviders`. */
339
+ detectProviders?: () => Promise<import('./llm/provider-detect.js').DetectedProvider[]>;
340
+ /** OB1 — does the codex CLI already hold a usable login (~/.codex/auth.json)? Tests inject. */
341
+ hasCodexCliLogin?: () => boolean;
342
+ loadCodexTokens?: typeof loadTokens;
343
+ /** Browser-first OAuth test seams. Defaults use the local environment. */
344
+ codexEnv?: NodeJS.ProcessEnv;
345
+ isHeadless?: (env: NodeJS.ProcessEnv) => boolean;
346
+ loginWithCodexBrowser?: typeof loginWithCodexBrowser;
347
+ spawnCodexLogin?: (opts: { deviceAuth: false; codexPath: string }) => Promise<CodexLoginResult>;
348
+ loginWithCodex?: typeof loginWithCodex;
349
+ /** Execute local browser/clipboard commands without a shell. Returns false when unavailable. */
350
+ runAuthCommand?: (command: string, args: string[], input?: string) => boolean;
351
+ authPlatform?: NodeJS.Platform;
352
+ /** OSC52 fallback, when clipboard commands are unavailable. */
353
+ writeAuthTerminal?: (data: string) => boolean;
354
+ }
355
+
356
+ function pathCodexBinary(env: NodeJS.ProcessEnv): string | null {
357
+ const binary = process.platform === 'win32' ? 'codex.exe' : 'codex';
358
+ for (const dir of (env.PATH ?? '').split(delimiter)) {
359
+ if (!dir) continue;
360
+ const candidate = join(dir, binary);
361
+ try { if (existsSync(candidate) && statSync(candidate).isFile()) return candidate; }
362
+ catch { /* skip inaccessible PATH entries */ }
363
+ }
364
+ return null;
365
+ }
366
+
367
+ function runAuthCommand(command: string, args: string[], input?: string): boolean {
368
+ try {
369
+ const result = spawnSync(command, args, {
370
+ input,
371
+ stdio: [input === undefined ? 'ignore' : 'pipe', 'ignore', 'ignore'],
372
+ timeout: 3_000,
373
+ });
374
+ return result.status === 0 && !result.error;
375
+ } catch { return false; }
376
+ }
377
+
378
+ function assistCodexDeviceLogin(
379
+ io: WizardIO,
380
+ url: string,
381
+ code: string,
382
+ deps: GrokOnboardingDeps,
383
+ ): void {
384
+ const env = deps.codexEnv ?? process.env;
385
+ const platform = deps.authPlatform ?? process.platform;
386
+ const execute = deps.runAuthCommand ?? runAuthCommand;
387
+ const local = !(deps.isHeadless ?? isHeadlessEnv)(env);
388
+ if (local) {
389
+ try {
390
+ if (platform === 'darwin') execute('open', [url]);
391
+ else if (platform === 'linux' || platform === 'freebsd' || platform === 'openbsd') execute('xdg-open', [url]);
392
+ } catch { /* browser is optional; URL remains visible */ }
393
+ }
394
+ let copied = false;
395
+ for (const [command, args] of platform === 'darwin'
396
+ ? [['pbcopy', []]] as Array<[string, string[]]>
397
+ : platform === 'linux' || platform === 'freebsd' || platform === 'openbsd'
398
+ ? [['wl-copy', []], ['xclip', ['-selection', 'clipboard']]] as Array<[string, string[]]>
399
+ : []) {
400
+ try { if (execute(command, args, code)) { copied = true; break; } }
401
+ catch { /* try the next clipboard */ }
402
+ }
403
+ if (!copied) {
404
+ try {
405
+ const write = deps.writeAuthTerminal ?? ((data: string) => {
406
+ if (!output.isTTY) return false;
407
+ output.write(data);
408
+ return true;
409
+ });
410
+ copied = write(`\x1b]52;c;${Buffer.from(code).toString('base64')}\x07`);
411
+ } catch { /* terminals without OSC52 still show the code */ }
412
+ }
413
+ if (copied) io.print(' 코드를 복사했습니다.');
414
+ if (local) notifyDeviceCode(code, copied, platform, execute);
415
+ }
416
+
417
+ /** OB5c — on a desktop, raise an OS notification with the code so the person can paste it without
418
+ * hunting for the terminal. Missing notifier (or headless) is skipped silently; the code never goes to logs. */
419
+ function notifyDeviceCode(
420
+ code: string,
421
+ copied: boolean,
422
+ platform: NodeJS.Platform,
423
+ execute: (command: string, args: string[], input?: string) => boolean,
424
+ ): void {
425
+ const message = copied ? `인증 코드 ${code} — 클립보드에 복사됨 · 붙여넣기만` : `인증 코드 ${code}`;
426
+ let notified = false;
427
+ try {
428
+ if (platform === 'darwin') {
429
+ const quoted = (s: string) => `"${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
430
+ notified = execute('osascript', ['-e', `display notification ${quoted(message)} with title "Elanous"`]);
431
+ } else if (platform === 'linux' || platform === 'freebsd' || platform === 'openbsd') {
432
+ notified = execute('notify-send', ['Elanous', message]);
433
+ }
434
+ } catch { /* no notifier — the code stays on screen */ }
435
+ debug.log('onboarding.codex', 'device-code-notify', { platform, copied, notified });
328
436
  }
329
437
 
330
438
  /** Default probe — calls into the local-LLM manager's quad-probe
@@ -425,7 +533,18 @@ async function askLLM(
425
533
  excerpt: getMessages().setupStepLLMExcerpt,
426
534
  severity: 'required',
427
535
  });
428
- const currentIdx = PROVIDER_CHOICES.findIndex(c => c.key === current.provider);
536
+ // OB1 (10-01): show subscriptions the machine already has and preselect one — the old default was the env-only
537
+ // «Auto-detect», so a ChatGPT/Codex or Grok subscriber pressing Enter got a setup that found nothing.
538
+ let detected: import('./llm/provider-detect.js').DetectedProvider[] = [];
539
+ try {
540
+ const detect = grokDeps.detectProviders ?? (async () => (await import('./llm/provider-detect.js')).detectProviders());
541
+ detected = await detect();
542
+ } catch { /* detection is advisory — the list still works */ }
543
+ const { subscriptionHint } = await import('./onboarding/subscription-hint.js');
544
+ const hint = subscriptionHint(detected);
545
+ for (const found of hint.found) io.print(found.line);
546
+ const configured = current.provider && current.provider !== 'auto' ? current.provider : undefined;
547
+ const currentIdx = PROVIDER_CHOICES.findIndex(c => c.key === (configured ?? hint.preferred));
429
548
  const providerOptions: ChoiceOption<ProviderChoice | WizardBackValue>[] =
430
549
  PROVIDER_CHOICES.map((c, i) => ({
431
550
  key: String(i + 1),
@@ -448,7 +567,7 @@ async function askLLM(
448
567
  && (grokDeps.resolveCredential ?? resolveGrokCredential)()?.kind === 'subscription';
449
568
  if (grokSubscription) io.print(' Found grok subscription (~/.grok/auth.json)');
450
569
  if (choice.key === 'auto') {
451
- io.print(' → auto mode: will detect from XAI/OPENAI/ANTHROPIC/LOCAL_LLM env vars.');
570
+ io.print(' → auto mode: uses a subscription login it finds (ChatGPT/Codex ~/.codex · Grok ~/.grok) first, then XAI/OPENAI/ANTHROPIC/LOCAL_LLM env vars.');
452
571
  return out;
453
572
  }
454
573
 
@@ -456,26 +575,55 @@ async function askLLM(
456
575
  // Keeps onboarding and `elanous codex setup` in sync — one picker, one
457
576
  // curated model catalog, one source of truth for provider defaults.
458
577
  if (choice.key === 'openai-codex') {
459
- const mode = await pickCodexAuthMode(io);
578
+ const mode = await pickCodexAuthMode(io, {
579
+ ...(grokDeps.hasCodexCliLogin ? { hasCodexCliLogin: grokDeps.hasCodexCliLogin } : {}),
580
+ ...(grokDeps.loadCodexTokens ? { loadTokens: grokDeps.loadCodexTokens } : {}),
581
+ });
460
582
  if (mode === 'oauth') {
461
- io.print(' Launching device-code flow…');
462
- try {
463
- await loginWithCodex({
464
- onProgress: (p) => {
465
- if (p.type === 'user_code') {
466
- io.print('');
467
- io.print(` 1) Open in any browser: ${p.loginUrl}`);
468
- io.print(` 2) Enter code: ${p.userCode}`);
469
- io.print('');
470
- io.print(' Waiting for sign-in…');
471
- }
472
- if (p.type === 'saved') io.print(' Signed in. Tokens saved.');
473
- },
474
- });
475
- } catch (err: any) {
476
- io.print(` ! OAuth failed: ${err?.message ?? err}`);
477
- io.print(` Navigate to ${CODEX_DEVICE_LOGIN_URL} manually,`);
478
- io.print(` or re-run \`elanous login openai-codex\` / \`elanous codex setup\` later.`);
583
+ const env = grokDeps.codexEnv ?? process.env;
584
+ let signedIn = false;
585
+ if (!(grokDeps.isHeadless ?? isHeadlessEnv)(env)) {
586
+ // Full-screen IO retains print() messages across redraws. Keep this
587
+ // waiting hint transient so it cannot reappear on the device-code screen.
588
+ if (io.showStep) output.write(' 브라우저에서 로그인을 마치세요 — 끝나면 자동으로 이어집니다.\n');
589
+ else io.print(' 브라우저에서 로그인을 마치세요 — 끝나면 자동으로 이어집니다.');
590
+ try {
591
+ await (grokDeps.loginWithCodexBrowser ?? loginWithCodexBrowser)();
592
+ signedIn = true;
593
+ } catch { /* try PATH Codex, then device code */ }
594
+ if (!signedIn) {
595
+ const codexPath = pathCodexBinary(env);
596
+ if (codexPath) {
597
+ try {
598
+ const result = await (grokDeps.spawnCodexLogin ?? spawnCodexLogin)({ deviceAuth: false, codexPath });
599
+ signedIn = result.ok;
600
+ } catch { /* device code remains available */ }
601
+ }
602
+ }
603
+ if (signedIn) io.print(' → using your ChatGPT/Codex login.');
604
+ else io.print(' 브라우저 로그인이 안 됐습니다 — 코드로 이어갑니다');
605
+ }
606
+ if (!signedIn) {
607
+ io.print(' Launching device-code flow…');
608
+ try {
609
+ await (grokDeps.loginWithCodex ?? loginWithCodex)({
610
+ onProgress: (p) => {
611
+ if (p.type === 'user_code') {
612
+ io.print('');
613
+ io.print(` 1) Open in any browser: ${p.loginUrl}`);
614
+ io.print(` 2) Enter code: ${p.userCode}`);
615
+ io.print('');
616
+ assistCodexDeviceLogin(io, p.loginUrl ?? CODEX_DEVICE_LOGIN_URL, p.userCode ?? '', grokDeps);
617
+ io.print(' Waiting for sign-in…');
618
+ }
619
+ if (p.type === 'saved') io.print(' Signed in. Tokens saved.');
620
+ },
621
+ });
622
+ } catch (err: any) {
623
+ io.print(` ! OAuth failed: ${err?.message ?? err}`);
624
+ io.print(` Navigate to ${CODEX_DEVICE_LOGIN_URL} manually,`);
625
+ io.print(` or re-run \`elanous login openai-codex\` / \`elanous codex setup\` later.`);
626
+ }
479
627
  }
480
628
  } else if (mode === 'apikey') {
481
629
  // PR-Δ14 — askValidated catches blank/short/whitespace before
@@ -493,6 +641,8 @@ async function askLLM(
493
641
  );
494
642
  if (key.trim()) out.apiKey = key.trim();
495
643
  else if (current.apiKey) out.apiKey = current.apiKey;
644
+ } else if (mode === 'oauth-keep') {
645
+ io.print(' → using your existing ChatGPT/Codex login.');
496
646
  } else {
497
647
  io.print(' → skipped. Run `elanous codex setup` or `elanous login openai-codex` later.');
498
648
  }
@@ -1163,6 +1313,12 @@ export interface RunWizardOpts {
1163
1313
  grokDeps?: GrokOnboardingDeps;
1164
1314
  /** Internal ownership transfer for wrappers that explicitly complete IO. */
1165
1315
  deferComplete?: boolean;
1316
+ /** Post-save connections; injection keeps wizard tests off the real daemon/token store. */
1317
+ continueHereDeps?: {
1318
+ showNexus?: () => Promise<Pick<NexusShowResult, 'urls' | 'status' | 'instance'>>;
1319
+ issueToken?: () => Pick<IssuedTempToken, 'token'>;
1320
+ renderQr?: (link: string) => string | undefined;
1321
+ };
1166
1322
  }
1167
1323
 
1168
1324
  /** Pick the default IO. TTY → `fullScreenIO` (Phase 5: cleared
@@ -1206,7 +1362,7 @@ export class OnboardingRefusedError extends Error {
1206
1362
  export function handleOnboardingRefusal(error: unknown, entrance: 'agent' | 'other'): boolean {
1207
1363
  if (!(error instanceof OnboardingRefusedError) || error.code !== 'onboarding-refused') return false;
1208
1364
  if (entrance === 'agent' && error.reason === 'non-tty') {
1209
- console.error('elanous agent needs a configured LLM. Run `elanous setup` in a terminal, or `elanous setup --non-interactive --config <path>` for unattended setup.');
1365
+ console.error(`elanous agent needs a configured LLM. Run \`elanous onboarding\` in a terminal, or ${unattendedSetupHint()} for unattended setup.`);
1210
1366
  } else {
1211
1367
  console.error(error.message);
1212
1368
  }
@@ -1231,7 +1387,7 @@ function refuseInteractiveOnboardingIfNeeded(opts: RunWizardOpts, path: string):
1231
1387
  if (nonTTY) {
1232
1388
  throw new OnboardingRefusedError(
1233
1389
  '대화형 온보딩은 stdin TTY가 있는 자리에서만 실행할 수 있다. '
1234
- + '무인 설정은 `elanous setup --non-interactive --config <path>`를 사용하라.',
1390
+ + `무인 설정은 ${unattendedSetupHint()}를 사용하라.`,
1235
1391
  'non-tty',
1236
1392
  );
1237
1393
  }
@@ -1383,6 +1539,57 @@ export async function runOnboarding(opts: RunWizardOpts = {}): Promise<UserConfi
1383
1539
  io.print(` Discord : ${marked.discord.enabled ? 'enabled' : 'disabled'}`);
1384
1540
  io.print(` ${i18nFormat(m.setupRerunHint, { cmd: 'elanous setup' })}`);
1385
1541
  io.print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
1542
+
1543
+ const interactive = !io.nonInteractive && !!(opts.io || input.isTTY);
1544
+ let shown: Pick<NexusShowResult, 'urls' | 'status' | 'instance'> | undefined;
1545
+ try {
1546
+ const showNexus = opts.continueHereDeps?.showNexus ?? (async () => {
1547
+ const { runNexusShow } = await import('./cli/nexus-show.js');
1548
+ return runNexusShow({ format: 'json', out: { log: () => {}, error: () => {} } });
1549
+ });
1550
+ shown = await showNexus();
1551
+ } catch { /* Config was saved; daemon discovery is advisory. */ }
1552
+ const daemonRunning = shown?.status === 'unregistered'
1553
+ || (shown?.status === 'registered' && shown.instance?.alive === true);
1554
+ const pwaUrl = daemonRunning ? shown?.urls?.pwa.tailnet ?? shown?.urls?.pwa.loopback : undefined;
1555
+ let phoneLink: string | undefined;
1556
+ if (interactive && daemonRunning && shown?.urls?.rest) {
1557
+ try {
1558
+ const { pickPhoneBase, endpointFromBaseUrl, buildPhoneConnectLink } = await import('./cli/phone-link.js');
1559
+ const picked = pickPhoneBase(shown.urls.rest);
1560
+ const endpoint = endpointFromBaseUrl(picked.base);
1561
+ if (endpoint) {
1562
+ const issued = opts.continueHereDeps?.issueToken
1563
+ ? opts.continueHereDeps.issueToken()
1564
+ : await (async () => {
1565
+ const { issueTempToken, parseTtl } = await import('./auth/temp-tokens.js');
1566
+ return issueTempToken({ ttlMs: parseTtl('24h'), label: 'phone' });
1567
+ })();
1568
+ phoneLink = buildPhoneConnectLink(endpoint, issued.token);
1569
+ try { debug.log('onboarding.continue-here', 'phone-link-issued', { kind: picked.kind, tls: endpoint.tls, temp: true }); }
1570
+ catch { /* Logging must not interrupt setup. */ }
1571
+ }
1572
+ } catch { /* No token/link on failure; show the manual command instead. */ }
1573
+ }
1574
+ const directions = continueHereLines({ daemonRunning, pwaUrl, phoneLink, telegramEnabled: marked.telegram.enabled, interactive });
1575
+ if (io.nonInteractive) {
1576
+ for (const line of directions) process.stdout.write(`${line}\n`);
1577
+ } else {
1578
+ io.print('');
1579
+ for (const line of directions) io.print(line);
1580
+ }
1581
+ if (interactive && phoneLink) {
1582
+ try {
1583
+ const renderQr = opts.continueHereDeps?.renderQr ?? ((link: string) => {
1584
+ const qr = spawnSync('qrencode', ['-t', 'ANSIUTF8', link], { encoding: 'utf8' });
1585
+ return qr.status === 0 ? qr.stdout : undefined;
1586
+ });
1587
+ const qr = renderQr(phoneLink);
1588
+ if (qr) io.print(qr);
1589
+ } catch { /* qrencode is optional, exactly as with phone link. */ }
1590
+ }
1591
+ try { debug.log('onboarding.continue-here', 'shown', { daemonRunning, interactive, phoneLinkIssued: !!phoneLink, telegramEnabled: marked.telegram.enabled }); }
1592
+ catch { /* Logging must not interrupt setup. */ }
1386
1593
  if (!opts.deferComplete) io.complete?.();
1387
1594
  return marked;
1388
1595
  } finally {
@@ -0,0 +1,92 @@
1
+ import { readFileSync, realpathSync } from 'node:fs';
2
+ import { resolve, sep } from 'node:path';
3
+ import { parse as parseYaml } from 'yaml';
4
+ import { debug } from '../../debug/log.js';
5
+ import { listNodeKinds } from '../../graph-kinds/registry.js';
6
+ import { loadPluginNodes } from '../../graph-kinds/plugin-nodes.js';
7
+ import { loadPluginManifestFromDir } from '../core/manifest.js';
8
+ import { installPlugin, type InstalledPlugin } from '../install/plugin-install.js';
9
+ import { addNode, type AddNodeOptions, type AddNodeResult } from './node-maker.js';
10
+
11
+ export interface AddAndInstallNodeOptions extends Omit<AddNodeOptions, 'deps'> {
12
+ /** Accept the plugin's requested capabilities without asking (same meaning as `plugin add --yes`). */
13
+ yes?: boolean;
14
+ deps?: {
15
+ addNode?: typeof addNode;
16
+ installPlugin?: typeof installPlugin;
17
+ consent?: (capabilities: string[]) => boolean | Promise<boolean>;
18
+ sync?: (installed: InstalledPlugin) => void | Promise<void>;
19
+ listNodeKinds?: typeof listNodeKinds;
20
+ codex?: NonNullable<AddNodeOptions['deps']>['codex'];
21
+ };
22
+ }
23
+
24
+ export interface AddAndInstallNodeResult extends Omit<AddNodeResult, 'status' | 'timings'> {
25
+ status: 'installed' | 'failed';
26
+ plugin?: string;
27
+ installedPath?: string;
28
+ timings: AddNodeResult['timings'] & { install: number; sync: number };
29
+ }
30
+
31
+ function installedNodeManifest(installed: InstalledPlugin, kind: string) {
32
+ const { manifest, inferred } = loadPluginManifestFromDir(installed.path, { id: installed.name });
33
+ if (inferred || manifest.id !== installed.name || manifest.version !== installed.version) {
34
+ throw new Error(`installed plugin manifest mismatch: ${installed.name}@${installed.version}`);
35
+ }
36
+ const root = realpathSync(installed.path);
37
+ const matches = (manifest.contributes.nodes ?? []).some(path => {
38
+ const file = realpathSync(resolve(root, path));
39
+ if (!path.startsWith('./') || !file.startsWith(`${root}${sep}`)) throw new Error(`unsafe installed node path: ${path}`);
40
+ const node: unknown = parseYaml(readFileSync(file, 'utf8'));
41
+ return node !== null && typeof node === 'object' && !Array.isArray(node)
42
+ && (node as Record<string, unknown>).kind === kind
43
+ && (node as Record<string, unknown>).graph === 'workflow';
44
+ });
45
+ if (!matches) throw new Error(`installed node not found: ${installed.name}:${kind}`);
46
+ return manifest;
47
+ }
48
+
49
+ export async function addAndInstallNode({ dir, request, kind, yes, deps }: AddAndInstallNodeOptions): Promise<AddAndInstallNodeResult> {
50
+ const added = await (deps?.addNode ?? addNode)({ dir, request, kind,
51
+ ...(deps?.codex ? { deps: { codex: deps.codex } } : {}),
52
+ });
53
+ const result: AddAndInstallNodeResult = {
54
+ ...added, status: 'failed', timings: { ...added.timings, install: 0, sync: 0 },
55
+ };
56
+ if (added.status !== 'added') return result;
57
+
58
+ try {
59
+ const startedInstall = performance.now();
60
+ let installed: InstalledPlugin;
61
+ try {
62
+ installed = await (deps?.installPlugin ?? installPlugin)(added.dir, {
63
+ ...(yes === undefined ? {} : { yes }),
64
+ consent: deps?.consent ?? (() => false),
65
+ });
66
+ } finally {
67
+ result.timings.install = Math.round(performance.now() - startedInstall);
68
+ debug.log('plugin.node-install', 'install', { ms: result.timings.install });
69
+ }
70
+ result.plugin = installed.name;
71
+ result.installedPath = installed.path;
72
+ const startedSync = performance.now();
73
+ try {
74
+ const manifest = installedNodeManifest(installed, added.kind);
75
+ if (deps?.sync) await deps.sync(installed);
76
+ else {
77
+ const loaded = loadPluginNodes(installed.path, manifest);
78
+ if (loaded.errors.length) throw new Error(`node sync failed: ${loaded.errors.join('; ')}`);
79
+ }
80
+ const registered = (deps?.listNodeKinds ?? listNodeKinds)('workflow')
81
+ .some(entry => entry.plugin === installed.name && entry.kind === `${installed.name}:${added.kind}`);
82
+ if (!registered) throw new Error(`node kind not registered: ${installed.name}:${added.kind}`);
83
+ result.status = 'installed';
84
+ } finally {
85
+ result.timings.sync = Math.round(performance.now() - startedSync);
86
+ debug.log('plugin.node-install', 'sync', { ms: result.timings.sync, status: result.status });
87
+ }
88
+ } catch (error) {
89
+ result.errors.push(error instanceof Error ? error.message : String(error));
90
+ }
91
+ return result;
92
+ }
@@ -0,0 +1,139 @@
1
+ import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, unlinkSync, writeFileSync } from 'node:fs';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { join, resolve, sep } from 'node:path';
4
+ import { parse as parseYaml } from 'yaml';
5
+ import { debug } from '../../debug/log.js';
6
+ import { getNodeKindRegistration, listNodeKinds, unregisterPluginNodeKind } from '../../graph-kinds/registry.js';
7
+ import { loadPluginNodes } from '../../graph-kinds/plugin-nodes.js';
8
+ import { loadPluginManifestFromDir } from '../core/manifest.js';
9
+ import { codexWrite } from './plugin-maker.js';
10
+
11
+ export interface AddNodeOptions {
12
+ dir: string;
13
+ request: string;
14
+ kind?: string;
15
+ deps?: { codex: (dir: string, prompt: string) => Promise<void> };
16
+ }
17
+
18
+ export interface AddNodeResult {
19
+ status: 'added' | 'failed';
20
+ dir: string;
21
+ kind: string;
22
+ node: string;
23
+ errors: string[];
24
+ timings: { write: number; validate: number; repair?: number };
25
+ version?: string;
26
+ }
27
+
28
+ const KIND = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
29
+ const VERSION = /^(\d+)\.(\d+)\.(\d+)$/;
30
+
31
+ function nodeErrors(dir: string, path: string, kind: string, manifest: ReturnType<typeof loadPluginManifestFromDir>['manifest']): string[] {
32
+ const errors: string[] = [];
33
+ let node: unknown;
34
+ try {
35
+ if (lstatSync(path).isSymbolicLink() || !realpathSync(path).startsWith(`${realpathSync(dir)}${sep}`)) {
36
+ return ['unsafe node path'];
37
+ }
38
+ node = parseYaml(readFileSync(path, 'utf8'));
39
+ } catch (error) { return [`node YAML read/parse failed: ${String(error)}`]; }
40
+ if (!node || typeof node !== 'object' || Array.isArray(node)) return ['node must be an object'];
41
+ const spec = node as Record<string, unknown>;
42
+ if (spec.kind !== kind) errors.push(`node kind must be ${kind}`);
43
+ if (spec.graph !== 'workflow') errors.push('node graph must be workflow');
44
+ if (errors.length) return errors;
45
+ // The loader is the same validation path used by plugin installation. Register under a
46
+ // temporary identity so installed kinds cannot collide with this inspection.
47
+ const inspectionId = `inspect-${randomUUID().replaceAll('-', '')}`;
48
+ try {
49
+ errors.push(...loadPluginNodes(dir, { ...manifest, id: inspectionId,
50
+ contributes: { ...manifest.contributes, nodes: [`./nodes/${kind}.yaml`] } }).errors);
51
+ } finally {
52
+ for (const entry of listNodeKinds()) {
53
+ if (entry.plugin !== inspectionId) continue;
54
+ const registered = getNodeKindRegistration(entry.graph, entry.kind);
55
+ if (registered) unregisterPluginNodeKind(entry.graph, entry.kind, inspectionId, registered);
56
+ }
57
+ }
58
+ return errors;
59
+ }
60
+
61
+ export async function addNode({ dir: inputDir, request, kind: requestedKind, deps }: AddNodeOptions): Promise<AddNodeResult> {
62
+ const dir = resolve(inputDir);
63
+ const kind = requestedKind ?? request.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
64
+ if (!request.trim() || !KIND.test(kind)) throw new Error(`invalid node request or kind: ${kind}`);
65
+ const manifestFile = join(dir, 'plugin.json');
66
+ if (!existsSync(manifestFile) || lstatSync(manifestFile).isSymbolicLink()) throw new Error(`plugin.json missing or unsafe: ${manifestFile}`);
67
+ const { manifest } = loadPluginManifestFromDir(dir, { id: 'local-plugin' });
68
+ const original = readFileSync(manifestFile);
69
+ const raw: unknown = JSON.parse(original.toString('utf8'));
70
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw) || typeof (raw as Record<string, unknown>).version !== 'string'
71
+ || !VERSION.test((raw as Record<string, unknown>).version as string)) throw new Error('plugin.json requires a numeric major.minor.patch version');
72
+ const nodesDir = join(dir, 'nodes');
73
+ const nodesEntry = lstatSync(nodesDir, { throwIfNoEntry: false });
74
+ if (nodesEntry && !nodesEntry.isDirectory()) throw new Error(`unsafe nodes directory: ${nodesDir}`);
75
+ const path = join(nodesDir, `${kind}.yaml`);
76
+ if (lstatSync(path, { throwIfNoEntry: false }) || manifest.contributes.nodes?.some(node => node === `./nodes/${kind}.yaml`)) throw new Error(`node already exists: ${kind}`);
77
+ const root = realpathSync(dir);
78
+ for (const declared of manifest.contributes.nodes ?? []) {
79
+ const declaredPath = resolve(dir, declared);
80
+ const entry = lstatSync(declaredPath, { throwIfNoEntry: false });
81
+ if (!entry) continue;
82
+ if (!entry.isFile() || !realpathSync(declaredPath).startsWith(`${root}${sep}`)) {
83
+ throw new Error(`unsafe declared node path: ${declared}`);
84
+ }
85
+ const spec: unknown = parseYaml(readFileSync(declaredPath, 'utf8'));
86
+ if (spec && typeof spec === 'object' && !Array.isArray(spec)
87
+ && (spec as Record<string, unknown>).kind === kind) throw new Error(`node already exists: ${kind}`);
88
+ }
89
+ const timings: AddNodeResult['timings'] = { write: 0, validate: 0 };
90
+ const result: AddNodeResult = { status: 'failed', dir, kind, node: path, errors: [], timings };
91
+ const step = async (stage: keyof typeof timings, action: () => void | Promise<void>): Promise<void> => {
92
+ const start = performance.now();
93
+ try { await action(); }
94
+ finally { timings[stage] = Math.round(performance.now() - start); debug.log('plugin.node-maker', stage, { ms: timings[stage], errors: result.errors }); }
95
+ };
96
+ const prompt = `요청 원문:\n${request}\n\n이 플러그인에 workflow 노드 종류 ${kind} 하나만 작성하라. nodes/${kind}.yaml 파일만 작성하고 plugin.json 및 다른 파일은 변경하지 말 것. graph: workflow, kind: ${kind}, inputs: { type: object }, run: 의 bash/http/skill/mcp 중 정확히 하나를 정의하라. harness 노드 및 코드 실행기/다른 플러그인 파일은 만들지 말 것. 폴더 밖에는 쓰지 말 것.`;
97
+ try {
98
+ mkdirSync(nodesDir, { recursive: true });
99
+ await step('write', () => (deps?.codex ?? codexWrite)(dir, prompt));
100
+ const validate = () => {
101
+ const entry = lstatSync(manifestFile, { throwIfNoEntry: false });
102
+ if (!entry) { result.errors = ['plugin.json was deleted — restore it unchanged']; return; }
103
+ result.errors = !entry.isSymbolicLink() && readFileSync(manifestFile).equals(original)
104
+ ? nodeErrors(dir, path, kind, manifest) : ['plugin.json was modified'];
105
+ };
106
+ await step('validate', validate);
107
+ if (result.errors.length) {
108
+ await step('repair', () => (deps?.codex ?? codexWrite)(dir, `${prompt}\n\n아래 오류를 모두 수리하라:\n${result.errors.join('\n')}`));
109
+ await step('validate', validate);
110
+ }
111
+ if (result.errors.length) return result;
112
+ const data = raw as Record<string, unknown>;
113
+ const [, major, minor, patch] = (data.version as string).match(VERSION)!;
114
+ data.version = `${major}.${minor}.${BigInt(patch!) + 1n}`;
115
+ const extension = (data.extensions as Record<string, unknown> | undefined)?.['ai.elanous'];
116
+ if (extension && typeof extension === 'object' && !Array.isArray(extension)) {
117
+ const entry = extension as Record<string, unknown>;
118
+ entry.nodes = [...(manifest.contributes.nodes ?? []), `./nodes/${kind}.yaml`];
119
+ } else {
120
+ const contributes = (data.contributes ??= {}) as Record<string, unknown>;
121
+ contributes.nodes = [...(manifest.contributes.nodes ?? []), `./nodes/${kind}.yaml`];
122
+ }
123
+ writeFileSync(manifestFile, JSON.stringify(data, null, 2) + '\n');
124
+ result.version = data.version as string;
125
+ result.status = 'added';
126
+ } catch (error) {
127
+ result.errors.push(error instanceof Error ? error.message : String(error));
128
+ } finally {
129
+ if (result.status === 'failed') {
130
+ try {
131
+ if (lstatSync(manifestFile, { throwIfNoEntry: false })?.isSymbolicLink()) unlinkSync(manifestFile);
132
+ if (!existsSync(manifestFile) || !readFileSync(manifestFile).equals(original)) writeFileSync(manifestFile, original);
133
+ } catch (error) {
134
+ result.errors.push(`plugin.json restoration failed: ${String(error)}`);
135
+ }
136
+ }
137
+ }
138
+ return result;
139
+ }
@@ -1,4 +1,5 @@
1
- import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, readdirSync, writeFileSync } from 'node:fs';
1
+ import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
2
3
  import { dirname, join, relative, resolve, sep } from 'node:path';
3
4
  import { parse as parseYaml } from 'yaml';
4
5
  import { elanousStateRoot } from '../../autopilot/state-paths.js';
@@ -33,12 +34,23 @@ export interface MakePluginResult {
33
34
  timings: { scaffold: number; write: number; validate: number; repair?: number; install: number; run?: number };
34
35
  }
35
36
 
36
- export async function codexWrite(dir: string, prompt: string): Promise<void> {
37
- const child = Bun.spawn(['codex', 'exec', '-C', dir, '-s', 'workspace-write', '--skip-git-repo-check', '-o', join(dir, '.codex-last-message.txt'), prompt], {
38
- cwd: dir, stdin: 'ignore', stdout: 'ignore', stderr: 'pipe',
39
- });
40
- const [code, stderr] = await Promise.all([child.exited, new Response(child.stderr).text()]);
41
- if (code !== 0) throw new Error(`codex exec failed (${code}): ${stderr.trim().slice(0, 500)}`);
37
+ /** Run codex in the plugin folder without leaving run debris there: the last message goes to a temp file, and a
38
+ * `.elanous/` that the run created (debug logs and a `latest` symlink from elanous processes codex starts in that
39
+ * folder) is removed — the installer refuses symlinks, so the debris made every freshly written plugin uninstallable
40
+ * (10-01 live `plugin node add`). A `.elanous/` that existed before the run is left alone. */
41
+ export async function codexWrite(dir: string, prompt: string, bin = 'codex'): Promise<void> {
42
+ const scratch = mkdtempSync(join(tmpdir(), 'elanous-codex-'));
43
+ const stateDirExisted = existsSync(join(dir, '.elanous'));
44
+ try {
45
+ const child = Bun.spawn([bin, 'exec', '-C', dir, '-s', 'workspace-write', '--skip-git-repo-check', '-o', join(scratch, 'last-message.txt'), prompt], {
46
+ cwd: dir, stdin: 'ignore', stdout: 'ignore', stderr: 'pipe',
47
+ });
48
+ const [code, stderr] = await Promise.all([child.exited, new Response(child.stderr).text()]);
49
+ if (code !== 0) throw new Error(`codex exec failed (${code}): ${stderr.trim().slice(0, 500)}`);
50
+ } finally {
51
+ if (!stateDirExisted) rmSync(join(dir, '.elanous'), { recursive: true, force: true });
52
+ rmSync(scratch, { recursive: true, force: true });
53
+ }
42
54
  }
43
55
 
44
56
  function scaffold(dir: string, slug: string, request: string): void {