elanous 0.2.6 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (450) hide show
  1. package/.env.example +10 -7
  2. package/README.md +4 -0
  3. package/apps/pwa/out/404/index.html +1 -1
  4. package/apps/pwa/out/404.html +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/{3995-bee512a7063036ce.js → 2375-cb6debea56cce4fb.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +25 -0
  16. package/apps/pwa/out/_next/static/chunks/{8d0d9faf-97887ff99713b84e.js → 2ea96958-e8c7e112de50b067.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/{5329.8b7e4bca684b6eec.js → 3403.4bc97b4066945a94.js} +2 -2
  18. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/{7633.0654fe6ea259c1ee.js → 4323.c5dfb6f503304775.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/4836-2eba6079f31b88d4.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/{2984-977cfa339fcd65a5.js → 5080-1613752891fcadb4.js} +3 -3
  24. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/{763-7d0e1913b5074e98.js → 5791-bb82743fa6299562.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/{4240-7153881b0b377306.js → 6062-73e68343a9814f6a.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/{91ff6dda-3b33d42d20657429.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8519-f6b5d8a5833a7603.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8894-e92491a164192cc1.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9740-67051141115ce775.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d5ebde04844689e6.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-10ed24f413ed66e9.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/board/page-211810b6c93ab66a.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/botlab/page-624f7a2f89b60c8f.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/bots/page-5a5fb385a4df0ffe.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/chat/page-80497e93a878ddc5.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/consult/page-7457f52683562161.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/control/page-cee86394de407aa6.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/design-check/page-4ff8edab44196b8c.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/editor/page-92e4f44428d5e290.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/exec/page-b7dcf5d48a854b76.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/field/page-51480757e7d68b92.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/intake/page-2a05dfe80c8bdf16.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/layout-d9979633aa82f8e6.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/live/page-30533117beb53625.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/market/page-f7857826c2f97ad7.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-07267f1391b96aba.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/morning/page-55d50e9b0006a91d.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/not-found-9699b1786be6171d.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/observatory/page-43dd772aee8af75f.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/page-a7e37af7d0399cb8.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/reflection/page-973ab7af59c718b7.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a9f8d3865b91e436.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/sessions/page-15e5a15db84c56aa.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a2e572e4511f2a16.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/settings/page-dd107bf1c7d52a90.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d42b99cdfb9f27c6.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/setup/page-2764ff40dad5a995.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/share/page-c5f1152db116ff96.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/showroom/page-f933bca81ea38a6f.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/tasks/page-c1c95739c0bff3b8.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/term/page-a7aaffcf771662d8.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/trace/page-71d18eec33b9c667.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/vault/page-56adbe1a1f1379bd.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-2c4e59079ee357ac.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a81f113f3ef89d72.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5d422f38c052967e.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-fc27b00e20733146.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/{webpack-37b8bf78a3c6eec5.js → webpack-e5bea36f183b25e6.js} +1 -1
  95. package/apps/pwa/out/_next/static/css/f537bdb19c69306a.css +1 -0
  96. package/apps/pwa/out/_next/static/keajW7DDyghEs1y8oJe6T/_buildManifest.js +1 -0
  97. package/apps/pwa/out/approvals/index.html +1 -1
  98. package/apps/pwa/out/approvals/index.txt +22 -22
  99. package/apps/pwa/out/autopilot/index.html +1 -1
  100. package/apps/pwa/out/autopilot/index.txt +22 -22
  101. package/apps/pwa/out/board/index.html +1 -1
  102. package/apps/pwa/out/board/index.txt +22 -22
  103. package/apps/pwa/out/botlab/index.html +1 -1
  104. package/apps/pwa/out/botlab/index.txt +21 -21
  105. package/apps/pwa/out/bots/index.html +1 -1
  106. package/apps/pwa/out/bots/index.txt +21 -21
  107. package/apps/pwa/out/chat/index.html +1 -1
  108. package/apps/pwa/out/chat/index.txt +21 -21
  109. package/apps/pwa/out/consult/index.html +1 -0
  110. package/apps/pwa/out/consult/index.txt +32 -0
  111. package/apps/pwa/out/control/index.html +1 -1
  112. package/apps/pwa/out/control/index.txt +22 -22
  113. package/apps/pwa/out/design-check/index.html +1 -1
  114. package/apps/pwa/out/design-check/index.txt +22 -22
  115. package/apps/pwa/out/editor/index.html +1 -1
  116. package/apps/pwa/out/editor/index.txt +22 -22
  117. package/apps/pwa/out/exec/index.html +1 -0
  118. package/apps/pwa/out/exec/index.txt +32 -0
  119. package/apps/pwa/out/field/index.html +1 -0
  120. package/apps/pwa/out/field/index.txt +32 -0
  121. package/apps/pwa/out/healthz +1 -1
  122. package/apps/pwa/out/index.html +1 -1
  123. package/apps/pwa/out/index.txt +21 -21
  124. package/apps/pwa/out/intake/index.html +1 -1
  125. package/apps/pwa/out/intake/index.txt +22 -22
  126. package/apps/pwa/out/live/index.html +1 -1
  127. package/apps/pwa/out/live/index.txt +22 -22
  128. package/apps/pwa/out/market/index.html +1 -1
  129. package/apps/pwa/out/market/index.txt +21 -21
  130. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  131. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  132. package/apps/pwa/out/morning/index.html +1 -1
  133. package/apps/pwa/out/morning/index.txt +22 -22
  134. package/apps/pwa/out/observatory/index.html +1 -1
  135. package/apps/pwa/out/observatory/index.txt +22 -22
  136. package/apps/pwa/out/reflection/index.html +1 -1
  137. package/apps/pwa/out/reflection/index.txt +22 -22
  138. package/apps/pwa/out/scheduler/index.html +1 -1
  139. package/apps/pwa/out/scheduler/index.txt +22 -22
  140. package/apps/pwa/out/sessions/index.html +1 -1
  141. package/apps/pwa/out/sessions/index.txt +21 -21
  142. package/apps/pwa/out/settings/devices/index.html +1 -1
  143. package/apps/pwa/out/settings/devices/index.txt +22 -22
  144. package/apps/pwa/out/settings/index.html +1 -1
  145. package/apps/pwa/out/settings/index.txt +22 -22
  146. package/apps/pwa/out/setup/done/index.html +1 -1
  147. package/apps/pwa/out/setup/done/index.txt +22 -22
  148. package/apps/pwa/out/setup/index.html +1 -1
  149. package/apps/pwa/out/setup/index.txt +22 -22
  150. package/apps/pwa/out/share/index.html +1 -1
  151. package/apps/pwa/out/share/index.txt +22 -22
  152. package/apps/pwa/out/showroom/index.html +1 -1
  153. package/apps/pwa/out/showroom/index.txt +21 -21
  154. package/apps/pwa/out/tasks/index.html +1 -1
  155. package/apps/pwa/out/tasks/index.txt +22 -22
  156. package/apps/pwa/out/term/index.html +1 -1
  157. package/apps/pwa/out/term/index.txt +22 -22
  158. package/apps/pwa/out/trace/index.html +1 -1
  159. package/apps/pwa/out/trace/index.txt +22 -22
  160. package/apps/pwa/out/vault/index.html +1 -1
  161. package/apps/pwa/out/vault/index.txt +22 -22
  162. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  163. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  164. package/apps/pwa/out/workflows/index.html +1 -1
  165. package/apps/pwa/out/workflows/index.txt +22 -22
  166. package/apps/pwa/out/workspace/index.html +1 -1
  167. package/apps/pwa/out/workspace/index.txt +22 -22
  168. package/apps/pwa/out/worktrees/index.html +1 -1
  169. package/apps/pwa/out/worktrees/index.txt +22 -22
  170. package/bin/elanous.cjs +107 -0
  171. package/graphs/field/field-feed.yaml +24 -0
  172. package/graphs/field/recipes.yaml +12 -0
  173. package/graphs/landing/landing-heal.yaml +3 -1
  174. package/graphs/landing/recipes.yaml +3 -0
  175. package/graphs/release/recipes.yaml +10 -0
  176. package/graphs/release/release-loop.yaml +10 -3
  177. package/package.json +5 -5
  178. package/plugins/card-followup/README.md +13 -4
  179. package/plugins/card-followup/graphs/card-followup.yaml +3 -1
  180. package/plugins/card-followup/graphs/recipes.yaml +5 -1
  181. package/plugins/card-followup/graphs/run-step.ts +122 -18
  182. package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
  183. package/plugins/card-followup/plugin.json +1 -1
  184. package/plugins/doc-draft/README.md +13 -0
  185. package/plugins/doc-draft/examples/brief-sample.json +8 -0
  186. package/plugins/doc-draft/graphs/doc-draft.yaml +14 -0
  187. package/plugins/doc-draft/graphs/recipes.yaml +9 -0
  188. package/plugins/doc-draft/graphs/run-step.ts +113 -0
  189. package/plugins/doc-draft/plugin.json +24 -0
  190. package/plugins/geo-check/README.md +3 -1
  191. package/plugins/geo-check/graphs/run-step.ts +24 -3
  192. package/plugins/geo-check/plugin.json +1 -1
  193. package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
  194. package/plugins/job-coach/README.md +1 -1
  195. package/plugins/job-coach/graphs/run-step.ts +2 -2
  196. package/plugins/job-coach/plugin.json +1 -1
  197. package/plugins/job-coach/plugin.ts +1 -1
  198. package/scripts/affinity/endcard.sh +3 -2
  199. package/scripts/affinity/reframe.sh +3 -1
  200. package/scripts/ci-public-export-leak-gate.ts +48 -0
  201. package/scripts/codex-quota-alert.ts +122 -258
  202. package/scripts/coord-tracks.json +4 -4
  203. package/scripts/field-feed/deliver-node.ts +52 -0
  204. package/scripts/field-feed/feed-node.ts +67 -0
  205. package/scripts/field-feed/lib.ts +162 -0
  206. package/scripts/field-feed/reel-node.ts +24 -0
  207. package/scripts/field-feed/start.ts +62 -0
  208. package/scripts/fixtures/vflow-sample.ndjson +6 -0
  209. package/scripts/install.sh +92 -13
  210. package/scripts/landing-heal/clean-checkout.ts +114 -0
  211. package/scripts/landing-heal/run-mission.ts +38 -3
  212. package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
  213. package/scripts/public-export.ts +8 -4
  214. package/scripts/release-loop/baseline-from-logs.ts +92 -0
  215. package/scripts/release-loop/env-known-failures.ts +31 -0
  216. package/scripts/release-loop/gate-node.ts +81 -19
  217. package/scripts/release-loop/known-issues-node.ts +159 -0
  218. package/scripts/release-loop/npm-publish-node.ts +118 -0
  219. package/scripts/release-loop/ops-upgrade-node.ts +87 -0
  220. package/scripts/release-loop/release-readiness.ts +58 -0
  221. package/scripts/release-loop/unattended-release.ts +93 -0
  222. package/scripts/ux/measure-first-text.ts +102 -0
  223. package/scripts/vflow-collect.ts +20 -7
  224. package/scripts/vflow-search.ts +73 -0
  225. package/scripts/video-free-line-test-walker.ts +36 -0
  226. package/scripts/video-pipeline.ts +2 -2
  227. package/skills/explainer-video/SKILL.md +12 -9
  228. package/skills/explainer-video/engine/build.mjs +1 -1
  229. package/skills/explainer-video/engine/reel.mjs +138 -31
  230. package/skills/explainer-video/engine/reel.sh +2 -1
  231. package/skills/explainer-video/examples/elanous-0.2.5/script.json +1 -1
  232. package/skills/grill-me/SKILL.md +1 -1
  233. package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +1 -1
  234. package/src/a2a/server.ts +2 -0
  235. package/src/acp/elanous-aware-peers.ts +30 -0
  236. package/src/acp/server.ts +91 -15
  237. package/src/agent/monad-agent-turn.ts +25 -6
  238. package/src/agent/shared-app-tools.ts +5 -3
  239. package/src/agent-substrate/review-intent.ts +6 -2
  240. package/src/agent-substrate/shard-boundary-intent.ts +70 -0
  241. package/src/auth/github-app-token.ts +4 -1
  242. package/src/autopilot/track-agent-shadow.ts +151 -0
  243. package/src/autopilot/track-agent.ts +23 -3
  244. package/src/boot/acp-server-help.ts +16 -0
  245. package/src/boot/daemon-prompt-turn.ts +31 -10
  246. package/src/boot/daemon-tools/index.ts +7 -1
  247. package/src/budget/codex-credit-pace.ts +84 -0
  248. package/src/budget/codex-credit-plan.ts +154 -0
  249. package/src/budget/codex-reset-credit-state.ts +2 -0
  250. package/src/chat/chat-cli.ts +4 -1
  251. package/src/chat/short-question.ts +40 -0
  252. package/src/cli/agent-cli.ts +24 -15
  253. package/src/cli/ask-bare.ts +42 -0
  254. package/src/cli/cli-user-error.ts +25 -0
  255. package/src/cli/codex-credit-plan-view.ts +25 -0
  256. package/src/cli/dependence-gauge.ts +237 -0
  257. package/src/cli/doctor-cli.ts +5 -0
  258. package/src/cli/doctor-modes.ts +48 -0
  259. package/src/cli/intake-cli.ts +12 -2
  260. package/src/cli/intake-source-cli.ts +58 -0
  261. package/src/cli/machine-cli.ts +23 -0
  262. package/src/cli/msg-cli.ts +137 -0
  263. package/src/cli/plugin-cli.ts +25 -0
  264. package/src/cli/pr-cli.ts +36 -0
  265. package/src/cli/provider-cli.ts +5 -0
  266. package/src/cli/release-cli.ts +42 -1
  267. package/src/cli/release-run-if-ready.ts +48 -0
  268. package/src/cli/setup-cli.ts +27 -6
  269. package/src/cli/setup-recommend.ts +8 -0
  270. package/src/cli/usage-cli.ts +11 -1
  271. package/src/codex/setup.ts +27 -8
  272. package/src/dashboard/index.ts +1 -1
  273. package/src/discord.ts +56 -6
  274. package/src/domains/core-tools.ts +37 -14
  275. package/src/domains/outbound-alert.ts +50 -11
  276. package/src/exec-requests/planner.ts +140 -0
  277. package/src/exec-requests/runner.ts +338 -0
  278. package/src/exec-requests/store.ts +76 -0
  279. package/src/field/field-media.ts +8 -3
  280. package/src/field/field-reel.ts +196 -0
  281. package/src/grok/credential.ts +7 -5
  282. package/src/harness/aside-run-status.ts +33 -0
  283. package/src/harness/browser-verify.ts +48 -17
  284. package/src/harness/harness-cli-command.ts +25 -3
  285. package/src/harness/image-load-state.ts +21 -0
  286. package/src/harness/process-garbage.ts +45 -0
  287. package/src/index.ts +86 -14
  288. package/src/instance/leader.ts +13 -1
  289. package/src/intake-plane/collect-github.ts +5 -3
  290. package/src/intake-plane/collect-telegram-saved.ts +15 -3
  291. package/src/intake-plane/discord-seat-work.ts +56 -0
  292. package/src/intake-plane/intake-sources.ts +202 -0
  293. package/src/intake-plane/items.ts +28 -12
  294. package/src/intake-plane/seat-doc-route.ts +207 -0
  295. package/src/intake-plane/submit-intake-work.ts +9 -1
  296. package/src/intake-plane/telegram-seat-work.ts +72 -0
  297. package/src/llm/model-defaults.ts +24 -1
  298. package/src/llm.ts +5 -4
  299. package/src/machines/machine-ledger.ts +127 -0
  300. package/src/market/pack-internal-refs.ts +28 -0
  301. package/src/market/publish.ts +31 -4
  302. package/src/maturity/feature-maturity.ts +65 -0
  303. package/src/model-tier/turn-tier.ts +24 -0
  304. package/src/msg/msg-store.ts +149 -0
  305. package/src/native-tool-catalog.ts +2 -1
  306. package/src/nexus/api/chat-fast-path-config.ts +28 -0
  307. package/src/nexus/api/consult-requests.ts +117 -0
  308. package/src/nexus/api/discord-trigger-bot.ts +3 -0
  309. package/src/nexus/api/exec-requests.ts +51 -0
  310. package/src/nexus/api/field-uploads.ts +40 -3
  311. package/src/nexus/api/graph-approvals-feed.ts +140 -0
  312. package/src/nexus/api/graph-approvals.ts +71 -4
  313. package/src/nexus/api/http-server.ts +61 -7
  314. package/src/nexus/api/seat-requests.ts +254 -0
  315. package/src/nexus/api/trace-run-scan.ts +37 -0
  316. package/src/nexus/api/trace.ts +19 -3
  317. package/src/nexus/index.ts +3 -0
  318. package/src/nexus/setup-status.ts +4 -5
  319. package/src/notify/llm-budget-alert.ts +16 -10
  320. package/src/oauth/codex-account-rotation.ts +26 -4
  321. package/src/oauth/codex-account-store.ts +20 -2
  322. package/src/oauth/codex-browser-login.ts +173 -0
  323. package/src/oauth/codex.ts +4 -1
  324. package/src/oauth/fallback-chain.ts +5 -5
  325. package/src/oauth/store.ts +42 -5
  326. package/src/onboarding/continue-here.ts +29 -0
  327. package/src/onboarding/entry-hints.ts +8 -0
  328. package/src/onboarding/full-screen-io.ts +35 -3
  329. package/src/onboarding/help/skills.md +1 -1
  330. package/src/onboarding/non-interactive.ts +1 -0
  331. package/src/onboarding/subscription-hint.ts +27 -0
  332. package/src/onboarding/web-first.ts +83 -0
  333. package/src/onboarding.ts +230 -23
  334. package/src/plugins/maker/node-install.ts +92 -0
  335. package/src/plugins/maker/node-maker.ts +139 -0
  336. package/src/plugins/maker/plugin-maker.ts +19 -7
  337. package/src/policy/llm-policy.ts +268 -0
  338. package/src/prompt-library/session-guidance.ts +7 -1
  339. package/src/prompt-library/universal-preamble.ts +2 -2
  340. package/src/relay/relay-cli.ts +20 -0
  341. package/src/relay/relay-server.ts +220 -0
  342. package/src/release-loop/checklist.ts +9 -8
  343. package/src/self-dev/decompose.ts +1 -0
  344. package/src/self-dev/dev-pipeline.ts +6 -3
  345. package/src/self-dev/run-record-mirror.ts +44 -0
  346. package/src/self-dev/run-store.ts +12 -3
  347. package/src/self-implement/harness-policy-gates.ts +4 -1
  348. package/src/self-implement/report-origin.ts +12 -1
  349. package/src/session/chat.ts +17 -5
  350. package/src/skills/shared-agent-skill-roots.ts +25 -0
  351. package/src/steward/failure-streak.ts +78 -0
  352. package/src/steward/steward-cards.ts +24 -0
  353. package/src/steward/triage-budget.ts +127 -0
  354. package/src/steward/triage-plan.ts +75 -0
  355. package/src/steward/triage.ts +244 -42
  356. package/src/task-cards/card-store.ts +1 -1
  357. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
  358. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +68 -0
  359. package/src/task-orchestrator/surfaces/self-implement-pod.ts +78 -4
  360. package/src/telegram.ts +38 -2
  361. package/src/tool-runtime/elanous-skills-list-runtime.ts +3 -3
  362. package/src/tool-runtime/self-cognition-runtimes.ts +160 -2
  363. package/src/user-config.ts +80 -4
  364. package/src/version/packed-revision.json +1 -1
  365. package/src/video-pipeline/recipes/character.ts +42 -5
  366. package/src/video-pipeline/recipes/ffmpeg.ts +1 -1
  367. package/src/video-pipeline/recipes/free-line.ts +16 -3
  368. package/src/video-pipeline/recipes/remote.ts +7 -1
  369. package/src/video-pipeline/vflow-reference.ts +109 -0
  370. package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +0 -1
  373. package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/3190-e9282753e7ebde85.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +0 -25
  379. package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +0 -1
  388. package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +0 -1
  403. package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +0 -1
  404. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +0 -1
  448. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
  449. package/apps/pwa/out/_next/static/qMI0jnlZaBjjm4aIDYNGM/_buildManifest.js +0 -1
  450. /package/apps/pwa/out/_next/static/{qMI0jnlZaBjjm4aIDYNGM → keajW7DDyghEs1y8oJe6T}/_ssgManifest.js +0 -0
@@ -22,17 +22,30 @@ const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36';
22
22
  const OUT_DIR = process.env.VFLOW_OUT ?? join(homedir(), 'docs', 'ref', 'vflow');
23
23
  const NDJSON = join(OUT_DIR, 'prompts.ndjson');
24
24
  const URLS = join(OUT_DIR, 'urls.txt');
25
- const CONCURRENCY = 4;
26
- const GAP_MS = 120;
25
+ // ⚠️ 2026-10-01 실측: 4병렬·120ms 로 전수를 돌리면 수백 건 뒤 사이트가 429 를 낸다(재수집 404건 중 158건).
26
+ // 기본을 낮추고, 429 는 «없다»가 아니라 «기다려라»로 읽는다(Retry-After 를 따른다).
27
+ const CONCURRENCY = Number(process.env.VFLOW_CONCURRENCY ?? 2);
28
+ const GAP_MS = Number(process.env.VFLOW_GAP_MS ?? 400);
29
+ const MAX_429_RETRIES = 5;
27
30
 
28
31
  const argv = process.argv.slice(2);
29
32
  const limitAt = argv.indexOf('--limit');
30
33
  const LIMIT = limitAt >= 0 ? Number(argv[limitAt + 1]) : Infinity;
31
34
 
35
+ let throttled = 0;
32
36
  async function get(url: string): Promise<string> {
33
- const r = await fetch(url, { headers: { 'User-Agent': UA } });
34
- if (!r.ok) throw new Error(`HTTP ${r.status}`);
35
- return r.text();
37
+ for (let attempt = 0; ; attempt++) {
38
+ const r = await fetch(url, { headers: { 'User-Agent': UA } });
39
+ if (r.status === 429 && attempt < MAX_429_RETRIES) {
40
+ throttled++;
41
+ const after = Number(r.headers.get('retry-after'));
42
+ const waitMs = Number.isFinite(after) && after > 0 ? after * 1000 : 5000 * 2 ** attempt;
43
+ await new Promise((res) => setTimeout(res, waitMs));
44
+ continue;
45
+ }
46
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
47
+ return r.text();
48
+ }
36
49
  }
37
50
 
38
51
  /** 사이트맵 전수 → 영문 `/prompts/` URL. ⛔ 번역판(`/{lang}/prompts/`)은 «같은 내용»이라 뺀다. */
@@ -166,14 +179,14 @@ async function main(): Promise<void> {
166
179
  catch { failed++; return null; } // ⛔ 「없다」가 아니라 «못 받았다»
167
180
  }));
168
181
  for (const r of rows) { if (r) { appendFileSync(NDJSON, JSON.stringify(r) + '\n'); ok++; } else noLd++; }
169
- if ((i / CONCURRENCY) % 25 === 0) console.log(` … ${ok + noLd}/${todo.length} ok=${ok} 건너뜀=${noLd}`);
182
+ if ((i / CONCURRENCY) % 25 === 0) console.log(` … ${ok + noLd}/${todo.length} ok=${ok} 못받음=${failed} jsonld없음=${noLd - failed} 429대기=${throttled}`);
170
183
  }
171
184
  // ⛔ 수를 «도구가» 낸다 — 사람이 세지 않는다.
172
185
  writeFileSync(join(OUT_DIR, 'MANIFEST.txt'),
173
186
  [`source: https://vflow.live (robots: Allow: / · 2026-09-23 확인)`,
174
187
  `fetched: ${new Date().toISOString()}`,
175
188
  `urls_total: ${urls.length}`, `rows_ok: ${ok + done.size}`,
176
- `skipped_no_jsonld: ${noLd - failed}`, `failed_fetch: ${failed}`,
189
+ `skipped_no_jsonld: ${noLd - failed}`, `failed_fetch: ${failed}`, `throttled_429_retries: ${throttled}`,
177
190
  `schema: url model category slug name description prompt keywords[] datePublished author authorUrl sourcePost thumb spec{duration,camera[],lighting[],mood[],difficulty,promptLanguage,includes[]} video{url,isoDuration,resolution,aspect}`,
178
191
  `refetch: bun scripts/vflow-collect.ts`, ''].join('\n'));
179
192
  console.log(`\n ✅ ok ${ok} · 건너뜀 ${noLd - failed} · 못 받음 ${failed} → ${NDJSON}`);
@@ -0,0 +1,73 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ import { searchVflowReferences, type VflowQuery } from '../src/video-pipeline/vflow-reference.js';
4
+
5
+ const args = process.argv.slice(2);
6
+ let file = join(homedir(), 'docs', 'ref', 'vflow', 'prompts.ndjson');
7
+ const query: { -readonly [K in keyof VflowQuery]: VflowQuery[K] } = { limit: 5 };
8
+ let json = false;
9
+ const techniques: string[] = [];
10
+ const terms: string[] = [];
11
+
12
+ for (let i = 0; i < args.length; i++) {
13
+ const arg = args[i]!;
14
+ if (arg === '--json') { json = true; continue; }
15
+ if (['--file', '--model', '--technique', '--category', '--limit', '--camera', '--lighting', '--mood', '--max-seconds'].includes(arg)) {
16
+ const value = args[++i];
17
+ if (!value || value.startsWith('--')) {
18
+ console.error(`값이 필요한 옵션: ${arg}`);
19
+ process.exit(1);
20
+ }
21
+ switch (arg) {
22
+ case '--file': file = value; break;
23
+ case '--model': query.model = value; break;
24
+ case '--technique': techniques.push(value); break;
25
+ case '--category': query.category = value; break;
26
+ case '--camera': query.camera = value; break;
27
+ case '--lighting': query.lighting = value; break;
28
+ case '--mood': query.mood = value; break;
29
+ case '--limit': {
30
+ const parsed = Number(value);
31
+ if (!Number.isSafeInteger(parsed) || parsed < 0) {
32
+ console.error(`--limit 은 0 이상의 정수여야 합니다: ${value}`);
33
+ process.exit(1);
34
+ }
35
+ query.limit = parsed;
36
+ break;
37
+ }
38
+ case '--max-seconds': {
39
+ const parsed = Number(value);
40
+ if (!Number.isFinite(parsed) || parsed < 0 || !value.trim()) {
41
+ console.error(`--max-seconds 은 0 이상의 숫자여야 합니다: ${value}`);
42
+ process.exit(1);
43
+ }
44
+ query.maxSeconds = parsed;
45
+ break;
46
+ }
47
+ }
48
+ } else if (arg.startsWith('--')) {
49
+ console.error(`알 수 없는 옵션: ${arg}`);
50
+ process.exit(1);
51
+ } else {
52
+ terms.push(arg);
53
+ }
54
+ }
55
+ query.terms = terms;
56
+ query.techniques = techniques;
57
+ const result = searchVflowReferences(file, query);
58
+ if (!result.available) {
59
+ console.error(`입력 파일을 읽을 수 없습니다: ${file} (${result.reason})`);
60
+ process.exit(1);
61
+ }
62
+ if (json) {
63
+ console.log(JSON.stringify(result.rows.map(({ url, model, category, name, prompt, keywords, author, authorUrl, video }) =>
64
+ ({ url, model, category, name, prompt, keywords, author, authorUrl, video: video ?? null }))));
65
+ if (result.matches === 0) console.error(`일치 0 · 읽은 줄 ${result.read}`);
66
+ } else if (result.matches === 0) {
67
+ console.log(`일치 0 · 읽은 줄 ${result.read}`);
68
+ } else {
69
+ for (const row of result.rows) {
70
+ console.log(`${row.name} · ${row.model}\n${row.url}\n${row.author ?? ''}\n${row.prompt.slice(0, 240)}${row.video?.url ? `\n${row.video.url}` : ''}`);
71
+ }
72
+ }
73
+ if (result.skipped) console.error(`건너뛴 줄 ${result.skipped}`);
@@ -0,0 +1,36 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { parse as parseYaml } from 'yaml';
3
+ import type { GraphSpecLike, WalkerApi } from '../src/video-pipeline/walk-line.js';
4
+
5
+ interface TestGraph extends GraphSpecLike {
6
+ entry_node: string;
7
+ terminal_nodes: string[];
8
+ nodes: ({ node_id: string; recipe?: string; max_visits?: number })[];
9
+ }
10
+
11
+ export const readGraphSpec: WalkerApi['readGraphSpec'] = (path) => {
12
+ try { return { spec: parseYaml(readFileSync(path, 'utf8')) as TestGraph }; }
13
+ catch (e) { return { error: (e as Error).message }; }
14
+ };
15
+
16
+ export const walkGraph: WalkerApi['walkGraph'] = async (graph, step, opts) => {
17
+ const spec = graph as TestGraph;
18
+ const visits = new Map<string, number>();
19
+ const steps: { node: string; visit: number }[] = [];
20
+ let next = spec.entry_node;
21
+ while (steps.length < opts.maxSteps) {
22
+ const node = spec.nodes.find((n) => n.node_id === next);
23
+ if (!node) return { terminal: null, stopReason: 'no-node', steps };
24
+ const visit = (visits.get(next) ?? 0) + 1;
25
+ if (visit > (node.max_visits ?? 1)) return { terminal: null, stopReason: 'budget-exceeded', steps };
26
+ visits.set(next, visit);
27
+ steps.push({ node: next, visit });
28
+ if (spec.terminal_nodes.includes(next)) return { terminal: next, stopReason: 'terminal', steps };
29
+ const outcome = await step(node);
30
+ if (outcome === null) { next = opts.unobservedNode; continue; }
31
+ const edge = spec.edges.find((e) => e.from === next && (e.to !== undefined || e.map?.[outcome] !== undefined));
32
+ if (!edge) return { terminal: null, stopReason: 'no-edge', steps };
33
+ next = edge.to ?? edge.map![outcome]!;
34
+ }
35
+ return { terminal: null, stopReason: 'budget-exceeded', steps };
36
+ };
@@ -998,9 +998,8 @@ async function plan() {
998
998
  return where.length ? `${u} → 구간을 «${where.join('/')}» 까지` : `${u} → ⛔ 스핀 결손`;
999
999
  });
1000
1000
 
1001
- sayUnprobed();
1002
-
1003
1001
  if (COMPACT) {
1002
+ sayUnprobed();
1004
1003
  // ⛔ 노드별로 찍지 않는다 — encode 가 세 번 나온다. 능력 단위로 «접는다».
1005
1004
  const seen = new Map<string, { impl?: Impl; required: boolean }>();
1006
1005
  for (const o of out) for (const p of o.picks) {
@@ -1138,6 +1137,7 @@ async function plan() {
1138
1137
  return;
1139
1138
  }
1140
1139
 
1140
+ sayUnprobed();
1141
1141
  console.log(`\n🧱 추천 스택 — 구간 ${nodes[0].id} → ${nodes[nodes.length - 1].id} · 선호 ${TIER_MARK[prefer]}`);
1142
1142
  console.log(` 설정: ${LOADED.source}${MACHINE ? ` · 기계 «${MACHINE_NAME}»` : ''}`);
1143
1143
  if (MISSING.size) console.log(` ⚠️ 없다고 «가정»한 것: ${[...MISSING].join(', ')}`);
@@ -35,7 +35,7 @@ The grammar of a chapter (take it from the reference, don't improvise):
35
35
 
36
36
  The outro gives one line that sums everything up, then the CTA.
37
37
 
38
- ⛔ Every claim must trace to a merged change or a measured run, listed in `sources`. Don't write «fully autonomous», dates you can't promise, or comparisons (`docs/marketing/GUIDE-launch-tone-and-banned-claims-*`).
38
+ ⛔ Every claim must trace to a merged change or a measured run, listed in `sources`. Don't write «fully autonomous», dates you can't promise, or comparisons with other products.
39
39
 
40
40
  ## 2. Voice — `bun $SKILL/engine/vo.ts <project>/script.json`
41
41
 
@@ -85,7 +85,7 @@ The reference supplies the **grammar** (components and staging), never its colou
85
85
  - **Text**: `#F2F1EE`.
86
86
  - **Motif**: the V6 «엘랑 소용돌이» mark (`engine/brand/`), used in three places — a slow, faint rotation behind the scene, the header, and the chapter cards.
87
87
 
88
- Why these choices, what the reference does, and what we can and cannot reproduce: `내부 문서 `RESEARCH-explainer-video-reference-decomposition-2026-09-30``.
88
+ The reference is used only for its structure (header, chapter cards, a diagram that moves one step per line, keyword-timed captions). Its colours, artwork and voice are not reproduced.
89
89
 
90
90
  ## Field reel — photos from the venue → a vertical video in about a minute
91
91
 
@@ -95,13 +95,16 @@ zsh $SKILL/engine/reel.sh <folder> --title "마케터의 밤" --sub "2026.10.02
95
95
 
96
96
  - **Input**: a folder of phone photos (jpg · png · heic) and short clips (mp4 · mov). The default upload folder is `~/.elanous/field/<event>/`.
97
97
  - **Order**: by capture time (`mdls` creation date), then by file name.
98
- - **Captions**: optional `<folder>/captions.txt`, one line per item as `파일명 | 자막`. Items without a line get no caption.
98
+ - **Captions**: optional `<folder>/captions.txt`, one line per item as `파일명 | 자막`. A photo without a line gets one line from a vision model (`codex exec -i`, all photos in parallel, 30 s cap each); if that fails it shows «현장 N». `FIELD_REEL_VISION=0` turns the vision step off.
99
+ - **Title**: `--title`, or `title.txt` through the daemon. A slug-like title (`field-2026-10-01`) is replaced by a vision headline read from the first three photos (event or company names on screen first). The date line defaults to the first photo's capture date (KST).
100
+ - **Music**: `FIELD_REEL_BGM=<file>` or `~/.cache/elanous-explainer/bgm/field.wav` — cut to length, faded in and out, leveled to about −16 LUFS. No file means no music. Use only licensed tracks (the default cache holds an Epidemic Sound download); the file is never committed.
99
101
  - **Output**:
100
- - `<folder>/reel/reel-9x16.mp4` at 1080×1920, 30–60 s.
101
- - A black title card, then each item: photos for 3.5 s with a slow zoom, clips for their first 5 s.
102
- - One caption line per item, the V6 header, an Icarus Red progress bar, and an end card with elanous.ai.
103
- - **Fitting to length**: over 60 s drops items from the middle, keeping the first and last. Under 30 s stretches the photos.
104
- - **Measured**: 7 items → 31.2 s video in 56–83 s end to end (M-series Mac, `hyperframes render`).
102
+ - `<folder>/reel/reel-9x16.mp4` at 1080×1920, 24–60 s.
103
+ - An intro card (kicker, mark, title word by word, red rule, date), then each item: photos for about 4–6 s. A landscape photo is shown whole over a blurred fill of itself; a portrait photo fills the frame. Each move overlaps the next with a slide and a red wipe on the cut.
104
+ - Per item a counter (`01 / 03`), the caption rising word by word, and a red rule. The V6 header, an Icarus Red progress bar, and an end card with elanous.ai.
105
+ - `--part body` renders the photos only (no cards, no music) — for an instant version that stitches pre-rendered cards around it.
106
+ - **Fitting to length**: over 60 s drops items from the middle, keeping the first and last. Under 24 s stretches the photos.
107
+ - **Measured**: 3 photos → 24 s video in 28 s end to end, of which vision 8.7 s and render 15.9 s (M-series Mac, 10-01). Earlier: 7 items → 31.2 s video in 56–83 s.
105
108
  - **Before posting**: people's faces need their consent. Run the same 1 fps OCR check as any public video.
106
109
 
107
110
  ## Graph
@@ -114,4 +117,4 @@ zsh $SKILL/engine/reel.sh <folder> --title "마케터의 밤" --sub "2026.10.02
114
117
  - speech mismatch → vo;
115
118
  - bad script → script;
116
119
  - public release → needs-human.
117
- - **Not coded yet**: the `explainer-*` recipes. Until they are, you run these steps from this skill.
120
+ - **Recipes**: the `explainer-*` recipes are in the elanous core, but `graph run` does not yet accept this graph's terminal nodes. The video-explainer plugin therefore ships the skill and nodes only; run these steps from this skill. The graph comes with plugin 0.2.0.
@@ -22,7 +22,7 @@ const W = 1920, H = 1080, FPS = 30;
22
22
  // ── palette (Elanvital CI · RCBC guide) ─────────────────────────────────────────
23
23
  const C = { deep: "#1D2751", deep2: "#141C3D", black: "#000000", ink: "#F2F1EE", red: "#E95047", muted: "rgba(242,241,238,0.62)", line: "rgba(242,241,238,0.86)", faint: "rgba(242,241,238,0.10)" };
24
24
 
25
- // ── mark (V6 «엘랑 소용돌이», docs/brand/icon) ───────────────────────────────────
25
+ // ── mark (V6 «엘랑 소용돌이», engine/brand/) ─────────────────────────────────────
26
26
  const markSrc = readFileSync(process.env.ELANOUS_MARK ?? join(here, "brand", "elanous-mark-on-dark.svg"), "utf8");
27
27
  const markPaths = [...markSrc.matchAll(/<path d="([^"]*)"/g)].map((m) => m[1]);
28
28
  const mark = (size, blade = C.ink, dot = C.red, cls = "") =>
@@ -1,22 +1,29 @@
1
- // Field reel — a folder of phone photos / short clips → one vertical 30–60 s HyperFrames composition (EV10).
2
- // Usage: node reel.mjs <folder> [--title "마케터의 밤"] [--sub "2026.10.02 · 서울"] [--max 60] [--min 30]
1
+ // Field reel — a folder of phone photos / short clips → one vertical HyperFrames composition (EV10 · EV10b).
2
+ // Usage: node reel.mjs <folder> [--title "마케터의 밤"] [--sub "2026.10.02 · 서울"] [--max 60] [--min 24] [--part full|body]
3
3
  // <folder>/captions.txt (optional): one line per item — `파일명 | 자막` (a line without « | » is ignored).
4
+ // Photos without a caption get one line from a vision model (codex · parallel · FIELD_REEL_VISION=0 turns it off);
5
+ // if that fails the caption is «현장 N». A slug-like title (`field-2026-10-01`) is replaced by a vision headline.
6
+ // BGM: FIELD_REEL_BGM=<wav/mp3> or ~/.cache/elanous-explainer/bgm/field.wav — cut to length, faded and leveled here.
7
+ // --part body: photos only, no intro/end cards and no music (the instant version stitches cached cards around it).
4
8
  // Output: <folder>/reel/hf/ (HyperFrames project) ⊕ reel/timeline.json. Render: npx hyperframes render --fps 30 in reel/hf.
5
9
  // Look = Elanvital CI: Real Black ground · Icarus Red single accent · V6 mark · Pretendard. Order = capture time (then name).
6
10
  import { readFileSync, writeFileSync, mkdirSync, copyFileSync, existsSync, readdirSync, statSync } from "node:fs";
7
11
  import { join, dirname, resolve, extname, basename } from "node:path";
8
12
  import { homedir } from "node:os";
9
- import { spawnSync } from "node:child_process";
13
+ import { spawn, spawnSync } from "node:child_process";
10
14
  import { fileURLToPath } from "node:url";
11
15
 
12
16
  const here = dirname(fileURLToPath(import.meta.url));
13
17
  const argv = process.argv.slice(2);
14
18
  const opt = (k, d) => { const i = argv.indexOf(`--${k}`); return i >= 0 ? argv[i + 1] : d; };
15
19
  const folder = resolve(argv.find((a, i) => !a.startsWith("--") && !(i > 0 && argv[i - 1].startsWith("--"))) ?? ".");
16
- const TITLE = opt("title", "현장 스케치"), SUB = opt("sub", ""), MAX = Number(opt("max", 60)), MIN = Number(opt("min", 30));
17
- const W = 1080, H = 1920, INTRO = 2.2, END = 3.0, PHOTO = 3.5, CLIP_MAX = 5, XF = 0.35;
20
+ const PART = opt("part", "full");
21
+ const MAX = Number(opt("max", 60)), MIN = Number(opt("min", 24));
22
+ const W = 1080, H = 1920, PHOTO = 4.2, CLIP_MAX = 5, XF = 0.5;
23
+ const INTRO = PART === "body" ? 0 : 3.0, END = PART === "body" ? 0 : 3.4;
18
24
  const out = join(folder, "reel"), hf = join(out, "hf"), media = join(hf, "assets", "media");
19
25
  mkdirSync(media, { recursive: true }); mkdirSync(join(hf, "assets", "fonts"), { recursive: true });
26
+ const T0 = Date.now(), lap = (s) => console.log(`reel: ${s} · ${((Date.now() - T0) / 1000).toFixed(1)}s`);
20
27
 
21
28
  // ── collect & order ────────────────────────────────────────────────────────────
22
29
  const IMG = [".jpg", ".jpeg", ".png", ".heic", ".webp"], VID = [".mp4", ".mov", ".m4v"];
@@ -41,9 +48,9 @@ if (!items.length) throw new Error(`no photos or clips in ${folder}`);
41
48
  for (const it of items) it.dur = it.video ? Math.min(CLIP_MAX, Math.max(1.5, probeDur(it.p))) : PHOTO;
42
49
  const body = () => items.reduce((a, it) => a + it.dur, 0);
43
50
  while (INTRO + body() + END > MAX && items.length > 1) items.splice(Math.floor(items.length / 2), 1); // drop from the middle, keep first & last
44
- if (INTRO + body() + END < MIN) { const photos = items.filter((i) => !i.video); const need = MIN - (INTRO + body() + END); for (const p of photos) p.dur += need / Math.max(1, photos.length); }
51
+ if (PART !== "body" && INTRO + body() + END < MIN) { const photos = items.filter((i) => !i.video); const need = MIN - (INTRO + body() + END); for (const p of photos) p.dur += need / Math.max(1, photos.length); }
45
52
 
46
- // ── media prep: HEIC → jpg · big photos down to 2160 · clips re-encoded short & muted ──
53
+ // ── media prep: HEIC → jpg · big photos down to 2160 · a small copy for the vision model · clips re-encoded short & muted ──
47
54
  let t = INTRO;
48
55
  items.forEach((it, i) => {
49
56
  it.start = +t.toFixed(3); t += it.dur;
@@ -55,10 +62,49 @@ items.forEach((it, i) => {
55
62
  it.src = `m${i}.jpg`;
56
63
  const r = run("sips", ["-s", "format", "jpeg", "-Z", "2160", it.p, "--out", join(media, it.src)]);
57
64
  if (r.status !== 0) throw new Error(`photo ${it.f}: ${r.stderr.slice(0, 200)}`);
65
+ const dim = run("sips", ["-g", "pixelWidth", "-g", "pixelHeight", join(media, it.src)]).stdout;
66
+ const pw = Number(/pixelWidth: (\d+)/.exec(dim)?.[1]), ph = Number(/pixelHeight: (\d+)/.exec(dim)?.[1]);
67
+ it.wide = pw > 0 && ph > 0 && pw / ph > 0.75; // anything wider than 3:4 is shown whole over a blurred fill
68
+ run("sips", ["-s", "format", "jpeg", "-Z", "1024", it.p, "--out", join(out, `v${i}.jpg`)]);
58
69
  }
59
- it.cap = caps[it.f] ?? "";
60
70
  });
61
71
  const TOTAL = +(t + END).toFixed(3);
72
+ lap(`${items.length} items prepared`);
73
+
74
+ // ── vision: one caption line per photo, in parallel; a headline when the title is only a slug ──
75
+ const CODEX = process.env.FIELD_REEL_CODEX_BIN || "codex";
76
+ const VISION = process.env.FIELD_REEL_VISION !== "0";
77
+ const ask = (prompt, images, ms = 30000) => new Promise((done) => {
78
+ const args = ["exec", "--skip-git-repo-check", "-c", "model_reasoning_effort=low", prompt, ...images.flatMap((im) => ["-i", im])];
79
+ let buf = "", settled = false;
80
+ const finish = (v) => { if (!settled) { settled = true; done(v); } };
81
+ let child;
82
+ try { child = spawn(CODEX, args, { stdio: ["ignore", "pipe", "ignore"] }); } catch { return finish(""); }
83
+ const timer = setTimeout(() => { child.kill("SIGKILL"); finish(""); }, ms);
84
+ child.stdout.on("data", (d) => { buf += d; });
85
+ child.on("error", () => { clearTimeout(timer); finish(""); });
86
+ child.on("close", (code) => { clearTimeout(timer); finish(code === 0 ? buf : ""); });
87
+ });
88
+ const oneLine = (s, max) => {
89
+ const line = s.split("\n").map((l) => l.trim().replace(/^["'«“]|["'»”]$/g, "")).filter(Boolean).pop() ?? "";
90
+ return line.length >= 2 && line.length <= max ? line.replace(/[.。]$/, "") : "";
91
+ };
92
+ const CAP_PROMPT = "행사 현장 사진이다. 세로 영상 자막으로 쓸 한국어 한 줄을 출력하라 — 18자 이내 · 마침표 없이 · 보이는 장면만 · 사람 이름·외모 묘사·추측 금지 · 화면 속 글자(행사명·회사명)는 읽히면 써도 된다. 그 한 줄만.";
93
+ const HEAD_PROMPT = "같은 행사에서 찍은 사진들이다. 영상 첫 화면 제목으로 쓸 한국어 행사 이름 한 줄을 출력하라 — 16자 이내 · 화면 속 글자에서 읽히는 행사·회사 이름을 우선 · 읽히지 않으면 장면을 짧게(예: «상장 기념식 현장») · 추측 금지. 그 한 줄만.";
94
+ const slugTitle = (s) => !s || /^[a-z0-9]+(?:-[a-z0-9]+)*$/i.test(s);
95
+ let TITLE = opt("title", ""), SUB = opt("sub", "");
96
+ const photos = items.filter((it) => !it.video);
97
+ const VT0 = Date.now();
98
+ const jobs = VISION ? [
99
+ ...photos.filter((it) => !caps[it.f]).map(async (it) => { it.vcap = oneLine(await ask(CAP_PROMPT, [join(out, `v${items.indexOf(it)}.jpg`)]), 22); }),
100
+ ...(PART !== "body" && slugTitle(TITLE) && photos.length ? [(async () => { TITLE = oneLine(await ask(HEAD_PROMPT, photos.slice(0, 3).map((it) => join(out, `v${items.indexOf(it)}.jpg`))), 20) || TITLE; })()] : []),
101
+ ] : [];
102
+ await Promise.all(jobs);
103
+ const visionMs = Date.now() - VT0;
104
+ items.forEach((it, i) => { it.cap = caps[it.f] || it.vcap || (it.video ? "" : `현장 ${i + 1}`); });
105
+ if (slugTitle(TITLE)) TITLE = "현장 스케치";
106
+ if (!SUB) { const d = new Date(items[0].t + 9 * 3600e3); SUB = `${d.getUTCFullYear()}.${String(d.getUTCMonth() + 1).padStart(2, "0")}.${String(d.getUTCDate()).padStart(2, "0")}`; }
107
+ lap(`vision ${jobs.length ? `${items.filter((i) => i.vcap).length}/${photos.filter((p) => !caps[p.f]).length} captions` : "off"} · title «${TITLE}»`);
62
108
 
63
109
  // ── assets (same cache as build.mjs) ───────────────────────────────────────────
64
110
  const CACHE = process.env.EXPLAINER_CACHE ?? join(homedir(), ".cache", "elanous-explainer");
@@ -69,54 +115,114 @@ for (const [rel, url] of Object.entries(ASSETS)) {
69
115
  if (!existsSync(c)) { const res = await fetch(url); if (!res.ok) throw new Error(`asset ${rel}: HTTP ${res.status}`); mkdirSync(dirname(c), { recursive: true }); writeFileSync(c, Buffer.from(await res.arrayBuffer())); }
70
116
  copyFileSync(c, join(hf, "assets", rel));
71
117
  }
118
+
119
+ // ── music: cut to length · fade in/out · level to about -16 LUFS (the whole mix is music) ──
120
+ const BGM = process.env.FIELD_REEL_BGM || join(CACHE, "bgm", "field.wav");
121
+ let music = "";
122
+ if (PART !== "body" && existsSync(BGM)) {
123
+ const r = run("ffmpeg", ["-v", "error", "-y", "-i", BGM, "-t", String(TOTAL), "-af", `afade=t=in:d=0.4,afade=t=out:st=${f(TOTAL - 1.6)}:d=1.6,loudnorm=I=-16:TP=-1.5:LRA=9`, "-ar", "48000", "-ac", "2", join(media, "bgm.wav")]);
124
+ if (r.status === 0) music = "bgm.wav"; else console.error(`reel: music skipped — ${r.stderr.slice(0, 160)}`);
125
+ }
126
+ function f(x) { return +x.toFixed(3); }
127
+
72
128
  const markPaths = [...readFileSync(join(here, "brand", "elanous-mark-on-dark.svg"), "utf8").matchAll(/<path d="([^"]*)"/g)].map((m) => m[1]);
73
129
  const mark = (s, cls = "") => `<svg class="${cls}" width="${s}" height="${s}" viewBox="150 150 724 724">${markPaths.map((d) => `<path d="${d}" fill="#F2F1EE"/>`).join("")}<circle cx="512" cy="512" r="76" fill="#E95047"/></svg>`;
74
130
  const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;");
75
- const f = (x) => +x.toFixed(3);
131
+ const words = (s, cls) => s.split(/\s+/).filter(Boolean).map((w) => `<span class="${cls}">${esc(w)}</span>`).join(" ");
76
132
 
77
133
  // ── composition ────────────────────────────────────────────────────────────────
134
+ // Each photo: blurred fill + the whole photo (wide) or a full-bleed crop (tall) · slow push · a cross-move into the next ·
135
+ // a red wipe on the cut · counter «0N / 0M» · caption words rising one by one.
78
136
  const tw = [];
137
+ const n = items.length;
79
138
  const clips = items.map((it, i) => {
80
- const vis = it.video
81
- ? `<video id="v${i}" class="clip med" src="assets/media/${it.src}" muted playsinline data-start="${f(it.start)}" data-duration="${f(it.dur)}" data-track-index="1"></video>`
82
- : `<div class="clip med" data-start="${f(it.start)}" data-duration="${f(it.dur)}" data-track-index="1"><img class="kb k${i}" src="assets/media/${it.src}"/></div>`;
83
- if (!it.video) tw.push(`tl.fromTo(".k${i}", {scale:1.0, x:0}, {scale:1.09, x:${i % 2 ? -24 : 24}, duration:${f(it.dur)}, ease:"none"}, ${f(it.start)});`);
84
- const cap = it.cap ? `<div class="clip capw" data-start="${f(it.start)}" data-duration="${f(it.dur)}" data-track-index="3"><div class="cap c${i}">${esc(it.cap)}</div></div>` : "";
85
- if (it.cap) tw.push(`tl.fromTo(".c${i}", {opacity:0, y:20}, {opacity:1, y:0, duration:0.4, ease:"power3.out"}, ${f(it.start + 0.15)});`);
139
+ const s = it.start, d = it.dur, last = i === n - 1;
140
+ const span = last ? d : d + XF; // overlap the next item so the move reads as one motion
141
+ let vis;
142
+ if (it.video) {
143
+ vis = `<video id="v${i}" class="clip med" src="assets/media/${it.src}" muted playsinline data-start="${f(s)}" data-duration="${f(d)}" data-track-index="1"></video>`;
144
+ } else if (it.wide) {
145
+ vis = `<div class="clip med" data-start="${f(s)}" data-duration="${f(span)}" data-track-index="${1 + (i % 2)}"><div class="g g${i}">`
146
+ + `<img class="fill" src="assets/media/${it.src}"/><div class="dim"></div>`
147
+ + `<div class="frame fr${i}"><img src="assets/media/${it.src}"/></div></div></div>`;
148
+ tw.push(`tl.fromTo(".fr${i}", {scale:1.0, x:${i % 2 ? 30 : -30}}, {scale:1.12, x:${i % 2 ? -30 : 30}, duration:${f(span)}, ease:"none"}, ${f(s)});`);
149
+ tw.push(`tl.fromTo(".g${i} .fill", {scale:1.25}, {scale:1.4, duration:${f(span)}, ease:"none"}, ${f(s)});`);
150
+ } else {
151
+ vis = `<div class="clip med" data-start="${f(s)}" data-duration="${f(span)}" data-track-index="${1 + (i % 2)}"><div class="g g${i}"><img class="kb k${i}" src="assets/media/${it.src}"/></div></div>`;
152
+ tw.push(`tl.fromTo(".k${i}", {scale:1.02, x:0}, {scale:1.12, x:${i % 2 ? -28 : 28}, duration:${f(span)}, ease:"none"}, ${f(s)});`);
153
+ }
154
+ if (!it.video && i > 0) tw.push(`tl.fromTo(".g${i}", {opacity:0, xPercent:${i % 2 ? 14 : -14}}, {opacity:1, xPercent:0, duration:${XF}, ease:"power3.out"}, ${f(s - XF)});`);
155
+ if (i > 0) tw.push(`tl.fromTo(".wipe", {scaleY:0, transformOrigin:"50% 100%"}, {scaleY:1, duration:${XF / 2}, ease:"power2.in", immediateRender:false}, ${f(s - XF / 2)});`
156
+ + `tl.to(".wipe", {scaleY:0, transformOrigin:"50% 0%", duration:${XF / 2}, ease:"power2.out"}, ${f(s)});`);
157
+ const no = `${String(i + 1).padStart(2, "0")} <i>/ ${String(n).padStart(2, "0")}</i>`;
158
+ const cap = it.cap ? `<div class="clip capw" data-start="${f(s)}" data-duration="${f(d)}" data-track-index="4"><div class="no n${i}">${no}</div><div class="cap c${i}">${words(it.cap, `w w${i}`)}</div><div class="rule r${i}"></div></div>` : "";
159
+ if (it.cap) {
160
+ tw.push(`tl.fromTo(".n${i}", {opacity:0, x:-24}, {opacity:1, x:0, duration:0.35, ease:"power3.out"}, ${f(s + 0.2)});`);
161
+ tw.push(`tl.fromTo(".r${i}", {scaleX:0}, {scaleX:1, duration:0.5, ease:"power3.out"}, ${f(s + 0.25)});`);
162
+ tw.push(`tl.fromTo(".w${i}", {opacity:0, y:46}, {opacity:1, y:0, duration:0.45, stagger:0.09, ease:"power3.out"}, ${f(s + 0.35)});`);
163
+ if (!last) tw.push(`tl.to(".c${i}, .n${i}, .r${i}", {opacity:0, duration:0.25}, ${f(s + d - 0.3)});`);
164
+ }
86
165
  return vis + cap;
87
166
  }).join("\n");
88
- tw.push(`tl.fromTo(".it", {opacity:0, y:30}, {opacity:1, y:0, duration:0.5, stagger:0.15, ease:"power3.out"}, 0.1);`);
89
- tw.push(`tl.fromTo(".im", {rotation:-160, scale:0.6}, {rotation:0, scale:1, duration:1.0, ease:"power3.out"}, 0.0);`);
90
- tw.push(`tl.fromTo(".em", {rotation:-200, scale:0.5}, {rotation:0, scale:1, duration:1.2, ease:"power3.out"}, ${f(t)});`);
91
- tw.push(`tl.fromTo(".et", {opacity:0, y:20}, {opacity:1, y:0, duration:0.5, stagger:0.12}, ${f(t + 0.5)});`);
167
+
168
+ const intro = PART === "body" ? "" : `<section class="clip card" data-start="0" data-duration="${INTRO}" data-track-index="6">`
169
+ + `<div class="kick ik">현장 스케치</div>${mark(200, "im")}<h1>${words(TITLE, "it")}</h1><div class="line il"></div><p class="is">${esc(SUB)}</p></section>`;
170
+ const end = PART === "body" ? "" : `<section class="clip card" data-start="${f(t)}" data-duration="${END}" data-track-index="6">`
171
+ + `${mark(230, "em")}<h1 class="et">Elanous</h1><p class="et">사진을 올리면, 영상이 됩니다</p><p class="et red">elanous.ai</p></section>`;
172
+ if (PART !== "body") {
173
+ tw.push(`tl.fromTo(".ik", {opacity:0, letterSpacing:"0.6em"}, {opacity:1, letterSpacing:"0.28em", duration:0.8, ease:"power3.out"}, 0.1);`);
174
+ tw.push(`tl.fromTo(".im", {rotation:-160, scale:0.6, opacity:0}, {rotation:0, scale:1, opacity:1, duration:1.0, ease:"power3.out"}, 0.0);`);
175
+ tw.push(`tl.fromTo(".it", {opacity:0, y:50}, {opacity:1, y:0, duration:0.55, stagger:0.1, ease:"power3.out"}, 0.45);`);
176
+ tw.push(`tl.fromTo(".il", {scaleX:0}, {scaleX:1, duration:0.6, ease:"power3.inOut"}, 0.9);`);
177
+ tw.push(`tl.fromTo(".is", {opacity:0, y:16}, {opacity:1, y:0, duration:0.45, ease:"power3.out"}, 1.15);`);
178
+ tw.push(`tl.fromTo(".em", {rotation:-200, scale:0.5}, {rotation:0, scale:1, duration:1.2, ease:"power3.out"}, ${f(t)});`);
179
+ tw.push(`tl.fromTo(".et", {opacity:0, y:20}, {opacity:1, y:0, duration:0.5, stagger:0.14, ease:"power3.out"}, ${f(t + 0.5)});`);
180
+ }
181
+ tw.unshift(`tl.set(".wipe", {scaleY:0}, 0);`);
92
182
  tw.push(`tl.fromTo(".bar i", {scaleX:0}, {scaleX:1, duration:${TOTAL}, ease:"none"}, 0);`);
183
+ const audio = music ? `<audio id="bgm" src="assets/media/${music}" data-start="0" data-duration="${TOTAL}" data-track-index="9"></audio>` : "";
93
184
  const html = `<!doctype html><html lang="ko"><head><meta charset="UTF-8"/><meta name="viewport" content="width=${W}, height=${H}"/><title>${esc(TITLE)}</title><script src="assets/gsap.min.js"></script><style>
94
185
  @font-face{font-family:"Pretendard";src:url("assets/fonts/Pretendard-Bold.woff2") format("woff2");font-weight:700}
95
186
  @font-face{font-family:"Pretendard";src:url("assets/fonts/Pretendard-ExtraBold.woff2") format("woff2");font-weight:800}
96
187
  @font-face{font-family:"Pretendard";src:url("assets/fonts/Pretendard-Black.woff2") format("woff2");font-weight:900}
97
188
  *{margin:0;padding:0;box-sizing:border-box}
98
189
  #root{position:relative;width:100%;height:100%;overflow:hidden;background:#000;font-family:"Pretendard",sans-serif;color:#F2F1EE;word-break:keep-all}
99
- .med{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;overflow:hidden}
190
+ .med{position:absolute;inset:0;width:100%;height:100%;overflow:hidden}
191
+ .g{position:absolute;inset:0;overflow:hidden}
100
192
  .kb{width:100%;height:100%;object-fit:cover;display:block}
101
- .shade{z-index:2;position:absolute;left:0;right:0;bottom:0;height:46%;background:linear-gradient(to top,rgba(0,0,0,.78),rgba(0,0,0,0));pointer-events:none}
193
+ .fill{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;filter:blur(42px) saturate(1.2);opacity:.85}
194
+ .dim{position:absolute;inset:0;background:rgba(0,0,0,.42)}
195
+ .frame{position:absolute;left:0;right:0;top:420px;height:960px;overflow:hidden;box-shadow:0 30px 80px rgba(0,0,0,.55)}
196
+ .frame img{width:100%;height:100%;object-fit:cover;display:block}
197
+ .shade{z-index:3;position:absolute;left:0;right:0;bottom:0;height:42%;background:linear-gradient(to top,rgba(0,0,0,.82),rgba(0,0,0,0));pointer-events:none}
102
198
  .top{z-index:5;position:absolute;left:56px;right:56px;top:72px;display:flex;align-items:center;gap:18px;font-weight:800;font-size:34px;text-shadow:0 2px 12px rgba(0,0,0,.6)}
103
199
  .top b{color:#E95047;font-weight:800}
104
- .capw{z-index:4;position:absolute;left:64px;right:64px;bottom:250px}
105
- .cap{font-weight:800;font-size:64px;line-height:1.25;letter-spacing:-0.02em;text-shadow:0 3px 16px rgba(0,0,0,.7)}
106
- .card{z-index:6;position:absolute;inset:0;background:#000;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:0 80px}
107
- .card h1{margin-top:60px;font-weight:900;font-size:104px;line-height:1.1;letter-spacing:-0.03em}
108
- .card p{margin-top:28px;font-weight:700;font-size:40px;color:rgba(242,241,238,.62)}
200
+ .capw{z-index:4;position:absolute;left:64px;right:64px;bottom:210px}
201
+ .no{font-weight:800;font-size:34px;letter-spacing:.12em;color:#E95047}
202
+ .no i{font-style:normal;color:rgba(242,241,238,.55)}
203
+ .cap{margin-top:18px;font-weight:900;font-size:76px;line-height:1.18;letter-spacing:-0.03em;text-shadow:0 3px 18px rgba(0,0,0,.7)}
204
+ .w{display:inline-block}
205
+ .rule{margin-top:26px;width:120px;height:8px;background:#E95047;transform-origin:0 50%}
206
+ .wipe{z-index:8;position:absolute;inset:0;background:#E95047}
207
+ .card{z-index:7;position:absolute;inset:0;background:#000;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:0 80px}
208
+ .kick{margin-bottom:56px;font-weight:800;font-size:32px;letter-spacing:.28em;color:#E95047}
209
+ .card h1{margin-top:56px;font-weight:900;font-size:100px;line-height:1.12;letter-spacing:-0.03em}
210
+ .card h1 span{display:inline-block}
211
+ .line{margin-top:40px;width:160px;height:8px;background:#E95047}
212
+ .card p{margin-top:28px;font-weight:700;font-size:42px;color:rgba(242,241,238,.66)}
109
213
  .card .red{color:#E95047}
110
- .bar{z-index:7;position:absolute;left:0;right:0;bottom:0;height:10px;background:rgba(242,241,238,.12)}
214
+ .bar{z-index:9;position:absolute;left:0;right:0;bottom:0;height:10px;background:rgba(242,241,238,.12)}
111
215
  .bar i{position:absolute;inset:0;background:#E95047;transform-origin:0 50%;display:block}
112
216
  </style></head><body>
113
217
  <div id="root" data-composition-id="main" data-start="0" data-width="${W}" data-height="${H}" data-duration="${TOTAL}">
114
- <section class="clip card" data-start="0" data-duration="${INTRO}" data-track-index="5">${mark(220, "im")}<h1 class="it">${esc(TITLE)}</h1>${SUB ? `<p class="it">${esc(SUB)}</p>` : ""}</section>
218
+ ${intro}
115
219
  ${clips}
116
220
  <div class="shade"></div>
117
221
  <div class="top">${mark(60)}<span>Elanous <b>·</b> ${esc(TITLE)}</span></div>
118
- <section class="clip card" data-start="${f(t)}" data-duration="${END}" data-track-index="5">${mark(240, "em")}<h1 class="et">Elanous</h1><p class="et">현장에서 바로 만든 영상</p><p class="et red">elanous.ai</p></section>
222
+ <div class="wipe"></div>
223
+ ${end}
119
224
  <div class="bar"><i></i></div>
225
+ ${audio}
120
226
  </div>
121
227
  <script>const tl = gsap.timeline({ paused: true });
122
228
  ${tw.join("\n")}
@@ -124,5 +230,6 @@ window.__timelines["main"] = tl;</script></body></html>`;
124
230
  writeFileSync(join(hf, "index.html"), html);
125
231
  writeFileSync(join(hf, "hyperframes.json"), JSON.stringify({ $schema: "https://hyperframes.heygen.com/schema/hyperframes.json", paths: { assets: "assets" }, media: { autoProxy: true } }, null, 2) + "\n");
126
232
  writeFileSync(join(hf, "meta.json"), JSON.stringify({ id: "reel", name: "reel" }, null, 2) + "\n");
127
- writeFileSync(join(out, "timeline.json"), JSON.stringify({ total: TOTAL, items: items.map(({ f: file, video, start, dur, cap }) => ({ file, video, start, dur: f(dur), cap })) }, null, 2));
128
- console.log(`reel: ${items.length} items · ${TOTAL}s · ${items.filter((i) => i.cap).length} captions → ${hf}`);
233
+ writeFileSync(join(out, "timeline.json"), JSON.stringify({ total: TOTAL, part: PART, title: TITLE, sub: SUB, music: Boolean(music),
234
+ vision: { enabled: VISION, calls: jobs.length, captioned: items.filter((i) => i.vcap).length, fallback: items.filter((i) => !i.video && !caps[i.f] && !i.vcap).length, ms: visionMs }, items: items.map(({ f: file, video, start, dur, cap, vcap }) => ({ file, video, start, dur: f(dur), cap, vision: Boolean(vcap) })) }, null, 2));
235
+ console.log(`reel: ${items.length} items · ${TOTAL}s · ${items.filter((i) => i.vcap).length} vision captions · music ${music ? "on" : "off"} → ${hf}`);
@@ -1,8 +1,9 @@
1
1
  #!/bin/zsh
2
2
  # Field reel in one command: folder of phone photos/clips → <folder>/reel/<name>-9x16.mp4 · prints the elapsed seconds.
3
+ # The layout/contrast check is advisory: phone photos behind captions often miss 3:1, and a failed check must not stop the reel.
3
4
  # Usage: zsh reel.sh <folder> [--title "…"] [--sub "…"] (captions: <folder>/captions.txt — `파일 | 자막`)
4
5
  E=${0:A:h}; F=${1:A}; shift
5
6
  [[ -d $F ]] || { print -u2 "reel: no folder $F"; exit 2 }
6
7
  T0=$(date +%s)
7
- rm -rf $F/reel && node $E/reel.mjs $F "$@" && (cd $F/reel/hf && npx -y hyperframes@${HYPERFRAMES_VERSION:-0.8.95} check >../render.log 2>&1 && npx -y hyperframes@${HYPERFRAMES_VERSION:-0.8.95} render --fps 30 --workers 4 --output ../reel-9x16.mp4 >>../render.log 2>&1) || { print -u2 "reel: FAILED — see $F/reel/render.log"; exit 1 }
8
+ rm -rf $F/reel && node $E/reel.mjs $F "$@" && (cd $F/reel/hf && { npx -y hyperframes@${HYPERFRAMES_VERSION:-0.8.95} check >../render.log 2>&1 || print "reel: check reported issues (continuing — user photos vary; see above)" >>../render.log }; npx -y hyperframes@${HYPERFRAMES_VERSION:-0.8.95} render --fps 30 --workers 4 --output ../reel-9x16.mp4 >>../render.log 2>&1) || { print -u2 "reel: FAILED — see $F/reel/render.log"; exit 1 }
8
9
  print "reel: $F/reel/reel-9x16.mp4 · $(( $(date +%s) - T0 ))s"
@@ -5,7 +5,7 @@
5
5
  "name": "Seulki",
6
6
  "model": "eleven_v3"
7
7
  },
8
- "sources": "docs/marketing (LAUNCH-0.2.5 초안) · PR #21849 #21887 #21878 #21855 #22054 #21908 #21995 #22001 — 전부 컷 e24cb6e 안 MERGED",
8
+ "sources": "elanous 0.2.5 release notes — https://github.com/ElanvitalAI/elanous/releases/tag/v0.2.5",
9
9
  "intro": {
10
10
  "beats": [
11
11
  {
@@ -123,7 +123,7 @@ requires: []
123
123
  | 다음 | 언제 | 어떻게 |
124
124
  |---|---|---|
125
125
  | 🧹 **`first-principles`** | 요구가 많이 나왔을 때 | ⭐ ***캐낸 다음엔 «지운다»*** — 5단계 ①의심 ②삭제 가 순서상 여기다 |
126
- | 🎯 **골 문면** (`docs/goals/`) | 하니스가 구현할 일일 때 | 카드의 `S`·`C`·`Q` 가 그대로 ask 의 세 칸이 된다 |
126
+ | 🎯 **골 문면** (골 파일) | 하니스가 구현할 일일 때 | 카드의 `S`·`C`·`Q` 가 그대로 ask 의 세 칸이 된다 |
127
127
  | 📄 **문서** (`docs/`) | 결정을 남길 일일 때 | `## ✅ 정해진 것` 표가 그대로 결정 기록이다 |
128
128
  | 🧑‍🤝‍🧑 **팀 짜기** | 일이 여럿으로 갈릴 때 | ⭐ 목표를 심은 뒤 *"이 일을 할 팀을 짜 보라"* — 인터뷰의 «응용»이다 |
129
129
 
@@ -14,7 +14,7 @@ flowchart TD
14
14
  H{nonCodeOutcome exists?}:::gate
15
15
  I[observe deploy-no-changes<br/>return { ok:false, kind:none }]:::fail
16
16
  J{Evidence is artifact-created?}:::gate
17
- K{PR #8731 · 2026-08-13<br/>existsSync(resolve(cwd, ref))?}:::gate
17
+ K{2026-08-13<br/>existsSync(resolve(cwd, ref))?}:::gate
18
18
  L[observe deploy-noncode-blocked<br/>reason: artifact-created-ref-missing<br/>then deploy-no-changes]:::fail
19
19
  M[Do not treat ref as a file<br/>published-url / execution-completed]:::data
20
20
  N[observe deploy-noncode<br/>return terminal:<br/>published or executed]:::pass
package/src/a2a/server.ts CHANGED
@@ -91,6 +91,8 @@ export function startA2AServer(opts: A2AServerOptions): Bun.Server<undefined> {
91
91
  history ??= new DaemonSessionHistory();
92
92
  const result = await (opts.runDaemonTurn ?? runDaemonPromptTurn)({
93
93
  history,
94
+ // A peer agent delegates work, not small talk: never drop its tools for a short message (#22430 fast path).
95
+ fastPathEnabled: false,
94
96
  request: {
95
97
  sessionId: contextId,
96
98
  userText: text,
@@ -0,0 +1,30 @@
1
+ import type { ClientCapabilities } from '@agentclientprotocol/sdk';
2
+
3
+ /** The server's sessionPeers registry remains the source of truth for which
4
+ * connections are attached to a session. Awareness belongs to the connection,
5
+ * not the session: a plain client can load an aware client's session. */
6
+ export class ElanousAwarePeers<Peer extends object> {
7
+ private readonly aware = new WeakSet<Peer>();
8
+
9
+ constructor(private readonly sessionPeers: ReadonlyMap<string, ReadonlySet<Peer>>) {}
10
+
11
+ markPeer(connectionOrSessionId: Peer, clientCapabilities: ClientCapabilities | undefined | null): void {
12
+ const elanous = (clientCapabilities?._meta as Record<string, unknown> | undefined)?.elanous;
13
+ if (elanous !== null && typeof elanous === 'object' && !Array.isArray(elanous)) {
14
+ this.aware.add(connectionOrSessionId);
15
+ } else {
16
+ this.aware.delete(connectionOrSessionId);
17
+ }
18
+ }
19
+
20
+ isPeerAware(peer: Peer): boolean {
21
+ return this.aware.has(peer);
22
+ }
23
+
24
+ isElanousAware(sessionId: string): boolean {
25
+ for (const peer of this.sessionPeers.get(sessionId) ?? []) {
26
+ if (this.isPeerAware(peer)) return true;
27
+ }
28
+ return false;
29
+ }
30
+ }