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
@@ -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
+ }
@@ -59,6 +59,9 @@ export interface IosBindOpts {
59
59
  readTokenFn?: () => string | null;
60
60
  /** Test seam — host fallback (production daily driver 의 default). */
61
61
  hostFallback?: string;
62
+ /** Test seam — `defaults write` 가 쓸 도메인. 기본은 설치된 앱 «컨테이너»의 plist 경로다.
63
+ * `spawnSyncFn` 만 주입된 시험은 bundle id 그대로(실 `xcrun` 을 부르지 않는다). */
64
+ resolveDomainFn?: (bundleId: string) => string;
62
65
  }
63
66
 
64
67
  export interface IosBindResult {
@@ -90,6 +93,16 @@ function defaultReadToken(): string | null {
90
93
  }
91
94
  }
92
95
 
96
+ /** 앱이 실제로 읽는 도메인 — 설치된 앱의 데이터 컨테이너 plist(확장자 없이).
97
+ * ⚠️ `defaults write <bundleId>` 는 시뮬레이터 «전역» 도메인에 쓴다. 앱은 자기 컨테이너 plist 를 읽으므로,
98
+ * 앱이 한 번이라도 설정을 저장한 뒤로는 그 값이 이긴다(2026-09-30 실측: 연결 링크로 받은 만료 임시 토큰이
99
+ * 컨테이너에 남아 ios-bind 가 «✓» 를 찍고도 앱은 401 을 받았다). 컨테이너를 못 찾으면 bundle id 로 폴백한다. */
100
+ function defaultResolveDomain(bundleId: string): string {
101
+ const r = defaultSpawnSync('xcrun', ['simctl', 'get_app_container', 'booted', bundleId, 'data'], { stdio: 'pipe' });
102
+ const dir = r.status === 0 ? String(r.stdout ?? '').trim() : '';
103
+ return dir ? join(dir, 'Library', 'Preferences', bundleId) : bundleId;
104
+ }
105
+
93
106
  /** UserDefaults write 1 회. spawnSync exit code 0 = 성공. */
94
107
  function defaultsWrite(
95
108
  spawnFn: typeof defaultSpawnSync,
@@ -119,6 +132,8 @@ export function runNexusIosBind(opts: IosBindOpts = {}): IosBindResult {
119
132
  const readToken = opts.readTokenFn ?? defaultReadToken;
120
133
  const hostFallback = opts.hostFallback ?? DEFAULT_HOST_FALLBACK;
121
134
  const dryRun = opts.dryRun ?? false;
135
+ const resolveDomain = opts.resolveDomainFn
136
+ ?? (opts.spawnSyncFn || dryRun ? (id: string) => id : defaultResolveDomain);
122
137
 
123
138
  // 1) daemon 현재 host/port — override 가 우선 · 없으면 runtime sidecar ·
124
139
  // port 가 없으면 현재 우주의 daemon endpoint 를 조회한다.
@@ -150,20 +165,22 @@ export function runNexusIosBind(opts: IosBindOpts = {}): IosBindResult {
150
165
  const tokenRaw = opts.noToken ? null : readToken();
151
166
  const token = tokenRaw ?? '';
152
167
 
153
- // 3) UserDefaults write 3 회 (host · port · token)
168
+ // 3) UserDefaults write 3 회 (host · port · token) — 앱 컨테이너 도메인에
169
+ const domain = resolveDomain(bundleId);
154
170
  const lines: string[] = [];
155
- const hostResult = defaultsWrite(spawnFn, bundleId, 'nexusHost', 'string', host, dryRun);
171
+ if (domain !== bundleId) lines.push(` domain 앱 컨테이너 (${bundleId}) — 앱이 실행 중이면 끄고 다시 켜야 읽는다`);
172
+ const hostResult = defaultsWrite(spawnFn, domain, 'nexusHost', 'string', host, dryRun);
156
173
  lines.push(` host ${hostResult.ok ? '✓' : '✗'} ${hostResult.cmd}`);
157
- const portResult = defaultsWrite(spawnFn, bundleId, 'nexusPort', 'int', String(port), dryRun);
174
+ const portResult = defaultsWrite(spawnFn, domain, 'nexusPort', 'int', String(port), dryRun);
158
175
  lines.push(` port ${portResult.ok ? '✓' : '✗'} ${portResult.cmd}`);
159
176
 
160
177
  let tokenInjected = false;
161
178
  let tokenLength = 0;
162
179
  if (!opts.noToken && token.length > 0) {
163
- const tokenResult = defaultsWrite(spawnFn, bundleId, 'bearerToken', 'string', token, dryRun);
180
+ const tokenResult = defaultsWrite(spawnFn, domain, 'bearerToken', 'string', token, dryRun);
164
181
  tokenInjected = tokenResult.ok;
165
182
  tokenLength = token.length;
166
- lines.push(` token ${tokenResult.ok ? '✓' : '✗'} xcrun simctl spawn booted defaults write ${bundleId} bearerToken <${tokenLength}-char redacted>`);
183
+ lines.push(` token ${tokenResult.ok ? '✓' : '✗'} xcrun simctl spawn booted defaults write ${domain} bearerToken <${tokenLength}-char redacted>`);
167
184
  } else if (opts.noToken) {
168
185
  lines.push(` token — (skipped · --no-token)`);
169
186
  } else {
@@ -173,19 +190,19 @@ export function runNexusIosBind(opts: IosBindOpts = {}): IosBindResult {
173
190
  let asciiKeyboardOk = true;
174
191
  if (opts.asciiKeyboard !== undefined) {
175
192
  const value = opts.asciiKeyboard ? 'true' : 'false';
176
- const r = defaultsWrite(spawnFn, bundleId, 'asciiKeyboard', 'bool', value, dryRun);
193
+ const r = defaultsWrite(spawnFn, domain, 'asciiKeyboard', 'bool', value, dryRun);
177
194
  asciiKeyboardOk = r.ok;
178
195
  lines.push(` ascii ${r.ok ? '✓' : '✗'} ${r.cmd}`);
179
196
  }
180
197
 
181
198
  let seedPromptOk = true;
182
199
  if (opts.seedPrompt !== undefined) {
183
- const r = defaultsWrite(spawnFn, bundleId, 'seedPrompt', 'string', opts.seedPrompt, dryRun);
200
+ const r = defaultsWrite(spawnFn, domain, 'seedPrompt', 'string', opts.seedPrompt, dryRun);
184
201
  seedPromptOk = r.ok;
185
202
  const preview = opts.seedPrompt.length > 24
186
203
  ? opts.seedPrompt.slice(0, 24) + '…'
187
204
  : opts.seedPrompt;
188
- lines.push(` seed ${r.ok ? '✓' : '✗'} xcrun simctl spawn booted defaults write ${bundleId} seedPrompt "${preview}" (${opts.seedPrompt.length} chars)`);
205
+ lines.push(` seed ${r.ok ? '✓' : '✗'} xcrun simctl spawn booted defaults write ${domain} seedPrompt "${preview}" (${opts.seedPrompt.length} chars)`);
189
206
  }
190
207
 
191
208
  const allOk = hostResult.ok && portResult.ok
@@ -0,0 +1,117 @@
1
+ // `elanous phone link` — 휴대폰 앱을 이 Mac 의 넥서스에 «한 번에» 붙이는 연결 링크(⊕ 터미널 QR).
2
+ // 대표 2026-09-30: 아이폰 첫 화면이 개발용 연결 카드라 복잡하다 → 앱은 링크 하나로 온보딩한다.
3
+ // 링크 = `elanous://connect?host=…&port=…&tls=1|0&token=…` — 앱이 `.onOpenURL` 로 받아 저장하고 바로 연결한다.
4
+ import { spawnSync } from 'node:child_process';
5
+ import { debug } from '../debug/log.js';
6
+
7
+ export interface PhoneEndpoint { host: string; port: number; tls: boolean }
8
+
9
+ /** REST/PWA 기준 URL(예 `https://mbp.x.ts.net:31415/v1/`) → 앱이 쓰는 host·port·tls. 못 읽으면 null. */
10
+ export function endpointFromBaseUrl(base: string): PhoneEndpoint | null {
11
+ let u: URL;
12
+ try { u = new URL(base); } catch { return null; }
13
+ if (u.protocol !== 'http:' && u.protocol !== 'https:') return null;
14
+ const tls = u.protocol === 'https:';
15
+ const port = u.port ? Number(u.port) : tls ? 443 : 80;
16
+ if (!u.hostname || !Number.isInteger(port) || port < 1 || port > 65_535) return null;
17
+ return { host: u.hostname, port, tls };
18
+ }
19
+
20
+ /** 앱이 여는 연결 링크. 토큰은 URL 인코딩한다. */
21
+ export function buildPhoneConnectLink(ep: PhoneEndpoint, token: string): string {
22
+ const q = new URLSearchParams({ host: ep.host, port: String(ep.port), tls: ep.tls ? '1' : '0', token });
23
+ return `elanous://connect?${q.toString()}`;
24
+ }
25
+
26
+ /** 어느 주소를 휴대폰에 줄까 — 기본은 tailnet(휴대폰이 닿는다) · `local` 이면 루프백(같은 Mac 의 시뮬레이터). */
27
+ export function pickPhoneBase(urls: { loopback: string; tailnet?: string }, opts: { local?: boolean } = {}): { base: string; kind: 'tailnet' | 'loopback' } {
28
+ if (!opts.local && urls.tailnet) return { base: urls.tailnet, kind: 'tailnet' };
29
+ return { base: urls.loopback, kind: 'loopback' };
30
+ }
31
+
32
+ /** USB 로 꽂힌 안드로이드에 링크를 «직접» 건넨다 — `adb reverse` 로 폰의 127.0.0.1:<port> 를 이 Mac 으로 잇고
33
+ * `am start` 로 elanous://connect 를 연다. 토큰은 화면·로그에 찍지 않는다(adb 인자로만 간다). */
34
+ export function androidDeliverySteps(link: string, port: number, serial?: string): string[][] {
35
+ const dev = serial ? ['-s', serial] : [];
36
+ return [
37
+ [...dev, 'reverse', `tcp:${port}`, `tcp:${port}`],
38
+ [...dev, 'shell', 'am', 'start', '-a', 'android.intent.action.VIEW', '-d', `'${link}'`, 'com.elanvitalai.elanous.android'],
39
+ ];
40
+ }
41
+
42
+ export interface RunPhoneLinkOpts {
43
+ local?: boolean;
44
+ /** USB 안드로이드에 adb 로 바로 건넨다(루프백 ⊕ adb reverse). 문자열이면 기기 serial. */
45
+ android?: boolean | string;
46
+ temp?: boolean;
47
+ ttl?: string;
48
+ qr?: boolean;
49
+ out?: { log: (s: string) => void; error: (s: string) => void };
50
+ }
51
+
52
+ export async function runPhoneLink(opts: RunPhoneLinkOpts = {}): Promise<number> {
53
+ const out = opts.out ?? { log: (s: string) => console.log(s), error: (s: string) => console.error(s) };
54
+ const { runNexusShow } = await import('./nexus-show.js');
55
+ const silent = { log: () => {}, error: () => {} };
56
+ const shown = await runNexusShow({ format: 'json', out: silent });
57
+ if (!shown.urls) {
58
+ out.error('✗ 넥서스가 떠 있지 않다 — 먼저 `elanous nexus run` (또는 `elanous nexus status` 로 확인).');
59
+ return 1;
60
+ }
61
+ const picked = pickPhoneBase(shown.urls.rest, { local: opts.local || !!opts.android });
62
+ const ep = endpointFromBaseUrl(picked.base);
63
+ if (!ep) { out.error(`✗ 넥서스 주소를 읽지 못했다: ${picked.base}`); return 1; }
64
+
65
+ let token: string;
66
+ let tokenNote: string;
67
+ if (opts.temp) {
68
+ const { issueTempToken, parseTtl } = await import('../auth/temp-tokens.js');
69
+ const issued = issueTempToken({ ttlMs: parseTtl(opts.ttl ?? '24h'), label: 'phone' });
70
+ token = issued.token;
71
+ tokenNote = `단기 토큰 · ${issued.expiresAt} 까지 · 폐기: elanous token revoke ${issued.id}`;
72
+ } else {
73
+ const { loadEnvelope } = await import('../auth/token-store.js');
74
+ const env = loadEnvelope();
75
+ if (!env?.active) { out.error('✗ 소유자 토큰이 없다 — `elanous token rotate` 로 한 번 만든 뒤 다시.'); return 1; }
76
+ token = env.active;
77
+ tokenNote = '소유자 토큰(설정의 Bearer 와 같다) · 바꾸려면 elanous token rotate';
78
+ }
79
+
80
+ const link = buildPhoneConnectLink(ep, token);
81
+ try { debug.log('phone.link', 'issued', { kind: picked.kind, tls: ep.tls, temp: !!opts.temp }); } catch { /* 관측 실패가 발급을 막지 않는다 */ }
82
+
83
+ if (opts.android) {
84
+ const serial = typeof opts.android === 'string' ? opts.android : undefined;
85
+ const lanEp = { ...ep, host: '127.0.0.1', tls: false };
86
+ const androidLink = buildPhoneConnectLink(lanEp, token);
87
+ for (const args of androidDeliverySteps(androidLink, lanEp.port, serial)) {
88
+ const r = spawnSync('adb', args, { encoding: 'utf8' });
89
+ if (r.status !== 0) {
90
+ // ⛔ 인자에 토큰이 있으므로 명령을 그대로 찍지 않는다 — 단계 이름과 adb 의 말만.
91
+ const why = (r.error?.message ?? r.stderr ?? '').trim().split('\n')[0] ?? '';
92
+ out.error(`✗ adb ${args.includes('reverse') ? 'reverse' : 'am start'} 실패 — ${why || `exit ${r.status}`}`);
93
+ out.error(' USB 디버깅 허용 · `adb devices` 에 기기가 «device» 로 보이는지 · 기기가 여럿이면 --android <serial>.');
94
+ return 1;
95
+ }
96
+ }
97
+ try { debug.log('phone.link', 'android-delivered', { port: lanEp.port, serial: !!serial, temp: !!opts.temp }); } catch { /* 관측 실패가 연결을 막지 않는다 */ }
98
+ out.log(`✓ 안드로이드 앱에 연결 링크를 건넸다 — 폰에서 앱이 열리고 바로 붙는다 (127.0.0.1:${lanEp.port} ⇄ 이 Mac · adb reverse).`);
99
+ out.error('⚠ USB 를 뽑으면 끊긴다. 계속 쓰려면 폰의 Tailscale 을 켜고 `elanous phone link` 의 QR 을 찍는다.');
100
+ out.error(`토큰: ${tokenNote}`);
101
+ return 0;
102
+ }
103
+
104
+ out.log('휴대폰에서 이 링크를 열면 앱이 바로 연결된다 (카메라로 QR · AirDrop · 메모 붙여넣기):');
105
+ out.log('');
106
+ out.log(link);
107
+ out.log('');
108
+ if (opts.qr !== false) {
109
+ const qr = spawnSync('qrencode', ['-t', 'ANSIUTF8', link], { encoding: 'utf8' });
110
+ if (qr.status === 0 && qr.stdout) out.log(qr.stdout);
111
+ }
112
+ out.error(`주소: ${ep.tls ? 'https' : 'http'}://${ep.host}:${ep.port} (${picked.kind === 'tailnet' ? 'tailnet — 휴대폰이 같은 tailnet 에 있어야 한다' : '루프백 — 이 Mac 의 시뮬레이터 전용'})`);
113
+ if (picked.kind === 'loopback' && !opts.local) out.error('⚠ tailnet 주소가 없다 — 실제 휴대폰에서는 닿지 않는다. `elanous nexus show` 로 tailnet 을 확인하라.');
114
+ out.error(`토큰: ${tokenNote}`);
115
+ out.error('⚠ 이 링크는 이 Mac 을 조작할 수 있는 토큰을 담는다 — 남에게 보내거나 공개 캡처에 싣지 않는다.');
116
+ return 0;
117
+ }
@@ -11,6 +11,8 @@ import { emitDecision } from '../live/detail-switch.js';
11
11
  import { OFFICIAL_INDEX_KEYS } from '../market/official-keys.js';
12
12
  import { installPlugin, listInstalledPlugins, PluginInstallError, removePlugin, type InstallEvent } from '../plugins/install/plugin-install.js';
13
13
  import { credentialStatus, setPluginCredentials } from '../plugins/install/plugin-credentials.js';
14
+ import { makePlugin } from '../plugins/maker/plugin-maker.js';
15
+ import { addAndInstallNode } from '../plugins/maker/node-install.js';
14
16
 
15
17
  function keys(): ReadonlyArray<{ keyId: string; publicKey: string }> {
16
18
  const configPath = userConfigPath();
@@ -56,6 +58,56 @@ function safeCause(error: unknown): string {
56
58
 
57
59
  export function registerPluginCommands(program: Command): void {
58
60
  const plugin = program.command('plugin').description('Install and manage Elanous plugins');
61
+ plugin.command('make <request>').description('Write, validate and install a local graph plugin from one request')
62
+ .option('--name <slug>', 'Local plugin name')
63
+ .option('--dir <parent>', 'Parent directory (default: instance plugins-local)')
64
+ .option('--run', 'Run the installed graph')
65
+ .option('--input <json>', 'JSON input for --run')
66
+ .option('--json', 'Print the result as JSON')
67
+ .action(async (request: string, opts: { name?: string; dir?: string; run?: boolean; input?: string; json?: boolean }) => {
68
+ try {
69
+ const result = await makePlugin({ request, name: opts.name, parentDir: opts.dir, run: opts.run,
70
+ ...(opts.input === undefined ? {} : { input: JSON.parse(opts.input) as unknown }) });
71
+ if (opts.json) stdout.write(JSON.stringify(result) + '\n');
72
+ else {
73
+ console.log(`뼈대 ${result.timings.scaffold}ms · 작성 ${result.timings.write}ms`);
74
+ console.log(`검증 오류 ${result.errors.length} · ${result.timings.validate}ms${result.timings.repair === undefined ? '' : ` · 수리 ${result.timings.repair}ms`}`);
75
+ console.log(`설치 ${result.timings.install === 0 && result.status === 'failed' ? '미실행' : result.status === 'failed' && result.timings.run === undefined ? '실패' : '완료'} · ${result.timings.install}ms (${result.dir})`);
76
+ if (opts.run) console.log(`실행 ${result.runStatus ?? '미실행'} · ${result.timings.run ?? 0}ms`);
77
+ for (const error of result.errors) console.error(error);
78
+ }
79
+ if (result.status === 'failed') process.exitCode = 1;
80
+ } catch (error) {
81
+ const message = error instanceof Error ? error.message : String(error);
82
+ if (opts.json) stdout.write(JSON.stringify({ status: 'failed', errors: [message] }) + '\n');
83
+ else console.error(`plugin make failed: ${message}`);
84
+ process.exitCode = 1;
85
+ }
86
+ });
87
+ plugin.command('node').description('Manage plugin workflow nodes')
88
+ .command('add <dir> <request>').description('Write, validate and install a workflow node in a plugin')
89
+ .option('--kind <slug>', 'Workflow node kind')
90
+ .option('--yes', 'Accept requested capabilities')
91
+ .option('--json', 'Print the result as one JSON line')
92
+ .action(async (dir: string, request: string, opts: { kind?: string; yes?: boolean; json?: boolean }) => {
93
+ try {
94
+ const result = await addAndInstallNode({ dir, request, kind: opts.kind, yes: opts.yes,
95
+ deps: { consent: capabilities => askConsent(capabilities, !!opts.json) } });
96
+ if (opts.json) stdout.write(JSON.stringify(result) + '\n');
97
+ else {
98
+ console.log(`작성 ${result.timings.write}ms · 검증 ${result.timings.validate}ms${result.timings.repair === undefined ? '' : ` · 수리 ${result.timings.repair}ms`}`);
99
+ console.log(`설치 ${result.plugin ? '완료' : result.status === 'failed' && result.timings.install === 0 ? '미실행' : '실패'} · ${result.timings.install}ms`);
100
+ console.log(`동기화 ${result.status === 'installed' ? '완료' : result.plugin ? '실패' : '미실행'} · ${result.timings.sync}ms`);
101
+ for (const error of result.errors) console.error(error);
102
+ }
103
+ if (result.status === 'failed') process.exitCode = 1;
104
+ } catch (error) {
105
+ const message = error instanceof Error ? error.message : String(error);
106
+ if (opts.json) stdout.write(JSON.stringify({ status: 'failed', errors: [message] }) + '\n');
107
+ else console.error(`plugin node add failed: ${message}`);
108
+ process.exitCode = 1;
109
+ }
110
+ });
59
111
  plugin.command('add <spec>').description('Install a local path, pinned git plugin or signed market plugin')
60
112
  .option('--yes', 'Accept requested capabilities')
61
113
  .option('--json', 'NDJSON progress events')
package/src/cli/pr-cli.ts CHANGED
@@ -17,6 +17,7 @@ import { runMockModuleRestoreGate } from '../../scripts/ci-mock-module-restore-g
17
17
  import { runModelHardcodeGate } from '../../scripts/ci-model-hardcode-gate.js';
18
18
  import { runDaemonPortGate } from '../../scripts/ci-daemon-port-gate.js';
19
19
  import { runPublicLeakGate } from '../../scripts/ci-public-leak-gate.js';
20
+ import { exportLeakCheck, type ExportLeakCheck } from '../../scripts/ci-public-export-leak-gate.js';
20
21
  import { checkCommands, extractElanousCommands, type Finding as DocsCliFinding } from '../../scripts/docs-cli-check.js';
21
22
  import { runTestInterferenceGate } from '../../scripts/ci-test-interference-gate.js';
22
23
  import { isGoalDocumentFileName } from '../self-implement/goal-document.js';
@@ -68,6 +69,8 @@ export interface PrLandOpts {
68
69
  excludeActiveRunFiles?: boolean;
69
70
  excludeActiveRunSources?: boolean;
70
71
  landReason?: string;
72
+ /** Land even though changed files would leak into the public export (the reason goes in --land-reason). */
73
+ allowPublicLeak?: boolean;
71
74
  }
72
75
 
73
76
  export interface PrGranularityOpts {
@@ -101,6 +104,8 @@ export interface PrLandDeps {
101
104
  runDaemonPortGate?: (out: { log: (message: string) => void; error: (message: string) => void }) => boolean;
102
105
  /** 공개 유출 래칫(경고 전용) — 반환 0 통과 · 1 늘었다 · 2 못 쟀다. */
103
106
  runPublicLeakGate?: (changedFiles: readonly string[], out: { log: (message: string) => void; error: (message: string) => void }) => number;
107
+ /** Test seam — the post-transform export leak check on changed files. */
108
+ runExportLeakCheck?: (changedFiles: readonly string[], cwd: string) => ExportLeakCheck;
104
109
  /** 공개 문서의 `elanous …` 호출 ↔ 실제 `--help` 대조(경고 전용) — 바뀐 공개 문서 경로를 받아 어긋남 목록을 돌려준다. */
105
110
  runDocsCliCheck?: (docFiles: readonly string[]) => DocsCliFinding[];
106
111
  /** 변경 시험 파일 간 간섭 검사 심(시험 주입용). */
@@ -269,6 +274,35 @@ function runPrLandPublicLeakGate(changedFiles: readonly string[], out: { log: (m
269
274
  return runPublicLeakGate({ args: ['--changed-files', ...changedFiles], log: out.log, error: out.log });
270
275
  }
271
276
 
277
+ /** The prepare check on this landing's changed files — scripts/ci-public-export-leak-gate.ts. */
278
+ export function runPrLandExportLeakCheck(changedFiles: readonly string[], cwd: string): ExportLeakCheck {
279
+ const top = runGitCommand(cwd, ['rev-parse', '--show-toplevel'], { encoding: 'utf-8' });
280
+ return exportLeakCheck(changedFiles, top.status === 0 && String(top.stdout ?? '').trim() ? String(top.stdout).trim() : cwd);
281
+ }
282
+
283
+ /** Blocks the landing when changed files would leak into the public export, unless allowPublicLeak. Returns false to stop. */
284
+ export function exportLeakBlock(
285
+ changedFiles: readonly string[] | undefined,
286
+ check: () => ExportLeakCheck,
287
+ allow: boolean,
288
+ out: { log: (message: string) => void; error: (message: string) => void },
289
+ ): boolean {
290
+ if (!changedFiles || changedFiles.length === 0) return true;
291
+ let result: ExportLeakCheck;
292
+ try { result = check(); } catch (error) { result = { measured: false, hits: [], detail: error instanceof Error ? error.message : String(error) }; }
293
+ record('public-export-leak', result.measured ? result.hits.length === 0 || allow : true, { measured: result.measured, hits: result.hits.length, allowed: allow && result.hits.length > 0 });
294
+ if (!result.measured) { out.log(`⚠ public-export-leak: 못 쟀다(${result.detail ?? '?'}) — 착지는 막지 않는다. 확인: bun scripts/public-export.ts --leak-check --files <파일…>`); return true; }
295
+ if (result.hits.length === 0) { out.log('✓ public-export-leak: 바뀐 파일이 공개본에 사적 흔적을 싣지 않는다(prepare 와 같은 변환 뒤).'); return true; }
296
+ const write = allow ? out.log : out.error;
297
+ write(`${allow ? '⚠' : '✗'} public-export-leak: 바뀐 파일 ${result.hits.length}곳이 공개본에 사적 흔적을 싣는다 — 다음 release prepare 가 여기서 실패한다:`);
298
+ for (const h of result.hits.slice(0, 20)) write(` ${h.file}:${h.line} ${h.marker}`);
299
+ if (result.hits.length > 20) write(` … 외 ${result.hits.length - 20}곳`);
300
+ write(' 확인: bun scripts/public-export.ts --leak-check --all --files <파일…>');
301
+ if (allow) { out.log(' --allow-public-leak 로 넘긴다(이유는 --land-reason).'); return true; }
302
+ out.error(' 고치거나, 공개본과 무관한 것이 확실하면 --allow-public-leak --land-reason "<이유>" 로 넘긴다.');
303
+ return false;
304
+ }
305
+
272
306
  export function publicLeakWarning(
273
307
  changedFiles: readonly string[] | undefined,
274
308
  gate: (changedFiles: readonly string[], out: { log: (message: string) => void; error: (message: string) => void }) => number,
@@ -1286,6 +1320,7 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
1286
1320
 
1287
1321
  const changedPaths = currentChangePaths(run, cwd, staged, base, out);
1288
1322
  publicLeakWarning(changedPaths, deps.runPublicLeakGate ?? runPrLandPublicLeakGate, out);
1323
+ if (!exportLeakBlock(changedPaths, () => (deps.runExportLeakCheck ?? runPrLandExportLeakCheck)(changedPaths ?? [], cwd), opts.allowPublicLeak === true, out)) return 1;
1289
1324
  if (publicDocPaths(changedPaths, () => true).length > 0) {
1290
1325
  const top = run('git', ['rev-parse', '--show-toplevel'], { cwd });
1291
1326
  const docsRoot = top.ok && top.out.trim() ? top.out.trim() : cwd;
@@ -1549,6 +1584,7 @@ export function registerPrCommands(program: Command, deps: PrLandDeps = {}): voi
1549
1584
  .option('--exclude-active-run-files', '도는 런과 겹친 파일을 이번 착지에서 제외')
1550
1585
  .option('--exclude-active-run-sources', '⚠ 위험: --exclude-active-run-files 가 골 문서가 아닌 파일(소스)을 빼도 착지를 강행')
1551
1586
  .option('--land-reason <text>', '권고를 보고도 지금 내는 이유(예: 남이 기다리는 차단 해제)')
1587
+ .option('--allow-public-leak', '바뀐 파일이 공개본에 사적 흔적을 실어도 착지(이유는 --land-reason)')
1552
1588
  .action(async (opts: PrLandOpts) => {
1553
1589
  // ⛔⭐⭐⭐ **관측 sink 를 먼저 건다** — 라이브 도그푸드가 잡은 결함(2026-08-03).
1554
1590
  // 단위 테스트는 `debug.log` 가 **불렸다**를 단언하지만, standalone CLI 는 sink 를 등록하지
@@ -0,0 +1,31 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { Command } from 'commander';
4
+
5
+ // Private local ledgers (decisions · directives) are absent from the public export;
6
+ // JS-only distributions still register them. Kept out of src/index.ts so the `self`
7
+ // block stays free of direct sink registration (see self-cli-sink-surface.test.ts).
8
+ export function registerPrivateLedgerCommands(program: Command): void {
9
+ if (existsSync(resolve(import.meta.dir, 'decisions-cli.ts')) || existsSync(resolve(import.meta.dir, 'decisions-cli.js'))) {
10
+ program.command('decisions').description('대표 결정 로컬 원장').allowUnknownOption().allowExcessArguments(true)
11
+ .action(async (_opts: unknown, command: Command) => {
12
+ try { await (await import('../domains/standalone-log-sink.js')).registerStandaloneLogSink('decisions'); }
13
+ catch { /* Logging must not prevent recording a decision. */ }
14
+ const privateDecisionsModule: string = './decisions-cli.js';
15
+ const { registerDecisionsCommands } = await import(privateDecisionsModule) as { registerDecisionsCommands: (program: Command) => void };
16
+ const privateProgram = new Command();
17
+ registerDecisionsCommands(privateProgram);
18
+ await privateProgram.parseAsync(['decisions', ...command.args], { from: 'user' });
19
+ });
20
+ }
21
+ if (existsSync(resolve(import.meta.dir, 'directives-cli.ts')) || existsSync(resolve(import.meta.dir, 'directives-cli.js'))) {
22
+ program.command('directives').description('대표 지시 로컬 색인·검색').allowUnknownOption().allowExcessArguments(true)
23
+ .action(async (_opts: unknown, command: Command) => {
24
+ const privateDirectivesModule: string = './directives-cli.js';
25
+ const { registerDirectivesCommands } = await import(privateDirectivesModule) as { registerDirectivesCommands: (program: Command) => void };
26
+ const privateProgram = new Command();
27
+ registerDirectivesCommands(privateProgram);
28
+ await privateProgram.parseAsync(['directives', ...command.args], { from: 'user' });
29
+ });
30
+ }
31
+ }
@@ -239,6 +239,9 @@ export function registerProviderCommands(program: Command): void {
239
239
  // ⛔⭐ 「후보」는 판정기가 «자기 자신을 뺀» 것이다 — 표면이 현재 계정을 후보로 보여 주면
240
240
  // ***있지도 않은 선택지를 말한다***(리뷰 must-fix). 판정기와 같은 기준(storeKey)으로 거른다.
241
241
  const shownCandidates = s.candidates.filter((c) => c.storeKey !== s.current.storeKey);
242
+ const { codexCreditPlanFromDisk } = await import('./codex-credit-plan-view.js');
243
+ const { formatCodexCreditPlan } = await import('../budget/codex-credit-plan.js');
244
+ const creditPlan = codexCreditPlanFromDisk(now);
242
245
  const ageMinOf = (home: string | undefined): number | null => {
243
246
  if (!home) return null;
244
247
  const at = home === s.currentHome ? s.currentObservedAt : s.observedAtByHome[home];
@@ -258,6 +261,7 @@ export function registerProviderCommands(program: Command): void {
258
261
  if (opts.json) {
259
262
  await writeStdoutJson(JSON.stringify({
260
263
  policy: { value: s.policy.policy, source: s.policy.source },
264
+ creditPlan,
261
265
  universe: {
262
266
  instanceRoot, signalDir, authStore: authStorePath(),
263
267
  // ⭐ JSON 에도 싣는다 — 화면만 알면 스크립트가 못 센다
@@ -303,6 +307,7 @@ export function registerProviderCommands(program: Command): void {
303
307
  const policySource = s.policy.source === 'legacy-credits' ? ' · 옛 codexCreditsAllowed 에서'
304
308
  : s.policy.source === 'default' ? ' · 기본값' : '';
305
309
  console.log(`정책 ${s.policy.policy} (${CODEX_QUOTA_POLICY_LABEL[s.policy.policy]} · llm.codexQuotaPolicy)${policySource}`);
310
+ for (const line of formatCodexCreditPlan(creditPlan)) console.log(line);
306
311
  console.log(`지금 계정 ${s.current.name} (source=${s.current.source})`);
307
312
  console.log(` 홈=${s.currentHome ?? '(모름)'} 사용=${s.currentUsedPercent ?? '?'}% 찼나=${s.currentReached ?? '모름'} 신호=${ageOf(s.currentHome)} 크레딧=${creditText(s.currentCreditBalance, s.currentHasCredits)}`);
308
313
  console.log('후보');