@gentbajko/slopify 3.25.8 → 4.0.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 (604) hide show
  1. package/dist/adapters/account-limits.js +30 -0
  2. package/dist/adapters/alignment/cache.js +3 -5
  3. package/dist/adapters/alignment/ctc.js +1 -2
  4. package/dist/adapters/alignment/lock.js +4 -3
  5. package/dist/adapters/alignment/multilingual.js +0 -3
  6. package/dist/adapters/alignment/prefetch.js +2 -1
  7. package/dist/adapters/alignment/worker.js +2 -1
  8. package/dist/adapters/explain.js +0 -3
  9. package/dist/adapters/ffmpeg.js +19 -1
  10. package/dist/adapters/host-cli/index.js +19 -3
  11. package/dist/adapters/http-failure.js +53 -0
  12. package/dist/adapters/image/codex.js +36 -4
  13. package/dist/adapters/image/fal.js +17 -50
  14. package/dist/adapters/image/google.js +31 -55
  15. package/dist/adapters/image/openai.js +20 -47
  16. package/dist/adapters/image/reference.js +36 -0
  17. package/dist/adapters/image/replicate.js +18 -47
  18. package/dist/adapters/interest/search-interest.js +113 -0
  19. package/dist/adapters/key-probes.js +53 -2
  20. package/dist/adapters/llm/antigravity.js +4 -0
  21. package/dist/adapters/llm/assistant-cli.js +388 -0
  22. package/dist/adapters/llm/child-session.js +83 -0
  23. package/dist/adapters/llm/claude-code-models.js +51 -98
  24. package/dist/adapters/llm/claude-code.js +13 -2
  25. package/dist/adapters/llm/codex-limits.js +27 -68
  26. package/dist/adapters/llm/codex-server-models.js +61 -109
  27. package/dist/adapters/llm/codex.js +12 -1
  28. package/dist/adapters/llm/openrouter.js +10 -47
  29. package/dist/adapters/llm/run-cli.js +6 -22
  30. package/dist/adapters/llm/sse-lines.js +5 -1
  31. package/dist/adapters/model-access.js +37 -0
  32. package/dist/adapters/tts/cartesia.js +15 -46
  33. package/dist/adapters/tts/elevenlabs.js +20 -52
  34. package/dist/adapters/tts/gemini.js +19 -40
  35. package/dist/adapters/tts/inworld-async.js +4 -1
  36. package/dist/adapters/tts/inworld.js +3 -0
  37. package/dist/adapters/tts/openai.js +20 -50
  38. package/dist/assets/models-lifecycle.yaml +6 -0
  39. package/dist/assistant-wiring.js +60 -0
  40. package/dist/autopilot-timers.js +59 -0
  41. package/dist/catalog/lifecycle.js +52 -0
  42. package/dist/catalog/propose.js +204 -0
  43. package/dist/catalog/registry.js +3 -1
  44. package/dist/catalog/schema.js +2 -0
  45. package/dist/catalog/store.js +39 -0
  46. package/dist/edge/autostart/native.js +3 -5
  47. package/dist/edge/autostart/prompt.js +2 -1
  48. package/dist/edge/autostart/service.js +3 -4
  49. package/dist/edge/cli-args.js +3 -4
  50. package/dist/edge/cli.js +2 -1
  51. package/dist/edge/docker-install/apply.js +11 -11
  52. package/dist/edge/docker-install/run.js +2 -1
  53. package/dist/edge/docker-install/state.js +4 -3
  54. package/dist/edge/docker-install/tree.js +2 -4
  55. package/dist/edge/docker-install/volume.js +3 -2
  56. package/dist/edge/docker.js +5 -4
  57. package/dist/edge/events/hub.js +5 -5
  58. package/dist/edge/http/actions.js +4 -25
  59. package/dist/edge/http/actor.js +5 -3
  60. package/dist/edge/http/app.js +85 -10
  61. package/dist/edge/http/assistant.js +127 -0
  62. package/dist/edge/http/audio-preview.js +4 -11
  63. package/dist/edge/http/auditions.js +1 -1
  64. package/dist/edge/http/autostart.js +5 -5
  65. package/dist/edge/http/backups.js +1 -3
  66. package/dist/edge/http/channel-memory.js +7 -20
  67. package/dist/edge/http/channels.js +13 -26
  68. package/dist/edge/http/checkpoints.js +33 -17
  69. package/dist/edge/http/design-templates.js +167 -0
  70. package/dist/edge/http/designs.js +178 -0
  71. package/dist/edge/http/diagnostics.js +5 -1
  72. package/dist/edge/http/document-themes.js +2 -11
  73. package/dist/edge/http/draft-files.js +1 -3
  74. package/dist/edge/http/draft-problem.js +4 -9
  75. package/dist/edge/http/drafts.js +3 -3
  76. package/dist/edge/http/entries.js +6 -9
  77. package/dist/edge/http/extension-auth.js +27 -0
  78. package/dist/edge/http/external-reviews.js +12 -11
  79. package/dist/edge/http/files.js +5 -11
  80. package/dist/edge/http/folder-location.js +1 -4
  81. package/dist/edge/http/fonts.js +0 -7
  82. package/dist/edge/http/home.js +38 -13
  83. package/dist/edge/http/host-cli.js +18 -8
  84. package/dist/edge/http/ids.js +8 -0
  85. package/dist/edge/http/keywords.js +35 -0
  86. package/dist/edge/http/model-checks.js +132 -0
  87. package/dist/edge/http/narration-peaks.js +5 -13
  88. package/dist/edge/http/onboarding.js +1 -14
  89. package/dist/edge/http/open-folder.js +5 -10
  90. package/dist/edge/http/outputs.js +4 -12
  91. package/dist/edge/http/patch-notes.js +4 -10
  92. package/dist/edge/http/planning.js +2 -6
  93. package/dist/edge/http/problem.js +2 -4
  94. package/dist/edge/http/project-create.js +1 -0
  95. package/dist/edge/http/project-templates.js +29 -17
  96. package/dist/edge/http/projects.js +2 -12
  97. package/dist/edge/http/prompts.js +7 -14
  98. package/dist/edge/http/pronunciations.js +1 -2
  99. package/dist/edge/http/providers.js +39 -24
  100. package/dist/edge/http/refusal.js +29 -0
  101. package/dist/edge/http/remote-access.js +40 -0
  102. package/dist/edge/http/remote-guard.js +71 -0
  103. package/dist/edge/http/request-guard.js +91 -0
  104. package/dist/edge/http/reviews.js +11 -24
  105. package/dist/edge/http/revision-files.js +5 -10
  106. package/dist/edge/http/revisions.js +10 -15
  107. package/dist/edge/http/run-cost.js +1 -2
  108. package/dist/edge/http/same-page.js +80 -0
  109. package/dist/edge/http/schedules.js +59 -32
  110. package/dist/edge/http/settings.js +22 -13
  111. package/dist/edge/http/social.js +282 -0
  112. package/dist/edge/http/staging.js +2 -4
  113. package/dist/edge/http/storage-files.js +5 -7
  114. package/dist/edge/http/storage.js +23 -23
  115. package/dist/edge/http/studio.js +23 -50
  116. package/dist/edge/http/style-preview.js +1 -4
  117. package/dist/edge/http/subtitles.js +3 -17
  118. package/dist/edge/http/template-studio.js +433 -0
  119. package/dist/edge/http/trash.js +10 -11
  120. package/dist/edge/http/tutorial.js +1 -2
  121. package/dist/edge/http/tutorials.js +5 -11
  122. package/dist/edge/http/update.js +7 -13
  123. package/dist/edge/http/waveform.js +3 -6
  124. package/dist/edge/http/youtube-edits.js +2 -3
  125. package/dist/edge/mcp/assistant.js +279 -0
  126. package/dist/edge/mcp/attribution.js +8 -2
  127. package/dist/edge/mcp/call-route.js +11 -2
  128. package/dist/edge/mcp/excluded-routes.js +25 -0
  129. package/dist/edge/mcp/http.js +11 -2
  130. package/dist/edge/mcp/instructions.js +5 -3
  131. package/dist/edge/mcp/library-tools.js +1 -0
  132. package/dist/edge/mcp/route-tool.js +35 -15
  133. package/dist/edge/mcp/server.js +13 -8
  134. package/dist/edge/mcp/tools-channels.js +2 -0
  135. package/dist/edge/mcp/tools-designs.js +165 -0
  136. package/dist/edge/mcp/tools-library.js +14 -2
  137. package/dist/edge/mcp/tools-planning.js +23 -4
  138. package/dist/edge/mcp/tools-projects.js +28 -1
  139. package/dist/edge/mcp/tools-runs.js +14 -1
  140. package/dist/edge/mcp/tools-schedules.js +28 -4
  141. package/dist/edge/mcp/tools-setup.js +61 -3
  142. package/dist/edge/mcp/tools-social.js +46 -0
  143. package/dist/edge/mcp/tools-storage.js +8 -0
  144. package/dist/edge/mcp/tools-template-studio.js +166 -0
  145. package/dist/edge/mcp/tools.js +6 -0
  146. package/dist/edge/remote-access/cli.js +71 -0
  147. package/dist/edge/remote-access/model.js +32 -0
  148. package/dist/edge/remote-access/rules.js +207 -0
  149. package/dist/edge/remote-access/service.js +203 -0
  150. package/dist/edge/remote-access/store.js +44 -0
  151. package/dist/edge/remote-access/views.js +43 -0
  152. package/dist/extension/slopify-studio-chrome.zip +0 -0
  153. package/dist/extension/slopify-studio-firefox.zip +0 -0
  154. package/dist/host-cli/install.js +2 -2
  155. package/dist/host-cli/open-folder.js +2 -2
  156. package/dist/host-cli/paths.js +2 -4
  157. package/dist/host-cli/server.js +4 -4
  158. package/dist/host-cli/service.js +3 -3
  159. package/dist/host-cli/status.js +7 -7
  160. package/dist/kernel/db/migrations/0063-model-checks.sql +20 -0
  161. package/dist/kernel/db/migrations/0064-template-studio.sql +46 -0
  162. package/dist/kernel/db/migrations/0065-promise-and-assistant-reviews.sql +58 -0
  163. package/dist/kernel/db/migrations/0066-assistant.sql +44 -0
  164. package/dist/kernel/db/migrations/0067-autopilot.sql +6 -0
  165. package/dist/kernel/db/migrations/0068-topic-scores.sql +17 -0
  166. package/dist/kernel/db/migrations/0070-designs.sql +53 -0
  167. package/dist/kernel/db/migrations/0071-design-usage.sql +33 -0
  168. package/dist/kernel/db/migrations/0072-design-templates.sql +33 -0
  169. package/dist/kernel/db/migrations/0073-schedule-design-kind.sql +5 -0
  170. package/dist/kernel/db/migrations/0074-template-bakeoffs.sql +44 -0
  171. package/dist/kernel/db/migrations/0075-social-posts.sql +25 -0
  172. package/dist/kernel/db/migrations/0076-channel-social.sql +5 -0
  173. package/dist/kernel/db/migrations/0077-post-prompts.sql +17 -0
  174. package/dist/kernel/db/tx.js +2 -4
  175. package/dist/kernel/errors.js +27 -1
  176. package/dist/kernel/json-answer.js +15 -0
  177. package/dist/kernel/lock.js +4 -9
  178. package/dist/kernel/paths.js +8 -0
  179. package/dist/kernel/ports/assistant-cli.js +4 -0
  180. package/dist/kernel/ports/host-cli.js +3 -0
  181. package/dist/kernel/ports/interest.js +4 -0
  182. package/dist/kernel/ports/languages.js +0 -3
  183. package/dist/kernel/ports/model.js +2 -0
  184. package/dist/kernel/runner/attempt.js +3 -1
  185. package/dist/kernel/runner/audition.js +3 -1
  186. package/dist/kernel/runner/checkpoint-authority.js +2 -1
  187. package/dist/kernel/runner/index.js +8 -10
  188. package/dist/kernel/runner/model-access.js +40 -0
  189. package/dist/kernel/runner/model-check.js +153 -0
  190. package/dist/kernel/runner/providers.js +15 -10
  191. package/dist/kernel/runner/standalone.js +9 -5
  192. package/dist/main.js +334 -19
  193. package/dist/model-checks.js +166 -0
  194. package/dist/patch-notes/3.25.9.md +5 -0
  195. package/dist/patch-notes/4.0.0.md +51 -0
  196. package/dist/patch-notes/index.json +12 -0
  197. package/dist/slices/admission/heads.js +2 -1
  198. package/dist/slices/admission/repo.js +2 -1
  199. package/dist/slices/admission/rules.js +43 -8
  200. package/dist/slices/admission/schema.js +8 -0
  201. package/dist/slices/admission/stage-writes.js +0 -3
  202. package/dist/slices/admission/start.js +48 -31
  203. package/dist/slices/admission/substitute.js +7 -4
  204. package/dist/slices/agents/attention-posts.js +31 -0
  205. package/dist/slices/agents/attention-templates.js +33 -0
  206. package/dist/slices/agents/attention.js +10 -1
  207. package/dist/slices/agents/connection.js +2 -6
  208. package/dist/slices/agents/model.js +6 -3
  209. package/dist/slices/agents/repo.js +15 -7
  210. package/dist/slices/article/continuation.js +0 -63
  211. package/dist/slices/article/plain.js +3 -2
  212. package/dist/slices/article/segments.js +2 -62
  213. package/dist/slices/assistant/access.js +1 -0
  214. package/dist/slices/assistant/asks.js +80 -0
  215. package/dist/slices/assistant/cost.js +54 -0
  216. package/dist/slices/assistant/gate.js +222 -0
  217. package/dist/slices/assistant/model.js +79 -0
  218. package/dist/slices/assistant/names.js +23 -0
  219. package/dist/slices/assistant/prompt.js +74 -0
  220. package/dist/slices/assistant/repo.js +74 -0
  221. package/dist/slices/assistant/tokens.js +18 -0
  222. package/dist/slices/assistant/turn.js +342 -0
  223. package/dist/slices/autopilot/buffer.js +76 -0
  224. package/dist/slices/autopilot/model.js +14 -0
  225. package/dist/slices/autopilot/notify.js +27 -0
  226. package/dist/slices/autopilot/repo.js +34 -0
  227. package/dist/slices/autopilot/signals.js +194 -0
  228. package/dist/slices/autopilot/warnings.js +85 -0
  229. package/dist/slices/autopilot/weekly.js +100 -0
  230. package/dist/slices/backups/files.js +2 -6
  231. package/dist/slices/backups/repo.js +3 -15
  232. package/dist/slices/backups/service.js +4 -3
  233. package/dist/slices/channels/cast-images.js +2 -1
  234. package/dist/slices/channels/model.js +0 -1
  235. package/dist/slices/channels/rebrand.js +0 -3
  236. package/dist/slices/channels/repo.js +4 -0
  237. package/dist/slices/channels/schema.js +6 -0
  238. package/dist/slices/channels/service.js +10 -0
  239. package/dist/slices/channels/videos.js +2 -14
  240. package/dist/slices/checkpoints/index.js +0 -1
  241. package/dist/slices/control/providers.js +0 -26
  242. package/dist/slices/designs/artwork.js +66 -0
  243. package/dist/slices/designs/budget.js +23 -0
  244. package/dist/slices/designs/calls.js +124 -0
  245. package/dist/slices/designs/check-context.js +38 -0
  246. package/dist/slices/designs/check-text.js +43 -0
  247. package/dist/slices/designs/fields.js +88 -0
  248. package/dist/slices/designs/frames.js +34 -0
  249. package/dist/slices/designs/gate.js +128 -0
  250. package/dist/slices/designs/layout.js +293 -0
  251. package/dist/slices/designs/model.js +35 -0
  252. package/dist/slices/designs/pdf-job.js +23 -0
  253. package/dist/slices/designs/pdf-pages.js +30 -0
  254. package/dist/slices/designs/pdf-runner.js +68 -0
  255. package/dist/slices/designs/pdf-worker.js +24 -0
  256. package/dist/slices/designs/picture-size.js +79 -0
  257. package/dist/slices/designs/pictures.js +55 -0
  258. package/dist/slices/designs/quote.js +24 -0
  259. package/dist/slices/designs/render-args.js +36 -0
  260. package/dist/slices/designs/render-input.js +59 -0
  261. package/dist/slices/designs/render.js +215 -0
  262. package/dist/slices/designs/repo.js +101 -0
  263. package/dist/slices/designs/requests.js +80 -0
  264. package/dist/slices/designs/runner.js +239 -0
  265. package/dist/slices/designs/sample-repo.js +91 -0
  266. package/dist/slices/designs/sample.js +222 -0
  267. package/dist/slices/designs/schema.js +87 -0
  268. package/dist/slices/designs/sections.js +44 -0
  269. package/dist/slices/designs/service.js +233 -0
  270. package/dist/slices/designs/steps-repo.js +102 -0
  271. package/dist/slices/designs/studio-kind.js +39 -0
  272. package/dist/slices/designs/template-checks.js +44 -0
  273. package/dist/slices/designs/template-make.js +118 -0
  274. package/dist/slices/designs/templates.js +218 -0
  275. package/dist/slices/document/fonts.js +2 -1
  276. package/dist/slices/episodes/summarize.js +5 -1
  277. package/dist/slices/estimate/index.js +45 -7
  278. package/dist/slices/fonts/catalog.js +3 -2
  279. package/dist/slices/fonts/discovery.js +3 -2
  280. package/dist/slices/fonts/files.js +2 -6
  281. package/dist/slices/fonts/index.js +0 -1
  282. package/dist/slices/fonts/sfnt.js +45 -10
  283. package/dist/slices/images/appearance.js +6 -15
  284. package/dist/slices/images/scenes.js +100 -27
  285. package/dist/slices/keywords/digest.js +84 -0
  286. package/dist/slices/keywords/facts.js +92 -0
  287. package/dist/slices/keywords/fill.js +80 -0
  288. package/dist/slices/keywords/model.js +75 -0
  289. package/dist/slices/keywords/rules.js +187 -0
  290. package/dist/slices/keywords/schema.js +28 -0
  291. package/dist/slices/keywords/specs.js +260 -0
  292. package/dist/slices/keywords/values.js +74 -0
  293. package/dist/slices/library/lint.js +29 -12
  294. package/dist/slices/library/model.js +2 -0
  295. package/dist/slices/library/photorealistic.js +4 -12
  296. package/dist/slices/library/rename.js +9 -7
  297. package/dist/slices/library/save.js +14 -10
  298. package/dist/slices/library/slots.js +37 -11
  299. package/dist/slices/library/transfer.js +2 -1
  300. package/dist/slices/library/used-by.js +25 -10
  301. package/dist/slices/loudness/loudnorm.js +51 -11
  302. package/dist/slices/model-checks/cost.js +61 -0
  303. package/dist/slices/model-checks/listing.js +124 -0
  304. package/dist/slices/model-checks/model.js +20 -0
  305. package/dist/slices/model-checks/record.js +32 -0
  306. package/dist/slices/model-checks/repo.js +120 -0
  307. package/dist/slices/model-checks/rules.js +112 -0
  308. package/dist/slices/model-checks/run.js +143 -0
  309. package/dist/slices/model-checks/service.js +198 -0
  310. package/dist/slices/model-upkeep/switch.js +70 -25
  311. package/dist/slices/model-upkeep/usage.js +26 -11
  312. package/dist/slices/narration/breaths.js +2 -1
  313. package/dist/slices/narration/clean-pieces.js +2 -1
  314. package/dist/slices/narration/concat.js +2 -1
  315. package/dist/slices/narration/peaks.js +3 -4
  316. package/dist/slices/narration/pronunciation-chunks.js +4 -3
  317. package/dist/slices/narration/pronunciation.js +3 -2
  318. package/dist/slices/narration/steering.js +4 -3
  319. package/dist/slices/notifications/notifier.js +4 -3
  320. package/dist/slices/notifications/rules.js +5 -2
  321. package/dist/slices/notifications/settings.js +4 -12
  322. package/dist/slices/notifications/subject.js +2 -1
  323. package/dist/slices/onboarding/copy.js +2 -1
  324. package/dist/slices/onboarding/first-run.js +3 -2
  325. package/dist/slices/onboarding/full-video.js +3 -2
  326. package/dist/slices/onboarding/install.js +4 -3
  327. package/dist/slices/onboarding/model.js +0 -16
  328. package/dist/slices/onboarding/packs.js +22 -22
  329. package/dist/slices/onboarding/quick-short.js +2 -1
  330. package/dist/slices/onboarding/sample.js +0 -1
  331. package/dist/slices/onboarding/state.js +2 -11
  332. package/dist/slices/onboarding/template.js +2 -1
  333. package/dist/slices/patch-notes/library.js +3 -2
  334. package/dist/slices/play-drafts/convert.js +4 -0
  335. package/dist/slices/play-drafts/review-inputs.js +8 -2
  336. package/dist/slices/play-drafts/review.js +5 -4
  337. package/dist/slices/play-drafts/schema.js +4 -0
  338. package/dist/slices/project-templates/from-project.js +9 -2
  339. package/dist/slices/project-templates/repo.js +6 -1
  340. package/dist/slices/project-templates/setup.js +8 -0
  341. package/dist/slices/project-templates/stand-ins.js +22 -0
  342. package/dist/slices/rebuild/activity.js +2 -0
  343. package/dist/slices/rebuild/admission-repo.js +3 -2
  344. package/dist/slices/rebuild/narration-history.js +4 -5
  345. package/dist/slices/rebuild/narration-retry.js +2 -1
  346. package/dist/slices/rebuild/preview-details.js +27 -9
  347. package/dist/slices/rebuild/preview-plan.js +3 -8
  348. package/dist/slices/rebuild/recipe-build.js +8 -3
  349. package/dist/slices/rebuild/recipe-describe.js +3 -3
  350. package/dist/slices/rebuild/recipe-digest.js +58 -0
  351. package/dist/slices/rebuild/recipe-input-schema.js +10 -0
  352. package/dist/slices/rebuild/recipe-keywords.js +71 -0
  353. package/dist/slices/rebuild/recipe-legacy.js +5 -1
  354. package/dist/slices/rebuild/recipe-model.js +4 -0
  355. package/dist/slices/rebuild/recipe-narration-text.js +2 -1
  356. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  357. package/dist/slices/rebuild/recipe-reference.js +8 -4
  358. package/dist/slices/rebuild/recipe-reviews.js +27 -7
  359. package/dist/slices/rebuild/recipe-save.js +6 -3
  360. package/dist/slices/rebuild/recipe-scenes.js +40 -11
  361. package/dist/slices/rebuild/recipe-shorts.js +8 -8
  362. package/dist/slices/rebuild/recipe-social.js +46 -0
  363. package/dist/slices/rebuild/recipe-text.js +64 -20
  364. package/dist/slices/rebuild/recipe-validation.js +98 -28
  365. package/dist/slices/rebuild/recipe-visual.js +55 -16
  366. package/dist/slices/rebuild/recipe-work.js +15 -3
  367. package/dist/slices/rebuild/recipe-youtube.js +4 -3
  368. package/dist/slices/rebuild/recovery-selection.js +3 -1
  369. package/dist/slices/rebuild/recovery.js +3 -2
  370. package/dist/slices/rebuild/repo.js +4 -3
  371. package/dist/slices/rebuild/review-promise.js +25 -0
  372. package/dist/slices/rebuild/review-redo.js +2 -1
  373. package/dist/slices/rebuild/runtime-admission.js +4 -3
  374. package/dist/slices/rebuild/runtime-agent-review.js +11 -5
  375. package/dist/slices/rebuild/runtime-animate.js +9 -8
  376. package/dist/slices/rebuild/runtime-article.js +3 -2
  377. package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
  378. package/dist/slices/rebuild/runtime-document.js +8 -11
  379. package/dist/slices/rebuild/runtime-export-bed.js +6 -11
  380. package/dist/slices/rebuild/runtime-export-edit.js +6 -15
  381. package/dist/slices/rebuild/runtime-export-inputs.js +7 -7
  382. package/dist/slices/rebuild/runtime-export-short.js +8 -4
  383. package/dist/slices/rebuild/runtime-export.js +34 -12
  384. package/dist/slices/rebuild/runtime-figure-card.js +2 -1
  385. package/dist/slices/rebuild/runtime-image.js +4 -9
  386. package/dist/slices/rebuild/runtime-instructions.js +2 -0
  387. package/dist/slices/rebuild/runtime-keywords.js +59 -0
  388. package/dist/slices/rebuild/runtime-lines.js +2 -11
  389. package/dist/slices/rebuild/runtime-local.js +14 -20
  390. package/dist/slices/rebuild/runtime-narration-publication.js +2 -5
  391. package/dist/slices/rebuild/runtime-narration-reuse.js +9 -13
  392. package/dist/slices/rebuild/runtime-narration-text.js +10 -15
  393. package/dist/slices/rebuild/runtime-pictures.js +2 -4
  394. package/dist/slices/rebuild/runtime-piece-label.js +10 -6
  395. package/dist/slices/rebuild/runtime-plan.js +31 -5
  396. package/dist/slices/rebuild/runtime-provider.js +60 -19
  397. package/dist/slices/rebuild/runtime-publication.js +10 -9
  398. package/dist/slices/rebuild/runtime-review.js +33 -28
  399. package/dist/slices/rebuild/runtime-run.js +4 -1
  400. package/dist/slices/rebuild/runtime-shorts.js +34 -50
  401. package/dist/slices/rebuild/runtime-store.js +3 -8
  402. package/dist/slices/rebuild/runtime-subtitles.js +16 -14
  403. package/dist/slices/rebuild/runtime-voices.js +8 -15
  404. package/dist/slices/rebuild/runtime-youtube.js +11 -29
  405. package/dist/slices/rebuild/transition-repo.js +6 -5
  406. package/dist/slices/rebuild/work-records.js +6 -0
  407. package/dist/slices/reruns/cascade.js +1 -2
  408. package/dist/slices/reruns/index.js +6 -9
  409. package/dist/slices/reviews/external-model.js +3 -0
  410. package/dist/slices/reviews/external-repo.js +2 -1
  411. package/dist/slices/reviews/external-rows.js +2 -3
  412. package/dist/slices/reviews/model.js +26 -8
  413. package/dist/slices/reviews/repo.js +2 -1
  414. package/dist/slices/reviews/rules.js +1 -0
  415. package/dist/slices/reviews/view.js +5 -2
  416. package/dist/slices/revisions/downloads.js +4 -5
  417. package/dist/slices/revisions/mutation-assets.js +7 -4
  418. package/dist/slices/revisions/mutation-cues.js +2 -5
  419. package/dist/slices/revisions/mutations.js +0 -1
  420. package/dist/slices/revisions/repo.js +3 -1
  421. package/dist/slices/revisions/schema.js +1 -0
  422. package/dist/slices/revisions/view.js +20 -0
  423. package/dist/slices/run-cost/limits.js +5 -2
  424. package/dist/slices/run-cost/meter.js +12 -5
  425. package/dist/slices/run-cost/panel.js +8 -74
  426. package/dist/slices/run-cost/plan-readings.js +127 -0
  427. package/dist/slices/run-cost/plan-use.js +169 -0
  428. package/dist/slices/run-cost/pricing.js +3 -3
  429. package/dist/slices/run-cost/week.js +32 -4
  430. package/dist/slices/schedules/agenda.js +11 -2
  431. package/dist/slices/schedules/by-date.js +42 -0
  432. package/dist/slices/schedules/calendar.js +5 -0
  433. package/dist/slices/schedules/design-agenda.js +36 -0
  434. package/dist/slices/schedules/design-check.js +41 -0
  435. package/dist/slices/schedules/design-run.js +126 -0
  436. package/dist/slices/schedules/next-run.js +62 -0
  437. package/dist/slices/schedules/prepare.js +18 -2
  438. package/dist/slices/schedules/repo.js +24 -16
  439. package/dist/slices/schedules/rescore.js +35 -0
  440. package/dist/slices/schedules/scheduler.js +13 -3
  441. package/dist/slices/schedules/schema.js +54 -3
  442. package/dist/slices/schedules/score.js +107 -0
  443. package/dist/slices/schedules/service.js +57 -24
  444. package/dist/slices/schedules/topic-list.js +15 -0
  445. package/dist/slices/schedules/topic-scores.js +104 -0
  446. package/dist/slices/schedules/topics.js +48 -36
  447. package/dist/slices/settings/cli-paths.js +4 -6
  448. package/dist/slices/settings/first-run.js +2 -11
  449. package/dist/slices/settings/model-reach.js +3 -17
  450. package/dist/slices/settings/model.js +10 -0
  451. package/dist/slices/settings/playback.js +33 -22
  452. package/dist/slices/settings/probe-read.js +16 -0
  453. package/dist/slices/settings/repo.js +33 -0
  454. package/dist/slices/settings/tutorial.js +2 -2
  455. package/dist/slices/settings/voices.js +4 -12
  456. package/dist/slices/shorts/pick.js +2 -15
  457. package/dist/slices/shorts/prompts.js +2 -13
  458. package/dist/slices/shorts/render.js +3 -0
  459. package/dist/slices/social/channel.js +44 -0
  460. package/dist/slices/social/defaults.js +17 -0
  461. package/dist/slices/social/due.js +36 -0
  462. package/dist/slices/social/extension.js +18 -0
  463. package/dist/slices/social/fit.js +115 -0
  464. package/dist/slices/social/limits.js +186 -0
  465. package/dist/slices/social/links.js +21 -0
  466. package/dist/slices/social/media.js +131 -0
  467. package/dist/slices/social/model.js +75 -0
  468. package/dist/slices/social/notice.js +19 -0
  469. package/dist/slices/social/pack.js +176 -0
  470. package/dist/slices/social/post-prompt.js +55 -0
  471. package/dist/slices/social/queue.js +31 -0
  472. package/dist/slices/social/repo.js +133 -0
  473. package/dist/slices/social/reshape.js +97 -0
  474. package/dist/slices/social/rules.js +5 -0
  475. package/dist/slices/social/schema.js +70 -0
  476. package/dist/slices/social/serve.js +51 -0
  477. package/dist/slices/social/service.js +128 -0
  478. package/dist/slices/social/settings.js +21 -0
  479. package/dist/slices/social/sources.js +114 -0
  480. package/dist/slices/social/state.js +110 -0
  481. package/dist/slices/social/text.js +149 -0
  482. package/dist/slices/storage/arrange.js +3 -1
  483. package/dist/slices/storage/assets.js +8 -10
  484. package/dist/slices/storage/backup-designs.js +74 -0
  485. package/dist/slices/storage/backup-export.js +8 -3
  486. package/dist/slices/storage/backup-format.js +10 -0
  487. package/dist/slices/storage/backup-import.js +20 -2
  488. package/dist/slices/storage/delete-project.js +6 -5
  489. package/dist/slices/storage/files-location.js +6 -8
  490. package/dist/slices/storage/layout.js +13 -2
  491. package/dist/slices/storage/model.js +3 -0
  492. package/dist/slices/storage/portable.js +16 -11
  493. package/dist/slices/storage/schema.js +2 -0
  494. package/dist/slices/storage/staging-refs.js +2 -1
  495. package/dist/slices/storage/staging.js +6 -9
  496. package/dist/slices/storage/trim.js +10 -3
  497. package/dist/slices/studio/calendar.js +4 -1
  498. package/dist/slices/studio/pick.js +2 -11
  499. package/dist/slices/studio/plan.js +6 -15
  500. package/dist/slices/studio/queue.js +3 -14
  501. package/dist/slices/studio/releases.js +26 -0
  502. package/dist/slices/studio/settings.js +14 -51
  503. package/dist/slices/studio/videos.js +12 -0
  504. package/dist/slices/style-preview/render.js +34 -13
  505. package/dist/slices/style-preview/schema.js +13 -0
  506. package/dist/slices/style-preview/service.js +2 -1
  507. package/dist/slices/style-preview/settings.js +33 -5
  508. package/dist/slices/telemetry/collector-client.js +2 -4
  509. package/dist/slices/telemetry/flush.js +2 -4
  510. package/dist/slices/telemetry/model.js +0 -7
  511. package/dist/slices/telemetry/record.js +10 -3
  512. package/dist/slices/template-studio/anatomy-view.js +118 -0
  513. package/dist/slices/template-studio/anatomy.js +73 -0
  514. package/dist/slices/template-studio/bakeoff-make.js +225 -0
  515. package/dist/slices/template-studio/bakeoff-repo.js +83 -0
  516. package/dist/slices/template-studio/bakeoff.js +333 -0
  517. package/dist/slices/template-studio/budget.js +103 -0
  518. package/dist/slices/template-studio/checks.js +273 -0
  519. package/dist/slices/template-studio/document-sample.js +56 -0
  520. package/dist/slices/template-studio/edit.js +15 -0
  521. package/dist/slices/template-studio/for-all.js +161 -0
  522. package/dist/slices/template-studio/free-video.js +31 -0
  523. package/dist/slices/template-studio/free.js +176 -0
  524. package/dist/slices/template-studio/gate.js +40 -0
  525. package/dist/slices/template-studio/hidden.js +20 -0
  526. package/dist/slices/template-studio/jobs.js +79 -0
  527. package/dist/slices/template-studio/loudness.js +111 -0
  528. package/dist/slices/template-studio/make.js +40 -0
  529. package/dist/slices/template-studio/model.js +54 -0
  530. package/dist/slices/template-studio/pack-import.js +253 -0
  531. package/dist/slices/template-studio/pack-samples.js +60 -0
  532. package/dist/slices/template-studio/pack.js +205 -0
  533. package/dist/slices/template-studio/paid-view.js +66 -0
  534. package/dist/slices/template-studio/paid.js +348 -0
  535. package/dist/slices/template-studio/quote.js +148 -0
  536. package/dist/slices/template-studio/ready.js +104 -0
  537. package/dist/slices/template-studio/repo.js +134 -0
  538. package/dist/slices/template-studio/reviews-path.js +58 -0
  539. package/dist/slices/template-studio/runner.js +84 -0
  540. package/dist/slices/template-studio/save.js +236 -0
  541. package/dist/slices/template-studio/say-it-as.js +248 -0
  542. package/dist/slices/template-studio/schedule-gate.js +34 -0
  543. package/dist/slices/template-studio/schedule-price.js +30 -0
  544. package/dist/slices/template-studio/schedule-sample.js +87 -0
  545. package/dist/slices/template-studio/schema.js +218 -0
  546. package/dist/slices/template-studio/scope.js +124 -0
  547. package/dist/slices/template-studio/sections.js +176 -0
  548. package/dist/slices/template-studio/service.js +173 -0
  549. package/dist/slices/template-studio/status.js +174 -0
  550. package/dist/slices/template-studio/topic.js +223 -0
  551. package/dist/slices/trash/designs.js +76 -0
  552. package/dist/slices/trash/model.js +13 -4
  553. package/dist/slices/trash/service.js +52 -2
  554. package/dist/slices/tutorials/library.js +3 -2
  555. package/dist/slices/video/ass-text.js +17 -0
  556. package/dist/slices/video/audio-export.js +2 -64
  557. package/dist/slices/video/encoding.js +160 -0
  558. package/dist/slices/video/ffmpeg.js +23 -21
  559. package/dist/slices/video/figure-card.js +1 -15
  560. package/dist/slices/video/slideshow.js +43 -18
  561. package/dist/slices/voices/palette.js +0 -3
  562. package/dist/slices/youtube/answer.js +2 -15
  563. package/dist/slices/youtube/edits-repo.js +2 -11
  564. package/dist/tutorials/Agents-MCP.md +2 -1
  565. package/dist/tutorials/Assistant.md +42 -0
  566. package/dist/tutorials/Channels.md +2 -0
  567. package/dist/tutorials/Home-and-Projects.md +19 -0
  568. package/dist/tutorials/Install.md +1 -0
  569. package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
  570. package/dist/tutorials/Models.md +33 -1
  571. package/dist/tutorials/Notifications.md +6 -0
  572. package/dist/tutorials/Play-Images.md +4 -0
  573. package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
  574. package/dist/tutorials/Prompts.md +10 -6
  575. package/dist/tutorials/Remote-Access.md +63 -0
  576. package/dist/tutorials/Reviews-and-Checkpoints.md +13 -2
  577. package/dist/tutorials/Schedules.md +21 -1
  578. package/dist/tutorials/Settings-Reference.md +50 -8
  579. package/dist/tutorials/Template-Studio.md +125 -0
  580. package/dist/tutorials/Templates.md +3 -0
  581. package/dist/tutorials/_Sidebar.md +5 -0
  582. package/dist/updater/work-in-progress.js +42 -12
  583. package/dist/web/assets/index-CkAI_2E7.css +1 -0
  584. package/dist/web/assets/index-DcD0cdfo.js +334 -0
  585. package/dist/web/assets/{pdf-T7MCwjIv.js → pdf-TODRwF31.js} +1 -1
  586. package/dist/web/index.html +2 -2
  587. package/package.json +5 -1
  588. package/dist/slices/article/provided-entries.js +0 -66
  589. package/dist/slices/article/run.js +0 -145
  590. package/dist/slices/images/run.js +0 -234
  591. package/dist/slices/narration/legacy-plan.js +0 -58
  592. package/dist/slices/narration/run.js +0 -270
  593. package/dist/slices/rebuild/runtime-actions.js +0 -90
  594. package/dist/slices/research/run.js +0 -224
  595. package/dist/slices/subtitles/prepare.js +0 -194
  596. package/dist/slices/subtitles/transcript.js +0 -34
  597. package/dist/slices/thumbnail/run.js +0 -219
  598. package/dist/slices/video/audio-inputs.js +0 -33
  599. package/dist/slices/video/reuse-audio.js +0 -62
  600. package/dist/slices/video/run.js +0 -78
  601. package/dist/slices/video/write-export.js +0 -158
  602. package/dist/slices/video/write-subtitles.js +0 -59
  603. package/dist/web/assets/index-BadsoORk.css +0 -1
  604. package/dist/web/assets/index-D8LqNsCl.js +0 -326
@@ -1,8 +1,10 @@
1
1
  import { copyFileSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
2
2
  import { extname, join, relative, sep } from "node:path";
3
+ import { internalError } from "../../kernel/errors.js";
3
4
  import { usesShortMode } from "../admission/short-mode.js";
4
5
  import { masterFile, masterReport } from "../loudness/loudnorm.js";
5
6
  import { masterGoal } from "../loudness/model.js";
7
+ import { isReadyOutput, readyOutput } from "../revisions/view.js";
6
8
  import { allocateAsset, discardPreparedAssets, sealAsset } from "../storage/assets.js";
7
9
  import { outputPath, projectDir, renderCacheDir } from "../storage/layout.js";
8
10
  import { audioExportArgs } from "../video/audio-export.js";
@@ -18,10 +20,21 @@ import { exportSnapshot, retainedOutput, revisionAudio, } from "./runtime-export
18
20
  import { executeShortExport } from "./runtime-export-short.js";
19
21
  import { lineLevelledAudio } from "./runtime-lines.js";
20
22
  import { preparedResult, preparedText, publishResult } from "./runtime-publication.js";
23
+ import { pieceAlreadyDone } from "./work-records.js";
24
+ // The machine settings for one render, and the notices it gathers for the project page.
25
+ export async function exportMachine(deps, projectId) {
26
+ const chosen = await deps.renderMachine?.(projectId);
27
+ const notices = chosen?.notice === undefined ? [] : [chosen.notice];
28
+ return {
29
+ machine: chosen?.machine,
30
+ notices,
31
+ notice: (sentence) => {
32
+ notices.push(sentence);
33
+ },
34
+ };
35
+ }
21
36
  export async function executeExportRecipe(deps, context, piece) {
22
- if (deps.db
23
- .prepare("SELECT state FROM revision_work_pieces WHERE id=? AND work_id=?")
24
- .get(piece.id, context.work.workId)?.state === "done")
37
+ if (pieceAlreadyDone(deps.db, context.work.workId, piece.id))
25
38
  return "done";
26
39
  if (!context.maySubmit(piece.id))
27
40
  return "held";
@@ -31,7 +44,7 @@ export async function executeExportRecipe(deps, context, piece) {
31
44
  const audio = await revisionAudio(deps, context, view, { levelled: true });
32
45
  const wav = piece.key === "export:wav";
33
46
  if (!wav && piece.key !== "export:video")
34
- throw new Error("Slopify hit an internal error (unknown kind of export). Try again; if it happens again, use Download diagnostics in Settings and report it.");
47
+ throw new Error(internalError("unknown kind of export"));
35
48
  if (wav && audio.length === 0)
36
49
  throw new Error("The audio export has nothing to export because narration is turned off for this project. Turn narration on in Edit project, then Try again.");
37
50
  if (!wav && usesShortMode(view.revision.config))
@@ -49,7 +62,7 @@ export async function executeExportRecipe(deps, context, piece) {
49
62
  const sound = linesDirectory === undefined
50
63
  ? audio
51
64
  : await lineLevelledAudio(deps, context, view, audio, linesDirectory);
52
- const captions = snapshot.view.outputs.filter((row) => row.selected && row.available && row.state === "ready" && row.workKey === "subtitles:files");
65
+ const captions = snapshot.view.outputs.filter((row) => isReadyOutput(row) && row.workKey === "subtitles:files");
53
66
  const burn = !wav && view.revision.config.subtitles?.mode === "burn-in";
54
67
  if (burn)
55
68
  directory = captionDirectory(deps, context, snapshot);
@@ -119,6 +132,8 @@ export async function executeExportRecipe(deps, context, piece) {
119
132
  };
120
133
  if (!context.maySubmit(piece.id))
121
134
  return "held";
135
+ const machine = await exportMachine(deps, context.work.projectId);
136
+ const priority = machine.machine?.priority;
122
137
  const onProgress = (elapsedMs) => context.emit({
123
138
  type: "stage.progress",
124
139
  projectId: context.work.projectId,
@@ -139,10 +154,11 @@ export async function executeExportRecipe(deps, context, piece) {
139
154
  args: audioExportArgs(sound, mixed),
140
155
  signal: context.signal,
141
156
  log: deps.log,
157
+ priority,
142
158
  onProgress,
143
159
  });
144
160
  if (goal !== undefined) {
145
- const run = { bin: deps.ffmpeg, log: deps.log, signal: context.signal };
161
+ const run = { bin: deps.ffmpeg, log: deps.log, signal: context.signal, priority };
146
162
  await masterFile(run, mixed, `${pending.absolutePath}.master.wav`, goal, {
147
163
  sampleRate: 48000,
148
164
  channels: 2,
@@ -167,6 +183,7 @@ export async function executeExportRecipe(deps, context, piece) {
167
183
  ],
168
184
  signal: context.signal,
169
185
  log: deps.log,
186
+ priority,
170
187
  onProgress: () => { },
171
188
  });
172
189
  master = await masterReport(run, pending.absolutePath, goal);
@@ -193,20 +210,21 @@ export async function executeExportRecipe(deps, context, piece) {
193
210
  log: deps.log,
194
211
  onProgress,
195
212
  ...(goal === undefined ? {} : { master: goal }),
213
+ machine: machine.machine,
214
+ notice: machine.notice,
196
215
  });
197
216
  context.signal.throwIfAborted();
198
217
  const asset = sealAsset(deps, pending);
199
218
  prepared.push(preparedResult(deps, context, piece, wav ? "audio_export" : "video", asset, Math.round(totalSeconds * 1000), {
200
219
  subtitlesMode: mode,
201
- ...(edited === undefined || edited.warnings.length === 0
202
- ? {}
203
- : { warnings: edited.warnings }),
220
+ // An audio export encodes no picture, so the encoder's notice is not its to show.
221
+ ...warningsOf([...(edited?.warnings ?? []), ...(wav ? [] : machine.notices)]),
204
222
  ...(master === undefined ? {} : { master }),
205
223
  }));
206
224
  prepared.push(preparedText(deps, context, piece, "render_params", "render.json", JSON.stringify(record)));
207
225
  prepared.push(...captions.map((row) => retainedOutput(deps, view, row)));
208
226
  await publishResult(deps, context, piece, prepared, { totalSeconds }, asset);
209
- deps.count?.("stage.completed", { stage: "video" });
227
+ deps.count?.("stage.completed", { stage: "video" }, context.work.projectId);
210
228
  return "done";
211
229
  }
212
230
  finally {
@@ -220,14 +238,14 @@ export async function executeExportRecipe(deps, context, piece) {
220
238
  // The project's images in slideshow order, as files.
221
239
  export function slideshowImages(deps, context, view) {
222
240
  return view.revision.content.imageOrder.map((key) => {
223
- const row = view.outputs.find((one) => one.workKey === `image:${key}` && one.selected && one.available && one.state === "ready");
241
+ const row = readyOutput(view, { workKey: `image:${key}` });
224
242
  if (row === undefined)
225
243
  throw new Error("The video export can't find one of the slideshow images. Use More → make it again in its section on Images (or regenerate that image in Edit project → Images), then Try again.");
226
244
  return outputPath(deps.paths, context.work.projectId, row.output.path);
227
245
  });
228
246
  }
229
247
  function captionDirectory(deps, context, snapshot) {
230
- const find = (role) => snapshot.view.outputs.find((row) => row.selected && row.available && row.state === "ready" && row.output.role === role);
248
+ const find = (role) => readyOutput(snapshot.view, { role });
231
249
  const ass = find("subtitle_ass");
232
250
  const font = find("subtitle_font");
233
251
  if (ass === undefined || font === undefined)
@@ -244,3 +262,7 @@ function captionDirectory(deps, context, snapshot) {
244
262
  throw error;
245
263
  }
246
264
  }
265
+ // What the video could not do as asked, for the project page; nothing when it did it all.
266
+ export function warningsOf(warnings) {
267
+ return warnings.length === 0 ? {} : { warnings };
268
+ }
@@ -1,6 +1,7 @@
1
1
  import { copyFileSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { z } from "zod";
4
+ import { internalError } from "../../kernel/errors.js";
4
5
  import { captionFont, captionFontDeps } from "../fonts/coverage.js";
5
6
  import { describedKinds } from "../narration/blocks.js";
6
7
  import { allocateAsset, discardPreparedAssets, sealAsset } from "../storage/assets.js";
@@ -25,7 +26,7 @@ const values = z.object({
25
26
  const frames = { "16:9": [1920, 1080], "9:16": [1080, 1920], "1:1": [1080, 1080] };
26
27
  export async function executeFigureCard(deps, context, piece) {
27
28
  if (piece.input.kind !== "local")
28
- throw new Error("Slopify hit an internal error (a figure card step was set up wrongly). Try again; if it happens again, use Download diagnostics in Settings and report it.");
29
+ throw new Error(internalError("a figure card step was set up wrongly"));
29
30
  const card = values.parse(piece.input.values);
30
31
  const view = executionView(deps, context.work.projectId, context.work.revisionId);
31
32
  // The article's own picture for a figure, when the project has it as an upload.
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
- import { z } from "zod";
3
2
  import { imageBlob } from "../channels/repo.js";
3
+ import { assetPathOrUndefined } from "../revisions/mutation-assets.js";
4
4
  import { outputPath } from "../storage/layout.js";
5
5
  // The provider call an image step makes: its request, the effort when one is chosen and, while
6
6
  // the project's Establishing image is on, that image's bytes as the visual reference.
@@ -25,17 +25,12 @@ function referenceImage(deps, projectId, input) {
25
25
  const missing = "The establishing image this image is drawn from is missing. Use Regenerate on the establishing image in the Images section (or upload it again in Edit project → Images), then use Try again.";
26
26
  if (input.reference.assetId === null)
27
27
  throw new Error(missing);
28
- const row = z
29
- .object({ path: z.string() })
30
- .optional()
31
- .parse(deps.db
32
- .prepare("SELECT path FROM project_assets WHERE id=? AND project_id=?")
33
- .get(input.reference.assetId, projectId));
34
- if (row === undefined)
28
+ const saved = assetPathOrUndefined(deps, projectId, input.reference.assetId);
29
+ if (saved === undefined)
35
30
  throw new Error(missing);
36
31
  let bytes;
37
32
  try {
38
- bytes = new Uint8Array(readFileSync(outputPath(deps.paths, projectId, row.path)));
33
+ bytes = new Uint8Array(readFileSync(outputPath(deps.paths, projectId, saved)));
39
34
  }
40
35
  catch {
41
36
  throw new Error(missing);
@@ -49,5 +49,7 @@ function label(key) {
49
49
  return "Intro";
50
50
  if (key === "entry:outro:text")
51
51
  return "Outro";
52
+ if (key === "article:digest")
53
+ return "Keyword digest";
52
54
  return "Thumbnail instructions";
53
55
  }
@@ -0,0 +1,59 @@
1
+ import { errorText } from "../../kernel/errors.js";
2
+ import { fillLateKeywords, mentionsLateKeyword } from "../keywords/fill.js";
3
+ import { durationText, releaseDateText } from "../keywords/values.js";
4
+ import { releasesOf } from "../studio/releases.js";
5
+ import { revisionAudio } from "./runtime-export-inputs.js";
6
+ import { executionView } from "./runtime-plan.js";
7
+ // The release date and the video's length are filled into a request only as it is sent: the
8
+ // planned request keeps `{{Slopify_Release_Date}}` and `{{Slopify_Duration}}`, so moving the
9
+ // date later, or a new length, never makes anything outdated. Every other call goes through as
10
+ // it was.
11
+ export function lateKeywordProviders(providers, values) {
12
+ let once;
13
+ const late = () => {
14
+ once ??= values();
15
+ return once;
16
+ };
17
+ const wrap = (inner) => ({
18
+ ...inner,
19
+ llm: async (call, onEvent) => {
20
+ if (!call.messages.some((message) => mentionsLateKeyword(message.content)))
21
+ return inner.llm(call, onEvent);
22
+ const filled = await late();
23
+ return inner.llm({
24
+ ...call,
25
+ messages: call.messages.map((message) => ({
26
+ ...message,
27
+ content: fillLateKeywords(message.content, filled),
28
+ })),
29
+ }, onEvent);
30
+ },
31
+ image: async (call) => mentionsLateKeyword(call.prompt)
32
+ ? inner.image({ ...call, prompt: fillLateKeywords(call.prompt, await late()) })
33
+ : inner.image(call),
34
+ forPiece: (pieceId) => wrap(inner.forPiece(pieceId)),
35
+ });
36
+ return wrap(providers);
37
+ }
38
+ // The long video's release time from the posting plan, and the narration's length once it is
39
+ // there. A length that can't be read yet (an article review runs before the narration) is
40
+ // blank, so its line is left out, and the reason is logged.
41
+ export async function lateValuesOf(deps, context) {
42
+ const releaseDate = releaseDateText(releasesOf(deps.db, context.work.projectId).find((release) => release.short === 0)?.at);
43
+ const view = executionView(deps, context.work.projectId, context.work.revisionId);
44
+ let duration;
45
+ if (view !== undefined && view.revision.config.sources.audio !== "off")
46
+ try {
47
+ const audio = await revisionAudio(deps, context, view);
48
+ const seconds = audio.reduce((sum, segment) => sum + segment.seconds, 0);
49
+ duration = seconds > 0 ? durationText(seconds) : undefined;
50
+ }
51
+ catch (error) {
52
+ deps.log.write("warn", "keywords.duration", {
53
+ projectId: context.work.projectId,
54
+ stage: context.work.kind,
55
+ detail: errorText(error),
56
+ });
57
+ }
58
+ return { releaseDate, duration };
59
+ }
@@ -1,9 +1,7 @@
1
- import { readFileSync } from "node:fs";
2
1
  import { join } from "node:path";
3
2
  import { levelLines } from "../loudness/line-level.js";
4
- import { outputPath } from "../storage/layout.js";
5
3
  import { linesLevelled } from "./recipe-lines.js";
6
- import { wordsSchema } from "./runtime-subtitles.js";
4
+ import { readTimingWords } from "./runtime-subtitles.js";
7
5
  // The sound a multi-voice run plays, with its body narration levelled line by line
8
6
  // (`loudness/line-level.ts`) into `directory`, for the video, the audio export and the audio
9
7
  // files alike. The words are timed on the timeline these segments lay out, so the body starts
@@ -11,14 +9,7 @@ import { wordsSchema } from "./runtime-subtitles.js";
11
9
  export async function lineLevelledAudio(deps, context, view, audio, directory) {
12
10
  if (!linesLevelled(view.revision.config))
13
11
  return audio;
14
- const timing = view.outputs.find((row) => row.selected &&
15
- row.available &&
16
- row.state === "ready" &&
17
- row.workKey === "subtitles:timing" &&
18
- row.output.role === "subtitle_words");
19
- if (timing === undefined)
20
- throw new Error("The narration's word timing, which evens out the speakers' volume, is missing. Use More → Render the video again in the Video section, then Try again.");
21
- const { words } = wordsSchema.parse(JSON.parse(readFileSync(outputPath(deps.paths, context.work.projectId, timing.output.path), "utf8")));
12
+ const { words } = readTimingWords(deps, context, view, "evens out the speakers' volume");
22
13
  const run = { bin: deps.ffmpeg, log: deps.log, signal: context.signal };
23
14
  const levelled = [];
24
15
  let offset = 0;
@@ -1,6 +1,7 @@
1
1
  import { rmSync, statSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { z } from "zod";
4
+ import { internalError } from "../../kernel/errors.js";
4
5
  import { referenceKey, thumbnailVariant } from "../admission/model.js";
5
6
  import { plainText } from "../article/plain.js";
6
7
  import { splitEndMatter } from "../article/split.js";
@@ -9,6 +10,8 @@ import { levelSentences } from "../loudness/sentence-level.js";
9
10
  import { cleanPieces } from "../narration/clean-pieces.js";
10
11
  import { joinNarration } from "../narration/concat.js";
11
12
  import { applyPauses, planPieces } from "../narration/pauses.js";
13
+ import { assetPath } from "../revisions/mutation-assets.js";
14
+ import { isDonePiece } from "../revisions/view.js";
12
15
  import { allocateAsset, discardPreparedAssets, sealAsset } from "../storage/assets.js";
13
16
  import { outputPath } from "../storage/layout.js";
14
17
  import { probeDurationMs, runFfmpeg } from "../video/ffmpeg.js";
@@ -17,12 +20,11 @@ import { breathsAsked, deEssAsked } from "./recipe-breaths.js";
17
20
  import { levelOperation, sentencesAsked } from "./recipe-loudness.js";
18
21
  import { executeFigureCard } from "./runtime-figure-card.js";
19
22
  import { publishNarrationText } from "./runtime-narration-text.js";
20
- import { executionPlan, executionView, savedCatalogue } from "./runtime-plan.js";
23
+ import { executionPlan, executionView, workCatalogue } from "./runtime-plan.js";
21
24
  import { preparedResult, preparedText, preparedTexts, publishResult, } from "./runtime-publication.js";
25
+ import { pieceAlreadyDone } from "./work-records.js";
22
26
  export async function executeLocalRecipe(deps, context, piece) {
23
- if (deps.db
24
- .prepare("SELECT state FROM revision_work_pieces WHERE id=? AND work_id=?")
25
- .get(piece.id, context.work.workId)?.state === "done")
27
+ if (pieceAlreadyDone(deps.db, context.work.workId, piece.id))
26
28
  return "done";
27
29
  if (!context.maySubmit(piece.id))
28
30
  return "held";
@@ -62,7 +64,7 @@ export async function executeLocalRecipe(deps, context, piece) {
62
64
  return "done";
63
65
  }
64
66
  if (input.kind !== "local")
65
- throw new Error("Slopify hit an internal error (a local step was set up wrongly). Try again; if it happens again, use Download diagnostics in Settings and report it.");
67
+ throw new Error(internalError("a local step was set up wrongly"));
66
68
  if (input.operation === "figure-card-v1")
67
69
  return executeFigureCard(deps, context, piece);
68
70
  if (input.operation === "narration-files-v1") {
@@ -98,31 +100,23 @@ export async function executeLocalRecipe(deps, context, piece) {
98
100
  });
99
101
  return "done";
100
102
  }
101
- throw new Error(`Slopify hit an internal error (no handler for the step "${input.operation}"). Try again; if it happens again, use Download diagnostics in Settings and report it.`);
103
+ throw new Error(internalError(`no handler for the step "${input.operation}"`));
102
104
  }
103
105
  async function concatenate(deps, context, piece) {
104
106
  const view = executionView(deps, context.work.projectId, context.work.revisionId);
105
107
  if (view === undefined)
106
- throw new Error("Slopify hit an internal error (the project version being built is missing). Try again; if it happens again, use Download diagnostics in Settings and report it.");
107
- const row = deps.db
108
- .prepare("SELECT recipe_context FROM revision_work WHERE id=?")
109
- .get(context.work.workId);
110
- const plan = executionPlan(deps, view, savedCatalogue(row?.recipe_context));
108
+ throw new Error(internalError("the project version being built is missing"));
109
+ const plan = executionPlan(deps, view, workCatalogue(deps.db, context.work.workId));
111
110
  const recipe = plan.recipes.find((candidate) => candidate.key === piece.key && candidate.fingerprint === piece.fingerprint);
112
111
  if (recipe === undefined)
113
- throw new Error("Slopify hit an internal error (the narration chunks to join no longer match the project's saved inputs). Try again; if it happens again, use Download diagnostics in Settings and report it.");
112
+ throw new Error(internalError("the narration chunks to join no longer match the project's saved inputs"));
114
113
  const files = recipe.dependsOn.map((key) => {
115
114
  const input = view.pieces.find((candidate) => candidate.key === key &&
116
- candidate.selected &&
117
- candidate.available &&
118
- candidate.piece.state === "done" &&
115
+ isDonePiece(candidate) &&
119
116
  candidate.fingerprint === plan.recipes.find((value) => value.key === key)?.fingerprint);
120
117
  if (input?.assetId === null || input === undefined)
121
118
  throw new Error("One narration chunk has no saved audio, so the narration can't be joined together. Regenerate the missing chunk in Edit project → Narration, then Try again.");
122
- const asset = deps.db
123
- .prepare("SELECT path FROM project_assets WHERE id=? AND project_id=?")
124
- .get(input.assetId, context.work.projectId);
125
- return outputPath(deps.paths, context.work.projectId, z.string().parse(asset?.path));
119
+ return outputPath(deps.paths, context.work.projectId, assetPath(deps, context.work.projectId, input.assetId));
126
120
  });
127
121
  // Level the volume repeats the join its recipe names, from the same pieces, each levelled
128
122
  // first (`recipe-loudness.ts`).
@@ -295,7 +289,7 @@ export async function joinTurns(run, files, values, output) {
295
289
  .rest(z.unknown())
296
290
  .parse(values)[1];
297
291
  if (layout.length !== files.length || files.length === 0)
298
- throw new Error("Slopify hit an internal error (the speaker turns to join don't match their audio). Try again; if it happens again, use Download diagnostics in Settings and report it.");
292
+ throw new Error(internalError("the speaker turns to join don't match their audio"));
299
293
  await runFfmpeg({
300
294
  bin: run.bin,
301
295
  args: turnJoinArgs(files.map((path, index) => ({
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { projectSelected } from "../revisions/projection.js";
3
3
  import { getRevisionView } from "../revisions/view.js";
4
4
  import { bindNarrationReuse, narrationOrdinal } from "./runtime-narration-reuse.js";
5
- import { executionPlan, savedCatalogue } from "./runtime-plan.js";
5
+ import { executionPlan, workCatalogue } from "./runtime-plan.js";
6
6
  export function rebindPublishedNarration(deps, work, piece) {
7
7
  if (piece.input.kind !== "tts")
8
8
  return;
@@ -11,10 +11,7 @@ export function rebindPublishedNarration(deps, work, piece) {
11
11
  .all(work.projectId, piece.id, piece.key, work.revisionId);
12
12
  if (targets.length === 0)
13
13
  return;
14
- const row = deps.db
15
- .prepare("SELECT recipe_context FROM revision_work WHERE id=?")
16
- .get(work.workId);
17
- const catalogue = savedCatalogue(row?.recipe_context);
14
+ const catalogue = workCatalogue(deps.db, work.workId);
18
15
  for (const target of targets) {
19
16
  const view = getRevisionView(deps, work.projectId, z.string().parse(target.revision_id));
20
17
  if (view === undefined)
@@ -1,5 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { insertManifestOutput, insertManifestPiece, selectOutputRecord, selectPieceRecord, } from "../revisions/manifest-repo.js";
3
+ import { assetPathOrUndefined } from "../revisions/mutation-assets.js";
4
+ import { isDonePiece } from "../revisions/view.js";
3
5
  import { retainedNarrationPieces } from "./narration-history.js";
4
6
  import { matchingNarrationPiece } from "./narration-reuse.js";
5
7
  export function bindNarrationReuse(deps, view, recipe, ordinal) {
@@ -30,17 +32,14 @@ export function bindNarrationReuse(deps, view, recipe, ordinal) {
30
32
  return;
31
33
  }
32
34
  if (recipe.input.kind === "provided" && /^audio:(body:.+|intro|outro):[0-9]+$/.test(recipe.key)) {
33
- if (view.pieces.some((one) => one.selected &&
34
- one.available &&
35
- one.key === recipe.key &&
35
+ if (view.pieces.some((one) => isDonePiece(one, recipe.key) &&
36
36
  one.fingerprint === recipe.fingerprint &&
37
- one.piece.state === "done" &&
38
37
  one.piece.idx === ordinal))
39
38
  return;
40
- const asset = deps.db
41
- .prepare("SELECT path FROM project_assets WHERE id=? AND project_id=?")
42
- .get(recipe.input.assetId, view.revision.projectId);
43
- if (asset === undefined)
39
+ const file = recipe.input.assetId === null
40
+ ? undefined
41
+ : assetPathOrUndefined(deps, view.revision.projectId, recipe.input.assetId);
42
+ if (file === undefined)
44
43
  return;
45
44
  const semantic = z
46
45
  .tuple([z.string(), z.array(z.string().nullable())])
@@ -67,7 +66,7 @@ export function bindNarrationReuse(deps, view, recipe, ordinal) {
67
66
  state: "done",
68
67
  payload: JSON.stringify({
69
68
  provided: true,
70
- file: asset.path,
69
+ file,
71
70
  logicalKey: recipe.key.slice(0, recipe.key.lastIndexOf(":")),
72
71
  logicalText: semantic[0],
73
72
  text: semantic[0],
@@ -85,11 +84,8 @@ export function bindNarrationReuse(deps, view, recipe, ordinal) {
85
84
  if (recipe.input.kind !== "tts")
86
85
  return;
87
86
  const input = recipe.input;
88
- if (view.pieces.some((one) => one.selected &&
89
- one.available &&
90
- one.key === recipe.key &&
87
+ if (view.pieces.some((one) => isDonePiece(one, recipe.key) &&
91
88
  one.fingerprint === recipe.fingerprint &&
92
- one.piece.state === "done" &&
93
89
  one.piece.idx === ordinal &&
94
90
  narrationMetadataMatches(one.piece.payload, input)))
95
91
  return;
@@ -1,5 +1,7 @@
1
1
  import { z } from "zod";
2
- import { executionPlan, executionView, savedCatalogue } from "./runtime-plan.js";
2
+ import { internalError } from "../../kernel/errors.js";
3
+ import { isDonePiece } from "../revisions/view.js";
4
+ import { executionPlan, executionView, savedCatalogue, workCatalogue } from "./runtime-plan.js";
3
5
  import { preparedTexts, publishResult } from "./runtime-publication.js";
4
6
  export function joinedNarration(parts) {
5
7
  return parts
@@ -9,14 +11,10 @@ export function joinedNarration(parts) {
9
11
  export function narrationTextParts(view, plan, segment) {
10
12
  const concat = plan.recipes.find((row) => row.key === (segment === "body" ? "audio:body:concat" : `audio:${segment}`));
11
13
  if (concat === undefined)
12
- throw new Error(`Slopify hit an internal error (the ${segment} narration is missing from the project plan). Try again; if it happens again, use Download diagnostics in Settings and report it.`);
14
+ throw new Error(internalError(`the ${segment} narration is missing from the project plan`));
13
15
  return concat.dependsOn.map((key) => {
14
16
  const recipe = plan.recipes.find((row) => row.key === key);
15
- const selected = view.pieces.find((row) => row.key === key &&
16
- row.selected &&
17
- row.available &&
18
- row.piece.state === "done" &&
19
- row.fingerprint === recipe?.fingerprint);
17
+ const selected = view.pieces.find((row) => row.key === key && isDonePiece(row) && row.fingerprint === recipe?.fingerprint);
20
18
  if (selected === undefined || recipe === undefined)
21
19
  throw new Error("Some narration chunks aren't finished, so the full narration text isn't available. Let the Narration stage finish (Resume, or Try again on it), or regenerate the missing chunk in Edit project → Narration.");
22
20
  const input = recipe.input;
@@ -29,7 +27,7 @@ export function narrationTextParts(view, plan, segment) {
29
27
  requestText: null,
30
28
  };
31
29
  if (input.kind !== "tts")
32
- throw new Error("Slopify hit an internal error (a narration chunk's text was never worked out). Try again; if it happens again, use Download diagnostics in Settings and report it.");
30
+ throw new Error(internalError("a narration chunk's text was never worked out"));
33
31
  const payload = z
34
32
  .object({ text: z.string().optional(), spokenText: z.string().optional() })
35
33
  .parse(JSON.parse(selected.piece.payload ?? "{}"));
@@ -45,19 +43,16 @@ export function narrationTextParts(view, plan, segment) {
45
43
  }
46
44
  export async function publishNarrationText(deps, context, piece) {
47
45
  if (piece.input.kind !== "local")
48
- throw new Error("Slopify hit an internal error (the narration text file step was set up wrongly). Try again; if it happens again, use Download diagnostics in Settings and report it.");
46
+ throw new Error(internalError("the narration text file step was set up wrongly"));
49
47
  const { segment } = z
50
48
  .object({ segment: z.enum(["body", "intro", "outro"]) })
51
49
  .parse(piece.input.values);
52
50
  const view = executionView(deps, context.work.projectId, context.work.revisionId);
53
- const row = deps.db
54
- .prepare("SELECT recipe_context FROM revision_work WHERE id=?")
55
- .get(context.work.workId);
56
51
  if (view === undefined)
57
- throw new Error("Slopify hit an internal error (the project version being narrated is missing). Try again; if it happens again, use Download diagnostics in Settings and report it.");
58
- const plan = executionPlan(deps, view, savedCatalogue(row?.recipe_context));
52
+ throw new Error(internalError("the project version being narrated is missing"));
53
+ const plan = executionPlan(deps, view, workCatalogue(deps.db, context.work.workId));
59
54
  if (!plan.recipes.some((recipe) => recipe.key === piece.key && recipe.fingerprint === piece.fingerprint))
60
- throw new Error("Slopify hit an internal error (the narration text no longer matches the project's saved inputs). Try again; if it happens again, use Download diagnostics in Settings and report it.");
55
+ throw new Error(internalError("the narration text no longer matches the project's saved inputs"));
61
56
  const parts = narrationTextParts(view, plan, segment);
62
57
  const outputs = preparedTexts(deps, context, piece, [
63
58
  ["narration_txt", `${segment}-narration.txt`, joinedNarration(parts)],
@@ -1,5 +1,6 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { basename } from "node:path";
3
+ import { assetPathOrUndefined } from "../revisions/mutation-assets.js";
3
4
  import { outputPath } from "../storage/layout.js";
4
5
  // The file behind a picture the article names (`![alt](maps/relief.png)`): an image uploaded
5
6
  // to the project under that file name. An article's pictures are otherwise web addresses
@@ -14,10 +15,7 @@ export function articlePicture(deps, view, named) {
14
15
  for (const image of Object.values(view.revision.content.imageDefinitions)) {
15
16
  if (image.source !== "provide" || image.assetId === null)
16
17
  continue;
17
- const row = deps.db
18
- .prepare("SELECT path FROM project_assets WHERE id=? AND project_id=?")
19
- .get(image.assetId, view.revision.projectId);
20
- const path = typeof row?.path === "string" ? row.path : undefined;
18
+ const path = assetPathOrUndefined(deps, view.revision.projectId, image.assetId);
21
19
  if (path === undefined || basename(path).toLowerCase() !== name)
22
20
  continue;
23
21
  const file = outputPath(deps.paths, view.revision.projectId, path);
@@ -1,4 +1,5 @@
1
- import { executionPlan, executionView, savedCatalogue } from "./runtime-plan.js";
1
+ import { platformLabels, socialPlatforms } from "../social/model.js";
2
+ import { executionPlan, executionView, workCatalogueIfSaved } from "./runtime-plan.js";
2
3
  // What a failed step is called on screen, so its error says which image, chunk or topic to
3
4
  // fix. Worked out only after a failure: numbering a narration chunk means building the plan.
4
5
  // Undefined for steps that name their own place (captions, export) or have no single one.
@@ -17,6 +18,9 @@ export function pieceLabel(deps, context, piece) {
17
18
  return "Document";
18
19
  if (key === "youtube:description")
19
20
  return "YouTube description";
21
+ const post = /^social:post:([a-z]+)$/.exec(key);
22
+ if (post !== null)
23
+ return `${platformLabels[socialPlatforms.find((one) => one === post[1]) ?? "x"]} post`;
20
24
  if (key.startsWith("review:")) {
21
25
  const item = key.slice("review:".length);
22
26
  const itemLabel = item === "narration"
@@ -47,6 +51,8 @@ export function pieceLabel(deps, context, piece) {
47
51
  return "Animated images";
48
52
  if (key === "images:scenes")
49
53
  return "Image scenes";
54
+ if (key === "article:digest")
55
+ return "Keyword digest";
50
56
  const short = /^shorts:(\d+):(prompts|image:(\d+)|render)$/.exec(key);
51
57
  if (short !== null)
52
58
  return short[2] === "prompts"
@@ -84,13 +90,11 @@ export function pieceLabel(deps, context, piece) {
84
90
  return narrationChunk(deps, context, segment, key, prefix) ?? prefix;
85
91
  }
86
92
  function narrationChunk(deps, context, segment, key, prefix) {
87
- const row = deps.db
88
- .prepare("SELECT recipe_context FROM revision_work WHERE id=?")
89
- .get(context.work.workId);
93
+ const catalogue = workCatalogueIfSaved(deps.db, context.work.workId);
90
94
  const view = executionView(deps, context.work.projectId, context.work.revisionId);
91
- if (row?.recipe_context === null || row === undefined || view === undefined)
95
+ if (catalogue === undefined || view === undefined)
92
96
  return undefined;
93
- const plan = executionPlan(deps, view, savedCatalogue(row.recipe_context));
97
+ const plan = executionPlan(deps, view, catalogue);
94
98
  const concat = plan.recipes.find((recipe) => recipe.key === (segment === "body" ? "audio:body:concat" : `audio:${segment}`));
95
99
  if (concat === undefined)
96
100
  return undefined;
@@ -1,7 +1,7 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { z } from "zod";
3
3
  import { catalogueSchema } from "../../catalog/schema.js";
4
- import { getRevisionView, withEarlierJoins } from "../revisions/view.js";
4
+ import { getRevisionView, readyOutput, withEarlierJoins } from "../revisions/view.js";
5
5
  import { outputPath } from "../storage/layout.js";
6
6
  import { referencedAssetsAvailable, retainedNarrationPieces } from "./narration-history.js";
7
7
  import { applyProvidedReviews } from "./provided-review.js";
@@ -22,12 +22,38 @@ export function executionCatalogue(catalogue, config) {
22
22
  export function savedCatalogue(value) {
23
23
  return catalogueSchema.parse(JSON.parse(z.string().parse(value)));
24
24
  }
25
+ function savedContext(db, workId) {
26
+ return db.prepare("SELECT recipe_context FROM revision_work WHERE id=?").get(workId)
27
+ ?.recipe_context;
28
+ }
29
+ // The catalogue a work was admitted with, saved beside it; a work without one is a bug.
30
+ export function workCatalogue(db, workId) {
31
+ return savedCatalogue(savedContext(db, workId));
32
+ }
33
+ // The same, or undefined for a work (or a missing one) that has no catalogue saved.
34
+ export function workCatalogueIfSaved(db, workId) {
35
+ const context = savedContext(db, workId);
36
+ return context === null || context === undefined ? undefined : savedCatalogue(context);
37
+ }
38
+ // A step's LLM call on its work's saved catalogue: the chosen model with the thinking settings
39
+ // the catalogue holds for its level, no web search, labelled with the step. The catalogue is
40
+ // read once, as the builder is made.
41
+ export function workLlm(db, workId, choice, label) {
42
+ const catalogued = workCatalogue(db, workId).llm.find((one) => one.provider === choice.provider && one.id === choice.model);
43
+ const { thinking } = choice;
44
+ return (call) => ({
45
+ provider: choice.provider,
46
+ model: choice.model,
47
+ ...(thinking === undefined ? {} : { thinking }),
48
+ thinkingConfig: thinking === undefined ? null : (catalogued?.llm.thinking?.[thinking] ?? null),
49
+ webSearch: false,
50
+ previewLabel: label,
51
+ ...call,
52
+ });
53
+ }
25
54
  export function executionPlan(deps, view, catalogue) {
26
55
  const textOutput = (role) => {
27
- const row = view.outputs.find((output) => output.selected &&
28
- output.available &&
29
- output.state === "ready" &&
30
- output.output.role === role);
56
+ const row = readyOutput(view, { role });
31
57
  return row === undefined
32
58
  ? null
33
59
  : readFileSync(outputPath(deps.paths, view.revision.projectId, row.output.path), "utf8");