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
@@ -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
+ }
@@ -1,4 +1,6 @@
1
1
  import type { Command } from 'commander';
2
+ import { readFileSync, writeFileSync } from 'node:fs';
3
+ import { loadMachineLedger, machineMarkdownPath, renderLedgerMarkdown, setMachineField } from '../machines/machine-ledger.js';
2
4
  import { MACHINE_NAME, resolveMachineName } from '../roles/machine-name.js';
3
5
  import { readMachineProfile, seatRank, validSeatRank, writeMachineProfile } from '../roles/machine-profile.js';
4
6
 
@@ -28,6 +30,27 @@ export function registerMachineCommands(program: Command, root?: string): void {
28
30
  writeMachineProfile({ id, duties, seats }, root);
29
31
  console.log(`machine · ${id} · ${duties.join(',') || '-'} · ${Object.entries(seats).map(([seat, value]) => `${seat}:${value.rank}`).join(',') || '-'}`);
30
32
  });
33
+ machine.command('render').description('YAML 원장에서 Markdown 생성·검증')
34
+ .option('--check', 'Markdown 과 YAML 동기화 검증')
35
+ .action((opts: { check?: boolean }) => {
36
+ const markdown = renderLedgerMarkdown(loadMachineLedger(process.cwd()));
37
+ const path = machineMarkdownPath(process.cwd());
38
+ if (opts.check) {
39
+ let current: string;
40
+ try { current = readFileSync(path, 'utf8'); } catch { throw new Error(`machine ledger Markdown missing: ${path}`); }
41
+ if (current !== markdown) throw new Error(`machine ledger Markdown stale: ${path}`);
42
+ console.log('machine render --check OK');
43
+ } else {
44
+ writeFileSync(path, markdown, 'utf8');
45
+ console.log(`machine render · ${path}`);
46
+ }
47
+ });
48
+ machine.command('ledger-set').description('YAML 보유 자원 필드 수정')
49
+ .argument('<id>').argument('<field>').argument('<value>')
50
+ .action((id: string, field: string, value: string) => {
51
+ setMachineField(process.cwd(), id, field, value);
52
+ console.log(`machine ledger-set · ${id} · ${field}`);
53
+ });
31
54
  machine.command('show').option('--json', 'JSON 출력')
32
55
  .action((opts: { json?: boolean }) => {
33
56
  const resolved = resolveMachineName({ root });
@@ -0,0 +1,137 @@
1
+ import type { Command } from 'commander';
2
+ import { canonicalSeatId, openMsgStore, type Message, type MsgStore } from '../msg/msg-store.js';
3
+
4
+ export interface MsgCliDeps {
5
+ openStore?: () => MsgStore;
6
+ out?: Pick<Console, 'log' | 'error'>;
7
+ readStdin?: () => Promise<string>;
8
+ sleep?: (ms: number) => Promise<void>;
9
+ setExitCode?: (code: number) => void;
10
+ signal?: AbortSignal;
11
+ }
12
+
13
+ type CommonOptions = { json?: boolean };
14
+ type RecipientOptions = CommonOptions & { to?: string; after?: string; limit?: string };
15
+
16
+ function seat(raw: string | undefined, flag: string): string {
17
+ if (raw === undefined) throw new Error(`${flag} is required`);
18
+ return canonicalSeatId(raw);
19
+ }
20
+
21
+ function integer(raw: string | undefined, flag: string, fallback: number, min: number, max: number): number {
22
+ if (raw === undefined) return fallback;
23
+ if (!/^(0|[1-9]\d*)$/.test(raw)) throw new Error(`${flag} must be an integer between ${min} and ${max}`);
24
+ const value = Number(raw);
25
+ if (!Number.isSafeInteger(value) || value < min || value > max) throw new Error(`${flag} must be an integer between ${min} and ${max}`);
26
+ return value;
27
+ }
28
+
29
+ function formatMessage(message: Message): string {
30
+ return `#${message.id} ${message.createdAt} ${message.from} → ${message.to}${message.kind ? ` [${message.kind}]` : ''}: ${message.body}`;
31
+ }
32
+
33
+ /** Register the durable, recipient-addressed CLI. Reading never advances the acknowledgement cursor. */
34
+ export function registerMsgCommands(program: Command, deps: MsgCliDeps = {}): void {
35
+ const open = deps.openStore ?? (() => openMsgStore());
36
+ const out = deps.out ?? console;
37
+ const readStdin = deps.readStdin ?? (() => Bun.stdin.text());
38
+ const sleep = deps.sleep ?? ((ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms)));
39
+ const setExitCode = deps.setExitCode ?? ((code: number) => { process.exitCode = code; });
40
+ const msg = program.command('msg').description('Durable seat-addressed messages');
41
+
42
+ const run = (action: () => void | Promise<void>) => async () => {
43
+ try { await action(); }
44
+ catch (error) {
45
+ out.error(`msg: ${error instanceof Error ? error.message : String(error)}`);
46
+ setExitCode(2);
47
+ }
48
+ };
49
+ const withStore = <T>(action: (store: MsgStore) => T): T => {
50
+ const store = open();
51
+ try { return action(store); }
52
+ finally { store.close(); }
53
+ };
54
+ const emit = (value: unknown, json: boolean | undefined, human: () => void) => {
55
+ if (json) out.log(JSON.stringify(value));
56
+ else human();
57
+ };
58
+
59
+ msg.command('post [body]').description('Post a message; use - or omit body to read stdin')
60
+ .option('--from <seat>').option('--to <seat>').option('--kind <kind>').option('--body <text>').option('--json')
61
+ .action((body: string | undefined, opts: CommonOptions & { from?: string; to?: string; kind?: string; body?: string }) => run(async () => {
62
+ const from = seat(opts.from, '--from');
63
+ const to = seat(opts.to, '--to');
64
+ if (body !== undefined && body !== '-' && opts.body !== undefined) throw new Error('provide body once (argument or --body)');
65
+ const text = opts.body ?? (body !== undefined && body !== '-' ? body : await readStdin());
66
+ const posted = withStore(store => store.post({ from, to, body: text, ...(opts.kind === undefined ? {} : { kind: opts.kind }) }));
67
+ emit(posted, opts.json, () => out.log(formatMessage(posted)));
68
+ })());
69
+
70
+ msg.command('list').description('List messages for one recipient without acknowledging them')
71
+ .option('--to <seat>').option('--after <id>', 'exclusive message ID (default: acknowledged cursor)')
72
+ .option('--limit <count>', 'maximum messages (1–1000)').option('--json')
73
+ .action((opts: RecipientOptions) => run(() => {
74
+ const to = seat(opts.to, '--to');
75
+ const limit = integer(opts.limit, '--limit', 100, 1, 1000);
76
+ const after = opts.after === undefined ? undefined : integer(opts.after, '--after', 0, 0, Number.MAX_SAFE_INTEGER);
77
+ const messages = withStore(store => store.list(to, after ?? store.getCursor(to), limit));
78
+ emit(messages, opts.json, () => {
79
+ if (!messages.length) out.log('No messages.');
80
+ for (const message of messages) out.log(formatMessage(message));
81
+ });
82
+ })());
83
+
84
+ msg.command('ack [id]').description('Explicitly acknowledge messages through this ID for one recipient')
85
+ .option('--to <seat>').option('--json')
86
+ .action((id: string | undefined, opts: CommonOptions & { to?: string }) => run(() => {
87
+ const to = seat(opts.to, '--to');
88
+ if (id === undefined) throw new Error('message ID is required');
89
+ const cursor = integer(id, 'message ID', 0, 0, Number.MAX_SAFE_INTEGER);
90
+ const acknowledged = withStore(store => store.ack(to, cursor));
91
+ emit({ recipient: to, cursor: acknowledged }, opts.json, () => out.log(`${to} acknowledged through #${acknowledged}`));
92
+ })());
93
+
94
+ msg.command('unread').description('Show recipients with unacknowledged messages')
95
+ .option('--to <seat>').option('--json')
96
+ .action((opts: CommonOptions & { to?: string }) => run(() => {
97
+ const to = opts.to === undefined ? undefined : seat(opts.to, '--to');
98
+ const rows = withStore(store => store.unread().filter(row => to === undefined || row.recipient === to));
99
+ emit(rows, opts.json, () => {
100
+ if (!rows.length) out.log('No unread messages.');
101
+ for (const row of rows) out.log(`${row.recipient}: ${row.count} unread`);
102
+ });
103
+ })());
104
+
105
+ msg.command('watch').description('Poll for new messages without acknowledging them (Ctrl-C to stop)')
106
+ .option('--to <seat>').option('--after <id>', 'exclusive message ID (default: acknowledged cursor)')
107
+ .option('--interval <ms>', 'poll interval in milliseconds', '1000')
108
+ .option('--once', 'poll once and exit').option('--json')
109
+ .action((opts: RecipientOptions & { interval?: string; once?: boolean }) => run(async () => {
110
+ const to = seat(opts.to, '--to');
111
+ const interval = integer(opts.interval, '--interval', 1000, 1, 60000);
112
+ const after = opts.after === undefined ? undefined : integer(opts.after, '--after', 0, 0, Number.MAX_SAFE_INTEGER);
113
+ const store = open();
114
+ try {
115
+ let cursor = after ?? store.getCursor(to);
116
+ do {
117
+ let messages: Message[];
118
+ do {
119
+ messages = store.list(to, cursor, 1000);
120
+ for (const message of messages) {
121
+ emit(message, opts.json, () => out.log(formatMessage(message)));
122
+ cursor = message.id;
123
+ }
124
+ } while (messages.length === 1000 && !deps.signal?.aborted);
125
+ if (opts.once || deps.signal?.aborted) break;
126
+ await sleep(interval);
127
+ } while (!deps.signal?.aborted);
128
+ } finally { store.close(); }
129
+ })());
130
+
131
+ // Commander reports option/argument errors before action handlers run. Scope their exit code
132
+ // to msg so unrelated commands keep their own error and help behavior.
133
+ for (const command of [msg, ...msg.commands]) {
134
+ const reportError = command.error.bind(command);
135
+ command.error = (message, options) => reportError(message, { ...options, exitCode: 2 });
136
+ }
137
+ }