elanous 0.2.5 → 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 (479) hide show
  1. package/.env.example +10 -7
  2. package/README.md +9 -4
  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/{5391-8793f88b03b30161.js → 2375-cb6debea56cce4fb.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/{1751.50b96ea9c37b49b6.js → 2415.ed6428ff334c7ce2.js} +1 -1
  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/{4daa2798-1363d725c79f3593.js → 2ea96958-e8c7e112de50b067.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
  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/{8617.465ca22a319c95b3.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/{8918-482b30ce2a8ebf4c.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/{5348-bbf838e119661eee.js → 5791-bb82743fa6299562.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/{9704-3221c102623c8d73.js → 6062-73e68343a9814f6a.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/{4440-18bb160d95819f4e.js → 6090-85a3f05f954e5697.js} +1 -1
  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/{f6f3850d-7f324c6566002113.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-f89e9a303515a033.js → page-10ed24f413ed66e9.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/board/{page-4f403cd060442c95.js → page-211810b6c93ab66a.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-0ac5d053f0444d21.js → page-624f7a2f89b60c8f.js} +1 -1
  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-bccce0babceae30f.js → page-55d50e9b0006a91d.js} +1 -1
  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-2bf59c720fe9542c.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/icons/elanous-16.png +0 -0
  123. package/apps/pwa/out/icons/elanous-180.png +0 -0
  124. package/apps/pwa/out/icons/elanous-192.png +0 -0
  125. package/apps/pwa/out/icons/elanous-32.png +0 -0
  126. package/apps/pwa/out/icons/elanous-512.png +0 -0
  127. package/apps/pwa/out/index.html +1 -1
  128. package/apps/pwa/out/index.txt +21 -21
  129. package/apps/pwa/out/intake/index.html +1 -1
  130. package/apps/pwa/out/intake/index.txt +22 -22
  131. package/apps/pwa/out/live/index.html +1 -1
  132. package/apps/pwa/out/live/index.txt +22 -22
  133. package/apps/pwa/out/market/index.html +1 -1
  134. package/apps/pwa/out/market/index.txt +21 -21
  135. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  136. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  137. package/apps/pwa/out/morning/index.html +1 -1
  138. package/apps/pwa/out/morning/index.txt +22 -22
  139. package/apps/pwa/out/observatory/index.html +1 -1
  140. package/apps/pwa/out/observatory/index.txt +22 -22
  141. package/apps/pwa/out/reflection/index.html +1 -1
  142. package/apps/pwa/out/reflection/index.txt +22 -22
  143. package/apps/pwa/out/scheduler/index.html +1 -1
  144. package/apps/pwa/out/scheduler/index.txt +22 -22
  145. package/apps/pwa/out/sessions/index.html +1 -1
  146. package/apps/pwa/out/sessions/index.txt +21 -21
  147. package/apps/pwa/out/settings/devices/index.html +1 -1
  148. package/apps/pwa/out/settings/devices/index.txt +22 -22
  149. package/apps/pwa/out/settings/index.html +1 -1
  150. package/apps/pwa/out/settings/index.txt +22 -22
  151. package/apps/pwa/out/setup/done/index.html +1 -1
  152. package/apps/pwa/out/setup/done/index.txt +22 -22
  153. package/apps/pwa/out/setup/index.html +1 -1
  154. package/apps/pwa/out/setup/index.txt +22 -22
  155. package/apps/pwa/out/share/index.html +1 -1
  156. package/apps/pwa/out/share/index.txt +22 -22
  157. package/apps/pwa/out/showroom/index.html +1 -1
  158. package/apps/pwa/out/showroom/index.txt +21 -21
  159. package/apps/pwa/out/tasks/index.html +1 -1
  160. package/apps/pwa/out/tasks/index.txt +22 -22
  161. package/apps/pwa/out/term/index.html +1 -1
  162. package/apps/pwa/out/term/index.txt +22 -22
  163. package/apps/pwa/out/trace/index.html +1 -1
  164. package/apps/pwa/out/trace/index.txt +22 -22
  165. package/apps/pwa/out/vault/index.html +1 -1
  166. package/apps/pwa/out/vault/index.txt +22 -22
  167. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  168. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  169. package/apps/pwa/out/workflows/index.html +1 -1
  170. package/apps/pwa/out/workflows/index.txt +22 -22
  171. package/apps/pwa/out/workspace/index.html +1 -1
  172. package/apps/pwa/out/workspace/index.txt +22 -22
  173. package/apps/pwa/out/worktrees/index.html +1 -1
  174. package/apps/pwa/out/worktrees/index.txt +22 -22
  175. package/bin/elanous.cjs +107 -0
  176. package/graphs/field/field-feed.yaml +24 -0
  177. package/graphs/field/recipes.yaml +12 -0
  178. package/graphs/landing/landing-heal.yaml +3 -1
  179. package/graphs/landing/recipes.yaml +3 -0
  180. package/graphs/release/recipes.yaml +13 -0
  181. package/graphs/release/release-loop.yaml +13 -4
  182. package/package.json +5 -5
  183. package/plugins/card-followup/README.md +42 -0
  184. package/plugins/card-followup/examples/card-sample.json +7 -0
  185. package/plugins/card-followup/graphs/card-followup.yaml +18 -0
  186. package/plugins/card-followup/graphs/recipes.yaml +16 -0
  187. package/plugins/card-followup/graphs/run-step.ts +276 -0
  188. package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
  189. package/plugins/card-followup/plugin.json +25 -0
  190. package/plugins/doc-draft/README.md +13 -0
  191. package/plugins/doc-draft/examples/brief-sample.json +8 -0
  192. package/plugins/doc-draft/graphs/doc-draft.yaml +14 -0
  193. package/plugins/doc-draft/graphs/recipes.yaml +9 -0
  194. package/plugins/doc-draft/graphs/run-step.ts +113 -0
  195. package/plugins/doc-draft/plugin.json +24 -0
  196. package/plugins/geo-check/README.md +15 -0
  197. package/plugins/geo-check/examples/brand-sample.json +8 -0
  198. package/plugins/geo-check/graphs/geo-check.yaml +20 -0
  199. package/plugins/geo-check/graphs/recipes.yaml +18 -0
  200. package/plugins/geo-check/graphs/run-step.ts +198 -0
  201. package/plugins/geo-check/plugin.json +24 -0
  202. package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
  203. package/plugins/job-coach/README.md +31 -1
  204. package/plugins/job-coach/graphs/run-step.ts +2 -2
  205. package/plugins/job-coach/plugin.json +1 -1
  206. package/plugins/job-coach/plugin.ts +1 -1
  207. package/scripts/affinity/endcard.sh +3 -2
  208. package/scripts/affinity/reframe.sh +3 -1
  209. package/scripts/ci-public-export-leak-gate.ts +48 -0
  210. package/scripts/codex-quota-alert.ts +122 -258
  211. package/scripts/coord-tracks.json +4 -4
  212. package/scripts/daemon-port-baseline.txt +1 -2
  213. package/scripts/field-feed/deliver-node.ts +52 -0
  214. package/scripts/field-feed/feed-node.ts +67 -0
  215. package/scripts/field-feed/lib.ts +162 -0
  216. package/scripts/field-feed/reel-node.ts +24 -0
  217. package/scripts/field-feed/start.ts +62 -0
  218. package/scripts/fixtures/vflow-sample.ndjson +6 -0
  219. package/scripts/install.sh +92 -13
  220. package/scripts/landing-heal/clean-checkout.ts +114 -0
  221. package/scripts/landing-heal/run-mission.ts +38 -3
  222. package/scripts/lib/android-test-device.ts +32 -0
  223. package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
  224. package/scripts/public-export.ts +8 -4
  225. package/scripts/release-loop/baseline-from-logs.ts +92 -0
  226. package/scripts/release-loop/checklist-gate-node.ts +35 -0
  227. package/scripts/release-loop/env-known-failures.ts +31 -0
  228. package/scripts/release-loop/gate-diff.ts +29 -0
  229. package/scripts/release-loop/gate-node.ts +124 -29
  230. package/scripts/release-loop/known-issues-node.ts +159 -0
  231. package/scripts/release-loop/npm-publish-node.ts +118 -0
  232. package/scripts/release-loop/ops-upgrade-node.ts +87 -0
  233. package/scripts/release-loop/release-readiness.ts +58 -0
  234. package/scripts/release-loop/shard-plan.ts +47 -0
  235. package/scripts/release-loop/unattended-release.ts +93 -0
  236. package/scripts/ux/measure-first-text.ts +102 -0
  237. package/scripts/ux/record-page.ts +4 -1
  238. package/scripts/vflow-collect.ts +20 -7
  239. package/scripts/vflow-search.ts +73 -0
  240. package/scripts/video-free-line-test-walker.ts +36 -0
  241. package/scripts/video-pipeline.ts +2 -2
  242. package/skills/explainer-video/SKILL.md +24 -3
  243. package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -1
  244. package/skills/explainer-video/engine/build.mjs +2 -2
  245. package/skills/explainer-video/engine/reel.mjs +235 -0
  246. package/skills/explainer-video/engine/reel.sh +9 -0
  247. package/skills/explainer-video/engine/vo.ts +58 -16
  248. package/skills/explainer-video/examples/elanous-0.2.5/script.json +1 -1
  249. package/skills/grill-me/SKILL.md +1 -1
  250. package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +1 -1
  251. package/src/a2a/server.ts +2 -0
  252. package/src/acp/elanous-aware-peers.ts +30 -0
  253. package/src/acp/server.ts +91 -15
  254. package/src/agent/monad-agent-turn.ts +25 -6
  255. package/src/agent/shared-app-tools.ts +5 -3
  256. package/src/agent-substrate/review-intent.ts +6 -2
  257. package/src/agent-substrate/shard-boundary-intent.ts +70 -0
  258. package/src/auth/github-app-token.ts +4 -1
  259. package/src/autopilot/mission-origin.ts +20 -4
  260. package/src/autopilot/mission-phase-track.ts +11 -1
  261. package/src/autopilot/track-agent-shadow.ts +151 -0
  262. package/src/autopilot/track-agent.ts +26 -6
  263. package/src/autopilot/track-courier.ts +2 -2
  264. package/src/boot/acp-server-help.ts +16 -0
  265. package/src/boot/daemon-prompt-turn.ts +31 -10
  266. package/src/boot/daemon-tools/index.ts +7 -1
  267. package/src/budget/codex-credit-pace.ts +84 -0
  268. package/src/budget/codex-credit-plan.ts +154 -0
  269. package/src/budget/codex-reset-credit-state.ts +2 -0
  270. package/src/chat/chat-cli.ts +4 -1
  271. package/src/chat/short-question.ts +40 -0
  272. package/src/cli/agent-cli.ts +24 -15
  273. package/src/cli/ask-bare.ts +42 -0
  274. package/src/cli/cli-user-error.ts +25 -0
  275. package/src/cli/codex-credit-plan-view.ts +25 -0
  276. package/src/cli/dependence-gauge.ts +237 -0
  277. package/src/cli/doctor-cli.ts +5 -0
  278. package/src/cli/doctor-modes.ts +48 -0
  279. package/src/cli/intake-cli.ts +12 -2
  280. package/src/cli/intake-source-cli.ts +58 -0
  281. package/src/cli/machine-cli.ts +23 -0
  282. package/src/cli/msg-cli.ts +137 -0
  283. package/src/cli/nexus-ios-bind.ts +25 -8
  284. package/src/cli/phone-link.ts +117 -0
  285. package/src/cli/plugin-cli.ts +52 -0
  286. package/src/cli/pr-cli.ts +36 -0
  287. package/src/cli/private-ledger-commands.ts +31 -0
  288. package/src/cli/provider-cli.ts +5 -0
  289. package/src/cli/release-cli.ts +56 -12
  290. package/src/cli/release-run-if-ready.ts +48 -0
  291. package/src/cli/setup-cli.ts +27 -6
  292. package/src/cli/setup-recommend.ts +8 -0
  293. package/src/cli/usage-cli.ts +11 -1
  294. package/src/codex/setup.ts +27 -8
  295. package/src/dashboard/index.ts +1 -1
  296. package/src/discord.ts +56 -6
  297. package/src/domains/core-tools.ts +37 -14
  298. package/src/domains/outbound-alert.ts +55 -16
  299. package/src/exec-requests/planner.ts +140 -0
  300. package/src/exec-requests/runner.ts +338 -0
  301. package/src/exec-requests/store.ts +76 -0
  302. package/src/field/field-media.ts +250 -0
  303. package/src/field/field-reel.ts +196 -0
  304. package/src/grok/credential.ts +7 -5
  305. package/src/harness/aside-run-status.ts +33 -0
  306. package/src/harness/browser-verify.ts +48 -17
  307. package/src/harness/harness-cli-command.ts +25 -3
  308. package/src/harness/image-load-state.ts +21 -0
  309. package/src/harness/process-garbage.ts +45 -0
  310. package/src/index.ts +104 -38
  311. package/src/instance/leader.ts +13 -1
  312. package/src/intake-plane/collect-github.ts +5 -3
  313. package/src/intake-plane/collect-telegram-saved.ts +15 -3
  314. package/src/intake-plane/discord-seat-work.ts +56 -0
  315. package/src/intake-plane/intake-sources.ts +202 -0
  316. package/src/intake-plane/items.ts +28 -12
  317. package/src/intake-plane/seat-doc-route.ts +207 -0
  318. package/src/intake-plane/submit-intake-work.ts +9 -1
  319. package/src/intake-plane/telegram-seat-work.ts +72 -0
  320. package/src/llm/model-defaults.ts +24 -1
  321. package/src/llm.ts +5 -4
  322. package/src/machines/machine-ledger.ts +127 -0
  323. package/src/market/pack-internal-refs.ts +28 -0
  324. package/src/market/publish.ts +31 -4
  325. package/src/maturity/feature-maturity.ts +65 -0
  326. package/src/model-tier/turn-tier.ts +24 -0
  327. package/src/msg/msg-store.ts +149 -0
  328. package/src/native-tool-catalog.ts +2 -1
  329. package/src/nexus/api/chat-fast-path-config.ts +28 -0
  330. package/src/nexus/api/consult-requests.ts +117 -0
  331. package/src/nexus/api/discord-trigger-bot.ts +3 -0
  332. package/src/nexus/api/exec-requests.ts +51 -0
  333. package/src/nexus/api/field-uploads.ts +180 -0
  334. package/src/nexus/api/graph-approvals-feed.ts +140 -0
  335. package/src/nexus/api/graph-approvals.ts +120 -4
  336. package/src/nexus/api/harness-api.ts +219 -5
  337. package/src/nexus/api/http-server.ts +68 -5
  338. package/src/nexus/api/seat-requests.ts +254 -0
  339. package/src/nexus/api/trace-run-scan.ts +37 -0
  340. package/src/nexus/api/trace.ts +19 -3
  341. package/src/nexus/index.ts +3 -0
  342. package/src/nexus/setup-status.ts +4 -5
  343. package/src/notify/llm-budget-alert.ts +125 -0
  344. package/src/oauth/codex-account-rotation.ts +26 -4
  345. package/src/oauth/codex-account-store.ts +20 -2
  346. package/src/oauth/codex-browser-login.ts +173 -0
  347. package/src/oauth/codex.ts +4 -1
  348. package/src/oauth/fallback-chain.ts +5 -5
  349. package/src/oauth/store.ts +42 -5
  350. package/src/onboarding/continue-here.ts +29 -0
  351. package/src/onboarding/entry-hints.ts +8 -0
  352. package/src/onboarding/full-screen-io.ts +35 -3
  353. package/src/onboarding/help/skills.md +1 -1
  354. package/src/onboarding/non-interactive.ts +1 -0
  355. package/src/onboarding/subscription-hint.ts +27 -0
  356. package/src/onboarding/web-first.ts +83 -0
  357. package/src/onboarding.ts +230 -23
  358. package/src/plugins/maker/node-install.ts +92 -0
  359. package/src/plugins/maker/node-maker.ts +139 -0
  360. package/src/plugins/maker/plugin-maker.ts +220 -0
  361. package/src/policy/llm-policy.ts +268 -0
  362. package/src/prompt-library/session-guidance.ts +7 -1
  363. package/src/prompt-library/universal-preamble.ts +2 -2
  364. package/src/relay/channel.ts +184 -0
  365. package/src/relay/mux.ts +182 -0
  366. package/src/relay/relay-cli.ts +20 -0
  367. package/src/relay/relay-server.ts +220 -0
  368. package/src/relay/test-vectors.json +29 -0
  369. package/src/release-loop/checklist.ts +36 -9
  370. package/src/seat-address/seat-address.ts +34 -0
  371. package/src/self-dev/decompose.ts +1 -0
  372. package/src/self-dev/dev-pipeline.ts +6 -3
  373. package/src/self-dev/run-record-mirror.ts +44 -0
  374. package/src/self-dev/run-store.ts +12 -3
  375. package/src/self-implement/harness-policy-gates.ts +4 -1
  376. package/src/self-implement/report-origin.ts +12 -1
  377. package/src/self-implement/run-ledger-compact.ts +1 -1
  378. package/src/session/chat.ts +17 -5
  379. package/src/session/index.ts +23 -10
  380. package/src/skills/shared-agent-skill-roots.ts +25 -0
  381. package/src/steward/failure-streak.ts +78 -0
  382. package/src/steward/steward-cards.ts +24 -0
  383. package/src/steward/triage-budget.ts +127 -0
  384. package/src/steward/triage-plan.ts +75 -0
  385. package/src/steward/triage.ts +244 -42
  386. package/src/task-cards/card-store.ts +1 -1
  387. package/src/task-orchestrator/surfaces/pod-bun-cache.ts +18 -0
  388. package/src/task-orchestrator/surfaces/pod-command-job.ts +12 -3
  389. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
  390. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +68 -0
  391. package/src/task-orchestrator/surfaces/self-implement-pod.ts +78 -4
  392. package/src/telegram.ts +145 -2
  393. package/src/tool-runtime/elanous-skills-list-runtime.ts +3 -3
  394. package/src/tool-runtime/self-cognition-runtimes.ts +160 -2
  395. package/src/user-config.ts +84 -4
  396. package/src/version/packed-revision.json +1 -1
  397. package/src/video-pipeline/recipes/character.ts +42 -5
  398. package/src/video-pipeline/recipes/explainer.ts +213 -0
  399. package/src/video-pipeline/recipes/ffmpeg.ts +1 -1
  400. package/src/video-pipeline/recipes/free-line.ts +16 -3
  401. package/src/video-pipeline/recipes/remote.ts +7 -1
  402. package/src/video-pipeline/vflow-reference.ts +109 -0
  403. package/src/video-pipeline/walk-line.ts +3 -2
  404. package/apps/pwa/out/_next/static/chunks/06ef2eba-d1b99ac6d2ea663c.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/1413-7179a49332db5a80.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/150-25c277137a9638ab.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/1640-bc99ec23bbe29d12.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/2300-de059d0ab2a16630.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/2684-05b0a81e75298c16.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/3026-2ed7e317d9b02102.js +0 -25
  411. package/apps/pwa/out/_next/static/chunks/3611-77496ada38cd6358.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/3717-68957c4dc9ceec90.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/3929-46ba9af6f9d895cc.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/4015-62c5a3744698a8da.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/4217-3c4317a7905f9eeb.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/4240-3230f230fae2d7d5.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/4271-e53e075ebd48593b.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/4377.3b125fef79576dfd.js +0 -5
  419. package/apps/pwa/out/_next/static/chunks/4853-35fdbe6bdbcdc0fc.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/5007-6a3b30e23ff68a6e.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/5244-12b424c42070f5ff.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/5611-df00af3e4e4a61bb.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/5890-dca2321773be829a.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/6123-37c9dc57ddaa7ab0.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/6588-b7975304a302ab91.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/6604-c03961962b6273b6.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/7109-cf8c87e968e83a65.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/7178-30f77877525a2efb.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/7262-871563fc9663730b.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/7294-f0eeebb92aa93459.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/7429-881af44deed6958f.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/7527.943753b6ed829f09.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/764-a01e858525a190c0.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/7868-e35d5f7985ecd3f1.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/8320-51484af7b01fdfa5.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/9131-52f5fad51cceca04.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/9238.2cd5f47f4bf7a2c3.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-70c404c7bb6d7f0f.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/approvals/page-3ffd80166b1050aa.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/bots/page-4bcc63e272ae8205.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/chat/page-880734bd07d62882.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/app/control/page-72bd384898d88955.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1018bbc9586ecae5.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/app/editor/page-de86f8c38076dd7b.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/app/healthz/route-70c404c7bb6d7f0f.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/app/intake/page-cd6447196d8bafe6.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/app/layout-e733630c47fa102b.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/app/live/page-d1c1a8357b161875.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/app/market/page-60c514128e81d787.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8b637b7e325e13c1.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/app/not-found-63301d11dbe73836.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/app/observatory/page-a8cf0dc472060db2.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/app/page-02fba54881ccfa49.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d5e8e90f0038f8de.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-b1ef6824f494466a.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/app/sessions/page-d589bb0f233b1009.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c9bbaebef4cda059.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/app/settings/page-3639ce7916b92fed.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7450ae7478955653.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/app/setup/layout-70c404c7bb6d7f0f.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/app/setup/page-b41ca4abfd618452.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/app/share/page-b333dd79ebee3fa9.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/app/showroom/page-8be1227492793655.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/app/tasks/page-6e03e9371b552e0d.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/app/term/page-911411e9fca18d03.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/app/trace/page-792170e649b37088.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/app/vault/page-c40463466937bc91.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-509c798b80384536.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/app/workflows/page-1cac2aa21d77c417.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9626799d9b4a2cd1.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-84cd419494e16b0f.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/framework-361487d2da603917.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/main-app-607f5beb94cb2e07.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/main-d0222d219e29dddf.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/pages/_app-a26980a31126decc.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/pages/_error-edb2b37decf23e4d.js +0 -1
  477. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
  478. package/apps/pwa/out/_next/static/ktMqfrBbf8BMGac0jqLrb/_buildManifest.js +0 -1
  479. /package/apps/pwa/out/_next/static/{ktMqfrBbf8BMGac0jqLrb → keajW7DDyghEs1y8oJe6T}/_ssgManifest.js +0 -0
@@ -0,0 +1,40 @@
1
+ // B1(UX · 0.2.7) — 짧은 «물음»은 도구 없이 빠른 경로로(첫 글자 ≤2초 · 내부 문서 `EVAL-bot-chat-vs-grok-2026-09-30`).
2
+ // 허용 방식(골 ASK-bot-short-question-fast-path-b1b): 아래가 «모두» 참일 때만 fast — 하나라도 거짓이면 fast 아님(보수 쪽).
3
+ // ⓐ 한 줄 · 80자 이하 · 첨부 없음 ⓑ 인사이거나 «물음 꼴» ⓒ 요청·작업 표지 · 경로·URL·코드 꼴 · 실시간 낱말이 없다.
4
+ // 거부 목록으로 변형을 다 막을 수는 없다 — 오분류 비용은 «확인해 볼까요?» 한 줄로 막힌다(도구가 안 실릴 뿐 대화는 이어진다).
5
+ // 판정 표본 = src/chat/short-question.test.ts 의 고정 표 20개(⊕ 리뷰가 든 변형).
6
+
7
+ export interface ShortQuestionContext {
8
+ hasAttachments?: boolean;
9
+ }
10
+ export interface ShortQuestionVerdict {
11
+ fast: boolean;
12
+ reason: string;
13
+ }
14
+
15
+ const GREETING = /^(?:안녕(?:하세요|하십니까)?|반가워(?:요)?|고마워(?:요)?|감사합니다|하이|hi|hello|hey|thanks|thank you)[!?.~\s]*$/i;
16
+ // «한 줄로» 같은 짧은 꼬리를 떼고 본다.
17
+ const TAIL = /\s*(?:한\s*줄로|짧게|간단히|briefly|in one line)[.!]?\s*$/i;
18
+ const KO_QUESTION_END = /(?:\?|까|니|나요|냐|야|어때|뭐|몇|언제|어디|누구|왜|인가|일까)[?.!~]*$/;
19
+ const EN_QUESTION = /^(?:what|why|how|who|where|when|which|is|are|does|do)\b.*\?$/i;
20
+ const REQUEST_KO = /(?:해\s*줘|해\s*주세요|줘(?:요)?(?=[?.!\s]|$)|주세요|줄래|할래|볼래|줄\s*수\s*있|돌려|실행|고쳐|만들어\s*줘|찾아|검색|확인해|열어|보내|올려|지워|써\s*줘|작성해|요약해|정리해|분석해)/;
21
+ const REQUEST_EN = /\b(?:please|can you|could you|would you|run|test|tests|fix|build|open|search|check|send|create|write|deploy|install|summarize)\b/i;
22
+ const TASK = /(?:작업|파일|폴더|디렉터리|레포|저장소|커밋|브랜치|\bPR\b|코드|스크립트|테스트|빌드|배포|설치|설정|명령어|로그|버그|이슈)/i;
23
+ const PATH_OR_CODE = /(?:https?:\/\/|www\.|[\w.-]+\/[\w./-]+|\.(?:ts|tsx|js|py|md|json|yaml|swift|kt)\b|[`{};=<>])/i;
24
+ const LIVE = /(?:오늘\s*(?:날짜|며칠|무슨\s*요일)|지금\s*몇\s*시|최신|뉴스|속보|주가|시세|환율|날씨|today'?s|latest|news|stock|weather|right now)/i;
25
+
26
+ export function classifyShortQuestion(text: string, ctx: ShortQuestionContext = {}): ShortQuestionVerdict {
27
+ const q = text.trim();
28
+ if (!q) return { fast: false, reason: 'empty' };
29
+ if (ctx.hasAttachments) return { fast: false, reason: 'attachment' };
30
+ if (/[\r\n\u2028\u2029]/.test(q)) return { fast: false, reason: 'multiline' };
31
+ if ([...q].length > 80) return { fast: false, reason: 'long' };
32
+ if (PATH_OR_CODE.test(q)) return { fast: false, reason: 'path-or-code' };
33
+ if (LIVE.test(q)) return { fast: false, reason: 'live' };
34
+ if (REQUEST_KO.test(q) || REQUEST_EN.test(q)) return { fast: false, reason: 'request' };
35
+ if (TASK.test(q)) return { fast: false, reason: 'task' };
36
+ if (GREETING.test(q)) return { fast: true, reason: 'greeting' };
37
+ const core = q.replace(TAIL, '').trim();
38
+ if (KO_QUESTION_END.test(core) || EN_QUESTION.test(core)) return { fast: true, reason: 'question' };
39
+ return { fast: false, reason: 'not-a-question' };
40
+ }
@@ -393,8 +393,8 @@ tierCmd
393
393
  * When `json` is true, the function suppresses the streaming text +
394
394
  * ui.info trailer and instead emits exactly ONE JSON line on stdout
395
395
  * at end-of-turn:
396
- * {sessionId, provider, model, reply, logPath, budget, durationMs,
397
- * turnIndex, ts}
396
+ * {sessionId, provider, model, reply, finalReply, transcript, logPath,
397
+ * budget, durationMs, ts}
398
398
  * Stable shape so LLMs can self-spawn `elanous chat` for follow-ups
399
399
  * without parsing human-readable terminal output. */
400
400
  export async function runChatTurnCli(opts: {
@@ -442,12 +442,14 @@ export async function runChatTurnCli(opts: {
442
442
  dbg.setAmbientSessionId?.(session.id);
443
443
  } catch { /* debug module unavailable — fine */ }
444
444
  const replyChunks: string[] = [];
445
- // 어시스턴트 메시지 경계(도구 호출)로 자른 본문 조각 — 마지막 «비어 있지 않은» 조각이 finalReply.
445
+ // Tool calls delimit assistant messages. finalReply retains its last non-empty-message meaning.
446
446
  let segmentChunks: string[] = [];
447
447
  let lastSegment = '';
448
+ let finalMessage = '';
449
+ let sawToolCall = false;
448
450
  const closeSegment = (): void => {
449
- const text = segmentChunks.join('').trim();
450
- if (text) lastSegment = text;
451
+ finalMessage = segmentChunks.join('').trim();
452
+ if (finalMessage) lastSegment = finalMessage;
451
453
  segmentChunks = [];
452
454
  };
453
455
  if (!opts.json) {
@@ -528,15 +530,17 @@ export async function runChatTurnCli(opts: {
528
530
  userText: opts.userText,
529
531
  systemPrompt: agentSystemPrompt,
530
532
  onDelta: (d) => {
531
- if (opts.json) { replyChunks.push(d); segmentChunks.push(d); }
532
- else process.stdout.write(d);
533
+ replyChunks.push(d);
534
+ segmentChunks.push(d);
535
+ if (!opts.json && !opts.enableTools) process.stdout.write(d);
533
536
  },
534
537
  tools: cliToolSpecs,
535
538
  dispatchTool,
536
539
  ...(opts.goalLoop ? { goalLoop: true } : {}),
537
540
  onToolCall: (call) => {
538
- // 도구 호출 뒤의 본문은 «새» 어시스턴트 메시지다 — 직전 조각을 마감한다(finalReply 용).
539
- if (opts.json) closeSegment();
541
+ // The next delta belongs to the next assistant message.
542
+ sawToolCall = true;
543
+ closeSegment();
540
544
  if (!opts.json) {
541
545
  process.stdout.write(`\n ⏺ ${call.name}(${truncateArgsForLog(call.args)})\n`);
542
546
  }
@@ -555,6 +559,12 @@ export async function runChatTurnCli(opts: {
555
559
  }
556
560
  throw err;
557
561
  }
562
+ if (!sawToolCall && replyChunks.length === 0 && result.text) {
563
+ replyChunks.push(result.text);
564
+ segmentChunks.push(result.text);
565
+ if (!opts.json && !opts.enableTools) process.stdout.write(result.text);
566
+ }
567
+ closeSegment();
558
568
  if (opts.json) {
559
569
  let logPath: string | null = null;
560
570
  try {
@@ -566,11 +576,9 @@ export async function runChatTurnCli(opts: {
566
576
  sessionId: session.id,
567
577
  provider: result.provider,
568
578
  model: result.model ?? null,
569
- reply: replyChunks.join(''),
570
- // ⭐ 2026-09-23 — `reply` 는 «모든 턴·모든 목표 루프 반복»의 본문을 이어 붙인다(뜻은 그대로 둔다 · 소비자 보호).
571
- // 그래서 모델이 반복마다 최종 답을 다시 말하면 같은 문장이 여러 번 나온다(실측: kimi 3회·grok 2회).
572
- // `finalReply` = 마지막 어시스턴트 메시지 본문 — 「최종 답만」이 필요한 소비자용(뒤호환 추가 칸).
573
- finalReply: (closeSegment(), lastSegment),
579
+ reply: finalMessage,
580
+ transcript: replyChunks.join(''),
581
+ finalReply: lastSegment,
574
582
  budget: sessionBudget(session.id),
575
583
  durationMs: Date.now() - startedAt,
576
584
  logPath,
@@ -579,7 +587,8 @@ export async function runChatTurnCli(opts: {
579
587
  await writeStdoutJson(JSON.stringify(out) + '\n');
580
588
  return;
581
589
  }
582
- process.stdout.write('\n');
590
+ if (opts.enableTools) process.stdout.write(`${finalMessage}\n`);
591
+ else process.stdout.write('\n');
583
592
  ui.info(`[session ${session.id.slice(0, 8)} ${result.provider}${result.model ? '/' + result.model : ''} ${sessionBudget(session.id)}]`);
584
593
  }
585
594
 
@@ -0,0 +1,42 @@
1
+ import { decideProviderForConfig, getProviderForConfig, streamLLM, type LLMMessage } from '../llm.js';
2
+ import { getUserConfig } from '../user-config.js';
3
+ import { DEFAULT_TOKEN_BUDGET, trimToBudget } from '../tokens.js';
4
+
5
+ export interface AskBareDeps {
6
+ getUserConfig: typeof getUserConfig;
7
+ decideProviderForConfig: typeof decideProviderForConfig;
8
+ getProviderForConfig: typeof getProviderForConfig;
9
+ streamLLM: typeof streamLLM;
10
+ trimToBudget: typeof trimToBudget;
11
+ }
12
+
13
+ export async function askBare({ text, provider, model, deps }: {
14
+ text: string;
15
+ provider?: string;
16
+ model?: string;
17
+ deps: AskBareDeps;
18
+ }): Promise<{ provider: string; model: string; reply: string }> {
19
+ const messages: LLMMessage[] = [{ role: 'user', content: text }];
20
+ if (deps.trimToBudget(messages, DEFAULT_TOKEN_BUDGET).used > DEFAULT_TOKEN_BUDGET) {
21
+ throw new Error(`ask --bare: input exceeds the ${DEFAULT_TOKEN_BUDGET}-token context budget`);
22
+ }
23
+ const cfg = deps.getUserConfig();
24
+ const selectedConfig = provider === undefined ? cfg : {
25
+ ...cfg,
26
+ llm: {
27
+ ...cfg.llm,
28
+ provider: provider as typeof cfg.llm.provider,
29
+ model: model ?? (provider === cfg.llm.provider ? cfg.llm.model : undefined),
30
+ },
31
+ };
32
+ const selected = deps.decideProviderForConfig(selectedConfig, model);
33
+ const chosenProvider = deps.getProviderForConfig(selectedConfig, model);
34
+ let resolvedProvider = chosenProvider.name;
35
+ let resolvedModel = selected.model;
36
+ const reply = await deps.streamLLM(messages, () => {}, {
37
+ ...(provider === undefined ? { initialProvider: chosenProvider } : { provider: chosenProvider }),
38
+ ...(selected.model === '(none)' ? {} : { model: selected.model }),
39
+ onResolvedProvider: (name, activeModel) => { resolvedProvider = name; resolvedModel = activeModel; },
40
+ });
41
+ return { provider: resolvedProvider, model: resolvedModel, reply };
42
+ }
@@ -0,0 +1,25 @@
1
+ import { HarnessCliInputError } from '../harness/harness-cli-command.js';
2
+
3
+ const cliUserErrorBrand = Symbol.for('elanous.cli.CliUserError');
4
+ const harnessCliInputErrorBrand = Symbol.for('elanous.cli.HarnessCliInputError');
5
+
6
+ export class CliUserError extends Error {
7
+ constructor(message: string, public readonly hint?: string) {
8
+ super(message);
9
+ this.name = 'CliUserError';
10
+ Object.defineProperty(this, cliUserErrorBrand, { value: true });
11
+ }
12
+ }
13
+
14
+ export function isCliUserError(err: unknown): err is Error & { hint?: string } {
15
+ return err instanceof HarnessCliInputError || err instanceof CliUserError || (
16
+ err instanceof Error && (
17
+ (err.name === 'CliUserError' && Object.getOwnPropertyDescriptor(err, cliUserErrorBrand)?.value === true) ||
18
+ (err.name === 'HarnessCliInputError' && Object.getOwnPropertyDescriptor(err, harnessCliInputErrorBrand)?.value === true)
19
+ )
20
+ );
21
+ }
22
+
23
+ export function formatCliUserError(err: Error & { hint?: string }): string {
24
+ return `❌ ${err.message}${err.hint ? `\n ↳ ${err.hint}` : ''}`;
25
+ }
@@ -0,0 +1,25 @@
1
+ // Assembles the codex credit plan from disk for `provider codex status` and `usage` — policy file, the last
2
+ // measured per-account balances and the balance history. No network.
3
+ import { codexCreditPlan, readCreditHistory, type CodexCreditPlan } from '../budget/codex-credit-plan.js';
4
+ import { quotaSignalDir } from '../budget/codex-reset-credit-state.js';
5
+ import { inspectCodexRotation } from '../oauth/codex-account-store.js';
6
+ import { loadLlmPolicy } from '../policy/llm-policy.js';
7
+
8
+ export function codexCreditPlanFromDisk(now: number = Date.now()): CodexCreditPlan {
9
+ const s = inspectCodexRotation(process.env, { now });
10
+ const seen = new Set<string>();
11
+ const balances: Array<{ name: string; balance?: number }> = [];
12
+ const push = (key: string, name: string, balance: number | undefined) => {
13
+ if (seen.has(key)) return;
14
+ seen.add(key);
15
+ balances.push({ name, ...(balance === undefined ? {} : { balance }) });
16
+ };
17
+ push(s.current.storeKey, s.current.name, s.currentCreditBalance);
18
+ for (const c of s.candidates) push(c.storeKey, c.name, c.creditBalance);
19
+ return codexCreditPlan({
20
+ now,
21
+ balances,
22
+ credits: loadLlmPolicy({ now: new Date(now) }).policy.credits,
23
+ history: readCreditHistory(quotaSignalDir()),
24
+ });
25
+ }
@@ -0,0 +1,237 @@
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { join, resolve } from 'node:path';
4
+ import { elanousStateRoot } from '../autopilot/state-paths.js';
5
+ import type { Command } from 'commander';
6
+
7
+ /** Attribution is evidence about the executor, not about the person who requested the work. */
8
+ export type DependenceAttribution = 'claude-code' | 'elanous' | 'other' | 'unknown';
9
+ export type DependenceAxis = 'positions' | 'executions' | 'changes' | 'decisions';
10
+ export interface DependenceObservation {
11
+ at?: string;
12
+ runtime?: string;
13
+ agent?: string;
14
+ track?: string;
15
+ /** A direct attribution supplied by a source record; never inferred from the author. */
16
+ actor?: string;
17
+ }
18
+ export interface DependenceSource {
19
+ observations: readonly DependenceObservation[];
20
+ /** A missing source is different from an observed empty source. */
21
+ unmeasurable?: string;
22
+ }
23
+ export interface DependenceAxisResult {
24
+ status: 'measured' | 'unmeasurable';
25
+ total: number;
26
+ covered: number;
27
+ claudeCode: number;
28
+ elanous: number;
29
+ other: number;
30
+ unknown: number;
31
+ /** Records whose timestamp cannot place them inside or outside the requested window. Not in total. */
32
+ undated: number;
33
+ reason?: string;
34
+ }
35
+ export interface DependenceReport {
36
+ since: string;
37
+ until: string;
38
+ axes: Record<DependenceAxis, DependenceAxisResult>;
39
+ }
40
+ export interface DependenceGaugeDeps {
41
+ now?: () => Date;
42
+ readPositions?: () => DependenceSource;
43
+ readExecutions?: () => DependenceSource;
44
+ readChanges?: (window: { since: string; until: string }) => DependenceSource;
45
+ readDecisions?: () => DependenceSource;
46
+ out?: Pick<Console, 'log'>;
47
+ }
48
+
49
+ const axes = ['positions', 'executions', 'changes', 'decisions'] as const;
50
+ const identity = (value: unknown): DependenceAttribution => {
51
+ if (typeof value !== 'string' || !value.trim()) return 'unknown';
52
+ const normalized = value.trim().toLowerCase();
53
+ if (normalized === 'claude-code' || normalized === 'claude code') return 'claude-code';
54
+ if (normalized === 'elanous') return 'elanous';
55
+ if (normalized === 'unknown') return 'unknown';
56
+ return 'other';
57
+ };
58
+
59
+ /** Explicit per-event runtime wins over an agent label, which wins over the seat default. */
60
+ export function attributeDependence(row: DependenceObservation, positions: ReadonlyMap<string, DependenceAttribution>): DependenceAttribution {
61
+ if (row.actor?.trim()) return identity(row.actor);
62
+ if (row.runtime?.trim()) return identity(row.runtime);
63
+ if (row.agent?.trim()) return identity(row.agent);
64
+ return row.track ? (positions.get(row.track) ?? 'unknown') : 'unknown';
65
+ }
66
+
67
+ function instant(raw: string, label: string): Date {
68
+ // Date.parse silently normalizes impossible days (e.g. February 30), including timestamps.
69
+ const calendarDate = /^(\d{4}-\d{2}-\d{2})(?:T|$)/.exec(raw)?.[1];
70
+ if (calendarDate) {
71
+ const day = new Date(`${calendarDate}T00:00:00.000Z`);
72
+ if (!Number.isFinite(day.getTime()) || day.toISOString().slice(0, 10) !== calendarDate) {
73
+ throw new Error(`invalid ${label}: ${raw}`);
74
+ }
75
+ }
76
+ const date = new Date(calendarDate === raw ? `${raw}T00:00:00.000Z` : raw);
77
+ if (!Number.isFinite(date.getTime())) throw new Error(`invalid ${label}: ${raw}`);
78
+ return date;
79
+ }
80
+
81
+ function observationTime(raw: string | undefined): number | undefined {
82
+ if (!raw) return undefined;
83
+ const match = /^(\d{4})-(\d{2})-(\d{2})T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.exec(raw);
84
+ if (!match) return undefined;
85
+ const day = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3])));
86
+ if (day.toISOString().slice(0, 10) !== raw.slice(0, 10)) return undefined;
87
+ const time = Date.parse(raw);
88
+ return Number.isFinite(time) ? time : undefined;
89
+ }
90
+
91
+ export function dependenceWindow(since: string | undefined, until: string | undefined, now: Date): { since: string; until: string } {
92
+ const end = until ? instant(until, '--until') : now;
93
+ if (!Number.isFinite(end.getTime())) throw new Error('invalid current date');
94
+ // Date-only --until includes that entire UTC day; relative lookback is anchored to its end.
95
+ const inclusiveEnd = until && /^\d{4}-\d{2}-\d{2}$/.test(until)
96
+ ? new Date(end.getTime() + 86_400_000 - 1) : end;
97
+ const match = /^(\d+)d$/.exec(since ?? '7d');
98
+ const start = match ? new Date(inclusiveEnd.getTime() - Number(match[1]) * 86_400_000)
99
+ : instant(since ?? '7d', '--since');
100
+ if (!Number.isFinite(start.getTime()) || start > inclusiveEnd) throw new Error('invalid --since: start must be at or before --until');
101
+ return { since: start.toISOString(), until: inclusiveEnd.toISOString() };
102
+ }
103
+
104
+ export function readSeatRegistry(): DependenceSource {
105
+ try {
106
+ const raw = JSON.parse(readFileSync(resolve(import.meta.dir, '../../scripts/coord-tracks.json'), 'utf8')) as { tracks?: unknown };
107
+ if (!Array.isArray(raw.tracks)) return { observations: [], unmeasurable: 'seat registry has no tracks' };
108
+ return { observations: raw.tracks.filter((track): track is { id: string; title: string; runtime?: string } =>
109
+ typeof track === 'object' && track !== null && typeof track.id === 'string' && typeof track.title === 'string')
110
+ .map(track => ({ track: track.id, runtime: track.runtime })) };
111
+ } catch (error) {
112
+ return { observations: [], unmeasurable: `seat registry unreadable: ${error instanceof Error ? error.message : String(error)}` };
113
+ }
114
+ }
115
+
116
+ function unknownSource(description: string): DependenceSource {
117
+ return { observations: [], unmeasurable: description };
118
+ }
119
+
120
+ function stringField(value: unknown): string | undefined {
121
+ return typeof value === 'string' && value.trim() ? value : undefined;
122
+ }
123
+
124
+ function object(value: unknown): Record<string, unknown> {
125
+ return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
126
+ }
127
+
128
+ export function readRunExecutions(directory = join(elanousStateRoot(), 'run-ledger')): DependenceSource {
129
+ try {
130
+ if (!existsSync(directory)) return unknownSource('run-ledger directory absent');
131
+ const observations: DependenceObservation[] = [];
132
+ for (const name of readdirSync(directory).filter(name => name.endsWith('.jsonl')).sort()) {
133
+ const lines = readFileSync(join(directory, name), 'utf8').split('\n').filter(Boolean);
134
+ let launch: DependenceObservation | undefined;
135
+ for (const line of lines) {
136
+ const entry = object(JSON.parse(line));
137
+ if (entry.event === 'run-origin') {
138
+ const data = object(entry.data);
139
+ launch = { at: stringField(entry.timestamp), runtime: stringField(data.runtime),
140
+ agent: stringField(data.agent), track: stringField(data.track) };
141
+ break;
142
+ }
143
+ if (!launch) launch = { at: stringField(entry.timestamp) };
144
+ }
145
+ if (launch) observations.push(launch);
146
+ }
147
+ return { observations };
148
+ } catch (error) {
149
+ return unknownSource(`run-ledger unreadable: ${error instanceof Error ? error.message : String(error)}`);
150
+ }
151
+ }
152
+
153
+ /** 이 저장소의 실제 표지 — `Co-Authored-By:` 트레일러(작성자·커미터는 전부 같은 사람이라 쓰지 않는다).
154
+ * 하니스 착지(`elanous pod child`) > codex > Claude 순 — 한 병합에 여러 표지면 실제로 코드를 쓴 쪽이 앞선다. */
155
+ export function runtimeFromCoAuthors(body: string): string | undefined {
156
+ const names = [...body.matchAll(/^co-authored-by:[^\S\r\n]*([^<\r\n]*)/gim)].map(m => m[1]!.trim().toLowerCase());
157
+ if (names.some(n => n.includes('elanous'))) return 'elanous';
158
+ if (names.some(n => n.includes('codex'))) return 'codex';
159
+ if (names.some(n => n.includes('claude'))) return 'claude-code';
160
+ return undefined;
161
+ }
162
+
163
+ export function readChangeCommits(window: { since: string; until: string }, repo = resolve(import.meta.dir, '../..')): DependenceSource {
164
+ // Git object reads only: author and committer are deliberately NOT treated as the executor.
165
+ // 착지 = main 의 첫 부모 줄(스쿼시 병합 하나 = 착지 하나) — `--all` 은 가지의 중간 커밋까지 세어 부풀린다.
166
+ const result = spawnSync('git', ['log', 'origin/main', '--first-parent', '--format=%cI%x1f%B%x1e', `--since-as-filter=${window.since}`, `--until=${window.until}`], {
167
+ cwd: repo, encoding: 'utf8', timeout: 15_000, maxBuffer: 16 * 1024 * 1024,
168
+ });
169
+ if (result.status !== 0 || result.error) return unknownSource(`git history unreadable: ${result.error?.message ?? result.stderr ?? result.status}`);
170
+ if (result.stdout.length >= 16 * 1024 * 1024 - 1024) return unknownSource('git history exceeds gauge read limit');
171
+ const observations = result.stdout.split('\x1e').filter(record => record.includes('\x1f')).map(record => {
172
+ const [at, body] = record.trim().split('\x1f', 2);
173
+ const attribution = /(?:^|\n)(?:Executed-By|Agent-Runtime|Runtime):[^\S\r\n]*([^\r\n]*)/im.exec(body ?? '');
174
+ return { at, runtime: attribution?.[1]?.trim() ?? runtimeFromCoAuthors(body ?? '') };
175
+ });
176
+ return { observations };
177
+ }
178
+
179
+ /** No mutation of ledgers or state; absent attribution data is reported, never guessed from an author or requester. */
180
+ export function measureDependence(deps: DependenceGaugeDeps = {}, options: { since?: string; until?: string } = {}): DependenceReport {
181
+ const window = dependenceWindow(options.since, options.until, (deps.now ?? (() => new Date()))());
182
+ const sources: Record<DependenceAxis, DependenceSource> = {
183
+ positions: (deps.readPositions ?? readSeatRegistry)(),
184
+ executions: (deps.readExecutions ?? readRunExecutions)(),
185
+ changes: (deps.readChanges ?? readChangeCommits)(window),
186
+ decisions: (deps.readDecisions ?? (() => unknownSource('decision actor cannot be measured from the available data')))(),
187
+ };
188
+ const positions = new Map<string, DependenceAttribution>();
189
+ for (const row of sources.positions.observations) {
190
+ if (row.track) positions.set(row.track, identity(row.runtime));
191
+ }
192
+ const result = {} as Record<DependenceAxis, DependenceAxisResult>;
193
+ for (const axis of axes) {
194
+ const source = sources[axis];
195
+ const counts: DependenceAxisResult = { status: source.unmeasurable ? 'unmeasurable' : 'measured', total: 0, covered: 0, claudeCode: 0, elanous: 0, other: 0, unknown: 0, undated: 0,
196
+ ...(source.unmeasurable ? { reason: source.unmeasurable } : {}) };
197
+ for (const row of source.observations) {
198
+ if (axis !== 'positions') {
199
+ const at = observationTime(row.at);
200
+ if (at === undefined) { counts.undated++; continue; }
201
+ if (at < Date.parse(window.since) || at > Date.parse(window.until)) continue;
202
+ }
203
+ counts.total++;
204
+ const attribution = attributeDependence(row, positions);
205
+ if (attribution === 'unknown') counts.unknown++;
206
+ else {
207
+ counts.covered++;
208
+ if (attribution === 'claude-code') counts.claudeCode++;
209
+ else if (attribution === 'elanous') counts.elanous++;
210
+ else counts.other++;
211
+ }
212
+ }
213
+ result[axis] = counts;
214
+ }
215
+ return { ...window, axes: result };
216
+ }
217
+
218
+ export function formatDependenceReport(report: DependenceReport): string {
219
+ return [
220
+ `Dependence · ${report.since} — ${report.until}`,
221
+ ...axes.map(axis => {
222
+ const row = report.axes[axis];
223
+ return `${axis}: ${row.status}${row.reason ? ` (${row.reason})` : ''} · coverage ${row.covered}/${row.total} · claude-code ${row.claudeCode} · elanous ${row.elanous} · other ${row.other} · unknown ${row.unknown} · undated ${row.undated}`;
224
+ }),
225
+ ].join('\n');
226
+ }
227
+
228
+ /** Register below the existing `self` command rather than creating a second top-level command. */
229
+ export function registerDependenceCommand(self: Command, deps: DependenceGaugeDeps = {}): void {
230
+ self.command('dependence').description('Read-only attribution gauge (unknown is not zero)')
231
+ .option('--since <date>', 'UTC date or lookback period (default 7d)', '7d')
232
+ .option('--until <date>', 'UTC date or timestamp').option('--json', 'JSON output')
233
+ .action((options: { since?: string; until?: string; json?: boolean }) => {
234
+ const report = measureDependence(deps, options);
235
+ (deps.out ?? console).log(options.json ? JSON.stringify(report) : formatDependenceReport(report));
236
+ });
237
+ }
@@ -34,6 +34,7 @@ import { ADVISABLE_DOCTOR_FIX_IDS, adviseDoctorFixes, type DoctorAdvice } from '
34
34
  import { gitInstallPlan } from './git-install-plan.js';
35
35
  import { detectDistroFamily, parseOsRelease } from './doctor-distro.js';
36
36
  import { checkPythonEnv } from './python-cli.js';
37
+ import { describeModes, formatModeLine, type DoctorMode } from './doctor-modes.js';
37
38
 
38
39
  function defaultCheckPythonEnv(): { status: 'ok' | 'fixable' | 'manual'; evidence: string; remedy?: string } {
39
40
  const c = checkPythonEnv(false);
@@ -99,6 +100,8 @@ export interface DoctorReport {
99
100
  externalCommands: DoctorExternalCommand[];
100
101
  requiredMissing?: string[];
101
102
  capabilitySummary?: DoctorCapabilitySummary;
103
+ /** Read-only configuration and chat-use status. Present on a successful report. */
104
+ modes?: DoctorMode[];
102
105
  /** F1 readiness. Present on a successful report. Absent when the report itself failed. */
103
106
  readiness?: ReadinessReport;
104
107
  reason?: string;
@@ -1044,6 +1047,7 @@ export function runDoctor(options: DoctorOptions = {}): DoctorReport {
1044
1047
  externalCommands: externalCommands.commands,
1045
1048
  requiredMissing,
1046
1049
  capabilitySummary: summarizeDoctorCapabilities(credentials),
1050
+ modes: describeModes(resolutionOptions.userConfig),
1047
1051
  readiness: evaluateReadiness(readiness),
1048
1052
  retiredConfigKeys: findRetiredConfigKeysInFile(options.configPath, readFile),
1049
1053
  ...(resourceMetadata.available ? {} : { catalogMetadataUnavailable: true }),
@@ -1136,6 +1140,7 @@ export function formatDoctorReport(report: DoctorReport, options: { credentials?
1136
1140
  }),
1137
1141
  ...(report.externalCommandsCatalogUnavailable ? [`External commands catalog unavailable: ${report.externalCommandsCatalogReason ?? 'catalog/external-commands.yaml is unavailable.'}`] : []),
1138
1142
  ...formatCapabilitySummary(capabilitySummary),
1143
+ ...(report.modes ? ['모드:', ...report.modes.map((row) => ` ${formatModeLine(row)}`)] : []),
1139
1144
  ].join('\n');
1140
1145
  }
1141
1146
 
@@ -0,0 +1,48 @@
1
+ import { DEFAULT_MODEL_TIER, isModelTier, type ModelTier } from '../model-tier/types.js';
2
+
3
+ export interface DoctorMode {
4
+ id: 'model-tier' | 'auto-route' | 'fast-mode';
5
+ value: ModelTier | boolean | 'none';
6
+ source: 'config' | 'default' | 'none';
7
+ usedByChat: boolean;
8
+ note: string;
9
+ }
10
+
11
+ /** Report configured switches without changing them or implying they affect chat turns. */
12
+ export function describeModes(cfg: { modelTier?: { llm?: unknown }; llm?: { autoRoute?: { enabled?: unknown } } }): DoctorMode[] {
13
+ const tier = cfg.modelTier?.llm;
14
+ const autoRoute = cfg.llm?.autoRoute?.enabled;
15
+ return [
16
+ {
17
+ id: 'model-tier',
18
+ value: isModelTier(tier) ? tier : DEFAULT_MODEL_TIER,
19
+ source: isModelTier(tier) ? 'config' : 'default',
20
+ usedByChat: false,
21
+ note: '대화 턴 모델 선택에 쓰이지 않는다(표시·전환 계획만)',
22
+ },
23
+ {
24
+ id: 'auto-route',
25
+ value: typeof autoRoute === 'boolean' ? autoRoute : false,
26
+ source: typeof autoRoute === 'boolean' ? 'config' : 'default',
27
+ usedByChat: false,
28
+ note: '오토파일럿 턴만 읽는다 · 켜기 = elanous config set llm.autoRoute.enabled true',
29
+ },
30
+ {
31
+ id: 'fast-mode',
32
+ value: 'none',
33
+ source: 'none',
34
+ usedByChat: false,
35
+ note: '스위치 없음(분류기 호출부 0)',
36
+ },
37
+ ];
38
+ }
39
+
40
+ export function formatModeLine(row: DoctorMode): string {
41
+ const label = {
42
+ 'model-tier': 'smart·등급',
43
+ 'auto-route': 'smart·자동 라우팅',
44
+ 'fast-mode': '빠른 모드',
45
+ }[row.id];
46
+ const source = { config: '설정', default: '기본', none: '없음' }[row.source];
47
+ return `${label}: ${String(row.value)}(${source}) · 대화에 ${row.usedByChat ? '쓰임' : '안 쓰임'} — ${row.note}`;
48
+ }
@@ -11,6 +11,7 @@ import { writeStdoutFully } from './stdout-flush.js';
11
11
  import { runGitCommand } from '../git-fs/runner.js';
12
12
  import { debug } from '../debug/log.js';
13
13
  import { getUserConfig } from '../user-config.js';
14
+ import { registerIntakeSourceCommands } from './intake-source-cli.js';
14
15
 
15
16
  export interface IntakeToTasksCliDeps {
16
17
  root: string;
@@ -91,6 +92,7 @@ export function resolveIntakeCheckRoot(
91
92
  export function registerIntakeCommands(program: Command): void {
92
93
  // ── intake check — 바깥 사실을 elanous 현재와 대조 (태스크 등록 없음) ──
93
94
  const intakeCmd = program.command('intake').description('바깥 사실·문서를 elanous 현재와 대조하거나 태스크로 받는다');
95
+ registerIntakeSourceCommands(intakeCmd);
94
96
  intakeCmd.hook('preAction', async () => {
95
97
  try {
96
98
  const { registerStandaloneLogSink } = await import('../domains/standalone-log-sink.js');
@@ -217,7 +219,9 @@ export function registerIntakeCommands(program: Command): void {
217
219
  const { readPipedStdin } = await import('./piped-stdin.js');
218
220
  const text = opts.file ? readFileSync(opts.file, 'utf8') : (await readPipedStdin()) ?? '';
219
221
  const { raws, bad } = parseRawIntakeJsonl(text);
220
- const result = ingestIntakeItems(effectiveInstanceRoot(), opts.source as (typeof INTAKE_SOURCES)[number], raws);
222
+ let result: ReturnType<typeof ingestIntakeItems>;
223
+ try { result = ingestIntakeItems(effectiveInstanceRoot(), opts.source as (typeof INTAKE_SOURCES)[number], raws); }
224
+ catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 2; return; }
221
225
  const out = { source: opts.source, inputLines: raws.length + bad, badInputLines: bad, ...result };
222
226
  if (opts.json) await writeStdoutFully(JSON.stringify(out, null, 2));
223
227
  else console.log(`흡수 원장 · ${opts.source}: 새 ${result.added} · 합침 ${result.merged} · 이미 끝난 것 ${result.seen} · 버림 ${result.skipped}${bad ? ` · 깨진 입력 ${bad}` : ''}${result.badLines ? ` · 깨진 원장 줄 ${result.badLines}` : ''}`);
@@ -228,14 +232,20 @@ export function registerIntakeCommands(program: Command): void {
228
232
  .description('흡수 원장 항목 보기 (최근 본 순)')
229
233
  .option('--status <status>', 'new | queued | absorbed | checked | routed | discarded | deferred')
230
234
  .option('--source <source>', '입력원으로 거르기')
235
+ .option('--seat <id>', '자리 id (별칭 허용; 자리 없는 옛 항목은 제외)')
231
236
  .option('--limit <n>', '최대 줄 수 (기본 30)')
232
237
  .option('--json', '구조화 출력')
233
- .action(async (opts: { status?: string; source?: string; limit?: string; json?: boolean }) => {
238
+ .action(async (opts: { status?: string; source?: string; seat?: string; limit?: string; json?: boolean }) => {
234
239
  const { listIntakeItems } = await import('../intake-plane/items.js');
235
240
  const { effectiveInstanceRoot } = await import('../instance/resolve.js');
241
+ const { canonicalSeat } = await import('../intake-plane/intake-sources.js');
242
+ let seat: string | undefined;
243
+ try { seat = opts.seat === undefined ? undefined : canonicalSeat(opts.seat); }
244
+ catch (error) { console.error((error as Error).message); process.exitCode = 2; return; }
236
245
  const all = listIntakeItems(effectiveInstanceRoot(), {
237
246
  ...(opts.status ? { status: opts.status as never } : {}),
238
247
  ...(opts.source ? { source: opts.source as never } : {}),
248
+ ...(seat ? { seat } : {}),
239
249
  });
240
250
  const limit = Math.max(1, Number(opts.limit ?? 30) || 30);
241
251
  const shown = all.slice(0, limit);
@@ -0,0 +1,58 @@
1
+ import type { Command } from 'commander';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
4
+ import { addSource, canonicalSeat, dueSources, expiredSource, listSources, removeSource, runDue, type SourceKind } from '../intake-plane/intake-sources.js';
5
+
6
+ export function registerIntakeSourceCommands(intake: Command, root: () => string = effectiveInstanceRoot): void {
7
+ const source = intake.command('source').description('자리별 흡수 원천과 주기 등록·조회·실행');
8
+ const fail = (error: unknown) => { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 2; };
9
+ source.command('add')
10
+ .requiredOption('--seat <id>', '자리 id 또는 별칭')
11
+ .requiredOption('--kind <kind>', 'github-query | rss | command')
12
+ .requiredOption('--spec <spec>', 'GitHub 질의 · 피드 URL · 명령')
13
+ .requiredOption('--every <cadence>', 'Nh | Nd | Nw')
14
+ .option('--until <date>', '만료 시작 날짜 YYYY-MM-DD (UTC)')
15
+ .option('--why <reason>', '등록 이유')
16
+ .option('--id <id>', '원천 id')
17
+ .action((opts: { seat: string; kind: string; spec: string; every: string; until?: string; why?: string; id?: string }) => {
18
+ try {
19
+ const added = addSource({ id: opts.id ?? `src-${randomUUID().slice(0, 8)}`, seat: opts.seat, kind: opts.kind as SourceKind,
20
+ spec: opts.spec, every: opts.every, ...(opts.until ? { until: opts.until } : {}), ...(opts.why ? { why: opts.why } : {}) }, root());
21
+ console.log(`${added.id} ${added.seat} ${added.kind} ${added.every}`);
22
+ } catch (error) { fail(error); }
23
+ });
24
+ source.command('list')
25
+ .option('--seat <id>', '자리 id 또는 별칭')
26
+ .option('--json', 'JSON 출력')
27
+ .action((opts: { seat?: string; json?: boolean }) => {
28
+ try {
29
+ const now = new Date();
30
+ const sources = listSources({ seat: opts.seat }, root()).map((entry) => ({ ...entry, expired: expiredSource(entry, now) }));
31
+ if (opts.json) console.log(JSON.stringify(sources));
32
+ else for (const entry of sources) console.log(`${entry.id} ${entry.seat} ${entry.kind} ${entry.every} ${entry.expired ? '만료' : '활성'} ${entry.spec}`);
33
+ } catch (error) { fail(error); }
34
+ });
35
+ source.command('remove <id>').action((id: string) => {
36
+ try { if (!removeSource(id, root())) { console.error(`원천 id 없음: ${id}`); process.exitCode = 2; return; } console.log(`removed ${id}`); }
37
+ catch (error) { fail(error); }
38
+ });
39
+ source.command('run-due')
40
+ .option('--seat <id>', '자리 id 또는 별칭')
41
+ .option('--dry-run', '기한 원천 목록만 · 수집·원장·마지막 실행 시각 무변')
42
+ .option('--json', 'JSON 출력')
43
+ .action(async (opts: { seat?: string; dryRun?: boolean; json?: boolean }) => {
44
+ try {
45
+ const seat = opts.seat === undefined ? undefined : canonicalSeat(opts.seat);
46
+ if (opts.dryRun) {
47
+ const sources = dueSources(new Date(), root()).filter((entry) => seat === undefined || entry.seat === seat);
48
+ if (opts.json) console.log(JSON.stringify(sources));
49
+ else for (const entry of sources) console.log(`${entry.id} ${entry.seat} ${entry.kind} ${entry.every}`);
50
+ return;
51
+ }
52
+ const result = await runDue({ seat, deps: { stateDir: root() } });
53
+ if (opts.json) console.log(JSON.stringify(result));
54
+ else for (const row of result.ran) console.log(`${row.id} ${row.items}${row.error ? ` 실패: ${row.error}` : ''}`);
55
+ if (result.ran.some((row) => row.error)) process.exitCode = 1;
56
+ } catch (error) { fail(error); }
57
+ });
58
+ }