elanous 0.2.5 → 0.2.6

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 (300) hide show
  1. package/README.md +5 -4
  2. package/apps/pwa/out/404/index.html +1 -1
  3. package/apps/pwa/out/404.html +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/{7527.943753b6ed829f09.js → 1883.c91a2648af4842fd.js} +1 -1
  6. package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/{8918-482b30ce2a8ebf4c.js → 2984-977cfa339fcd65a5.js} +3 -3
  9. package/apps/pwa/out/_next/static/chunks/{4853-35fdbe6bdbcdc0fc.js → 3165-95515ca5f299a120.js} +1 -1
  10. package/apps/pwa/out/_next/static/chunks/3190-e9282753e7ebde85.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/{5391-8793f88b03b30161.js → 3995-bee512a7063036ce.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/{9704-3221c102623c8d73.js → 4240-7153881b0b377306.js} +1 -1
  13. package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +25 -0
  16. package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/5329.8b7e4bca684b6eec.js +5 -0
  19. package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/{5348-bbf838e119661eee.js → 763-7d0e1913b5074e98.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/{8617.465ca22a319c95b3.js → 7633.0654fe6ea259c1ee.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/{9238.2cd5f47f4bf7a2c3.js → 8250.4a2c5553a07cdf47.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/{4daa2798-1363d725c79f3593.js → 8d0d9faf-97887ff99713b84e.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/{f6f3850d-7f324c6566002113.js → 91ff6dda-3b33d42d20657429.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-84cd419494e16b0f.js → page-c3890e90bd232732.js} +1 -1
  84. package/apps/pwa/out/_next/static/chunks/{06ef2eba-d1b99ac6d2ea663c.js → beb5cf10-9adcc10b83930090.js} +1 -1
  85. package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/{webpack-2bf59c720fe9542c.js → webpack-37b8bf78a3c6eec5.js} +1 -1
  91. package/apps/pwa/out/_next/static/{ktMqfrBbf8BMGac0jqLrb → qMI0jnlZaBjjm4aIDYNGM}/_buildManifest.js +1 -1
  92. package/apps/pwa/out/approvals/index.html +1 -1
  93. package/apps/pwa/out/approvals/index.txt +21 -21
  94. package/apps/pwa/out/autopilot/index.html +1 -1
  95. package/apps/pwa/out/autopilot/index.txt +21 -21
  96. package/apps/pwa/out/board/index.html +1 -1
  97. package/apps/pwa/out/board/index.txt +21 -21
  98. package/apps/pwa/out/botlab/index.html +1 -1
  99. package/apps/pwa/out/botlab/index.txt +20 -20
  100. package/apps/pwa/out/bots/index.html +1 -1
  101. package/apps/pwa/out/bots/index.txt +20 -20
  102. package/apps/pwa/out/chat/index.html +1 -1
  103. package/apps/pwa/out/chat/index.txt +20 -20
  104. package/apps/pwa/out/control/index.html +1 -1
  105. package/apps/pwa/out/control/index.txt +21 -21
  106. package/apps/pwa/out/design-check/index.html +1 -1
  107. package/apps/pwa/out/design-check/index.txt +21 -21
  108. package/apps/pwa/out/editor/index.html +1 -1
  109. package/apps/pwa/out/editor/index.txt +21 -21
  110. package/apps/pwa/out/healthz +1 -1
  111. package/apps/pwa/out/icons/elanous-16.png +0 -0
  112. package/apps/pwa/out/icons/elanous-180.png +0 -0
  113. package/apps/pwa/out/icons/elanous-192.png +0 -0
  114. package/apps/pwa/out/icons/elanous-32.png +0 -0
  115. package/apps/pwa/out/icons/elanous-512.png +0 -0
  116. package/apps/pwa/out/index.html +1 -1
  117. package/apps/pwa/out/index.txt +20 -20
  118. package/apps/pwa/out/intake/index.html +1 -1
  119. package/apps/pwa/out/intake/index.txt +21 -21
  120. package/apps/pwa/out/live/index.html +1 -1
  121. package/apps/pwa/out/live/index.txt +21 -21
  122. package/apps/pwa/out/market/index.html +1 -1
  123. package/apps/pwa/out/market/index.txt +20 -20
  124. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  125. package/apps/pwa/out/missions/placeholder/index.txt +20 -20
  126. package/apps/pwa/out/morning/index.html +1 -1
  127. package/apps/pwa/out/morning/index.txt +21 -21
  128. package/apps/pwa/out/observatory/index.html +1 -1
  129. package/apps/pwa/out/observatory/index.txt +21 -21
  130. package/apps/pwa/out/reflection/index.html +1 -1
  131. package/apps/pwa/out/reflection/index.txt +21 -21
  132. package/apps/pwa/out/scheduler/index.html +1 -1
  133. package/apps/pwa/out/scheduler/index.txt +21 -21
  134. package/apps/pwa/out/sessions/index.html +1 -1
  135. package/apps/pwa/out/sessions/index.txt +20 -20
  136. package/apps/pwa/out/settings/devices/index.html +1 -1
  137. package/apps/pwa/out/settings/devices/index.txt +21 -21
  138. package/apps/pwa/out/settings/index.html +1 -1
  139. package/apps/pwa/out/settings/index.txt +21 -21
  140. package/apps/pwa/out/setup/done/index.html +1 -1
  141. package/apps/pwa/out/setup/done/index.txt +21 -21
  142. package/apps/pwa/out/setup/index.html +1 -1
  143. package/apps/pwa/out/setup/index.txt +21 -21
  144. package/apps/pwa/out/share/index.html +1 -1
  145. package/apps/pwa/out/share/index.txt +21 -21
  146. package/apps/pwa/out/showroom/index.html +1 -1
  147. package/apps/pwa/out/showroom/index.txt +20 -20
  148. package/apps/pwa/out/tasks/index.html +1 -1
  149. package/apps/pwa/out/tasks/index.txt +21 -21
  150. package/apps/pwa/out/term/index.html +1 -1
  151. package/apps/pwa/out/term/index.txt +21 -21
  152. package/apps/pwa/out/trace/index.html +1 -1
  153. package/apps/pwa/out/trace/index.txt +21 -21
  154. package/apps/pwa/out/vault/index.html +1 -1
  155. package/apps/pwa/out/vault/index.txt +21 -21
  156. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  157. package/apps/pwa/out/workflows/chat-ui/index.txt +21 -21
  158. package/apps/pwa/out/workflows/index.html +1 -1
  159. package/apps/pwa/out/workflows/index.txt +21 -21
  160. package/apps/pwa/out/workspace/index.html +1 -1
  161. package/apps/pwa/out/workspace/index.txt +21 -21
  162. package/apps/pwa/out/worktrees/index.html +1 -1
  163. package/apps/pwa/out/worktrees/index.txt +21 -21
  164. package/graphs/release/recipes.yaml +3 -0
  165. package/graphs/release/release-loop.yaml +3 -1
  166. package/package.json +1 -1
  167. package/plugins/card-followup/README.md +33 -0
  168. package/plugins/card-followup/examples/card-sample.json +7 -0
  169. package/plugins/card-followup/graphs/card-followup.yaml +16 -0
  170. package/plugins/card-followup/graphs/recipes.yaml +12 -0
  171. package/plugins/card-followup/graphs/run-step.ts +172 -0
  172. package/plugins/card-followup/plugin.json +25 -0
  173. package/plugins/geo-check/README.md +13 -0
  174. package/plugins/geo-check/examples/brand-sample.json +8 -0
  175. package/plugins/geo-check/graphs/geo-check.yaml +20 -0
  176. package/plugins/geo-check/graphs/recipes.yaml +18 -0
  177. package/plugins/geo-check/graphs/run-step.ts +177 -0
  178. package/plugins/geo-check/plugin.json +24 -0
  179. package/plugins/job-coach/README.md +30 -0
  180. package/scripts/coord-tracks.json +2 -2
  181. package/scripts/daemon-port-baseline.txt +1 -2
  182. package/scripts/lib/android-test-device.ts +32 -0
  183. package/scripts/release-loop/checklist-gate-node.ts +35 -0
  184. package/scripts/release-loop/gate-diff.ts +29 -0
  185. package/scripts/release-loop/gate-node.ts +46 -13
  186. package/scripts/release-loop/shard-plan.ts +47 -0
  187. package/scripts/ux/record-page.ts +4 -1
  188. package/skills/explainer-video/SKILL.md +18 -0
  189. package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -1
  190. package/skills/explainer-video/engine/build.mjs +1 -1
  191. package/skills/explainer-video/engine/reel.mjs +128 -0
  192. package/skills/explainer-video/engine/reel.sh +8 -0
  193. package/skills/explainer-video/engine/vo.ts +58 -16
  194. package/src/autopilot/mission-origin.ts +20 -4
  195. package/src/autopilot/mission-phase-track.ts +11 -1
  196. package/src/autopilot/track-agent.ts +3 -3
  197. package/src/autopilot/track-courier.ts +2 -2
  198. package/src/cli/nexus-ios-bind.ts +25 -8
  199. package/src/cli/phone-link.ts +117 -0
  200. package/src/cli/plugin-cli.ts +27 -0
  201. package/src/cli/private-ledger-commands.ts +31 -0
  202. package/src/cli/release-cli.ts +14 -11
  203. package/src/domains/outbound-alert.ts +5 -5
  204. package/src/field/field-media.ts +245 -0
  205. package/src/index.ts +18 -24
  206. package/src/nexus/api/field-uploads.ts +143 -0
  207. package/src/nexus/api/graph-approvals.ts +50 -1
  208. package/src/nexus/api/harness-api.ts +219 -5
  209. package/src/nexus/api/http-server.ts +9 -0
  210. package/src/notify/llm-budget-alert.ts +119 -0
  211. package/src/plugins/maker/plugin-maker.ts +208 -0
  212. package/src/relay/channel.ts +184 -0
  213. package/src/relay/mux.ts +182 -0
  214. package/src/relay/test-vectors.json +29 -0
  215. package/src/release-loop/checklist.ts +28 -2
  216. package/src/seat-address/seat-address.ts +34 -0
  217. package/src/self-implement/run-ledger-compact.ts +1 -1
  218. package/src/session/index.ts +23 -10
  219. package/src/task-orchestrator/surfaces/pod-bun-cache.ts +18 -0
  220. package/src/task-orchestrator/surfaces/pod-command-job.ts +12 -3
  221. package/src/telegram.ts +108 -1
  222. package/src/user-config.ts +4 -0
  223. package/src/version/packed-revision.json +1 -1
  224. package/src/video-pipeline/recipes/explainer.ts +213 -0
  225. package/src/video-pipeline/walk-line.ts +3 -2
  226. package/apps/pwa/out/_next/static/chunks/1413-7179a49332db5a80.js +0 -1
  227. package/apps/pwa/out/_next/static/chunks/150-25c277137a9638ab.js +0 -1
  228. package/apps/pwa/out/_next/static/chunks/1640-bc99ec23bbe29d12.js +0 -1
  229. package/apps/pwa/out/_next/static/chunks/1751.50b96ea9c37b49b6.js +0 -1
  230. package/apps/pwa/out/_next/static/chunks/2300-de059d0ab2a16630.js +0 -1
  231. package/apps/pwa/out/_next/static/chunks/2684-05b0a81e75298c16.js +0 -1
  232. package/apps/pwa/out/_next/static/chunks/3026-2ed7e317d9b02102.js +0 -25
  233. package/apps/pwa/out/_next/static/chunks/3611-77496ada38cd6358.js +0 -1
  234. package/apps/pwa/out/_next/static/chunks/3717-68957c4dc9ceec90.js +0 -1
  235. package/apps/pwa/out/_next/static/chunks/3929-46ba9af6f9d895cc.js +0 -1
  236. package/apps/pwa/out/_next/static/chunks/4015-62c5a3744698a8da.js +0 -1
  237. package/apps/pwa/out/_next/static/chunks/4217-3c4317a7905f9eeb.js +0 -1
  238. package/apps/pwa/out/_next/static/chunks/4240-3230f230fae2d7d5.js +0 -1
  239. package/apps/pwa/out/_next/static/chunks/4271-e53e075ebd48593b.js +0 -1
  240. package/apps/pwa/out/_next/static/chunks/4377.3b125fef79576dfd.js +0 -5
  241. package/apps/pwa/out/_next/static/chunks/4440-18bb160d95819f4e.js +0 -1
  242. package/apps/pwa/out/_next/static/chunks/5007-6a3b30e23ff68a6e.js +0 -1
  243. package/apps/pwa/out/_next/static/chunks/5244-12b424c42070f5ff.js +0 -1
  244. package/apps/pwa/out/_next/static/chunks/5611-df00af3e4e4a61bb.js +0 -1
  245. package/apps/pwa/out/_next/static/chunks/5890-dca2321773be829a.js +0 -1
  246. package/apps/pwa/out/_next/static/chunks/6123-37c9dc57ddaa7ab0.js +0 -1
  247. package/apps/pwa/out/_next/static/chunks/6588-b7975304a302ab91.js +0 -1
  248. package/apps/pwa/out/_next/static/chunks/6604-c03961962b6273b6.js +0 -1
  249. package/apps/pwa/out/_next/static/chunks/7109-cf8c87e968e83a65.js +0 -1
  250. package/apps/pwa/out/_next/static/chunks/7178-30f77877525a2efb.js +0 -1
  251. package/apps/pwa/out/_next/static/chunks/7262-871563fc9663730b.js +0 -1
  252. package/apps/pwa/out/_next/static/chunks/7294-f0eeebb92aa93459.js +0 -1
  253. package/apps/pwa/out/_next/static/chunks/7429-881af44deed6958f.js +0 -1
  254. package/apps/pwa/out/_next/static/chunks/764-a01e858525a190c0.js +0 -1
  255. package/apps/pwa/out/_next/static/chunks/7868-e35d5f7985ecd3f1.js +0 -1
  256. package/apps/pwa/out/_next/static/chunks/8320-51484af7b01fdfa5.js +0 -1
  257. package/apps/pwa/out/_next/static/chunks/9131-52f5fad51cceca04.js +0 -1
  258. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-70c404c7bb6d7f0f.js +0 -1
  259. package/apps/pwa/out/_next/static/chunks/app/approvals/page-3ffd80166b1050aa.js +0 -1
  260. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f89e9a303515a033.js +0 -1
  261. package/apps/pwa/out/_next/static/chunks/app/board/page-4f403cd060442c95.js +0 -1
  262. package/apps/pwa/out/_next/static/chunks/app/botlab/page-0ac5d053f0444d21.js +0 -1
  263. package/apps/pwa/out/_next/static/chunks/app/bots/page-4bcc63e272ae8205.js +0 -1
  264. package/apps/pwa/out/_next/static/chunks/app/chat/page-880734bd07d62882.js +0 -1
  265. package/apps/pwa/out/_next/static/chunks/app/control/page-72bd384898d88955.js +0 -1
  266. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1018bbc9586ecae5.js +0 -1
  267. package/apps/pwa/out/_next/static/chunks/app/editor/page-de86f8c38076dd7b.js +0 -1
  268. package/apps/pwa/out/_next/static/chunks/app/healthz/route-70c404c7bb6d7f0f.js +0 -1
  269. package/apps/pwa/out/_next/static/chunks/app/intake/page-cd6447196d8bafe6.js +0 -1
  270. package/apps/pwa/out/_next/static/chunks/app/layout-e733630c47fa102b.js +0 -1
  271. package/apps/pwa/out/_next/static/chunks/app/live/page-d1c1a8357b161875.js +0 -1
  272. package/apps/pwa/out/_next/static/chunks/app/market/page-60c514128e81d787.js +0 -1
  273. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8b637b7e325e13c1.js +0 -1
  274. package/apps/pwa/out/_next/static/chunks/app/morning/page-bccce0babceae30f.js +0 -1
  275. package/apps/pwa/out/_next/static/chunks/app/not-found-63301d11dbe73836.js +0 -1
  276. package/apps/pwa/out/_next/static/chunks/app/observatory/page-a8cf0dc472060db2.js +0 -1
  277. package/apps/pwa/out/_next/static/chunks/app/page-02fba54881ccfa49.js +0 -1
  278. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d5e8e90f0038f8de.js +0 -1
  279. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-b1ef6824f494466a.js +0 -1
  280. package/apps/pwa/out/_next/static/chunks/app/sessions/page-d589bb0f233b1009.js +0 -1
  281. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c9bbaebef4cda059.js +0 -1
  282. package/apps/pwa/out/_next/static/chunks/app/settings/page-3639ce7916b92fed.js +0 -1
  283. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7450ae7478955653.js +0 -1
  284. package/apps/pwa/out/_next/static/chunks/app/setup/layout-70c404c7bb6d7f0f.js +0 -1
  285. package/apps/pwa/out/_next/static/chunks/app/setup/page-b41ca4abfd618452.js +0 -1
  286. package/apps/pwa/out/_next/static/chunks/app/share/page-b333dd79ebee3fa9.js +0 -1
  287. package/apps/pwa/out/_next/static/chunks/app/showroom/page-8be1227492793655.js +0 -1
  288. package/apps/pwa/out/_next/static/chunks/app/tasks/page-6e03e9371b552e0d.js +0 -1
  289. package/apps/pwa/out/_next/static/chunks/app/term/page-911411e9fca18d03.js +0 -1
  290. package/apps/pwa/out/_next/static/chunks/app/trace/page-792170e649b37088.js +0 -1
  291. package/apps/pwa/out/_next/static/chunks/app/vault/page-c40463466937bc91.js +0 -1
  292. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-509c798b80384536.js +0 -1
  293. package/apps/pwa/out/_next/static/chunks/app/workflows/page-1cac2aa21d77c417.js +0 -1
  294. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9626799d9b4a2cd1.js +0 -1
  295. package/apps/pwa/out/_next/static/chunks/framework-361487d2da603917.js +0 -1
  296. package/apps/pwa/out/_next/static/chunks/main-app-607f5beb94cb2e07.js +0 -1
  297. package/apps/pwa/out/_next/static/chunks/main-d0222d219e29dddf.js +0 -1
  298. package/apps/pwa/out/_next/static/chunks/pages/_app-a26980a31126decc.js +0 -1
  299. package/apps/pwa/out/_next/static/chunks/pages/_error-edb2b37decf23e4d.js +0 -1
  300. /package/apps/pwa/out/_next/static/{ktMqfrBbf8BMGac0jqLrb → qMI0jnlZaBjjm4aIDYNGM}/_ssgManifest.js +0 -0
@@ -13,20 +13,36 @@ import { getUserConfig } from '../user-config.js';
13
13
  import { resolveChannelBotToken } from '../channel-bot-token.js';
14
14
  import { join } from 'node:path';
15
15
 
16
- export interface MissionOrigin {
17
- channel: 'telegram' | 'pwa' | 'voice' | 'cli' | 'api' | 'tui';
16
+ interface MissionOriginBase {
18
17
  /** 텔레그램: 답장할 chat id. */
19
18
  chatId?: number;
20
19
  /** 텔레그램: 수신 봇 식별(botId=토큰 prefix). 발송 시 config 에서 토큰 해석. */
21
20
  botId?: string;
22
- /** 텔레그램 스레드(포럼) — 있으면 답장에 붙임. */
23
- threadId?: number;
24
21
  /** HITL 승인/거절 버튼을 단 알림 메시지의 message_id. 크로스서피스 싱크용 —
25
22
  * PWA/텔레그램 어느 쪽에서 해소돼도 이 좌표(chatId+botId+이 id)로 텔레그램 메시지를
26
23
  * edit("승인됨/거절됨")하고 버튼을 제거한다. 버튼 발송 성공 시에만 기록. */
27
24
  hitlMessageId?: number;
28
25
  }
29
26
 
27
+ export type MissionOrigin = MissionOriginBase & (
28
+ | {
29
+ channel: 'telegram' | 'pwa' | 'voice' | 'cli' | 'api' | 'tui';
30
+ /** 텔레그램 스레드(포럼) — 있으면 답장에 붙임. */
31
+ threadId?: number;
32
+ channelId?: never;
33
+ }
34
+ | {
35
+ channel: 'discord';
36
+ /** Discord origin channel (snowflake). */
37
+ channelId: string;
38
+ /** Discord thread (snowflake), if the request came from a thread.
39
+ * ⛔ `threadId` 가 아니다 — 텔레그램 `threadId?: number` 와 같은 이름이면 유니온에서 `string|number` 로 넓어져
40
+ * 텔레그램 발송부 20여 곳의 타입이 깨진다(2026-10-01 PWA 빌드 실패 · #22246). */
41
+ discordThreadId?: string;
42
+ threadId?: never;
43
+ }
44
+ );
45
+
30
46
  function originDir(): string {
31
47
  // core 미션 fabric = autopilot/ (conatus/ 는 투자 customer 네임스페이스·대표 정정 2026-07-11).
32
48
  // [ISO-3] ELANOUS_STATE_DIR 존중 — 격리 테스트 데몬은 운영 origin 을 못 본다.
@@ -1,7 +1,17 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { resolve } from 'node:path';
3
3
 
4
- type TrackEntry = { id: string; mark: string; owns: string[] };
4
+ type TrackEntry = { id: string; mark: string; owns: string[]; alias?: string };
5
+
6
+ // Seat ids became two letters on 09-30 (#22085) with the old one-letter id kept as `alias`.
7
+ // A track named either way is the same seat — posts and inputs still carry the old letter during the switch.
8
+ export function findTrack(idOrAlias: string, registry: TrackEntry[] = loadTrackRegistry()): TrackEntry | undefined {
9
+ return registry.find((track) => track.id === idOrAlias || track.alias === idOrAlias);
10
+ }
11
+ export function trackPostIds(idOrAlias: string, registry: TrackEntry[] = loadTrackRegistry()): string[] {
12
+ const track = findTrack(idOrAlias, registry);
13
+ return [...new Set([idOrAlias, ...(track ? [track.id, ...(track.alias ? [track.alias] : [])] : [])])];
14
+ }
5
15
 
6
16
  export function loadTrackRegistry(path = resolve(import.meta.dir, '../../scripts/coord-tracks.json')): TrackEntry[] {
7
17
  try {
@@ -6,7 +6,7 @@ import { debug, redactSecretText } from '../debug/log.js';
6
6
  import { getOrCreatePersonaSession } from '../session/index.js';
7
7
  import { getUserConfig } from '../user-config.js';
8
8
  import { decideBudget, readBudgetInputsLive } from '../self-implement/budget-gate.js';
9
- import { loadTrackRegistry } from './mission-phase-track.js';
9
+ import { findTrack } from './mission-phase-track.js';
10
10
  import { effectiveInstanceRoot } from '../instance/resolve.js';
11
11
 
12
12
  export type TrackAgentInput = { missionId: string; taskId: string; title: string; prompt: string; track: string };
@@ -45,7 +45,7 @@ const forbidden = /\b(?:delete|remove|deploy|publish|release|restart|reboot|merg
45
45
  let running = 0;
46
46
 
47
47
  function context(input: TrackAgentInput, repo: string): string {
48
- const track = loadTrackRegistry().find(({ id }) => id === input.track);
48
+ const track = findTrack(input.track);
49
49
  if (!track) throw new Error('unknown track');
50
50
  const docs = resolve(repo, 'docs');
51
51
  let handoff = '';
@@ -94,7 +94,7 @@ export async function decideTrackAction(input: TrackAgentInput, deps: RunnerDeps
94
94
  export async function executeTrackAction(decision: TrackAgentDecision, input: TrackAgentInput, deps: RunnerDeps = {}): Promise<TrackAgentResult> {
95
95
  if (decision.action !== 'say') return { status: 'held', detail: decision.reason ?? '사람 확인' };
96
96
  if (forbidden.test(`${input.title}\n${input.prompt}`)) return { status: 'held', detail: '금지 작업 — 사람 확인' };
97
- if (!loadTrackRegistry().some(({ id }) => id === input.track)) return { status: 'held', detail: '트랙 미정 — 사람 확인' };
97
+ if (!findTrack(input.track)) return { status: 'held', detail: '트랙 미정 — 사람 확인' };
98
98
  try {
99
99
  const checkBudget = deps.checkBudget ?? (async () => {
100
100
  const action = decideBudget(await readBudgetInputsLive()).action;
@@ -6,7 +6,7 @@ import { join, resolve } from 'node:path';
6
6
  import { debug, redactSecretText } from '../debug/log.js';
7
7
  import { TaskStore } from '../task-orchestrator/store.js';
8
8
  import { getUserConfig, type UserConfig } from '../user-config.js';
9
- import { inferPhaseTrack, loadTrackRegistry } from './mission-phase-track.js';
9
+ import { inferPhaseTrack, loadTrackRegistry, trackPostIds } from './mission-phase-track.js';
10
10
  import type { TrackAgentInput, TrackAgentRunner } from './track-agent.js';
11
11
 
12
12
  type CourierConfig = NonNullable<NonNullable<UserConfig['autopilot']>['trackCourier']>;
@@ -66,7 +66,7 @@ export async function trackAlive(track: string, deps: CourierDeps = {}): Promise
66
66
  const comments = await (deps.listComments ?? (() => defaultComments(config.channelPr!, now, minutesFor(config))))();
67
67
  if (!Array.isArray(comments)) throw new Error('comments response is not an array');
68
68
  const timestamps = comments
69
- .filter((c) => c && typeof c.body === 'string' && c.body.startsWith(`**[${track}]**`))
69
+ .filter((c) => c && typeof c.body === 'string' && trackPostIds(track).some((id) => c.body.startsWith(`**[${id}]**`)))
70
70
  .map((c) => Date.parse(c.created_at))
71
71
  .filter((at) => Number.isFinite(at) && at <= now);
72
72
  if (!timestamps.length) return { alive: false, lastPostAt: null, reason: 'no-post' };
@@ -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,7 @@ 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';
14
15
 
15
16
  function keys(): ReadonlyArray<{ keyId: string; publicKey: string }> {
16
17
  const configPath = userConfigPath();
@@ -56,6 +57,32 @@ function safeCause(error: unknown): string {
56
57
 
57
58
  export function registerPluginCommands(program: Command): void {
58
59
  const plugin = program.command('plugin').description('Install and manage Elanous plugins');
60
+ plugin.command('make <request>').description('Write, validate and install a local graph plugin from one request')
61
+ .option('--name <slug>', 'Local plugin name')
62
+ .option('--dir <parent>', 'Parent directory (default: instance plugins-local)')
63
+ .option('--run', 'Run the installed graph')
64
+ .option('--input <json>', 'JSON input for --run')
65
+ .option('--json', 'Print the result as JSON')
66
+ .action(async (request: string, opts: { name?: string; dir?: string; run?: boolean; input?: string; json?: boolean }) => {
67
+ try {
68
+ const result = await makePlugin({ request, name: opts.name, parentDir: opts.dir, run: opts.run,
69
+ ...(opts.input === undefined ? {} : { input: JSON.parse(opts.input) as unknown }) });
70
+ if (opts.json) stdout.write(JSON.stringify(result) + '\n');
71
+ else {
72
+ console.log(`뼈대 ${result.timings.scaffold}ms · 작성 ${result.timings.write}ms`);
73
+ console.log(`검증 오류 ${result.errors.length} · ${result.timings.validate}ms${result.timings.repair === undefined ? '' : ` · 수리 ${result.timings.repair}ms`}`);
74
+ console.log(`설치 ${result.timings.install === 0 && result.status === 'failed' ? '미실행' : result.status === 'failed' && result.timings.run === undefined ? '실패' : '완료'} · ${result.timings.install}ms (${result.dir})`);
75
+ if (opts.run) console.log(`실행 ${result.runStatus ?? '미실행'} · ${result.timings.run ?? 0}ms`);
76
+ for (const error of result.errors) console.error(error);
77
+ }
78
+ if (result.status === 'failed') process.exitCode = 1;
79
+ } catch (error) {
80
+ const message = error instanceof Error ? error.message : String(error);
81
+ if (opts.json) stdout.write(JSON.stringify({ status: 'failed', errors: [message] }) + '\n');
82
+ else console.error(`plugin make failed: ${message}`);
83
+ process.exitCode = 1;
84
+ }
85
+ });
59
86
  plugin.command('add <spec>').description('Install a local path, pinned git plugin or signed market plugin')
60
87
  .option('--yes', 'Accept requested capabilities')
61
88
  .option('--json', 'NDJSON progress events')
@@ -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
+ }
@@ -22,7 +22,8 @@ import { effectiveInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js
22
22
  import { getElanousConfigDirOverride } from '../elanous-config-dir.js';
23
23
  import { envLiteral } from '../platform/env-literal.js';
24
24
  import { userConfigPath } from '../user-config.js';
25
- import { addItem, devVersion, listChecklist, removeItem, seedFromRoadmap, setItem, summarizeChecklist, type ChecklistStatus } from '../release-loop/checklist.js';
25
+ import { addItem, devVersion, listChecklist, removeItem, seedFromRoadmap, setItem, summarizeChecklist, type ChecklistStatus, type ChecklistDisposition } from '../release-loop/checklist.js';
26
+ import { writeStdoutJson } from './stdout-json.js';
26
27
 
27
28
  export const DEFAULT_PUBLIC_REPO = 'ElanvitalAI/elanous';
28
29
  const SEMVER = /^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/;
@@ -466,11 +467,11 @@ function checklistVersion(input: string | undefined, codenames: Record<string, s
466
467
  return resolved;
467
468
  }
468
469
 
469
- function checklistOutput(v: string, codenames: Record<string, string>, mode: 'list' | 'status', json?: boolean): void {
470
+ async function checklistOutput(v: string, codenames: Record<string, string>, mode: 'list' | 'status', json?: boolean): Promise<void> {
470
471
  const data = listChecklist(v);
471
472
  const summary = summarizeChecklist(data);
472
473
  const result = { ...data, codename: codenames[v] ?? '', ...summary };
473
- if (json) { console.log(JSON.stringify(result)); return; }
474
+ if (json) { await writeStdoutJson(`${JSON.stringify(result)}\n`); return; }
474
475
  console.log(`${v}${result.codename ? ` (${result.codename})` : ''} · 공개 ${data.released || '없음'} · 개발 ${data.dev}`);
475
476
  if (mode === 'list') {
476
477
  for (const item of data.items) console.log(`${({ green: '🟢', yellow: '🟡', red: '🔴', done: '✅' } as const)[item.status]} ${item.id} ${item.title}${item.owner ? ` · ${item.owner}` : ''}`);
@@ -492,26 +493,28 @@ export function registerReleaseCommands(program: Command): void {
492
493
  return { version: checklistVersion(opts.version, codenames), codenames, json: opts.json };
493
494
  };
494
495
  const withContext = (cmd: Command) => cmd.option('--version <v>', '판 또는 별칭').option('--json', '결과 JSON');
495
- withContext(checklist.command('list').description('모든 칸')).action((_opts: unknown, cmd: Command) => {
496
+ withContext(checklist.command('list').description('모든 칸')).action(async (_opts: unknown, cmd: Command) => {
496
497
  const { version, codenames, json } = context(cmd);
497
- checklistOutput(version, codenames, 'list', json);
498
+ await checklistOutput(version, codenames, 'list', json);
498
499
  });
499
- withContext(checklist.command('status').description('상태 요약')).action((_opts: unknown, cmd: Command) => {
500
+ withContext(checklist.command('status').description('상태 요약')).action(async (_opts: unknown, cmd: Command) => {
500
501
  const { version, codenames, json } = context(cmd);
501
- checklistOutput(version, codenames, 'status', json);
502
+ await checklistOutput(version, codenames, 'status', json);
502
503
  });
503
504
  withContext(checklist.command('add <id> <title>').description('칸 추가')).option('--owner <owner>', '담당').action((id: string, title: string, opts: { owner?: string }, cmd: Command) => {
504
505
  const { version, json } = context(cmd);
505
506
  const data = addItem(version, { id, title, ...(opts.owner !== undefined ? { owner: opts.owner } : {}) });
506
507
  if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 추가`);
507
508
  });
508
- withContext(checklist.command('set <id>').description('칸 상태·근거·담당 갱신'))
509
+ withContext(checklist.command('set <id>').description('칸 상태·근거·담당·처분 갱신'))
509
510
  .option('--status <status>', 'green|yellow|red|done').option('--evidence <evidence>', '근거').option('--owner <owner>', '담당')
510
- .action((id: string, opts: { status?: string; evidence?: string; owner?: string }, cmd: Command) => {
511
+ .option('--disposition <disposition>', 'move|known-issue|block')
512
+ .action((id: string, opts: { status?: string; evidence?: string; owner?: string; disposition?: string }, cmd: Command) => {
511
513
  const { version, json } = context(cmd);
512
514
  if (opts.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(opts.status)) throw new Error(`잘못된 상태: ${opts.status}`);
513
- if (opts.status === undefined && opts.evidence === undefined && opts.owner === undefined) throw new Error('갱신할 칸을 지정하라');
514
- const data = setItem(version, id, { ...(opts.status !== undefined ? { status: opts.status as ChecklistStatus } : {}), ...(opts.evidence !== undefined ? { evidence: opts.evidence } : {}), ...(opts.owner !== undefined ? { owner: opts.owner } : {}) }, process.env.ELANOUS_TRACK || 'cli');
515
+ if (opts.disposition !== undefined && !['move', 'known-issue', 'block'].includes(opts.disposition)) throw new Error(`잘못된 처분: ${opts.disposition}`);
516
+ if (opts.status === undefined && opts.evidence === undefined && opts.owner === undefined && opts.disposition === undefined) throw new Error('갱신할 칸을 지정하라');
517
+ const data = setItem(version, id, { ...(opts.status !== undefined ? { status: opts.status as ChecklistStatus } : {}), ...(opts.evidence !== undefined ? { evidence: opts.evidence } : {}), ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.disposition !== undefined ? { disposition: opts.disposition as ChecklistDisposition } : {}) }, process.env.ELANOUS_TRACK || 'cli');
515
518
  if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 갱신`);
516
519
  });
517
520
  withContext(checklist.command('rm <id>').description('칸 삭제')).action((id: string, _opts: unknown, cmd: Command) => {
@@ -116,10 +116,10 @@ function deferOutbound(text: string, kind: string, origin?: MissionOrigin | null
116
116
  mkdirSync(dirname(path), { recursive: true });
117
117
  const rec = { ts: new Date().toISOString(), kind, text, ...(origin ? { origin } : {}) };
118
118
  appendFileSync(path, JSON.stringify(rec) + '\n');
119
- console.log(`[outbound] 야간 무음(00:00~06:30 KST) — 보류 적재 (${kind})`);
119
+ console.error(`[outbound] 야간 무음(00:00~06:30 KST) — 보류 적재 (${kind})`);
120
120
  logSend('deferred', kind, { reason: 'quiet-hours' }); // 밀림 적재 관측
121
121
  } catch (e) {
122
- console.log(`[outbound] 보류 적재 실패 — 콘솔 출력\n${text}`, e instanceof Error ? e.message : '');
122
+ console.error(`[outbound] 보류 적재 실패 — 콘솔 출력\n${text}`, e instanceof Error ? e.message : '');
123
123
  }
124
124
  }
125
125
 
@@ -238,7 +238,7 @@ export function sendOutbound(text: string, kind = 'alert', origin?: MissionOrigi
238
238
  return true; // 보류 = 수락 (호출측 재시도/에러 루프 방지)
239
239
  }
240
240
  const bypass = inQuietHours();
241
- if (bypass) console.log('[outbound] 야간 무음 우회 — 최근 사용자 활동(깨어있음) → 즉시 발송 + 보류분 flush');
241
+ if (bypass) console.error('[outbound] 야간 무음 우회 — 최근 사용자 활동(깨어있음) → 즉시 발송 + 보류분 flush');
242
242
  try { flushDeferred(); } catch { /* fail-soft */ }
243
243
  // ★ 발송 관측(대표 지시) — 발송 시각·mode·밀림(burst) 판정을 logs.db 에. burst=최근 2분 5건+
244
244
  // (몰려 나가는 중 = 밀림 의심). `elanous logs --category outbound.send` 로 실시간/밀림 구분.
@@ -275,7 +275,7 @@ export function classifyDaemonResponse(j: unknown): DaemonPathClass {
275
275
  function logDaemonPath(classification: DaemonPathClass, kind: string, extra: Record<string, unknown> = {}): void {
276
276
  try { debug.log('outbound.send', 'daemon-path', { classification, kind, ...extra }); } catch { /* fail-soft */ }
277
277
  if (classification === 'ok') return;
278
- try { console.log(`[outbound] daemon-path ${classification}`); } catch { /* fail-soft */ }
278
+ try { console.error(`[outbound] daemon-path ${classification}`); } catch { /* fail-soft */ }
279
279
  }
280
280
 
281
281
  /** elanous `/v1/outbound` 우선 → 실패 시 텔레그램 직접. 성공 경로 반환(원장 중복방지용). */
@@ -334,6 +334,6 @@ function sendTelegramDirect(text: string, kind?: string): boolean {
334
334
  tok = tok || env.TELEGRAM_BOT_TOKEN || '';
335
335
  chat = chat || env.TELEGRAM_CHAT_ID || '';
336
336
  }
337
- if (!tok || !chat) { console.log('[outbound] 토큰/chat 미설정 — 콘솔 출력\n' + text); return false; }
337
+ if (!tok || !chat) { console.error('[outbound] 토큰/chat 미설정 — 콘솔 출력\n' + text); return false; }
338
338
  return sendTelegramRaw(tok, chat, text);
339
339
  }