@desplega.ai/agent-swarm 1.159.0 → 1.161.0

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 (415) hide show
  1. package/README.md +2 -2
  2. package/dist/{acp-adapter-qhch0n2v.js → acp-adapter-xx489vvb.js} +286 -75
  3. package/dist/{actions-dtsh1mfb.js → actions-ykjkn0m9.js} +32 -30
  4. package/dist/{anthropic-messages-f2h06k64.js → anthropic-messages-5qx80j9k.js} +99 -14
  5. package/dist/{app-y1b9srva.js → app-0hdqa4k1.js} +13 -11
  6. package/dist/{assistant-31hwy5xj.js → assistant-am2g5g8a.js} +40 -32
  7. package/dist/{azure-openai-responses-0k1mfax5.js → azure-openai-responses-fszbkp0e.js} +3 -3
  8. package/dist/{boot-reembed-48d2qpkt.js → boot-reembed-gt5gjkt4.js} +14 -12
  9. package/dist/{boot-reembed-w0bryhkr.js → boot-reembed-xy9t7avg.js} +15 -13
  10. package/dist/{boot-scrub-logs-t65wbrn7.js → boot-scrub-logs-9x0985y5.js} +12 -10
  11. package/dist/boot-seeds-trv0d4vg.js +29 -0
  12. package/dist/{claude-adapter-1t6611ne.js → claude-adapter-jb6e1e54.js} +32 -26
  13. package/dist/{claude-managed-adapter-werp659j.js → claude-managed-adapter-yxkfxg8j.js} +7 -7
  14. package/dist/{claude-managed-setup-y6rdgbc1.js → claude-managed-setup-d1q9hv2b.js} +26 -27
  15. package/dist/{claude-sdk-session-zhkyk8yw.js → claude-sdk-session-zga700s2.js} +11737 -11256
  16. package/dist/{cli-79aq9cgb.js → cli-0383bp7f.js} +1222 -406
  17. package/dist/{cli-xgpf00sk.js → cli-0c7ata9h.js} +24 -23
  18. package/dist/{cli-qjxy52w7.js → cli-121ph74b.js} +2 -2
  19. package/dist/{cli-20035trh.js → cli-17xpf7cm.js} +14 -7
  20. package/dist/{cli-cqt19wg6.js → cli-1a2j50m0.js} +11 -2
  21. package/dist/{cli-g9df2m37.js → cli-1cz0c18q.js} +249 -200
  22. package/dist/{cli-fjtp3463.js → cli-1er0rhz8.js} +13 -12
  23. package/dist/{cli-ca2w7zv7.js → cli-2hhfhfq5.js} +11 -6
  24. package/dist/{cli-npssxgaz.js → cli-2pdaysab.js} +52 -2
  25. package/dist/{cli-rykfmgwg.js → cli-2x0e561b.js} +1 -1
  26. package/dist/{cli-503ya656.js → cli-35pj2yva.js} +20 -150
  27. package/dist/cli-3qhyh31j.js +166 -0
  28. package/dist/{cli-cygyqj3b.js → cli-3zc55ctx.js} +8 -6
  29. package/dist/{cli-96qd5psa.js → cli-58e8yr0d.js} +1 -1
  30. package/dist/{cli-4aj25szq.js → cli-5nbmpy8w.js} +2 -2
  31. package/dist/{cli-mg24hkt4.js → cli-5p3x4zna.js} +2 -2
  32. package/dist/{cli-vwbbaecp.js → cli-7119ja3w.js} +3 -3
  33. package/dist/{cli-mzkqpxee.js → cli-798vkbks.js} +2 -2
  34. package/dist/{cli-3gvrr8vj.js → cli-82c1pacn.js} +111 -54
  35. package/dist/{cli-hgqfttfc.js → cli-89s4ngw3.js} +1 -1
  36. package/dist/{cli-9b13ggd1.js → cli-89v6kyq7.js} +1 -0
  37. package/dist/{cli-b6thtzac.js → cli-8g6mc14k.js} +7 -7
  38. package/dist/{cli-eg7cdg68.js → cli-9m03pzga.js} +5 -5
  39. package/dist/cli-9s68w752.js +303 -0
  40. package/dist/{cli-48527fpw.js → cli-a8c82p2t.js} +16 -15
  41. package/dist/{cli-5f2hq128.js → cli-ad4qxm6j.js} +12 -11
  42. package/dist/{cli-e4xnc0qp.js → cli-akfj32vj.js} +33 -7
  43. package/dist/{cli-ejpygnqz.js → cli-bh7jed3k.js} +212 -22
  44. package/dist/{cli-0z2v4nhw.js → cli-cat6h73s.js} +1 -1
  45. package/dist/{cli-e0ndxhtk.js → cli-cfkrztza.js} +190 -13
  46. package/dist/{cli-kc3dp84d.js → cli-d56tgd45.js} +216 -59
  47. package/dist/cli-dbmwby6a.js +9 -0
  48. package/dist/{cli-0n2e4x49.js → cli-ecfxwnyb.js} +15 -2
  49. package/dist/{cli-gzyh0j0g.js → cli-ee8bc46j.js} +66 -166
  50. package/dist/{cli-kdnehe0t.js → cli-et8m9xsv.js} +1 -1
  51. package/dist/cli-f9w1semm.js +413 -0
  52. package/dist/cli-gt4hxb8p.js +99 -0
  53. package/dist/{cli-qv4jv8bx.js → cli-hgmnf38m.js} +2 -2
  54. package/dist/{cli-10ym38qc.js → cli-hn04t9db.js} +3 -3
  55. package/dist/{cli-qvnnx4ct.js → cli-jpf7e1zq.js} +105 -243
  56. package/dist/cli-jyy6pm2f.js +333872 -0
  57. package/dist/{cli-dvjpkkjd.js → cli-kgd3p0bm.js} +7 -7
  58. package/dist/cli-ma3zvh39.js +10404 -0
  59. package/dist/{cli-r2ap2czm.js → cli-nexqx4ve.js} +6 -0
  60. package/dist/{cli-j9z8mfx3.js → cli-nsdxtcbg.js} +37 -20
  61. package/dist/{cli-wgjx0q33.js → cli-phfg8vy8.js} +392 -53
  62. package/dist/{cli-rgjprct3.js → cli-ptr8h7kp.js} +19 -1
  63. package/dist/{cli-s8s499xy.js → cli-qbksc292.js} +3 -2
  64. package/dist/{cli-bv0a38v9.js → cli-qwsnmjdd.js} +515 -229
  65. package/dist/{cli-68bmme5p.js → cli-r6fsxc76.js} +40018 -52654
  66. package/dist/{cli-rrqmqr1s.js → cli-rtx9dygp.js} +1 -1
  67. package/dist/{cli-rfezr056.js → cli-sqm3r5ps.js} +83 -15
  68. package/dist/{cli-zw65mab6.js → cli-t6v2ah3r.js} +67 -13
  69. package/dist/{cli-vn0qv9cj.js → cli-t845n9xk.js} +28 -14
  70. package/dist/cli-trc24wk2.js +143 -0
  71. package/dist/{cli-wd9hz40d.js → cli-tre4bdgq.js} +5 -5
  72. package/dist/{cli-9yg65c1j.js → cli-tsk3nz8a.js} +29 -1
  73. package/dist/{cli-phsaj4xq.js → cli-v0pvvhfq.js} +1 -1
  74. package/dist/{cli-hj65wsts.js → cli-vnnz5ncg.js} +4695 -4915
  75. package/dist/{cli-cpvsaqsw.js → cli-w0bwte75.js} +8 -5
  76. package/dist/{cli-z9x0sh1t.js → cli-we0q7t0e.js} +3 -3
  77. package/dist/{cli-r94z9p1z.js → cli-wgbezhw2.js} +3 -0
  78. package/dist/{cli-19jenss3.js → cli-xk79303e.js} +51 -17
  79. package/dist/{cli-31nkw3g3.js → cli-ybgv4kw2.js} +15 -8
  80. package/dist/{cli-ardpzhjm.js → cli-yrg7c73k.js} +13 -6
  81. package/dist/{cli-de1396j2.js → cli-ywybs5as.js} +7 -1
  82. package/dist/{cli-vhjwh6cz.js → cli-yy6dew39.js} +2 -2
  83. package/dist/{cli-bgv70kym.js → cli-zb6xbk61.js} +3 -2
  84. package/dist/{cli-4tvgz99m.js → cli-ze1d3wzv.js} +79 -27
  85. package/dist/cli.js +33 -32
  86. package/dist/{cloudflare-workers-ai-system-one-1sg586qv.js → cloudflare-workers-ai-system-one-k6fbxw9d.js} +2 -2
  87. package/dist/{codex-adapter-jqfqqa21.js → codex-adapter-04v97vep.js} +23 -20
  88. package/dist/{codex-hook-5eys3dya.js → codex-hook-20me6byw.js} +2 -2
  89. package/dist/{codex-login-he9vrn90.js → codex-login-cf7mzsgg.js} +48 -3
  90. package/dist/{codex-session-runner-zd341dsm.js → codex-session-runner-ax69mqq3.js} +21 -20
  91. package/dist/{commands-vfsysz4s.js → commands-18gy08n1.js} +12 -10
  92. package/dist/{db-s3znhvbn.js → db-d2pvjpyt.js} +22 -10
  93. package/dist/{devin-adapter-8vgkwh29.js → devin-adapter-dpsv6hd9.js} +1 -1
  94. package/dist/{dispatcher-5xn0c7rf.js → dispatcher-8wbwyzjg.js} +31 -29
  95. package/dist/{dsh-adapter-894mgfvs.js → dsh-adapter-ysqk9snv.js} +9 -4
  96. package/dist/{e2b-ez07znam.js → e2b-esmt1r16.js} +3 -1
  97. package/dist/{execute-71ysgv35.js → execute-cmagrbrz.js} +235 -52
  98. package/dist/{google-generative-ai-1nfcdphp.js → google-generative-ai-k6n5nbpp.js} +3 -3
  99. package/dist/{google-vertex-1tqcd7b1.js → google-vertex-44xxtcvp.js} +3 -3
  100. package/dist/{handlers-5w4jwycr.js → handlers-6b69m5kp.js} +34 -32
  101. package/dist/{hook-z0hbxj85.js → hook-k3mydf2n.js} +16 -14
  102. package/dist/{hook-853bxrr6.js → hook-srnkt0hb.js} +16 -14
  103. package/dist/{http-akc3wy3m.js → http-hrf80ca1.js} +836 -189
  104. package/dist/{index-ttpbhnjg.js → index-1asbaj62.js} +32 -30
  105. package/dist/{index-cd151qkq.js → index-45evq4ee.js} +31 -29
  106. package/dist/{index-k661dzzr.js → index-9rxdvj6w.js} +32 -30
  107. package/dist/index-k4nj18qs.js +31 -0
  108. package/dist/{index-5sz16ksh.js → index-xmevk2b9.js} +89 -61
  109. package/dist/{keepalive-8p9rpvz5.js → keepalive-sfdz6ksp.js} +14 -12
  110. package/dist/{lead-jp69z5h3.js → lead-ej91e9hw.js} +52 -50
  111. package/dist/{llama-cpp-classify-t2tccams.js → llama-cpp-classify-tgj35k8d.js} +1 -1
  112. package/dist/{maintenance-fwx4ev0k.js → maintenance-80dnd7ke.js} +15 -13
  113. package/dist/{mistral-conversations-bhvwqcnk.js → mistral-conversations-pc3ntnxt.js} +1 -1
  114. package/dist/node-f0ykebbv.js +2107 -0
  115. package/dist/node-wyamey5r.js +19 -0
  116. package/dist/{oauth-refresh-sweep-cwcq1ctx.js → oauth-refresh-sweep-m1etk2mx.js} +13 -11
  117. package/dist/{onboard-2b4q0t84.js → onboard-0q8sdfqc.js} +2 -2
  118. package/dist/{openai-codex-responses-nekthgks.js → openai-codex-responses-n7n683p2.js} +2 -2
  119. package/dist/{openai-completions-hxtzwj0s.js → openai-completions-jpaj5g2b.js} +2 -2
  120. package/dist/{openai-responses-w4r118w7.js → openai-responses-9a3nme9j.js} +3 -3
  121. package/dist/{opencode-adapter-zz7xvjj6.js → opencode-adapter-4wcgmvgf.js} +13 -11
  122. package/dist/{openrouter-images-b6sqqjgn.js → openrouter-images-8yx3487g.js} +1 -1
  123. package/dist/{otel-impl-g8rs4vwr.js → otel-impl-hnqm9mxq.js} +2 -2
  124. package/dist/pi-logo-animation-e9a0t68z.js +1003 -0
  125. package/dist/{pi-mono-adapter-n0me28wt.js → pi-mono-adapter-d6sqm9wr.js} +301 -78
  126. package/dist/{pricing-refresh-0v77385v.js → pricing-refresh-y60z64pm.js} +14 -13
  127. package/dist/quickjs-r1pq2107.wasm +0 -0
  128. package/dist/{rbac-roles-krjyazbj.js → rbac-roles-8pa0754b.js} +13 -11
  129. package/dist/{render-v2-3wjsp9aw.js → render-v2-418s1vj7.js} +13 -11
  130. package/dist/{runtime-ktjysacx.js → runtime-3pknc168.js} +5 -4
  131. package/dist/{setup-yeqryke6.js → setup-yghdkj4x.js} +2 -2
  132. package/dist/{typesafe-system-one-96960jxp.js → typesafe-system-one-2ef30pec.js} +2 -2
  133. package/dist/{virtual-modules-dzdeq1sf.js → virtual-modules-jcceevt5.js} +8 -6
  134. package/dist/{work-objects-3pakpkw0.js → work-objects-j1sz20v9.js} +34 -32
  135. package/dist/{worker-85testgj.js → worker-d4gcvkjt.js} +52 -50
  136. package/dist/{x-n3e0v3xa.js → x-f1gt1pz3.js} +2 -2
  137. package/openapi.json +587 -19
  138. package/package.json +16 -15
  139. package/src/azure-devops/api.ts +121 -0
  140. package/src/azure-devops/auth.ts +78 -0
  141. package/src/azure-devops/handlers.ts +413 -0
  142. package/src/azure-devops/index.ts +27 -0
  143. package/src/azure-devops/templates.ts +108 -0
  144. package/src/azure-devops/types.ts +90 -0
  145. package/src/be/db/api-keys.ts +70 -0
  146. package/src/be/db/tasks/read.ts +1 -1
  147. package/src/be/db/tasks/write.ts +16 -0
  148. package/src/be/db-client.ts +11 -2
  149. package/src/be/db-retention.ts +95 -32
  150. package/src/be/db.ts +187 -24
  151. package/src/be/memory/constants.ts +16 -0
  152. package/src/be/memory/graph-expansion.ts +8 -1
  153. package/src/be/memory/index-content.ts +20 -1
  154. package/src/be/memory/key-guard.ts +93 -0
  155. package/src/be/memory/key-paths.ts +103 -0
  156. package/src/be/memory/providers/sqlite-store.ts +203 -60
  157. package/src/be/memory/raters/llm-summarizer.ts +10 -4
  158. package/src/be/memory/raters/llm.ts +15 -5
  159. package/src/be/memory/raters/store.ts +6 -3
  160. package/src/be/memory/raters/types.ts +11 -0
  161. package/src/be/memory/reranker.ts +38 -7
  162. package/src/be/memory/types.ts +12 -1
  163. package/src/be/migrations/185_api_key_auth_failures.sql +6 -0
  164. package/src/be/migrations/186_api_key_seat_mismatch.sql +8 -0
  165. package/src/be/migrations/190_memory_rating_model.sql +7 -0
  166. package/src/be/migrations/191_context_versions_previous_index.sql +8 -0
  167. package/src/be/model-tier-keys.ts +16 -1
  168. package/src/be/model-tier-resolution.ts +63 -18
  169. package/src/be/model-validation.ts +86 -10
  170. package/src/be/pricing-normalize.ts +4 -2
  171. package/src/be/scripts/typecheck.ts +4 -3
  172. package/src/be/seed/runner.ts +18 -12
  173. package/src/be/seed/types.ts +1 -0
  174. package/src/be/seed-pricing.ts +16 -0
  175. package/src/be/seed-scripts/catalog/task-failure-audit.ts +26 -26
  176. package/src/be/swarm-config-guard.ts +13 -1
  177. package/src/commands/claude-managed-setup.ts +33 -31
  178. package/src/commands/codex-login.ts +67 -0
  179. package/src/commands/credential-outcome-report.ts +191 -0
  180. package/src/commands/provider-credentials.ts +28 -15
  181. package/src/commands/rate-limit-outcome.ts +34 -4
  182. package/src/commands/runner.ts +479 -228
  183. package/src/commands/seat-mismatch-report.ts +46 -0
  184. package/src/e2b/env.ts +2 -0
  185. package/src/extensions/apply-task-create.ts +27 -1
  186. package/src/extensions/catalog.generated.json +17 -0
  187. package/src/extensions/contract-types.generated.ts +1 -1
  188. package/src/github/handlers.ts +10 -74
  189. package/src/github/reactions.ts +119 -138
  190. package/src/github/task-reactions.ts +34 -11
  191. package/src/heartbeat/heartbeat.ts +15 -2
  192. package/src/hooks/hook.ts +1 -1
  193. package/src/http/agents.ts +15 -2
  194. package/src/http/api-keys.ts +140 -2
  195. package/src/http/codex-oauth-keep-warm.ts +3 -5
  196. package/src/http/core.ts +12 -0
  197. package/src/http/extensions.ts +51 -27
  198. package/src/http/index.ts +4 -0
  199. package/src/http/mcp-user.ts +8 -13
  200. package/src/http/mcp.ts +19 -12
  201. package/src/http/memory.ts +91 -18
  202. package/src/http/poll.ts +9 -1
  203. package/src/http/schedules.ts +17 -2
  204. package/src/http/script-runs.ts +12 -0
  205. package/src/http/session-data.ts +6 -4
  206. package/src/http/stats.ts +1 -0
  207. package/src/http/status.ts +15 -1
  208. package/src/http/tasks.ts +80 -8
  209. package/src/http/users.ts +1 -1
  210. package/src/http/webhooks.ts +120 -2
  211. package/src/http/workflows.ts +11 -0
  212. package/src/mcp-client/http-client.ts +5 -0
  213. package/src/memory/recall-query.ts +74 -0
  214. package/src/prompts/base-prompt.ts +15 -4
  215. package/src/prompts/session-templates.ts +22 -1
  216. package/src/providers/acp-adapter.ts +1 -0
  217. package/src/providers/claude-adapter.ts +98 -21
  218. package/src/providers/claude-managed-adapter.ts +30 -2
  219. package/src/providers/claude-sdk-session.ts +3 -1
  220. package/src/providers/codex-adapter.ts +11 -11
  221. package/src/providers/codex-skill-resolver.ts +6 -0
  222. package/src/providers/devin-adapter.ts +1 -0
  223. package/src/providers/dsh-adapter.ts +205 -7
  224. package/src/providers/harness-version.ts +83 -7
  225. package/src/providers/opencode-adapter.ts +77 -4
  226. package/src/providers/pi-binary-root/src/extensions/codemode/worker.ts +11 -0
  227. package/src/providers/pi-codemode-runtime.ts +17 -0
  228. package/src/providers/pi-mono-adapter.ts +387 -83
  229. package/src/providers/pi-mono-extension.ts +17 -2
  230. package/src/providers/pricing-sources.md +5 -1
  231. package/src/providers/reasoning-effort.ts +10 -2
  232. package/src/providers/types.ts +9 -1
  233. package/src/rbac/legacy-policy.ts +20 -0
  234. package/src/rbac/permissions.ts +15 -0
  235. package/src/scripts-runtime/types/stdlib.d.ts +13 -5
  236. package/src/scripts-runtime/types/swarm-extension.d.ts +4 -2
  237. package/src/scripts-runtime/types/swarm-sdk.d.ts +13 -5
  238. package/src/slack/assistant.ts +13 -0
  239. package/src/slack/render-v2.ts +87 -37
  240. package/src/slack/session-status.ts +541 -0
  241. package/src/tasks/context-key.ts +38 -1
  242. package/src/tasks/sibling-awareness.ts +3 -2
  243. package/src/telemetry.ts +5 -4
  244. package/src/templates.d.ts +6 -0
  245. package/src/tests/api-key-auth-failure-route.test.ts +142 -0
  246. package/src/tests/api-key-tracking.test.ts +368 -1
  247. package/src/tests/azure-devops-auth.test.ts +100 -0
  248. package/src/tests/azure-devops-handlers.test.ts +324 -0
  249. package/src/tests/azure-devops-webhook-malformed.test.ts +96 -0
  250. package/src/tests/base-prompt.test.ts +40 -2
  251. package/src/tests/claude-adapter.test.ts +58 -0
  252. package/src/tests/claude-default-route.test.ts +260 -0
  253. package/src/tests/claude-managed-adapter.test.ts +47 -0
  254. package/src/tests/claude-managed-setup.test.ts +48 -0
  255. package/src/tests/claude-prompt-body-once.test.ts +117 -0
  256. package/src/tests/claude-routes-e2e.test.ts +316 -0
  257. package/src/tests/codex-auth-failure.test.ts +52 -0
  258. package/src/tests/codex-login.test.ts +116 -0
  259. package/src/tests/context-versions-retention.test.ts +314 -0
  260. package/src/tests/core-auth.test.ts +7 -0
  261. package/src/tests/credential-exhaustion-policy.test.ts +118 -3
  262. package/src/tests/credential-outcome-report.test.ts +171 -0
  263. package/src/tests/credential-status-api.test.ts +103 -2
  264. package/src/tests/db-retention.test.ts +14 -14
  265. package/src/tests/db-task-read-contract.test.ts +6 -0
  266. package/src/tests/db-task-write-contract.test.ts +533 -1
  267. package/src/tests/docs-index-footprint.test.js +3 -3
  268. package/src/tests/dokploy-deploy.test.ts +315 -0
  269. package/src/tests/dsh-adapter.test.ts +134 -2
  270. package/src/tests/dsh-usage.test.ts +153 -0
  271. package/src/tests/explicit-model-validation.test.ts +253 -2
  272. package/src/tests/extensions-catalog.test.ts +1 -0
  273. package/src/tests/extensions-example-deploy-awareness.test.ts +301 -0
  274. package/src/tests/extensions-http.test.ts +137 -10
  275. package/src/tests/extensions-mcp-tools.test.ts +28 -0
  276. package/src/tests/extensions-pre-task.test.ts +73 -0
  277. package/src/tests/fetch-resolved-env.test.ts +70 -0
  278. package/src/tests/fixtures/azure-devops/pull-request-commented.json +129 -0
  279. package/src/tests/fixtures/azure-devops/pull-request-created.json +88 -0
  280. package/src/tests/fixtures/fake-claude-routes.ts +229 -0
  281. package/src/tests/github-handlers-reactions.test.ts +486 -0
  282. package/src/tests/github-reactions.test.ts +280 -0
  283. package/src/tests/hang-watchdog-worker.ts +47 -0
  284. package/src/tests/hang-watchdog.test.ts +45 -0
  285. package/src/tests/hang-watchdog.ts +30 -0
  286. package/src/tests/harness-cli-version.test.ts +62 -0
  287. package/src/tests/harness-model-guard.test.ts +123 -0
  288. package/src/tests/harness-version.test.ts +70 -3
  289. package/src/tests/http-api-integration.test.ts +21 -0
  290. package/src/tests/http-task-write-ownership.test.ts +195 -0
  291. package/src/tests/internal-ai/complete-structured.test.ts +94 -0
  292. package/src/tests/internal-ai/credentials.test.ts +22 -0
  293. package/src/tests/internal-ai/models.test.ts +36 -2
  294. package/src/tests/internal-ai/schema-parity.test.ts +25 -1
  295. package/src/tests/internal-ai/summarize-session.test.ts +23 -7
  296. package/src/tests/mcp-user-route.test.ts +133 -0
  297. package/src/tests/memory-fts-populate.test.ts +139 -0
  298. package/src/tests/memory-http-recall-gating.test.ts +125 -1
  299. package/src/tests/memory-key-paths.test.ts +1055 -0
  300. package/src/tests/memory-rate-endpoint.test.ts +66 -0
  301. package/src/tests/memory-rater-llm-summarizer.test.ts +2 -1
  302. package/src/tests/memory-rater-llm.test.ts +73 -0
  303. package/src/tests/memory-rater-store.test.ts +31 -0
  304. package/src/tests/memory-reranker.test.ts +40 -0
  305. package/src/tests/model-rate-limit-windows.test.ts +19 -0
  306. package/src/tests/model-tier-resolution.test.ts +92 -0
  307. package/src/tests/opencode-adapter.test.ts +214 -2
  308. package/src/tests/opencode-plugin.test.ts +4 -0
  309. package/src/tests/pi-codemode.test.ts +324 -0
  310. package/src/tests/pi-mono-adapter.test.ts +3 -0
  311. package/src/tests/pi-native-mcp.test.ts +193 -0
  312. package/src/tests/pi-tool-exposure.test.ts +374 -0
  313. package/src/tests/preload.ts +9 -0
  314. package/src/tests/prompt-template-session.test.ts +5 -3
  315. package/src/tests/provider-registration.test.ts +0 -2
  316. package/src/tests/providers/pi-cost.test.ts +190 -4
  317. package/src/tests/rate-limit-event.test.ts +65 -0
  318. package/src/tests/rate-limit-outcome.test.ts +90 -0
  319. package/src/tests/rbac-engine.test.ts +41 -2
  320. package/src/tests/runner-key-completion-outcome.test.ts +112 -0
  321. package/src/tests/runner-key-outcome-late-reset.test.ts +224 -0
  322. package/src/tests/runner-registration-retry.test.ts +149 -0
  323. package/src/tests/runner-slot-reconcile.test.ts +199 -0
  324. package/src/tests/script-connections.test.ts +8 -0
  325. package/src/tests/seat-mismatch-report.test.ts +85 -0
  326. package/src/tests/seed-scripts.test.ts +49 -0
  327. package/src/tests/seed.test.ts +25 -0
  328. package/src/tests/session-costs-recompute-all-providers.test.ts +23 -0
  329. package/src/tests/slack-render-v2.test.ts +96 -0
  330. package/src/tests/slack-session-status.test.ts +626 -0
  331. package/src/tests/status.test.ts +29 -0
  332. package/src/tests/steering-promotion.test.ts +51 -0
  333. package/src/tests/store-progress-ownership.test.ts +136 -0
  334. package/src/tests/subscription-plans.test.ts +28 -0
  335. package/src/tests/task-citations.test.ts +3 -2
  336. package/src/tests/task-reactions.test.ts +29 -6
  337. package/src/tests/vcs-azure-devops.test.ts +73 -0
  338. package/src/tests/webhooks-task-blocked.test.ts +23 -0
  339. package/src/tests/workflow-agent-task.test.ts +30 -0
  340. package/src/tests/workflow-llm.test.ts +14 -0
  341. package/src/tests/workflow-triggers-v2.test.ts +320 -0
  342. package/src/tools/context-diff.ts +20 -0
  343. package/src/tools/extension-install.ts +27 -16
  344. package/src/tools/memory-edit.ts +47 -3
  345. package/src/tools/memory-search.ts +13 -1
  346. package/src/tools/memory-store.ts +55 -3
  347. package/src/tools/schedules/create-schedule.ts +1 -1
  348. package/src/tools/schedules/patch-schedule.ts +16 -3
  349. package/src/tools/schedules/update-schedule.ts +16 -3
  350. package/src/tools/send-task.ts +10 -7
  351. package/src/tools/store-progress.ts +49 -4
  352. package/src/tools/task-action.ts +2 -1
  353. package/src/tools/workflows/create-workflow.ts +8 -2
  354. package/src/tools/workflows/update-workflow.ts +8 -2
  355. package/src/types.ts +54 -7
  356. package/src/utils/codex-auth-failure.ts +34 -0
  357. package/src/utils/credentials.test.ts +31 -1
  358. package/src/utils/credentials.ts +92 -39
  359. package/src/utils/dsh-usage.ts +53 -0
  360. package/src/utils/error-tracker.ts +80 -0
  361. package/src/utils/harness-cli-version.ts +6 -4
  362. package/src/utils/internal-ai/complete-structured.ts +26 -3
  363. package/src/utils/internal-ai/credentials.ts +23 -9
  364. package/src/utils/internal-ai/index.ts +8 -1
  365. package/src/utils/internal-ai/models.ts +34 -8
  366. package/src/utils/internal-ai/summarize-session.ts +22 -8
  367. package/src/utils/model-rate-limit-windows.ts +12 -0
  368. package/src/utils/runtime-model-catalog.ts +15 -0
  369. package/src/utils/secret-scrubber.ts +8 -0
  370. package/src/utils/spawn-model-guard.ts +43 -0
  371. package/src/utils/subscription-plans.ts +22 -0
  372. package/src/vcs/azure-devops.ts +80 -0
  373. package/src/vcs/index.ts +5 -1
  374. package/src/vcs/types.ts +2 -2
  375. package/src/workflows/executors/agent-task.ts +7 -6
  376. package/src/workflows/model-validation.ts +45 -0
  377. package/src/workflows/patch-definition.ts +5 -0
  378. package/src/workflows/triggers.ts +85 -0
  379. package/templates/community/open-prompt-showreel/PLAYBOOK.md +336 -0
  380. package/templates/community/open-prompt-showreel/README.md +33 -0
  381. package/templates/community/open-prompt-showreel/example/tla-races/facts.ts +145 -0
  382. package/templates/community/open-prompt-showreel/example/tla-races/facts.v3.json +140 -0
  383. package/templates/community/open-prompt-showreel/example/tla-races/music.mist-city.json +8 -0
  384. package/templates/community/open-prompt-showreel/example/tla-races/reel.html +805 -0
  385. package/templates/community/open-prompt-showreel/example/tla-races/smoke.sh +48 -0
  386. package/templates/community/open-prompt-showreel/lead-prompt.md +89 -0
  387. package/templates/community/open-prompt-showreel/setup.sh +167 -0
  388. package/templates/community/open-prompt-showreel/skills/motion-design-replication/SKILL.md +85 -0
  389. package/templates/community/open-prompt-showreel/skills/motion-design-video-analysis/SKILL.md +115 -0
  390. package/templates/community/open-prompt-showreel/skills/open-prompt-showreel/SKILL.md +147 -0
  391. package/templates/community/open-prompt-showreel/skills/video-generation/SKILL.md +195 -0
  392. package/templates/community/open-prompt-showreel/tools/fetch-music.sh +65 -0
  393. package/templates/community/open-prompt-showreel/tools/music-analyze.py +69 -0
  394. package/templates/community/open-prompt-showreel/tools/music-current.sh +34 -0
  395. package/templates/community/open-prompt-showreel/tools/music-cut.sh +36 -0
  396. package/templates/community/open-prompt-showreel/tools/music-refine.py +39 -0
  397. package/templates/community/open-prompt-showreel/tools/render.mjs +157 -0
  398. package/templates/community/open-prompt-showreel/tools/sheet.sh +44 -0
  399. package/templates/community/open-prompt-showreel/tools/web-compress.sh +17 -0
  400. package/templates/community/open-prompt-showreel/tools/x-reencode.sh +21 -0
  401. package/templates/extensions/README.md +1 -0
  402. package/templates/extensions/deploy-awareness/README.md +28 -0
  403. package/templates/extensions/deploy-awareness/hooks.ts +152 -0
  404. package/templates/extensions/deploy-awareness/manifest.json +8 -0
  405. package/templates/skills/memory/config.json +2 -2
  406. package/templates/skills/memory/content.md +59 -5
  407. package/templates/skills/swarm-extensions/config.json +1 -1
  408. package/templates/skills/swarm-extensions/content.md +28 -5
  409. package/templates/skills/workflow-iterate/content.md +1 -0
  410. package/dist/boot-seeds-h7c32hvm.js +0 -27
  411. package/dist/cli-2djtr51b.js +0 -48
  412. package/dist/cli-7zttzetj.js +0 -324
  413. package/dist/cli-j490egb5.js +0 -5632
  414. package/dist/cli-m4excttz.js +0 -333481
  415. package/dist/index-bjmjh9h4.js +0 -29
@@ -0,0 +1,147 @@
1
+ ---
2
+ name: open-prompt-showreel
3
+ description: Make a design-led motion-graphics video (showreel, eval comparison, feature or bug-hunt story) from one open prompt. Claude Opus at max effort designs, codes and renders the whole thing itself in a scratch dir, with no template and no repo PR. Use for any "make a video for X" / "do a showreel for Y" / "a little comparison video" ask aimed at X, LinkedIn or a launch post. Covers the Lead brief template, grounding every on-screen number in a real source, the real Agent Swarm end logo, CC0/CC BY music with credit, render specs (16:9 + 4:5), the contact-sheet self-critique loop, agent-fs delivery, feedback rounds and crash-safe retries. Not for fixed-template creatives or for replicating a reference clip.
4
+ metadata:
5
+ type: procedure
6
+ ---
7
+
8
+ # Open-prompt showreel videos
9
+
10
+ The approach: give a strong model on max effort one open creative prompt plus hard content rules, and let it design, code and render the whole video itself. Template pipelines produce generic cuts. This one produced the showreels and the TLA+ races cuts that the team liked.
11
+
12
+ ## When to use which skill
13
+
14
+ | Ask | Skill |
15
+ |---|---|
16
+ | A video that should look designed: showreel, model/eval comparison, "we found N bugs", launch hype | **this skill** |
17
+ | A standard on-brand card or clip from a fixed template (release, proof stat, quote, OG image) | a template toolkit, if your swarm has one |
18
+ | Rebuild the feel of a reference clip someone linked | `motion-design-video-analysis`, then `motion-design-replication` |
19
+ | Repo-committed Remotion video (README hero, landing demo, case-study clip) | `video-generation` |
20
+
21
+ If the ask says "like the showreel", "your own design" or "go all out", or someone complained a cut looks generic, it is this skill.
22
+
23
+ ## Model, effort, harness
24
+
25
+ - The maker runs **model `claude-opus-5-5`, effort `max`**, on a **Claude Code harness worker** (`provider: claude`). Set both on the task.
26
+ - Other harnesses were not tried for this workflow. Do not route to them without a test run.
27
+ - Every brief ends with a gate: "If you are not on claude-opus-5-5, stop and say so instead of rendering." The maker checks this first.
28
+ - The worker needs Playwright + Chromium, ffmpeg, python3 with numpy, and write access to agent-fs. The community template's `setup.sh` checks and installs all of them (numpy goes in a venv; the stock image does not ship it).
29
+
30
+ ## Lead brief template
31
+
32
+ One task, one open prompt. Do not write a storyboard, shot list, palette or template for the maker; that is what made earlier cuts generic.
33
+
34
+ ```
35
+ GOAL: <one line: what the video is and where it goes (X / LinkedIn / internal)>.
36
+ Made by you on claude-opus-5-5, max effort, ad hoc, your own design.
37
+
38
+ <Requester>, verbatim: "<the ask>"
39
+
40
+ Your prompt: make a dynamic <15 | 15-30> second motion graphics video <about SUBJECT>.
41
+ Make it your showreel: show what an incredible motion designer you are. Go all out.
42
+
43
+ Content rule: every number or claim on screen comes from <SOURCE: eval writeup path /
44
+ test file on origin/main / PR numbers> and matches it exactly. Anything not in the
45
+ source stays off screen. Fewer numbers is fine: design-led, not a table.
46
+
47
+ How:
48
+ - Ad hoc: scratch dir, any tools or code you like. No repo, no PR, no shared template.
49
+ - Real brand: Agent Swarm logo = origin/main apps/ui/public/logo.png (verify vs agent-swarm.dev/logo.png). Never draw your own mark.
50
+ - Look at a contact sheet at least 3 times, fixing what looks weak each round; in the last round check every on-screen number against the source and look at the end-card frame.
51
+ - Music optional; CC0 or CC BY only (no NC, no ND, no SA); short credit on the end card; in your output the full attribution from music/current.json (source link, license link, changes made).
52
+ - If you are not on claude-opus-5-5, stop and say so.
53
+ - Nothing is owed to a human by you; Lead reviews and relays.
54
+
55
+ Deliver to agent-fs thoughts/<maker-agent-id>/videos/<slug>/: <1920x1080 [+ 1080x1350]> h264 yuv420p mp4,
56
+ contact sheet(s), facts.json.
57
+ Output: agent-fs paths, a share-create link per mp4 (url + share id), ffprobe line, model you ran on, each on-screen number with its source, music attribution (source link, license link, changes made).
58
+ ```
59
+
60
+ Optional style add-on for a calmer, premium cut (the "Apple framework"): 3 colors, one background, one font, no HUD/grain; one shot = one idea; ~3 s open and ~2 s held end; key object centered; eased, overlapping keyframes; no hard cuts; ~108 BPM, phrase hit on the first product frame; no SFX. Add it only when asked.
61
+
62
+ ## Maker procedure
63
+
64
+ ### 1. Scratch dir first, and say where it is
65
+
66
+ `mkdir /workspace/personal/<slug>` and store-progress with the path in the first minute. `/workspace/personal` survives a container restart; the session does not. A retry that found the scratch dir of its crashed predecessor delivered in 8 minutes.
67
+
68
+ ### 2. Facts before design
69
+
70
+ Write a small script (`facts.ts`, run with `bun facts.ts > facts.json`) that reads every on-screen fact live from its source right before render, and have the reel read `facts.json` instead of hard-coding numbers.
71
+
72
+ - Code facts: `git fetch origin main`, read the file at `origin/main` (for example count `test.failing` vs `test` in `src/tests/workflow-tla-races.test.ts`), `gh pr view` / `gh pr list` for PR states. Record the short SHA in facts.json.
73
+ - Eval facts: the writeup the eval task produced on agent-fs, and the run id. Copy numbers exactly.
74
+ - A fact turns "green" only if main says so. Unmerged stays in its old state, even if the brief expects otherwise.
75
+ - If the source disagrees with the brief, the source wins; say so in the output (the TLA+ v3 brief listed four merged PRs; main also had a fifth, so the reel said "ALL 10 FIXED ON MAIN").
76
+ - Drop any claim the source does not support.
77
+ - If the source is not ready yet (eval still running, PRs still merging), build the design on draft facts, then `defer-task` with `wakeOn:{event:"settled",taskIds:[...]}` and re-read the facts on wake. Never ship draft numbers.
78
+
79
+ ### 3. Build: one HTML canvas reel, frame-addressable
80
+
81
+ A single `reel.html` that draws each frame on a `<canvas>` as a pure function of the frame number, with scenes as `sceneN(f)` functions. It exposes:
82
+
83
+ ```js
84
+ window.boot = async (facts, fonts, logo) => { /* load fonts + logo, stash facts */ };
85
+ window.frame = (f) => { render(f); return cv.toDataURL("image/png"); };
86
+ ```
87
+
88
+ `render.mjs` (Playwright, `chromium.launch({args:["--disable-gpu-vsync"]})`) opens the file at the target viewport, calls `boot` with facts, base64 fonts and the base64 logo, then either grabs a comma list of frames to PNGs (stills mode, for sheets) or grabs every frame and pipes PNGs to ffmpeg over `image2pipe` (video mode). The community template ships a working `tools/render.mjs`.
89
+
90
+ - Always pass `-pix_fmt yuv420p` to the ffmpeg that encodes the piped PNGs. Without it libx264 keeps the PNG's 4:4:4 and writes `yuv444p` (High 4:4:4 Predictive), which phones and social uploaders handle badly.
91
+ - ffmpeg in the worker image has no `drawtext` filter. Draw text on the canvas (this approach does anyway), or burn captions with the `ass` filter.
92
+ - Fonts are local TTFs inlined as base64 (Space Grotesk, JetBrains Mono, Instrument Serif Italic in the TLA+ reels; all SIL OFL).
93
+ - For long 60 fps cuts, render frames in several parallel Playwright workers and encode once.
94
+
95
+ ### 4. Brand: the real end logo
96
+
97
+ - Agent Swarm logo = `apps/ui/public/logo.png` on origin/main: a solid gold hexagon inside a lighter gold honeycomb. It is byte-identical to `docs-site/public/logo.png` and `https://agent-swarm.dev/logo.png`. Check the sha256 against the live site; if they differ, the live site wins and you say so.
98
+ - Not an old cartoon asset. Not a mark you draw yourself: the first TLA+ cut drew a ring + hex + rays and the requester asked for the correct logo.
99
+ - Your own hex motif may morph into the real logo; the last frames must show the real asset, sharp.
100
+ - Colors from agent-swarm.dev (ink background, amber/gold accents). Do not invent brand elements.
101
+
102
+ ### 5. Music: CC0 / CC BY, timed to the cut
103
+
104
+ - License: CC0 or CC BY only. No NC (company post), no ND (we cut and stretch it), no SA (it would carry over to the video). `fetch-music.sh` enforces an allowlist (CC0 1.0, CC BY 1.0-4.0); read the license on the source page yourself. Sources that worked: incompetech.com (Kevin MacLeod, CC BY 4.0), OpenGameArt (filter by license).
105
+ - Pick a fresh track per video; the requester asked for different music on the first TLA+ cut. Offer two alternates when music is in question.
106
+ - `fetch-music.sh` writes `music/<track>.json` (`{title, artist, license, license_url, source, credit}`). After `music-cut.sh`, run `music-current.sh music/<track>.json music/cut.wav`: it writes `music/current.json` with `wav` (without it `render.mjs` renders silent) and `attribution`. Render with `--music music/current.json` and check the mp4 has an `aac` stream. `credit` is the short end-card line; CC BY also needs the source link, the license link and the changes made in the post, which is what `attribution` carries.
107
+ - Time the cut to the music: find tempo, beat phase and the drop (numpy spectral-flux + low-end energy: `music-analyze.py`, `music-refine.py`), then time-stretch so the beats sit on the reel's frame grid (120 BPM = 15 frames per beat at 30 fps) and the drop lands on the key reveal (`music-cut.sh`: `atempo` + trim, fade in 0.3 s, fade out ~1.7 s).
108
+ - When the music is the feedback, also render the 16:9 with two runner-up tracks (audio swap only).
109
+
110
+ ### 6. Render specs
111
+
112
+ - Container: h264 High, `yuv420p`, AAC 192k, `-movflags +faststart`, `libx264 -preset slow -crf 16`.
113
+ - 16:9: 1920x1080, stream at exactly that size. Add 4:5 1080x1350 for LinkedIn/X feeds. Build the layout responsive to `?w=&h=` rather than cropping.
114
+ - 30 fps by default; 60 fps for heavy motion.
115
+ - Length: 15 s for a brand showreel; 20-30 s when the story carries data.
116
+ - The worker image has no `ffprobe`. Read stream lines from `ffmpeg -i` stderr, or install ffprobe with the template's `setup.sh`.
117
+ - A clip from a screen recording, a JPEG pipeline or `remotion render` can be tagged `yuvj420p` (full range). Re-encode it with `x-reencode.sh` before posting to X.
118
+
119
+ ### 7. Self-critique loop (minimum 3 rounds)
120
+
121
+ - Contact sheet: render 16 stills across the timeline and tile them 4x4 with ffmpeg `xstack` (`sheet.sh`); look at it with Read. Keep sheets per round (`r1-16x9.png`, `r2-...`).
122
+ - Transition strip: 20 stills densely around each cut (5x4 grid) to catch pops and empty frames.
123
+ - Each round, write down what looks weak (dead space, text too small for a phone, cramped 4:5, jumpy easing, off-beat hit) and fix it.
124
+ - Final round: check every on-screen number against facts.json and the source, and open the end-card frame on its own at full size to confirm the logo and credit.
125
+
126
+ ### 8. Deliver
127
+
128
+ - agent-fs: `thoughts/<maker-agent-id>/videos/<slug>/` (versions as `<slug>-v2`, `<slug>-v3`, never overwrite an earlier version).
129
+ - Upload the mp4s, contact sheet(s), `facts.json`, the music json, and `reel.html` so the next round can start from it. After every non-trivial write, `agent-fs stat <path> --json` and check the size.
130
+ - Share link for each final mp4: `agent-fs --org <ORG_ID> --drive <DRIVE_ID> share-create <path> --expires-in 604800 --json`. The share page plays the video inline with no login. Max 7 days; report the url, the share id and the expiry. Do not hand out raw signed URLs or viewer URLs for video.
131
+ - agent-fs serves a raw `.mp4` download as `application/octet-stream`; the share page's player gets `video/mp4`. Mention it rather than retrying.
132
+ - Output (short): paths, share links, ffprobe line, model, each on-screen fact with its source and SHA/run id, music attribution (source link, license link, changes made), logo source. No Slack post by the maker; Lead relays.
133
+
134
+ ## Feedback rounds (v2, v3)
135
+
136
+ - Lead writes a new task per round that quotes the requester verbatim and maps each point to a concrete change.
137
+ - The maker copies the previous scratch dir to `<slug>-vN`, changes only what the feedback names, and keeps design, music and logo otherwise identical.
138
+ - Always re-run `facts.ts` against current origin/main or the latest writeup; never carry numbers over.
139
+ - If a point depends on other work (PRs merging), do the rest now, deliver an interim, then `defer-task` on the blocking task with a ceiling.
140
+
141
+ ## Failures to avoid
142
+
143
+ - **Dying mid final render.** Two runs were failed by a reboot sweep during the last render. Keep everything under `/workspace/personal/<slug>`, name it in progress early, upload an interim cut before the long render, and on a retry look for the prior working dir before starting over.
144
+ - **Pipeline cuts read as generic.** Fixed-template cuts of the same subjects were rejected as "too meh". Ad hoc scratch dir, no PR.
145
+ - **Invented logo** on the end card. Use the real asset.
146
+ - **Stock music the requester already heard.** Pick a fresh track each time.
147
+ - **Numbers ahead of the source.** Draft or expected states on screen before main or the eval says so.
@@ -0,0 +1,195 @@
1
+ ---
2
+ name: video-generation
3
+ description: How to generate repo-committed video content (README hero videos, case-study clips, landing demos) with Remotion. Covers project layout, fonts and colors that match the brand, audio sourcing, rendering in a worker container, the full-range yuvj420p gotcha, CI exclusions, and the "honest read" pacing principle. Use whenever you are asked to create a video from a Remotion project, animate a wireframe, or re-render an existing Remotion project. For a design-led one-off showreel use open-prompt-showreel instead.
4
+ metadata:
5
+ type: procedure
6
+ ---
7
+
8
+ # Video generation (Remotion)
9
+
10
+ Remotion (React + TypeScript) is the stack for videos that live in a repo: README heroes, landing demos, case-study clips, thread dramatizations. `assets/video-source/` in `desplega-ai/agent-swarm` is the reference implementation; read it before starting a new project.
11
+
12
+ ## Golden rules
13
+
14
+ 1. **Remotion for repo-committed video.** `npx remotion studio` for previews, `remotion render` for mp4s. For a one-off design-led cut with no repo, use `open-prompt-showreel` instead.
15
+ 2. **Ask for audio, don't pick it.** Music is a brand call. A person on the team chooses the track (CC0 or CC BY 4.0, for example from https://freesound.org). Never grab something yourself; ask. There is no default bed.
16
+ 3. **Attribute in the video folder's README.** Artist name, source URL, license. Not in the repo root README; in the video project's own README.
17
+ 4. **Design tokens mirror the product.** Pull colors and fonts from the product's own `globals.css` (Agent Swarm: Space Grotesk + Space Mono, zinc dark palette, amber `#f2a93b` accent). Do not invent new ones.
18
+ 5. **Honest read pacing.** The first draft always over-packs the opening and under-weights the payoff. Compress setup scenes, expand the one that carries the message. See "Pacing" below.
19
+ 6. **One Remotion project, many compositions.** Do not spin up a new repo per video. Register additional `<Composition>` entries in `src/Root.tsx` and add a `build:<slug>` script.
20
+
21
+ ## Project layout
22
+
23
+ ```
24
+ <project>/
25
+ package.json # remotion + @remotion/cli + @remotion/google-fonts + react 18
26
+ remotion.config.ts
27
+ tsconfig.json # isolated: exclude from the root tsconfig (see CI below)
28
+ README.md # render commands + audio attribution
29
+ src/
30
+ index.ts # registerRoot(Root)
31
+ Root.tsx # <Composition> per video
32
+ fonts.ts # loadFont() for Space Grotesk + Space Mono at module scope
33
+ theme.ts # brand tokens (bg/fg/accent/sans/mono)
34
+ compositions/ # one per video: stitches scenes + audio
35
+ scenes/<video-slug>/ # per-scene components, one folder per composition
36
+ public/
37
+ audio/bed.mp3 # music bed, referenced via staticFile("audio/bed.mp3")
38
+ out/ # rendered mp4s (gitignored)
39
+ ```
40
+
41
+ ## Bootstrapping a new video project
42
+
43
+ Ran in a worker container (Remotion 4.0.532, 23 s install):
44
+
45
+ ```bash
46
+ mkdir -p assets/video-source && cd assets/video-source
47
+ npm init -y
48
+ npm install remotion @remotion/cli @remotion/google-fonts react@^18.3.1 react-dom@^18.3.1
49
+ npm install -D @types/react typescript
50
+ npx remotion studio # scaffolds config + opens preview
51
+ ```
52
+
53
+ `npx remotion studio` opens an interactive preview and was not run for this playbook.
54
+
55
+ ## Rendering in a worker container
56
+
57
+ The worker image already ships Chromium (`/opt/playwright/chromium`) and its system libraries, so no apt install is needed. Point Remotion at it instead of letting it download its own browser:
58
+
59
+ ```bash
60
+ npx remotion render src/index.ts <CompositionId> out/<name>.mp4 \
61
+ --browser-executable=/opt/playwright/chromium --color-space=bt709
62
+ ```
63
+
64
+ A 30-frame 1280x720 test composition rendered in 14 s this way.
65
+
66
+ **Color-range gotcha.** Without `--color-space=bt709`, `remotion render` writes `yuvj420p(pc, bt470bg)`: full range, not the limited-range `yuv420p` that social uploaders expect. With the flag it writes `yuv420p(tv, bt709)`. Check with `ffmpeg -hide_banner -i out.mp4 2>&1 | grep Stream`. If a clip is already rendered, re-encode it with the template's `x-reencode.sh`.
67
+
68
+ For a bare Debian/Ubuntu container that is not a worker image, Chromium needs system libraries and root. **Unverified, not run for this playbook:**
69
+
70
+ ```bash
71
+ sudo apt-get install -y ffmpeg libnspr4 libnss3 libatk1.0-0 libatk-bridge2.0-0 \
72
+ libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 \
73
+ libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2t64 libatspi2.0-0
74
+ ```
75
+
76
+ ## Fonts
77
+
78
+ Load once at module scope in `src/fonts.ts` and import from `src/Root.tsx`:
79
+
80
+ ```ts
81
+ import { loadFont as loadSpaceGrotesk } from "@remotion/google-fonts/SpaceGrotesk";
82
+ import { loadFont as loadSpaceMono } from "@remotion/google-fonts/SpaceMono";
83
+
84
+ loadSpaceGrotesk("normal", { weights: ["400", "500", "600", "700"], subsets: ["latin"] });
85
+ loadSpaceMono("normal", { weights: ["400", "700"], subsets: ["latin"] });
86
+ ```
87
+
88
+ ## Theme tokens (dark palette, amber accent)
89
+
90
+ ```ts
91
+ export const theme = {
92
+ bg: "#09090b",
93
+ card: "#18181b",
94
+ fg: "#fafafa",
95
+ muted: "#a1a1aa",
96
+ mutedDim: "#52525b",
97
+ border: "rgba(255,255,255,0.10)",
98
+ borderStrong: "rgba(255,255,255,0.18)",
99
+ accent: "#f2a93b", // amber: brand primary in dark mode
100
+ accentDim: "#7a4d12",
101
+ accentFg: "#1a1409",
102
+ success: "#4ade80",
103
+ danger: "#f87171",
104
+ sans: "'Space Grotesk', system-ui, -apple-system, sans-serif",
105
+ mono: "'Space Mono', ui-monospace, 'SF Mono', Menlo, monospace",
106
+ };
107
+ ```
108
+
109
+ ## Multiple compositions
110
+
111
+ Register each video as its own `<Composition>` in `src/Root.tsx`. `durationInFrames` is at 30 fps unless you raise the fps:
112
+
113
+ ```tsx
114
+ <Composition id="DailyEvolution" component={DailyEvolution}
115
+ durationInFrames={900} fps={30} width={1920} height={1080} />
116
+ <Composition id="SlackToPR" component={SlackToPR}
117
+ durationInFrames={1350} fps={30} width={1920} height={1080} />
118
+ ```
119
+
120
+ Add a `build:<slug>` script per composition in `package.json`:
121
+
122
+ ```json
123
+ "build:daily-evolution": "remotion render src/index.ts DailyEvolution out/daily-evolution.mp4",
124
+ "build:slack-to-pr": "remotion render src/index.ts SlackToPR out/slack-to-pr.mp4",
125
+ "build:all": "npm run build:daily-evolution && npm run build:slack-to-pr"
126
+ ```
127
+
128
+ ## Audio
129
+
130
+ ```tsx
131
+ import { Audio, staticFile } from "remotion";
132
+
133
+ // Inside a composition, per composition, so some can ship silent:
134
+ <Audio src={staticFile("audio/bed.mp3")} volume={0.4} />
135
+ ```
136
+
137
+ - Put attribution in `<video-project>/README.md` with artist, source URL and license, for example:
138
+ > _Track title_ by **Artist** (https://freesound.org/s/<id>/), License: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
139
+ - Not every composition needs music. A case-study clip that pairs with a written thread can ship silent on purpose.
140
+ - If someone says "I can't hear the audio", first check the actual bytes of the mp3 (`file public/audio/bed.mp3`, `ls -lh`); stub or broken files are the usual cause. Then verify `<Audio>` is mounted inside the composition that is being rendered, not a sibling. Verify the render has an audio stream with `ffmpeg -i out.mp4`.
141
+
142
+ ## Pacing: the "honest read"
143
+
144
+ First drafts always front-load setup and shortchange the payoff. After the first render, do an honest read:
145
+
146
+ 1. **Which single scene carries the message?** That one gets the most frames.
147
+ 2. **Which scenes are setup or framing?** Compress them, usually 60-120 frames (2-4 s at 30 fps).
148
+ 3. **Does the payoff have room to breathe?** 450+ frames (about 15 s) is typical for a scene the viewer needs to absorb.
149
+ 4. **Do the beats spread across the available time, or cluster at the start?** If they cluster, spread the `delay` / `spring` staggers across the full scene duration.
150
+
151
+ Example distribution for a 30 s composition (900 frames at 30 fps): scenes 1-4 (setup) 60 / 90 / 120 / 120 frames, scene 5 (payoff) 450 frames, scene 6 (tagline) 60 frames.
152
+
153
+ ## CI and repo integration
154
+
155
+ A Remotion project lives inside the repo but must be isolated from its build tooling:
156
+
157
+ 1. **Exclude from the root `tsconfig.json`.** The project has its own deps (React, Remotion) that the parent repo does not resolve. Add `"assets/video-source"` (or wherever it lives) to the root tsconfig `exclude`.
158
+ 2. **Scope the linter.** If Biome or ESLint is scoped to `src/`, no change is needed. If it scans the whole repo, add an ignore pattern.
159
+ 3. **Keep its own `tsconfig.json`** inside the video folder. Run `npx tsc --noEmit` there for type coverage.
160
+ 4. **`out/` and `node_modules/` must be gitignored.** Commit `public/audio/bed.mp3` and source, not render outputs.
161
+ 5. **Do not install Remotion's heavy deps at the repo root.** They belong only in the video project's `package.json`.
162
+
163
+ ## README video embed (GitHub renderer gotchas)
164
+
165
+ For README hero videos, use a raw-URL `<video>` tag:
166
+
167
+ ```html
168
+ <video src="https://github.com/<org>/<repo>/raw/main/assets/<name>.mp4"
169
+ autoplay muted loop playsinline></video>
170
+ ```
171
+
172
+ - GitHub's markdown renderer is strict about `<video>` attributes. Keep it simple.
173
+ - Prefer `raw/main/...` over `blob/main/...`; the latter serves the HTML page, not the mp4 bytes.
174
+ - Target file size: under 3 MB if possible.
175
+ - If the embed does not render, fall back to a link preview: `[![demo](thumbnail.png)](video-url)`.
176
+
177
+ ## When to use what
178
+
179
+ | Need | Approach |
180
+ |---|---|
181
+ | README hero (the "what is this?") | 30 s, 1080p, one composition, audio bed, low-fi wireframe tokens |
182
+ | Case study or thread dramatization | 45 s, multiple short scenes, typically silent |
183
+ | Landing demo (product in action) | Remotion scaffolding + real screen recordings composited in |
184
+ | Design-led one-off (showreel, eval comparison) | `open-prompt-showreel`, not this skill |
185
+ | Audio-first asset (podcast clip, TTS demo) | A text-to-speech service, not Remotion |
186
+
187
+ ## Common mistakes
188
+
189
+ - Picking music without asking.
190
+ - Inventing colors or fonts instead of pulling them from the product.
191
+ - Cramming 6 scenes into the first 10 s. That is the drift the honest read fixes.
192
+ - Forgetting to exclude the video folder from the root tsconfig: CI fails on React/Remotion types the root repo does not have.
193
+ - Shipping a silent video where audio was expected, or the reverse.
194
+ - Dropping attribution. CC BY 4.0 requires credit.
195
+ - Posting a `remotion render` output to X without checking its pixel format.
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env bash
2
+ # Downloads one CC0 / CC BY track and writes the credit file the reel and the output need.
3
+ #
4
+ # fetch-music.sh incompetech <Title> <out-dir> # Kevin MacLeod, CC BY 4.0
5
+ # fetch-music.sh url <audio-url> <out-dir> <title> <artist> <license> <source-page>
6
+ #
7
+ # Writes <out-dir>/<slug>.<ext> and <out-dir>/<slug>.json =
8
+ # { title, artist, license, license_url, source, credit }
9
+ # `credit` is the short line for the end card. The full attribution (source link, license link and
10
+ # the modification notice) is added by music-current.sh once the track has been cut.
11
+ #
12
+ # Allowlist: CC0 1.0 and CC BY 1.0 to 4.0, matched case-insensitively ("cc-by 4.0" works). Everything
13
+ # else is refused: NC (it is a company post), ND (we cut and stretch the track), SA (it would carry
14
+ # over to the video), "All rights reserved", unknown strings. Check the license on the source page
15
+ # yourself; the script cannot.
16
+ set -euo pipefail
17
+
18
+ mode=${1:-}
19
+ case "$mode" in
20
+ incompetech)
21
+ title=$2 outdir=$3
22
+ url="https://incompetech.com/music/royalty-free/mp3-royaltyfree/${title// /%20}.mp3"
23
+ # Kevin MacLeod asks for the site root, not a per-track page.
24
+ artist="Kevin MacLeod" license="CC BY 4.0" source="https://incompetech.com"
25
+ ;;
26
+ url)
27
+ url=$2 outdir=$3 title=$4 artist=$5 license=$6 source=$7
28
+ ;;
29
+ *)
30
+ echo "usage: fetch-music.sh incompetech <Title> <out-dir> | url <url> <out-dir> <title> <artist> <license> <source>" >&2
31
+ exit 2
32
+ ;;
33
+ esac
34
+
35
+ # Normalize ("cc-by_4.0" -> "CC BY 4.0") and map to the canonical name and the license deed URL.
36
+ norm=$(tr '[:lower:]' '[:upper:]' <<<"$license" | tr -s ' _-' ' ' | sed 's/^ //; s/ $//')
37
+ if [[ "$norm" =~ ^CC\ ?0(\ 1\.0)?(\ UNIVERSAL)?$ ]]; then
38
+ license="CC0 1.0" license_url="https://creativecommons.org/publicdomain/zero/1.0/"
39
+ elif [[ "$norm" =~ ^CC\ BY\ (1\.0|2\.0|2\.5|3\.0|4\.0)$ ]]; then
40
+ license="CC BY ${BASH_REMATCH[1]}" license_url="https://creativecommons.org/licenses/by/${BASH_REMATCH[1]}/"
41
+ else
42
+ echo "refusing license '$license': only CC0 1.0 and CC BY 1.0-4.0 are allowed (no NC, ND, SA or unknown terms)" >&2
43
+ exit 1
44
+ fi
45
+ case "$source" in
46
+ http://* | https://*) ;;
47
+ *) echo "source must be a http(s) link to the track's page, got '$source'" >&2; exit 1 ;;
48
+ esac
49
+
50
+ mkdir -p "$outdir"
51
+ slug=$(tr '[:upper:] ' '[:lower:]-' <<<"$title" | tr -cd 'a-z0-9-')
52
+ ext="${url##*.}"
53
+ curl -fsSL "$url" -o "$outdir/$slug.$ext"
54
+ python3 - "$outdir/$slug.json" "$title" "$artist" "$license" "$license_url" "$source" <<'EOF'
55
+ import json, sys
56
+ path, title, artist, license, license_url, source = sys.argv[1:]
57
+ credit = f'"{title}" by {artist} ({source.split("//")[-1].split("/")[0]}) · {license}'
58
+ json.dump(
59
+ {"title": title, "artist": artist, "license": license, "license_url": license_url, "source": source, "credit": credit},
60
+ open(path, "w"),
61
+ indent=2,
62
+ )
63
+ print(credit)
64
+ EOF
65
+ echo "$outdir/$slug.$ext $(stat -c%s "$outdir/$slug.$ext") bytes"
@@ -0,0 +1,69 @@
1
+ # usage: music-analyze.py <audio> -> tempo, beat phase, drop candidates (json on stdout)
2
+ # Drop = a beat where the next 4 bars carry much more low-end + total energy than the previous 4 bars.
3
+ import json, subprocess, sys
4
+ import numpy as np
5
+
6
+ SR = 22050
7
+ path = sys.argv[1]
8
+ raw = subprocess.run(["ffmpeg", "-v", "error", "-i", path, "-ac", "1", "-ar", str(SR), "-f", "f32le", "-"], capture_output=True, check=True).stdout
9
+ x = np.frombuffer(raw, dtype=np.float32)
10
+ dur = len(x) / SR
11
+
12
+ hop, win = 512, 2048
13
+ n = 1 + (len(x) - win) // hop
14
+ frames = np.lib.stride_tricks.as_strided(x, shape=(n, win), strides=(x.strides[0] * hop, x.strides[0]))
15
+ spec = np.abs(np.fft.rfft(frames * np.hanning(win), axis=1))
16
+ logs = np.log1p(spec * 10)
17
+ flux = np.maximum(0, np.diff(logs, axis=0)).sum(axis=1)
18
+ flux = np.concatenate([[0], flux])
19
+ flux = flux - np.convolve(flux, np.ones(16) / 16, mode="same")
20
+ flux = np.maximum(flux, 0)
21
+ fps = SR / hop
22
+ freqs = np.fft.rfftfreq(win, 1 / SR)
23
+ low = spec[:, freqs < 150].sum(axis=1)
24
+ rms = np.sqrt((frames ** 2).mean(axis=1))
25
+
26
+ # tempo: autocorrelation of onset envelope, 90..160 bpm, with half/double resolution
27
+ ac = np.correlate(flux, flux, mode="full")[len(flux) - 1:]
28
+ best = None
29
+ for bpm in np.arange(90, 160.01, 0.1):
30
+ lag = 60 / bpm * fps
31
+ s = sum(np.interp(lag * k, np.arange(len(ac)), ac) for k in (1, 2, 4)) / 3
32
+ if best is None or s > best[1]:
33
+ best = (bpm, s)
34
+ bpm = float(best[0])
35
+ period = 60 / bpm
36
+ # phase
37
+ tt = np.arange(len(flux)) / fps
38
+ bestp = None
39
+ for ph in np.arange(0, period, 0.005):
40
+ beats = np.arange(ph, dur, period)
41
+ s = np.interp(beats * fps, np.arange(len(flux)), flux).sum()
42
+ if bestp is None or s > bestp[1]:
43
+ bestp = (ph, s)
44
+ phase = float(bestp[0])
45
+ beats = np.arange(phase, dur, period)
46
+
47
+ def seg(arr, a, b):
48
+ ia, ib = int(max(0, a) * fps), int(min(dur, b) * fps)
49
+ return float(arr[ia:ib].mean()) if ib > ia else 0.0
50
+
51
+ bar = 4 * period
52
+ cands = []
53
+ for b in beats:
54
+ if b < 2 * bar or b > dur - 2 * bar:
55
+ continue
56
+ pre_l, post_l = seg(low, b - 2 * bar, b), seg(low, b, b + 2 * bar)
57
+ pre_r, post_r = seg(rms, b - 2 * bar, b), seg(rms, b, b + 2 * bar)
58
+ score = np.log((post_l + 1e-6) / (pre_l + 1e-6)) + np.log((post_r + 1e-6) / (pre_r + 1e-6))
59
+ cands.append((float(score), float(b), post_r / (pre_r + 1e-9), post_l / (pre_l + 1e-9)))
60
+ cands.sort(reverse=True)
61
+ top = []
62
+ for c in cands:
63
+ if all(abs(c[1] - t["t"]) > 2 * bar for t in top):
64
+ top.append({"t": round(c[1], 3), "score": round(c[0], 2), "rmsx": round(c[2], 2), "lowx": round(c[3], 2)})
65
+ if len(top) == 6:
66
+ break
67
+ # energy curve per 2s for eyeballing
68
+ curve = [round(seg(rms, t, t + 2) * 100, 1) for t in np.arange(0, dur, 2)]
69
+ print(json.dumps({"file": path, "dur": round(dur, 2), "bpm": round(bpm, 2), "phase": round(phase, 3), "drops": top, "rms2s": curve}))
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bash
2
+ # Writes the music file that render.mjs reads: the credit json from fetch-music.sh plus the cut wav.
3
+ #
4
+ # music-current.sh <track.json> <cut.wav> [out.json] # out.json defaults to <cut.wav dir>/current.json
5
+ #
6
+ # Run it after music-cut.sh and before the final render. It adds two things to the track json:
7
+ # wav the cut wav, as given (render.mjs muxes it in; without it the video is silent)
8
+ # attribution the full credit for the post: title, artist, source link, license link and, for
9
+ # CC BY, a notice that the track was edited (CC BY requires all of these)
10
+ # `credit` stays the short line for the end card. MODIFIED overrides the default edit description.
11
+ set -euo pipefail
12
+
13
+ track=${1:?usage: music-current.sh <track.json> <cut.wav> [out.json]} wav=${2:?missing <cut.wav>}
14
+ out=${3:-$(dirname "$wav")/current.json}
15
+ [ -f "$track" ] || { echo "no such track json: $track" >&2; exit 1; }
16
+ [ -s "$wav" ] || { echo "no such wav (run music-cut.sh first): $wav" >&2; exit 1; }
17
+
18
+ MODIFIED=${MODIFIED:-"trimmed, time-stretched to the video's beat grid, faded in and out, and loudness-normalized"} \
19
+ python3 - "$track" "$wav" "$out" <<'EOF'
20
+ import json, os, sys
21
+ track, wav, out = sys.argv[1:]
22
+ m = json.load(open(track))
23
+ for k in ("title", "artist", "license", "license_url", "source", "credit"):
24
+ if not m.get(k):
25
+ sys.exit(f"{track} has no '{k}'; re-run fetch-music.sh (older credit files lack license_url)")
26
+ m["wav"] = wav
27
+ m["attribution"] = f'"{m["title"]}" by {m["artist"]}, {m["source"]}, licensed under {m["license"]}, {m["license_url"]}.'
28
+ if m["license"].startswith("CC BY"):
29
+ m["modified"] = os.environ["MODIFIED"]
30
+ m["attribution"] += f' Changes: {m["modified"]}.'
31
+ json.dump(m, open(out, "w"), indent=2)
32
+ print(out)
33
+ print(m["attribution"])
34
+ EOF
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ # Time-stretches a track so its beats sit on the reel's frame grid and its drop lands on the key reveal.
3
+ #
4
+ # music-cut.sh <in> <track_bpm> <drop_sec_in_track> <out.wav>
5
+ #
6
+ # Get <track_bpm> and <drop_sec_in_track> from music-analyze.py / music-refine.py.
7
+ # Env (defaults match the TLA+ reel: 30 fps, 120 BPM = 15 frames per beat, drop at 14.0 s, 24 s cut):
8
+ # TARGET_BPM=120 DROP_AT=14.0 DUR=24 DIP=0
9
+ # DIP=1 ducks the track by ~86% for the 0.6 s before the drop, then snaps it back on the drop.
10
+ set -euo pipefail
11
+
12
+ in=$1 bpm=$2 drop=$3 out=$4
13
+ target=${TARGET_BPM:-120} drop_at=${DROP_AT:-14.0} dur=${DUR:-24}
14
+
15
+ # atempo ratio, where in the source to start, and how much source to read.
16
+ read -r a start len < <(python3 - "$bpm" "$drop" "$target" "$drop_at" "$dur" <<'EOF'
17
+ import sys
18
+ bpm, drop, target, drop_at, dur = map(float, sys.argv[1:])
19
+ a = target / bpm
20
+ print(a, drop - drop_at * a, (dur + 0.6) * a)
21
+ EOF
22
+ )
23
+ if python3 -c "import sys; sys.exit(0 if $start < 0 else 1)"; then
24
+ echo "drop too early in the track: start=$start" >&2
25
+ exit 1
26
+ fi
27
+
28
+ env="afade=t=in:st=0:d=0.3,afade=t=out:st=$(python3 -c "print($dur - 1.7)"):d=1.7"
29
+ if [ "${DIP:-0}" = 1 ]; then
30
+ d0=$(python3 -c "print($drop_at - 0.6)")
31
+ env="$env,volume='1-0.86*min(1,max(0,(t-$d0)/0.06))*min(1,max(0,($drop_at-t)/0.02))':eval=frame"
32
+ fi
33
+ ffmpeg -v error -y -ss "$start" -t "$len" -i "$in" \
34
+ -af "atempo=$a,atrim=0:$dur,asetpts=N/SR/TB,$env,loudnorm=I=-14:TP=-1.5:LRA=11" \
35
+ -ar 48000 -ac 2 "$out"
36
+ echo "atempo=$a start=$start"
@@ -0,0 +1,39 @@
1
+ # usage: music-refine.py <audio> <bpm_guess> <drop_guess>
2
+ # Fine tempo+phase over the window the reel uses (drop-16s .. drop+12s), snap drop to the nearest beat,
3
+ # and print the 0.5s RMS envelope of that window (to see the build, the drop, the tail).
4
+ import subprocess, sys
5
+ import numpy as np
6
+
7
+ SR = 22050
8
+ path, bpm0, d0 = sys.argv[1], float(sys.argv[2]), float(sys.argv[3])
9
+ raw = subprocess.run(["ffmpeg", "-v", "error", "-i", path, "-ac", "1", "-ar", str(SR), "-f", "f32le", "-"], capture_output=True, check=True).stdout
10
+ x = np.frombuffer(raw, dtype=np.float32)
11
+ hop, win = 256, 1024
12
+ n = 1 + (len(x) - win) // hop
13
+ fr = np.lib.stride_tricks.as_strided(x, shape=(n, win), strides=(x.strides[0] * hop, x.strides[0]))
14
+ spec = np.log1p(10 * np.abs(np.fft.rfft(fr * np.hanning(win), axis=1)))
15
+ flux = np.concatenate([[0], np.maximum(0, np.diff(spec, axis=0)).sum(axis=1)])
16
+ flux = np.maximum(0, flux - np.convolve(flux, np.ones(32) / 32, mode="same"))
17
+ fps = SR / hop
18
+ a0, a1 = max(0, d0 - 16), min(len(x) / SR, d0 + 12)
19
+ idx = np.arange(len(flux))
20
+ best = None
21
+ for bpm in np.arange(bpm0 - 2, bpm0 + 2, 0.02):
22
+ p = 60 / bpm
23
+ for ph in np.arange(0, p, 0.004):
24
+ bt = np.arange(a0 + ph, a1, p)
25
+ s = np.interp(bt * fps, idx, flux).sum() / len(bt)
26
+ if best is None or s > best[0]:
27
+ best = (s, bpm, a0 + ph)
28
+ _, bpm, ph = best
29
+ p = 60 / bpm
30
+ k = round((d0 - ph) / p)
31
+ drop = ph + k * p
32
+ rms = np.sqrt((fr ** 2).mean(axis=1))
33
+ env = []
34
+ for t in np.arange(drop - 14 * 120 / bpm * bpm / 120, drop + 10.5, 0.5):
35
+ i, j = int(t * fps), int((t + 0.5) * fps)
36
+ env.append(int(rms[max(0, i):max(1, j)].mean() * 100) if j > 0 else 0)
37
+ print(f"{path} bpm={bpm:.2f} drop={drop:.3f}")
38
+ print(" pre :", env[:28])
39
+ print(" post:", env[28:])