@gentbajko/slopify 3.25.9 → 4.1.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 (718) hide show
  1. package/dist/adapters/account-limits.js +30 -0
  2. package/dist/adapters/alignment/audio.js +1 -1
  3. package/dist/adapters/alignment/cache.js +4 -6
  4. package/dist/adapters/alignment/ctc.js +1 -2
  5. package/dist/adapters/alignment/lock.js +4 -3
  6. package/dist/adapters/alignment/multilingual.js +2 -4
  7. package/dist/adapters/alignment/prefetch.js +2 -1
  8. package/dist/adapters/alignment/runner.js +1 -1
  9. package/dist/adapters/alignment/worker.js +3 -2
  10. package/dist/adapters/explain.js +10 -12
  11. package/dist/adapters/ffmpeg.js +19 -1
  12. package/dist/adapters/host-cli/index.js +21 -4
  13. package/dist/adapters/host-cli/transport.js +2 -1
  14. package/dist/adapters/http-failure.js +53 -0
  15. package/dist/adapters/image/bytes.js +2 -1
  16. package/dist/adapters/image/codex.js +42 -9
  17. package/dist/adapters/image/fal.js +21 -53
  18. package/dist/adapters/image/google.js +33 -56
  19. package/dist/adapters/image/models.js +2 -2
  20. package/dist/adapters/image/openai.js +22 -48
  21. package/dist/adapters/image/reference.js +36 -0
  22. package/dist/adapters/image/replicate.js +21 -49
  23. package/dist/adapters/image/video.js +2 -1
  24. package/dist/adapters/interest/search-interest.js +113 -0
  25. package/dist/adapters/key-probes.js +54 -3
  26. package/dist/adapters/llm/antigravity-models.js +1 -1
  27. package/dist/adapters/llm/antigravity.js +11 -6
  28. package/dist/adapters/llm/assistant-cli.js +388 -0
  29. package/dist/adapters/llm/child-session.js +83 -0
  30. package/dist/adapters/llm/claude-code-models.js +52 -99
  31. package/dist/adapters/llm/claude-code.js +16 -4
  32. package/dist/adapters/llm/codex-limits.js +27 -68
  33. package/dist/adapters/llm/codex-models.js +1 -1
  34. package/dist/adapters/llm/codex-server-models.js +61 -109
  35. package/dist/adapters/llm/codex.js +12 -1
  36. package/dist/adapters/llm/document-workspace.js +3 -2
  37. package/dist/adapters/llm/image-workspace.js +3 -2
  38. package/dist/adapters/llm/openrouter.js +12 -48
  39. package/dist/adapters/llm/run-cli.js +9 -24
  40. package/dist/adapters/llm/sse-lines.js +5 -1
  41. package/dist/adapters/model-access.js +37 -0
  42. package/dist/adapters/tts/cartesia.js +15 -46
  43. package/dist/adapters/tts/elevenlabs.js +20 -52
  44. package/dist/adapters/tts/gemini.js +21 -41
  45. package/dist/adapters/tts/inworld-async.js +6 -2
  46. package/dist/adapters/tts/inworld.js +6 -2
  47. package/dist/adapters/tts/openai.js +20 -50
  48. package/dist/adapters/tts/system.js +2 -1
  49. package/dist/assets/models-lifecycle.yaml +6 -0
  50. package/dist/assistant-wiring.js +60 -0
  51. package/dist/autopilot-timers.js +59 -0
  52. package/dist/catalog/lifecycle.js +52 -0
  53. package/dist/catalog/propose.js +204 -0
  54. package/dist/catalog/registry.js +23 -14
  55. package/dist/catalog/schema.js +4 -1
  56. package/dist/catalog/store.js +39 -0
  57. package/dist/catalog/validate.js +5 -2
  58. package/dist/edge/autostart/native.js +3 -5
  59. package/dist/edge/autostart/prompt.js +2 -1
  60. package/dist/edge/autostart/service.js +3 -4
  61. package/dist/edge/cli-args.js +3 -4
  62. package/dist/edge/cli.js +2 -1
  63. package/dist/edge/docker-install/apply.js +11 -11
  64. package/dist/edge/docker-install/run.js +2 -1
  65. package/dist/edge/docker-install/state.js +4 -3
  66. package/dist/edge/docker-install/tree.js +2 -4
  67. package/dist/edge/docker-install/volume.js +3 -2
  68. package/dist/edge/docker.js +5 -4
  69. package/dist/edge/events/hub.js +5 -5
  70. package/dist/edge/http/actions.js +11 -32
  71. package/dist/edge/http/actor.js +5 -3
  72. package/dist/edge/http/app.js +86 -11
  73. package/dist/edge/http/assistant.js +129 -0
  74. package/dist/edge/http/audio-preview.js +4 -11
  75. package/dist/edge/http/auditions.js +1 -1
  76. package/dist/edge/http/autostart.js +5 -5
  77. package/dist/edge/http/backups.js +2 -4
  78. package/dist/edge/http/channel-memory.js +9 -22
  79. package/dist/edge/http/channels.js +14 -27
  80. package/dist/edge/http/checkpoints.js +39 -21
  81. package/dist/edge/http/design-templates.js +167 -0
  82. package/dist/edge/http/designs.js +178 -0
  83. package/dist/edge/http/diagnostics.js +5 -1
  84. package/dist/edge/http/document-themes.js +5 -14
  85. package/dist/edge/http/draft-files.js +1 -3
  86. package/dist/edge/http/draft-problem.js +5 -10
  87. package/dist/edge/http/drafts.js +3 -3
  88. package/dist/edge/http/entries.js +6 -9
  89. package/dist/edge/http/extension-auth.js +27 -0
  90. package/dist/edge/http/external-reviews.js +12 -11
  91. package/dist/edge/http/files.js +5 -11
  92. package/dist/edge/http/folder-location.js +1 -4
  93. package/dist/edge/http/fonts.js +0 -7
  94. package/dist/edge/http/home.js +47 -14
  95. package/dist/edge/http/host-cli.js +18 -8
  96. package/dist/edge/http/ids.js +8 -0
  97. package/dist/edge/http/keywords.js +35 -0
  98. package/dist/edge/http/model-checks.js +132 -0
  99. package/dist/edge/http/narration-peaks.js +5 -13
  100. package/dist/edge/http/onboarding.js +30 -26
  101. package/dist/edge/http/open-folder.js +5 -10
  102. package/dist/edge/http/outputs.js +7 -14
  103. package/dist/edge/http/patch-notes.js +6 -12
  104. package/dist/edge/http/planning.js +3 -7
  105. package/dist/edge/http/problem.js +2 -4
  106. package/dist/edge/http/project-create.js +2 -1
  107. package/dist/edge/http/project-templates.js +31 -19
  108. package/dist/edge/http/projects.js +24 -16
  109. package/dist/edge/http/prompts.js +9 -16
  110. package/dist/edge/http/pronunciations.js +1 -2
  111. package/dist/edge/http/providers.js +48 -34
  112. package/dist/edge/http/refusal.js +29 -0
  113. package/dist/edge/http/remote-access.js +40 -0
  114. package/dist/edge/http/remote-guard.js +71 -0
  115. package/dist/edge/http/request-guard.js +91 -0
  116. package/dist/edge/http/reviews.js +12 -25
  117. package/dist/edge/http/revision-files.js +6 -11
  118. package/dist/edge/http/revisions.js +14 -19
  119. package/dist/edge/http/run-cost.js +1 -2
  120. package/dist/edge/http/same-page.js +80 -0
  121. package/dist/edge/http/schedules.js +69 -40
  122. package/dist/edge/http/settings.js +27 -14
  123. package/dist/edge/http/social.js +283 -0
  124. package/dist/edge/http/staging.js +3 -5
  125. package/dist/edge/http/storage-files.js +5 -7
  126. package/dist/edge/http/storage.js +33 -25
  127. package/dist/edge/http/studio.js +94 -69
  128. package/dist/edge/http/style-preview.js +3 -6
  129. package/dist/edge/http/subtitles.js +3 -17
  130. package/dist/edge/http/template-studio.js +436 -0
  131. package/dist/edge/http/trash.js +11 -12
  132. package/dist/edge/http/tutorial.js +1 -2
  133. package/dist/edge/http/tutorials.js +5 -11
  134. package/dist/edge/http/update.js +7 -13
  135. package/dist/edge/http/waveform.js +3 -6
  136. package/dist/edge/http/youtube-edits.js +3 -4
  137. package/dist/edge/mcp/assistant.js +279 -0
  138. package/dist/edge/mcp/attribution.js +8 -2
  139. package/dist/edge/mcp/call-route.js +11 -2
  140. package/dist/edge/mcp/excluded-routes.js +29 -3
  141. package/dist/edge/mcp/http.js +11 -2
  142. package/dist/edge/mcp/instructions.js +6 -4
  143. package/dist/edge/mcp/library-tools.js +9 -5
  144. package/dist/edge/mcp/remake-tools.js +7 -4
  145. package/dist/edge/mcp/route-tool.js +35 -15
  146. package/dist/edge/mcp/server.js +13 -8
  147. package/dist/edge/mcp/tools-channels.js +11 -9
  148. package/dist/edge/mcp/tools-designs.js +165 -0
  149. package/dist/edge/mcp/tools-library.js +19 -7
  150. package/dist/edge/mcp/tools-planning.js +40 -21
  151. package/dist/edge/mcp/tools-projects.js +38 -11
  152. package/dist/edge/mcp/tools-publishing.js +9 -9
  153. package/dist/edge/mcp/tools-runs.js +23 -10
  154. package/dist/edge/mcp/tools-schedules.js +32 -8
  155. package/dist/edge/mcp/tools-setup.js +75 -11
  156. package/dist/edge/mcp/tools-social.js +47 -0
  157. package/dist/edge/mcp/tools-storage.js +11 -3
  158. package/dist/edge/mcp/tools-template-studio.js +166 -0
  159. package/dist/edge/mcp/tools.js +6 -0
  160. package/dist/edge/remote-access/cli.js +71 -0
  161. package/dist/edge/remote-access/model.js +32 -0
  162. package/dist/edge/remote-access/rules.js +207 -0
  163. package/dist/edge/remote-access/service.js +203 -0
  164. package/dist/edge/remote-access/store.js +44 -0
  165. package/dist/edge/remote-access/views.js +43 -0
  166. package/dist/extension/slopify-studio-chrome.zip +0 -0
  167. package/dist/extension/slopify-studio-firefox.zip +0 -0
  168. package/dist/host-cli/install.js +2 -2
  169. package/dist/host-cli/open-folder.js +2 -2
  170. package/dist/host-cli/paths.js +2 -4
  171. package/dist/host-cli/server.js +4 -4
  172. package/dist/host-cli/service.js +3 -3
  173. package/dist/host-cli/status.js +7 -7
  174. package/dist/kernel/db/migrations/0063-model-checks.sql +20 -0
  175. package/dist/kernel/db/migrations/0064-template-studio.sql +46 -0
  176. package/dist/kernel/db/migrations/0065-promise-and-assistant-reviews.sql +58 -0
  177. package/dist/kernel/db/migrations/0066-assistant.sql +44 -0
  178. package/dist/kernel/db/migrations/0067-autopilot.sql +6 -0
  179. package/dist/kernel/db/migrations/0068-topic-scores.sql +17 -0
  180. package/dist/kernel/db/migrations/0070-designs.sql +53 -0
  181. package/dist/kernel/db/migrations/0071-design-usage.sql +33 -0
  182. package/dist/kernel/db/migrations/0072-design-templates.sql +33 -0
  183. package/dist/kernel/db/migrations/0073-schedule-design-kind.sql +5 -0
  184. package/dist/kernel/db/migrations/0074-template-bakeoffs.sql +44 -0
  185. package/dist/kernel/db/migrations/0075-social-posts.sql +25 -0
  186. package/dist/kernel/db/migrations/0076-channel-social.sql +5 -0
  187. package/dist/kernel/db/migrations/0077-post-prompts.sql +17 -0
  188. package/dist/kernel/db/migrations/0078-uploaded-from-links.sql +7 -0
  189. package/dist/kernel/db/tx.js +2 -4
  190. package/dist/kernel/errors.js +27 -1
  191. package/dist/kernel/json-answer.js +15 -0
  192. package/dist/kernel/lock.js +4 -9
  193. package/dist/kernel/paths.js +8 -0
  194. package/dist/kernel/pipeline.js +12 -0
  195. package/dist/kernel/ports/assistant-cli.js +4 -0
  196. package/dist/kernel/ports/host-cli.js +3 -0
  197. package/dist/kernel/ports/interest.js +4 -0
  198. package/dist/kernel/ports/languages.js +0 -3
  199. package/dist/kernel/ports/model.js +2 -0
  200. package/dist/kernel/ports/settings-places.js +31 -0
  201. package/dist/kernel/ports/system-speech.js +31 -3
  202. package/dist/kernel/runner/attempt.js +14 -4
  203. package/dist/kernel/runner/audition.js +3 -1
  204. package/dist/kernel/runner/checkpoint-authority.js +2 -1
  205. package/dist/kernel/runner/index.js +8 -10
  206. package/dist/kernel/runner/model-access.js +40 -0
  207. package/dist/kernel/runner/model-check.js +153 -0
  208. package/dist/kernel/runner/providers.js +18 -12
  209. package/dist/kernel/runner/standalone.js +11 -7
  210. package/dist/main.js +338 -21
  211. package/dist/model-catalog.js +20 -0
  212. package/dist/model-checks.js +166 -0
  213. package/dist/patch-notes/4.0.0.md +51 -0
  214. package/dist/patch-notes/4.1.0.md +42 -0
  215. package/dist/patch-notes/index.json +12 -0
  216. package/dist/slices/admission/heads.js +2 -1
  217. package/dist/slices/admission/listing-facts.js +95 -0
  218. package/dist/slices/admission/repo.js +11 -7
  219. package/dist/slices/admission/rules.js +50 -15
  220. package/dist/slices/admission/schema.js +8 -0
  221. package/dist/slices/admission/stage-writes.js +0 -3
  222. package/dist/slices/admission/start.js +48 -31
  223. package/dist/slices/admission/state-words.js +34 -5
  224. package/dist/slices/admission/substitute.js +7 -4
  225. package/dist/slices/agents/attention-posts.js +31 -0
  226. package/dist/slices/agents/attention-templates.js +33 -0
  227. package/dist/slices/agents/attention.js +10 -1
  228. package/dist/slices/agents/connection.js +2 -6
  229. package/dist/slices/agents/model.js +6 -3
  230. package/dist/slices/agents/repo.js +15 -7
  231. package/dist/slices/article/continuation.js +0 -63
  232. package/dist/slices/article/plain.js +3 -2
  233. package/dist/slices/article/segments.js +2 -62
  234. package/dist/slices/assistant/access.js +1 -0
  235. package/dist/slices/assistant/asks.js +80 -0
  236. package/dist/slices/assistant/cost.js +54 -0
  237. package/dist/slices/assistant/gate.js +222 -0
  238. package/dist/slices/assistant/model.js +86 -0
  239. package/dist/slices/assistant/names.js +23 -0
  240. package/dist/slices/assistant/prompt.js +74 -0
  241. package/dist/slices/assistant/repo.js +74 -0
  242. package/dist/slices/assistant/tokens.js +18 -0
  243. package/dist/slices/assistant/turn.js +344 -0
  244. package/dist/slices/autopilot/buffer.js +76 -0
  245. package/dist/slices/autopilot/model.js +14 -0
  246. package/dist/slices/autopilot/notify.js +27 -0
  247. package/dist/slices/autopilot/repo.js +34 -0
  248. package/dist/slices/autopilot/signals.js +194 -0
  249. package/dist/slices/autopilot/warnings.js +85 -0
  250. package/dist/slices/autopilot/weekly.js +100 -0
  251. package/dist/slices/backups/files.js +2 -6
  252. package/dist/slices/backups/repo.js +3 -15
  253. package/dist/slices/backups/service.js +8 -7
  254. package/dist/slices/channels/cast-images.js +3 -2
  255. package/dist/slices/channels/model.js +0 -1
  256. package/dist/slices/channels/rebrand.js +0 -3
  257. package/dist/slices/channels/repo.js +4 -0
  258. package/dist/slices/channels/schema.js +7 -1
  259. package/dist/slices/channels/service.js +10 -0
  260. package/dist/slices/channels/videos.js +3 -15
  261. package/dist/slices/checkpoints/index.js +0 -1
  262. package/dist/slices/control/providers.js +2 -28
  263. package/dist/slices/designs/artwork.js +66 -0
  264. package/dist/slices/designs/budget.js +23 -0
  265. package/dist/slices/designs/calls.js +124 -0
  266. package/dist/slices/designs/check-context.js +38 -0
  267. package/dist/slices/designs/check-text.js +43 -0
  268. package/dist/slices/designs/fields.js +88 -0
  269. package/dist/slices/designs/frames.js +34 -0
  270. package/dist/slices/designs/gate.js +130 -0
  271. package/dist/slices/designs/layout.js +293 -0
  272. package/dist/slices/designs/model.js +35 -0
  273. package/dist/slices/designs/pdf-job.js +23 -0
  274. package/dist/slices/designs/pdf-pages.js +30 -0
  275. package/dist/slices/designs/pdf-runner.js +68 -0
  276. package/dist/slices/designs/pdf-worker.js +24 -0
  277. package/dist/slices/designs/picture-size.js +79 -0
  278. package/dist/slices/designs/pictures.js +55 -0
  279. package/dist/slices/designs/quote.js +24 -0
  280. package/dist/slices/designs/render-args.js +36 -0
  281. package/dist/slices/designs/render-input.js +59 -0
  282. package/dist/slices/designs/render.js +215 -0
  283. package/dist/slices/designs/repo.js +101 -0
  284. package/dist/slices/designs/requests.js +80 -0
  285. package/dist/slices/designs/runner.js +239 -0
  286. package/dist/slices/designs/sample-repo.js +91 -0
  287. package/dist/slices/designs/sample.js +223 -0
  288. package/dist/slices/designs/schema.js +87 -0
  289. package/dist/slices/designs/sections.js +44 -0
  290. package/dist/slices/designs/service.js +233 -0
  291. package/dist/slices/designs/steps-repo.js +102 -0
  292. package/dist/slices/designs/studio-kind.js +39 -0
  293. package/dist/slices/designs/template-checks.js +44 -0
  294. package/dist/slices/designs/template-make.js +118 -0
  295. package/dist/slices/designs/templates.js +218 -0
  296. package/dist/slices/document/fonts.js +2 -1
  297. package/dist/slices/document/model.js +7 -4
  298. package/dist/slices/document/render.js +2 -1
  299. package/dist/slices/document/theme-schema.js +1 -1
  300. package/dist/slices/document/theme.js +49 -3
  301. package/dist/slices/episodes/summarize.js +6 -2
  302. package/dist/slices/estimate/index.js +72 -28
  303. package/dist/slices/estimate/requests.js +4 -4
  304. package/dist/slices/fixes/rules.js +3 -1
  305. package/dist/slices/fonts/catalog.js +5 -3
  306. package/dist/slices/fonts/coverage.js +2 -1
  307. package/dist/slices/fonts/discovery.js +3 -2
  308. package/dist/slices/fonts/files.js +2 -6
  309. package/dist/slices/fonts/index.js +0 -1
  310. package/dist/slices/fonts/sfnt.js +45 -10
  311. package/dist/slices/images/appearance.js +8 -16
  312. package/dist/slices/images/scenes.js +102 -28
  313. package/dist/slices/keywords/digest.js +85 -0
  314. package/dist/slices/keywords/facts.js +92 -0
  315. package/dist/slices/keywords/fill.js +80 -0
  316. package/dist/slices/keywords/model.js +75 -0
  317. package/dist/slices/keywords/rules.js +187 -0
  318. package/dist/slices/keywords/schema.js +28 -0
  319. package/dist/slices/keywords/specs.js +260 -0
  320. package/dist/slices/keywords/values.js +74 -0
  321. package/dist/slices/library/lint.js +29 -12
  322. package/dist/slices/library/model.js +2 -0
  323. package/dist/slices/library/photorealistic.js +4 -12
  324. package/dist/slices/library/rename.js +9 -7
  325. package/dist/slices/library/save.js +14 -10
  326. package/dist/slices/library/slots.js +37 -11
  327. package/dist/slices/library/transfer.js +5 -4
  328. package/dist/slices/library/used-by.js +25 -10
  329. package/dist/slices/loudness/loudnorm.js +53 -12
  330. package/dist/slices/loudness/model.js +3 -2
  331. package/dist/slices/loudness/sentence-level.js +2 -1
  332. package/dist/slices/model-checks/cost.js +61 -0
  333. package/dist/slices/model-checks/listing.js +124 -0
  334. package/dist/slices/model-checks/model.js +20 -0
  335. package/dist/slices/model-checks/record.js +32 -0
  336. package/dist/slices/model-checks/repo.js +120 -0
  337. package/dist/slices/model-checks/rules.js +125 -0
  338. package/dist/slices/model-checks/run.js +143 -0
  339. package/dist/slices/model-checks/service.js +198 -0
  340. package/dist/slices/model-upkeep/model.js +1 -1
  341. package/dist/slices/model-upkeep/switch.js +72 -27
  342. package/dist/slices/model-upkeep/usage.js +39 -13
  343. package/dist/slices/narration/breaths.js +3 -1
  344. package/dist/slices/narration/clean-pieces.js +3 -1
  345. package/dist/slices/narration/concat.js +2 -1
  346. package/dist/slices/narration/pauses-model.js +3 -2
  347. package/dist/slices/narration/peaks.js +3 -4
  348. package/dist/slices/narration/pronunciation-chunks.js +4 -3
  349. package/dist/slices/narration/pronunciation.js +6 -4
  350. package/dist/slices/narration/steering.js +4 -3
  351. package/dist/slices/notifications/notifier.js +4 -3
  352. package/dist/slices/notifications/rules.js +5 -2
  353. package/dist/slices/notifications/settings.js +4 -12
  354. package/dist/slices/notifications/subject.js +4 -3
  355. package/dist/slices/onboarding/copy.js +21 -3
  356. package/dist/slices/onboarding/first-run.js +16 -40
  357. package/dist/slices/onboarding/full-video.js +3 -2
  358. package/dist/slices/onboarding/install.js +6 -4
  359. package/dist/slices/onboarding/model.js +26 -37
  360. package/dist/slices/onboarding/packs.js +22 -22
  361. package/dist/slices/onboarding/preferred-model.js +14 -0
  362. package/dist/slices/onboarding/quick-short.js +105 -65
  363. package/dist/slices/onboarding/sample.js +4 -2
  364. package/dist/slices/onboarding/state.js +9 -11
  365. package/dist/slices/onboarding/template.js +8 -6
  366. package/dist/slices/outputs/add.js +3 -2
  367. package/dist/slices/outputs/service.js +2 -2
  368. package/dist/slices/patch-notes/library.js +4 -3
  369. package/dist/slices/play-drafts/convert.js +4 -0
  370. package/dist/slices/play-drafts/readiness.js +3 -3
  371. package/dist/slices/play-drafts/review-inputs.js +9 -3
  372. package/dist/slices/play-drafts/review.js +5 -4
  373. package/dist/slices/play-drafts/schema.js +4 -0
  374. package/dist/slices/play-drafts/service.js +17 -7
  375. package/dist/slices/project-templates/from-project.js +9 -2
  376. package/dist/slices/project-templates/next-chapter.js +2 -1
  377. package/dist/slices/project-templates/provider-defaults.js +22 -0
  378. package/dist/slices/project-templates/repo.js +6 -1
  379. package/dist/slices/project-templates/setup-sections.js +26 -0
  380. package/dist/slices/project-templates/setup.js +14 -1
  381. package/dist/slices/project-templates/stand-ins.js +22 -0
  382. package/dist/slices/rebuild/activity.js +2 -0
  383. package/dist/slices/rebuild/admission-repo.js +3 -2
  384. package/dist/slices/rebuild/narration-history.js +4 -5
  385. package/dist/slices/rebuild/narration-retry.js +2 -1
  386. package/dist/slices/rebuild/preview-details.js +32 -14
  387. package/dist/slices/rebuild/preview-plan.js +3 -8
  388. package/dist/slices/rebuild/recipe-build.js +8 -3
  389. package/dist/slices/rebuild/recipe-describe.js +3 -3
  390. package/dist/slices/rebuild/recipe-digest.js +58 -0
  391. package/dist/slices/rebuild/recipe-input-schema.js +10 -0
  392. package/dist/slices/rebuild/recipe-keywords.js +71 -0
  393. package/dist/slices/rebuild/recipe-legacy.js +5 -1
  394. package/dist/slices/rebuild/recipe-model.js +4 -0
  395. package/dist/slices/rebuild/recipe-narration-text.js +2 -1
  396. package/dist/slices/rebuild/recipe-preparation.js +4 -4
  397. package/dist/slices/rebuild/recipe-reference.js +8 -4
  398. package/dist/slices/rebuild/recipe-reviews.js +27 -7
  399. package/dist/slices/rebuild/recipe-save.js +8 -5
  400. package/dist/slices/rebuild/recipe-scenes.js +40 -11
  401. package/dist/slices/rebuild/recipe-shorts.js +8 -8
  402. package/dist/slices/rebuild/recipe-social.js +46 -0
  403. package/dist/slices/rebuild/recipe-text.js +64 -20
  404. package/dist/slices/rebuild/recipe-validation.js +102 -31
  405. package/dist/slices/rebuild/recipe-visual.js +55 -16
  406. package/dist/slices/rebuild/recipe-voices.js +3 -2
  407. package/dist/slices/rebuild/recipe-work.js +19 -7
  408. package/dist/slices/rebuild/recipe-youtube.js +4 -3
  409. package/dist/slices/rebuild/recovery-selection.js +3 -1
  410. package/dist/slices/rebuild/recovery.js +9 -8
  411. package/dist/slices/rebuild/remake-outdated.js +5 -5
  412. package/dist/slices/rebuild/repo.js +4 -3
  413. package/dist/slices/rebuild/review-promise.js +25 -0
  414. package/dist/slices/rebuild/review-redo.js +3 -2
  415. package/dist/slices/rebuild/runtime-admission.js +4 -3
  416. package/dist/slices/rebuild/runtime-agent-review.js +11 -5
  417. package/dist/slices/rebuild/runtime-animate.js +10 -9
  418. package/dist/slices/rebuild/runtime-article.js +7 -5
  419. package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
  420. package/dist/slices/rebuild/runtime-document.js +11 -13
  421. package/dist/slices/rebuild/runtime-export-bed.js +8 -12
  422. package/dist/slices/rebuild/runtime-export-edit.js +8 -16
  423. package/dist/slices/rebuild/runtime-export-inputs.js +12 -11
  424. package/dist/slices/rebuild/runtime-export-short.js +9 -5
  425. package/dist/slices/rebuild/runtime-export.js +38 -15
  426. package/dist/slices/rebuild/runtime-figure-card.js +2 -1
  427. package/dist/slices/rebuild/runtime-image.js +8 -12
  428. package/dist/slices/rebuild/runtime-instructions.js +2 -0
  429. package/dist/slices/rebuild/runtime-keywords.js +59 -0
  430. package/dist/slices/rebuild/runtime-lines.js +2 -11
  431. package/dist/slices/rebuild/runtime-local.js +17 -22
  432. package/dist/slices/rebuild/runtime-narration-publication.js +2 -5
  433. package/dist/slices/rebuild/runtime-narration-reuse.js +9 -13
  434. package/dist/slices/rebuild/runtime-narration-text.js +13 -17
  435. package/dist/slices/rebuild/runtime-pictures.js +2 -4
  436. package/dist/slices/rebuild/runtime-piece-label.js +10 -6
  437. package/dist/slices/rebuild/runtime-plan.js +31 -5
  438. package/dist/slices/rebuild/runtime-provider.js +67 -25
  439. package/dist/slices/rebuild/runtime-publication.js +10 -9
  440. package/dist/slices/rebuild/runtime-review.js +39 -33
  441. package/dist/slices/rebuild/runtime-run.js +4 -1
  442. package/dist/slices/rebuild/runtime-shorts.js +31 -50
  443. package/dist/slices/rebuild/runtime-store.js +20 -10
  444. package/dist/slices/rebuild/runtime-subtitles.js +28 -25
  445. package/dist/slices/rebuild/runtime-voices.js +10 -16
  446. package/dist/slices/rebuild/runtime-youtube.js +14 -31
  447. package/dist/slices/rebuild/service-readiness.js +11 -3
  448. package/dist/slices/rebuild/transition-repo.js +6 -5
  449. package/dist/slices/rebuild/work-records.js +6 -0
  450. package/dist/slices/reruns/cascade.js +1 -2
  451. package/dist/slices/reruns/index.js +6 -9
  452. package/dist/slices/research/synthesis.js +3 -2
  453. package/dist/slices/reviews/external-model.js +4 -1
  454. package/dist/slices/reviews/external-repo.js +2 -1
  455. package/dist/slices/reviews/external-rows.js +2 -3
  456. package/dist/slices/reviews/holds.js +5 -4
  457. package/dist/slices/reviews/model.js +32 -11
  458. package/dist/slices/reviews/repo.js +2 -1
  459. package/dist/slices/reviews/rules.js +4 -3
  460. package/dist/slices/reviews/verdict.js +2 -1
  461. package/dist/slices/reviews/view.js +5 -2
  462. package/dist/slices/revisions/downloads.js +4 -5
  463. package/dist/slices/revisions/image-plan.js +1 -1
  464. package/dist/slices/revisions/mutation-assets.js +9 -5
  465. package/dist/slices/revisions/mutation-cues.js +2 -5
  466. package/dist/slices/revisions/mutations.js +3 -3
  467. package/dist/slices/revisions/repo.js +3 -1
  468. package/dist/slices/revisions/schema.js +1 -0
  469. package/dist/slices/revisions/view.js +20 -0
  470. package/dist/slices/run-cost/limits.js +5 -2
  471. package/dist/slices/run-cost/meter.js +12 -5
  472. package/dist/slices/run-cost/panel.js +8 -74
  473. package/dist/slices/run-cost/plan-readings.js +127 -0
  474. package/dist/slices/run-cost/plan-use.js +169 -0
  475. package/dist/slices/run-cost/pricing.js +3 -3
  476. package/dist/slices/run-cost/week.js +32 -4
  477. package/dist/slices/schedules/agenda.js +18 -7
  478. package/dist/slices/schedules/by-date.js +42 -0
  479. package/dist/slices/schedules/calendar.js +5 -0
  480. package/dist/slices/schedules/design-agenda.js +36 -0
  481. package/dist/slices/schedules/design-check.js +41 -0
  482. package/dist/slices/schedules/design-run.js +126 -0
  483. package/dist/slices/schedules/next-run.js +62 -0
  484. package/dist/slices/schedules/prepare.js +18 -2
  485. package/dist/slices/schedules/repo.js +30 -21
  486. package/dist/slices/schedules/rescore.js +35 -0
  487. package/dist/slices/schedules/scheduler.js +13 -3
  488. package/dist/slices/schedules/schema.js +57 -3
  489. package/dist/slices/schedules/score.js +107 -0
  490. package/dist/slices/schedules/service.js +57 -24
  491. package/dist/slices/schedules/topic-list.js +15 -0
  492. package/dist/slices/schedules/topic-scores.js +104 -0
  493. package/dist/slices/schedules/topics.js +53 -41
  494. package/dist/slices/settings/cli-paths.js +4 -6
  495. package/dist/slices/settings/first-run.js +8 -43
  496. package/dist/slices/settings/health.js +30 -15
  497. package/dist/slices/settings/key-test.js +1 -1
  498. package/dist/slices/settings/model-reach.js +3 -17
  499. package/dist/slices/settings/model.js +14 -2
  500. package/dist/slices/settings/playback.js +34 -23
  501. package/dist/slices/settings/probe-read.js +16 -0
  502. package/dist/slices/settings/repo.js +33 -0
  503. package/dist/slices/settings/tutorial.js +2 -2
  504. package/dist/slices/settings/voices.js +4 -12
  505. package/dist/slices/shorts/keep.js +2 -1
  506. package/dist/slices/shorts/pick.js +6 -18
  507. package/dist/slices/shorts/prompts.js +4 -14
  508. package/dist/slices/shorts/render.js +6 -2
  509. package/dist/slices/social/channel.js +44 -0
  510. package/dist/slices/social/defaults.js +17 -0
  511. package/dist/slices/social/due.js +36 -0
  512. package/dist/slices/social/extension.js +18 -0
  513. package/dist/slices/social/fit.js +115 -0
  514. package/dist/slices/social/limits.js +186 -0
  515. package/dist/slices/social/links.js +21 -0
  516. package/dist/slices/social/media.js +132 -0
  517. package/dist/slices/social/model.js +75 -0
  518. package/dist/slices/social/notice.js +19 -0
  519. package/dist/slices/social/pack.js +176 -0
  520. package/dist/slices/social/post-prompt.js +56 -0
  521. package/dist/slices/social/queue.js +31 -0
  522. package/dist/slices/social/repo.js +133 -0
  523. package/dist/slices/social/reshape.js +97 -0
  524. package/dist/slices/social/rules.js +5 -0
  525. package/dist/slices/social/schema.js +70 -0
  526. package/dist/slices/social/serve.js +51 -0
  527. package/dist/slices/social/service.js +129 -0
  528. package/dist/slices/social/settings.js +21 -0
  529. package/dist/slices/social/sources.js +114 -0
  530. package/dist/slices/social/state.js +110 -0
  531. package/dist/slices/social/text.js +149 -0
  532. package/dist/slices/storage/arrange.js +3 -1
  533. package/dist/slices/storage/assets.js +8 -10
  534. package/dist/slices/storage/backup-designs.js +74 -0
  535. package/dist/slices/storage/backup-export.js +8 -3
  536. package/dist/slices/storage/backup-format.js +10 -0
  537. package/dist/slices/storage/backup-import.js +22 -4
  538. package/dist/slices/storage/delete-project.js +6 -5
  539. package/dist/slices/storage/files-location.js +6 -8
  540. package/dist/slices/storage/layout.js +13 -2
  541. package/dist/slices/storage/model.js +3 -0
  542. package/dist/slices/storage/portable.js +17 -12
  543. package/dist/slices/storage/schema.js +2 -0
  544. package/dist/slices/storage/staging-refs.js +2 -1
  545. package/dist/slices/storage/staging.js +6 -9
  546. package/dist/slices/storage/trim.js +26 -9
  547. package/dist/slices/studio/calendar.js +7 -2
  548. package/dist/slices/studio/disclosure.js +4 -4
  549. package/dist/slices/studio/model.js +1 -1
  550. package/dist/slices/studio/pack.js +9 -8
  551. package/dist/slices/studio/pick.js +2 -11
  552. package/dist/slices/studio/plan.js +7 -16
  553. package/dist/slices/studio/queue.js +3 -14
  554. package/dist/slices/studio/releases.js +26 -0
  555. package/dist/slices/studio/settings.js +20 -57
  556. package/dist/slices/studio/videos.js +35 -4
  557. package/dist/slices/style-preview/render.js +37 -16
  558. package/dist/slices/style-preview/schema.js +14 -1
  559. package/dist/slices/style-preview/service.js +2 -1
  560. package/dist/slices/style-preview/settings.js +33 -5
  561. package/dist/slices/subtitles/captions.js +1 -1
  562. package/dist/slices/telemetry/collector-client.js +2 -4
  563. package/dist/slices/telemetry/flush.js +2 -4
  564. package/dist/slices/telemetry/model.js +0 -7
  565. package/dist/slices/telemetry/record.js +10 -3
  566. package/dist/slices/template-studio/anatomy-view.js +161 -0
  567. package/dist/slices/template-studio/anatomy.js +73 -0
  568. package/dist/slices/template-studio/bakeoff-make.js +225 -0
  569. package/dist/slices/template-studio/bakeoff-repo.js +83 -0
  570. package/dist/slices/template-studio/bakeoff.js +333 -0
  571. package/dist/slices/template-studio/budget.js +106 -0
  572. package/dist/slices/template-studio/cards.js +78 -0
  573. package/dist/slices/template-studio/checks.js +322 -0
  574. package/dist/slices/template-studio/document-sample.js +56 -0
  575. package/dist/slices/template-studio/edit.js +15 -0
  576. package/dist/slices/template-studio/for-all.js +161 -0
  577. package/dist/slices/template-studio/free-video.js +31 -0
  578. package/dist/slices/template-studio/free.js +176 -0
  579. package/dist/slices/template-studio/gate.js +40 -0
  580. package/dist/slices/template-studio/hidden.js +20 -0
  581. package/dist/slices/template-studio/jobs.js +79 -0
  582. package/dist/slices/template-studio/loudness.js +111 -0
  583. package/dist/slices/template-studio/make.js +40 -0
  584. package/dist/slices/template-studio/model.js +58 -0
  585. package/dist/slices/template-studio/pack-import.js +253 -0
  586. package/dist/slices/template-studio/pack-samples.js +60 -0
  587. package/dist/slices/template-studio/pack.js +207 -0
  588. package/dist/slices/template-studio/paid-view.js +66 -0
  589. package/dist/slices/template-studio/paid.js +355 -0
  590. package/dist/slices/template-studio/quote.js +155 -0
  591. package/dist/slices/template-studio/ready.js +104 -0
  592. package/dist/slices/template-studio/repo.js +134 -0
  593. package/dist/slices/template-studio/reviews-path.js +58 -0
  594. package/dist/slices/template-studio/runner.js +84 -0
  595. package/dist/slices/template-studio/save.js +236 -0
  596. package/dist/slices/template-studio/say-it-as.js +248 -0
  597. package/dist/slices/template-studio/schedule-gate.js +34 -0
  598. package/dist/slices/template-studio/schedule-price.js +32 -0
  599. package/dist/slices/template-studio/schedule-sample.js +91 -0
  600. package/dist/slices/template-studio/schema.js +243 -0
  601. package/dist/slices/template-studio/scope.js +124 -0
  602. package/dist/slices/template-studio/sections.js +176 -0
  603. package/dist/slices/template-studio/service.js +175 -0
  604. package/dist/slices/template-studio/status.js +191 -0
  605. package/dist/slices/template-studio/topic.js +223 -0
  606. package/dist/slices/thumbnail/by-llm.js +1 -1
  607. package/dist/slices/trash/designs.js +76 -0
  608. package/dist/slices/trash/model.js +13 -4
  609. package/dist/slices/trash/service.js +52 -2
  610. package/dist/slices/tutorials/library.js +3 -2
  611. package/dist/slices/uploads/repo.js +37 -1
  612. package/dist/slices/uploads/rules.js +3 -2
  613. package/dist/slices/video/ass-text.js +17 -0
  614. package/dist/slices/video/audio-export.js +2 -64
  615. package/dist/slices/video/edit-list.js +2 -2
  616. package/dist/slices/video/edit-settings.js +1 -1
  617. package/dist/slices/video/encoding.js +169 -0
  618. package/dist/slices/video/ffmpeg.js +23 -21
  619. package/dist/slices/video/figure-card.js +1 -15
  620. package/dist/slices/video/plan.js +2 -1
  621. package/dist/slices/video/slideshow.js +43 -18
  622. package/dist/slices/voices/attribution.js +3 -2
  623. package/dist/slices/voices/model.js +9 -8
  624. package/dist/slices/voices/palette.js +0 -3
  625. package/dist/slices/voices/script.js +2 -1
  626. package/dist/slices/youtube/answer.js +4 -16
  627. package/dist/slices/youtube/edits-repo.js +3 -12
  628. package/dist/tutorials/AI-CLIs.md +25 -25
  629. package/dist/tutorials/Agents-MCP.md +34 -33
  630. package/dist/tutorials/Assistant.md +42 -0
  631. package/dist/tutorials/Backups.md +7 -7
  632. package/dist/tutorials/Calendar.md +17 -25
  633. package/dist/tutorials/Cast-Library.md +19 -19
  634. package/dist/tutorials/Channels.md +42 -35
  635. package/dist/tutorials/Costs-and-Run-Cost.md +38 -40
  636. package/dist/tutorials/Create-Article.md +114 -0
  637. package/dist/tutorials/Create-Extras.md +112 -0
  638. package/dist/tutorials/Create-Images.md +165 -0
  639. package/dist/tutorials/Create-Narration.md +178 -0
  640. package/dist/tutorials/Create-Overview.md +244 -0
  641. package/dist/tutorials/Create-Video-and-Style.md +144 -0
  642. package/dist/tutorials/Docker.md +3 -3
  643. package/dist/tutorials/Document-Themes.md +32 -32
  644. package/dist/tutorials/Editing-a-Project.md +94 -94
  645. package/dist/tutorials/FAQ.md +18 -18
  646. package/dist/tutorials/First-Launch-and-Welcome.md +50 -64
  647. package/dist/tutorials/Home-and-Projects.md +115 -71
  648. package/dist/tutorials/Home.md +33 -33
  649. package/dist/tutorials/Install.md +14 -13
  650. package/dist/tutorials/Intros-and-Outros.md +33 -34
  651. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +62 -58
  652. package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
  653. package/dist/tutorials/Models.md +53 -21
  654. package/dist/tutorials/Multiple-Voices.md +41 -40
  655. package/dist/tutorials/Narration-Aliases-and-Glossary.md +14 -14
  656. package/dist/tutorials/Notifications.md +27 -17
  657. package/dist/tutorials/Other-Languages.md +15 -15
  658. package/dist/tutorials/PDF-Documents.md +38 -29
  659. package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
  660. package/dist/tutorials/Project-Page.md +118 -120
  661. package/dist/tutorials/Prompts-and-Templates.md +84 -0
  662. package/dist/tutorials/Prompts.md +48 -44
  663. package/dist/tutorials/Providers-and-Keys.md +105 -98
  664. package/dist/tutorials/Publishing-to-YouTube.md +31 -31
  665. package/dist/tutorials/Recovery-and-Retries.md +31 -31
  666. package/dist/tutorials/Remote-Access.md +63 -0
  667. package/dist/tutorials/Reviews-and-Checkpoints.md +98 -90
  668. package/dist/tutorials/Schedules.md +46 -27
  669. package/dist/tutorials/Settings-Reference.md +194 -174
  670. package/dist/tutorials/Shorts.md +34 -33
  671. package/dist/tutorials/Start-at-Login.md +3 -3
  672. package/dist/tutorials/Studio-Extension.md +22 -24
  673. package/dist/tutorials/Template-Studio.md +128 -0
  674. package/dist/tutorials/Templates.md +51 -77
  675. package/dist/tutorials/Trash.md +4 -4
  676. package/dist/tutorials/Troubleshooting.md +44 -44
  677. package/dist/tutorials/Uninstalling-and-Moving.md +5 -5
  678. package/dist/tutorials/Updating-and-Patch-Notes.md +18 -18
  679. package/dist/tutorials/Video-Editing.md +34 -34
  680. package/dist/tutorials/Where-Your-Files-Live.md +10 -10
  681. package/dist/tutorials/YouTube-Description.md +26 -26
  682. package/dist/tutorials/Your-First-Short.md +35 -35
  683. package/dist/tutorials/_Sidebar.md +19 -14
  684. package/dist/updater/work-in-progress.js +42 -12
  685. package/dist/web/assets/index-BNftKQrA.css +1 -0
  686. package/dist/web/assets/index-Cpa58woP.js +459 -0
  687. package/dist/web/assets/pdf-BKh8f7R6.js +56 -0
  688. package/dist/web/assets/pdf.worker.min-BmVo14Nb.mjs +28 -0
  689. package/dist/web/assets/pdf.worker.min-D7i8o3wR.js +1 -0
  690. package/dist/web/index.html +2 -2
  691. package/package.json +6 -2
  692. package/dist/slices/article/provided-entries.js +0 -66
  693. package/dist/slices/article/run.js +0 -145
  694. package/dist/slices/images/run.js +0 -234
  695. package/dist/slices/narration/legacy-plan.js +0 -58
  696. package/dist/slices/narration/run.js +0 -270
  697. package/dist/slices/rebuild/runtime-actions.js +0 -90
  698. package/dist/slices/research/run.js +0 -224
  699. package/dist/slices/subtitles/prepare.js +0 -194
  700. package/dist/slices/subtitles/transcript.js +0 -34
  701. package/dist/slices/thumbnail/run.js +0 -219
  702. package/dist/slices/video/audio-inputs.js +0 -33
  703. package/dist/slices/video/reuse-audio.js +0 -62
  704. package/dist/slices/video/run.js +0 -78
  705. package/dist/slices/video/write-export.js +0 -158
  706. package/dist/slices/video/write-subtitles.js +0 -59
  707. package/dist/tutorials/Library-Overview.md +0 -76
  708. package/dist/tutorials/Play-Images.md +0 -161
  709. package/dist/tutorials/Play-Narration.md +0 -167
  710. package/dist/tutorials/Play-Outputs.md +0 -109
  711. package/dist/tutorials/Play-Overview.md +0 -227
  712. package/dist/tutorials/Play-Title-and-Article.md +0 -136
  713. package/dist/tutorials/Play-Video-and-Style.md +0 -143
  714. package/dist/web/assets/index-BadsoORk.css +0 -1
  715. package/dist/web/assets/index-D8LqNsCl.js +0 -326
  716. package/dist/web/assets/pdf-T7MCwjIv.js +0 -54
  717. package/dist/web/assets/pdf.worker.min-Dswkl-cV.mjs +0 -29
  718. package/dist/web/assets/pdf.worker.min-zC6nfbxG.js +0 -1
@@ -1,17 +1,22 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { z } from "zod";
3
+ import { internalError } from "../../kernel/errors.js";
3
4
  import { thinkingModes } from "../../kernel/ports/llm.js";
5
+ import { editSettings } from "../../kernel/ports/settings-places.js";
4
6
  import { plainText } from "../article/plain.js";
5
7
  import { defaultReviewPrompts, narrationItemKey, reviewStages, } from "../reviews/model.js";
6
8
  import { nextAttempt, reviewOutcome } from "../reviews/outcome.js";
7
9
  import { latestVerdict, saveVerdict } from "../reviews/repo.js";
8
10
  import { parseVerdict, reviewMessages } from "../reviews/verdict.js";
11
+ import { isReadyOutput } from "../revisions/view.js";
9
12
  import { pickedShortsOf } from "../shorts/clips.js";
10
13
  import { outputPath } from "../storage/layout.js";
14
+ import { openingOf, spokenArticle } from "./review-promise.js";
11
15
  import { exportSnapshot, revisionTranscript, } from "./runtime-export-inputs.js";
12
- import { savedCatalogue } from "./runtime-plan.js";
16
+ import { workLlm } from "./runtime-plan.js";
13
17
  import { publishResult } from "./runtime-publication.js";
14
18
  import { wordsSchema } from "./runtime-subtitles.js";
19
+ import { pieceAlreadyDone } from "./work-records.js";
15
20
  // What `recipe-reviews.ts` saves in the step's values, by position.
16
21
  const valuesSchema = z.tuple([
17
22
  z.enum(reviewStages),
@@ -32,9 +37,7 @@ const textMax = 60_000;
32
37
  // saved as a pending redo, which `review-redo.ts` starts once this step has finished, and
33
38
  // until then the item's dependents are held (`reviewHold`).
34
39
  export async function executeReviewRecipe(deps, context, providers, piece) {
35
- if (deps.db
36
- .prepare("SELECT state FROM revision_work_pieces WHERE id=? AND work_id=?")
37
- .get(piece.id, context.work.workId)?.state === "done")
40
+ if (pieceAlreadyDone(deps.db, context.work.workId, piece.id))
38
41
  return "done";
39
42
  if (!context.maySubmit(piece.id))
40
43
  return "held";
@@ -44,33 +47,24 @@ export async function executeReviewRecipe(deps, context, providers, piece) {
44
47
  ? valuesSchema.safeParse(input.values)
45
48
  : undefined;
46
49
  if (values === undefined || !values.success)
47
- throw new Error("Slopify hit an internal error (a review step was set up wrongly). Try again; if it happens again, use Download diagnostics in Settings and report it.");
50
+ throw new Error(internalError("a review step was set up wrongly"));
48
51
  const [stage, itemKey, , provider, model, thinking, prompt, mode, retries] = values.data;
49
52
  const snapshot = exportSnapshot(deps, context, piece);
50
53
  const material = reviewMaterial(deps, context, snapshot, stage, itemKey, prompt);
51
54
  const previous = latestVerdict(deps.db, context.work.projectId, itemKey);
52
55
  const attempt = nextAttempt(previous, material.itemFingerprint);
53
- const row = deps.db
54
- .prepare("SELECT recipe_context FROM revision_work WHERE id=?")
55
- .get(context.work.workId);
56
- const catalogued = savedCatalogue(row?.recipe_context).llm.find((one) => one.provider === provider && one.id === model);
57
56
  const level = thinking === null ? undefined : z.enum(thinkingModes).parse(thinking);
57
+ const call = workLlm(deps.db, context.work.workId, { provider, model, thinking: level }, piece.key);
58
58
  if (!context.maySubmit(piece.id))
59
59
  return "held";
60
- const answer = await providers.forPiece(piece.id).llm({
61
- provider,
62
- model,
63
- ...(level === undefined ? {} : { thinking: level }),
64
- thinkingConfig: level === undefined ? null : (catalogued?.llm.thinking?.[level] ?? null),
60
+ const answer = await providers.forPiece(piece.id).llm(call({
65
61
  messages: reviewMessages(material.request),
66
- webSearch: false,
67
62
  ...(material.images.length === 0 ? {} : { images: material.images }),
68
- previewLabel: piece.key,
69
63
  check: (value) => {
70
64
  const parsed = parseVerdict(value.text);
71
65
  return parsed.ok ? undefined : parsed.reason;
72
66
  },
73
- });
67
+ }));
74
68
  if (!answer.ok)
75
69
  return "held";
76
70
  const verdict = parseVerdict(answer.value.text);
@@ -126,10 +120,7 @@ export async function executeReviewRecipe(deps, context, providers, piece) {
126
120
  // and outro, and the prepared or hand-edited spoken text rather than the article as written.
127
121
  // Falls back to the article when those texts can't be read.
128
122
  function spokenText(deps, snapshot) {
129
- const present = ["intro", "body", "outro"].filter((kind) => snapshot.view.outputs.some((one) => one.selected &&
130
- one.available &&
131
- one.state === "ready" &&
132
- one.output.role === `audio_${kind}`));
123
+ const present = ["intro", "body", "outro"].filter((kind) => snapshot.view.outputs.some((one) => isReadyOutput(one) && one.output.role === `audio_${kind}`));
133
124
  try {
134
125
  const text = present.map((kind) => revisionTranscript(deps, snapshot, kind)).join("\n\n");
135
126
  if (text.trim() !== "")
@@ -145,9 +136,7 @@ export function reviewMaterial(deps, context, snapshot, stage, itemKey, prompt)
145
136
  const recipes = snapshot.plan.recipes;
146
137
  const config = view.revision.config;
147
138
  const output = (workKey, role) => view.outputs.find((row) => row.workKey === workKey &&
148
- row.selected &&
149
- row.available &&
150
- row.state === "ready" &&
139
+ isReadyOutput(row) &&
151
140
  (role === undefined || row.output.role === role));
152
141
  const path = (row) => outputPath(deps.paths, context.work.projectId, row.output.path);
153
142
  const text = (row) => clip(readFileSync(path(row), "utf8"));
@@ -178,13 +167,33 @@ export function reviewMaterial(deps, context, snapshot, stage, itemKey, prompt)
178
167
  itemFingerprint: article.fingerprint,
179
168
  };
180
169
  }
170
+ // The opening as the viewer will hear it, against what the title and the thumbnail brief
171
+ // promise; the whole article shows whether the promise is kept later on.
172
+ case "promise": {
173
+ const article = output("article:body", "article_md");
174
+ if (article === undefined)
175
+ throw missing("article", "Use More → Write the article again in the Article section");
176
+ const spoken = spokenArticle(readFileSync(path(article), "utf8"));
177
+ const thumbnail = recipes.find((one) => one.key === "thumbnail:image");
178
+ const brief = thumbnail?.input.kind === "image" ? thumbnail.input.prompt.trim() : "";
179
+ return {
180
+ request: request([
181
+ { label: "Video title", text: config.title },
182
+ ...(brief === "" ? [] : [{ label: "Thumbnail brief", text: clip(brief) }]),
183
+ { label: "Opening (first ~30 s)", text: openingOf(spoken) },
184
+ { label: "Whole article", text: clip(spoken) },
185
+ ]),
186
+ images: [],
187
+ itemFingerprint: article.fingerprint,
188
+ };
189
+ }
181
190
  case "images":
182
191
  case "thumbnail": {
183
192
  const image = output(itemKey);
184
193
  if (image === undefined)
185
194
  throw missing(stage === "images" ? "image" : "thumbnail", stage === "images"
186
- ? "Use More → make it again in its section on Images"
187
- : "Use More → make it again in its section on Thumbnail");
195
+ ? "Press Make this image again on it in the project's Images section"
196
+ : "Press Make the thumbnail again in the project's Images section");
188
197
  const recipe = recipes.find((one) => one.key === itemKey);
189
198
  const brief = recipe?.input.kind === "image" ? recipe.input.prompt : "";
190
199
  const reference = stage === "images" ? output("reference:image") : undefined;
@@ -218,7 +227,7 @@ export function reviewMaterial(deps, context, snapshot, stage, itemKey, prompt)
218
227
  case "narration": {
219
228
  const timing = output("subtitles:timing", "subtitle_words");
220
229
  if (timing === undefined || itemKey !== narrationItemKey)
221
- throw missing("narration's word timing", "Use More → Render the video again in the Video section");
230
+ throw missing("narration's word timing", "Press Make the video again in the project's Video section");
222
231
  const { words, omissions } = wordsSchema.parse(JSON.parse(readFileSync(path(timing), "utf8")));
223
232
  const unsure = words.filter((word) => (word.confidence ?? 1) < 0.3).length;
224
233
  return {
@@ -246,19 +255,16 @@ export function reviewMaterial(deps, context, snapshot, stage, itemKey, prompt)
246
255
  case "shorts": {
247
256
  const render = output(`${itemKey}:render`);
248
257
  if (render === undefined)
249
- throw missing("short", "Use Make this short again in Edit project → Shorts");
258
+ throw missing("short", `Use Make this short again in ${editSettings.shorts}`);
250
259
  const number = Number(itemKey.slice("shorts:".length));
251
260
  const pick = view.pieces.find((row) => row.key === "shorts:pick" && row.selected && row.piece.state === "done");
252
261
  const title = pickedShortsOf(pick?.piece.payload)?.shorts.find((clip) => clip.number === number)?.title ??
253
262
  "";
254
263
  const stills = view.outputs
255
- .filter((row) => row.workKey.startsWith(`${itemKey}:image:`) &&
256
- row.selected &&
257
- row.available &&
258
- row.state === "ready")
264
+ .filter((row) => row.workKey.startsWith(`${itemKey}:image:`) && isReadyOutput(row))
259
265
  .slice(0, 6);
260
266
  if (stills.length === 0)
261
- throw missing("short's images", "Use Make this short again in Edit project → Shorts");
267
+ throw missing("short's images", `Use Make this short again in ${editSettings.shorts}`);
262
268
  const names = stills.map((_, index) => `image ${String(index + 1)} of the short`);
263
269
  return {
264
270
  request: request([{ label: "Title of the short", text: title }], names),
@@ -2,6 +2,7 @@ import { executeAgentReviewRecipe } from "./runtime-agent-review.js";
2
2
  import { executeAnimateRecipe } from "./runtime-animate.js";
3
3
  import { executeDocumentRecipe } from "./runtime-document.js";
4
4
  import { executeExportRecipe } from "./runtime-export.js";
5
+ import { lateKeywordProviders, lateValuesOf } from "./runtime-keywords.js";
5
6
  import { executeLocalRecipe } from "./runtime-local.js";
6
7
  import { pieceLabel } from "./runtime-piece-label.js";
7
8
  import { executeProviderRecipe } from "./runtime-provider.js";
@@ -11,7 +12,9 @@ import { executeSubtitleRecipe } from "./runtime-subtitles.js";
11
12
  import { executeVoicesRecipe } from "./runtime-voices.js";
12
13
  import { executeYoutubeRecipe } from "./runtime-youtube.js";
13
14
  import { workPieces } from "./work-records.js";
14
- export async function runRevisionInvocation(deps, context, providers) {
15
+ export async function runRevisionInvocation(deps, context, sent) {
16
+ // `{{Slopify_Release_Date}}` and `{{Slopify_Duration}}` are filled as a request is sent.
17
+ const providers = lateKeywordProviders(sent, () => lateValuesOf(deps, context));
15
18
  const pieces = workPieces(deps.db, context.work.workId).filter((piece) => !piece.key.startsWith("article:continuation:"));
16
19
  let waiting = false;
17
20
  for (const piece of pieces) {
@@ -1,10 +1,11 @@
1
- import { readFileSync } from "node:fs";
2
- import { z } from "zod";
1
+ import { errorText, internalError } from "../../kernel/errors.js";
3
2
  import { withLanguage, wordTimingUnavailable } from "../../kernel/ports/languages.js";
3
+ import { editSettings } from "../../kernel/ports/settings-places.js";
4
4
  import { usesFigureCards, usesShorts } from "../admission/rules.js";
5
5
  import { captionBoldFont } from "../fonts/coverage.js";
6
- import { resolveBoldFont } from "../fonts/index.js";
7
6
  import { masterGoal } from "../loudness/model.js";
7
+ import { assetPathOrUndefined } from "../revisions/mutation-assets.js";
8
+ import { readyOutput } from "../revisions/view.js";
8
9
  import { clipWords } from "../shorts/captions.js";
9
10
  import { effectiveClips, pickedShortsOf } from "../shorts/clips.js";
10
11
  import { anchorKept, lostMessage, withNewPicks } from "../shorts/keep.js";
@@ -18,21 +19,21 @@ import { probeDurationMs } from "../video/ffmpeg.js";
18
19
  import { usesVoices } from "../voices/model.js";
19
20
  import { sentencesText, transcriptSentences } from "../youtube/transcript.js";
20
21
  import { shortsPickKey } from "./recipe-shorts.js";
22
+ import { exportMachine, warningsOf } from "./runtime-export.js";
21
23
  import { figureShots } from "./runtime-export-edit.js";
22
24
  import { exportSnapshot, revisionAudio } from "./runtime-export-inputs.js";
23
25
  import { imageCall } from "./runtime-image.js";
24
- import { savedCatalogue } from "./runtime-plan.js";
26
+ import { workLlm } from "./runtime-plan.js";
25
27
  import { softenIfAsked } from "./runtime-provider.js";
26
28
  import { preparedResult, preparedTexts, publishResult } from "./runtime-publication.js";
27
- import { wordsSchema } from "./runtime-subtitles.js";
29
+ import { readTimingWords } from "./runtime-subtitles.js";
28
30
  import { clearSoftening } from "./soften.js";
31
+ import { pieceAlreadyDone } from "./work-records.js";
29
32
  // The Shorts step's four kinds of work (`recipe-shorts.ts`): the pick, each clip's image
30
33
  // prompts, each image, and each clip's render. Each is one piece of its own, so a failure
31
34
  // names the clip it belongs to and Try again redoes only what did not finish.
32
35
  export async function executeShortsRecipe(deps, context, providers, piece) {
33
- if (deps.db
34
- .prepare("SELECT state FROM revision_work_pieces WHERE id=? AND work_id=?")
35
- .get(piece.id, context.work.workId)?.state === "done")
36
+ if (pieceAlreadyDone(deps.db, context.work.workId, piece.id))
36
37
  return "done";
37
38
  if (!context.maySubmit(piece.id))
38
39
  return "held";
@@ -48,7 +49,7 @@ export async function executeShortsRecipe(deps, context, providers, piece) {
48
49
  throw setupError();
49
50
  const clip = savedClips(snapshot).find((one) => one.number === number);
50
51
  if (clip === undefined)
51
- throw new Error("The clip this short was cut from is no longer among the picked shorts. Use Pick different moments in Edit project → Shorts, then Try again.");
52
+ throw new Error(`The clip this short was cut from is no longer among the picked shorts. Use Pick different moments in ${editSettings.shorts}, then Try again.`);
52
53
  if (matched[2] === "prompts")
53
54
  return prompts(deps, context, providers, piece, snapshot, clip);
54
55
  if (matched[2] === "render")
@@ -66,10 +67,10 @@ async function pick(deps, context, providers, piece, snapshot) {
66
67
  throw setupError();
67
68
  const llm = config.llm;
68
69
  if (llm === undefined || llm.provider.trim() === "" || llm.model.trim() === "")
69
- throw new Error("Shorts need an AI text model to pick the clips, and none is chosen. Choose one in Edit project → Providers, then Try again.");
70
- const sentences = transcriptSentences(timingWords(deps, context, view));
70
+ throw new Error(`Shorts need an AI model to pick the clips, and none is chosen. Choose a Service and a Model in ${editSettings.writer}, then Try again.`);
71
+ const sentences = transcriptSentences(readTimingWords(deps, context, view, shortsTimingPurpose).words);
71
72
  if (sentences.length === 0)
72
- throw new Error("The narration's word timing holds no words, so there is nothing to cut shorts from. Use More → Render the video again in the Video section, then Try again.");
73
+ throw new Error("The narration's word timing holds no words, so there is nothing to cut shorts from. Press Make the video again in the project's Video section, then Try again.");
73
74
  const audio = await revisionAudio(deps, context, view);
74
75
  const durationSeconds = audio.reduce((sum, segment) => sum + segment.seconds, 0);
75
76
  // Kept shorts are found again in this timing; the model is asked only for the rest.
@@ -236,12 +237,12 @@ async function render(deps, context, piece, snapshot, clip) {
236
237
  .filter((recipe) => recipe.key.startsWith(prefix))
237
238
  .toSorted((left, right) => imageIndex(left.key) - imageIndex(right.key))
238
239
  .map((recipe) => {
239
- const row = view.outputs.find((one) => one.workKey === recipe.key && one.selected && one.available && one.state === "ready");
240
+ const row = readyOutput(view, { workKey: recipe.key });
240
241
  if (row === undefined)
241
- throw new Error(`Short ${String(clip.number)} is missing one of its images. Try again to make it again; if it keeps happening, use Make this short again in Edit project → Shorts.`);
242
+ throw new Error(`Short ${String(clip.number)} is missing one of its images. Try again to make it again; if it keeps happening, use Make this short again in ${editSettings.shorts}.`);
242
243
  return outputPath(deps.paths, context.work.projectId, row.output.path);
243
244
  });
244
- const allWords = timingWords(deps, context, view);
245
+ const allWords = readTimingWords(deps, context, view, shortsTimingPurpose).words;
245
246
  const words = clipWords(allWords, clip.start, clip.end);
246
247
  // The upright cards whose descriptions fall in the clip, on the clip's own timeline.
247
248
  const figures = usesFigureCards(config)
@@ -268,8 +269,11 @@ async function render(deps, context, piece, snapshot, clip) {
268
269
  const prepared = [];
269
270
  const seconds = (clip.end - clip.start) / speed;
270
271
  try {
272
+ const machine = await exportMachine(deps, context.work.projectId);
271
273
  const master = await renderShort({
272
274
  bin: deps.ffmpeg,
275
+ machine: machine.machine,
276
+ notice: machine.notice,
273
277
  master: masterGoal(config, "video"),
274
278
  timeline,
275
279
  start: clip.start,
@@ -303,12 +307,13 @@ async function render(deps, context, piece, snapshot, clip) {
303
307
  prepared.push(preparedResult(deps, context, piece, "short_video", asset, durationMs, {
304
308
  short: clip.number,
305
309
  sentences: [clip.first, clip.last],
310
+ ...warningsOf(machine.notices),
306
311
  ...(master === undefined ? {} : { master }),
307
312
  }));
308
313
  await publishResult(deps, context, piece, prepared, { short: clip.number, start: clip.start, end: clip.end, durationMs }, asset);
309
314
  // One per rendered short, with no stage: stage "video" would read as a finished video.
310
315
  // Its pick and prompt tokens are not counted here.
311
- deps.count?.("stage.completed", { shorts: 1 });
316
+ deps.count?.("stage.completed", { shorts: 1 }, context.work.projectId);
312
317
  return "done";
313
318
  }
314
319
  finally {
@@ -340,58 +345,34 @@ async function backgroundMusic(deps, context, view) {
340
345
  const shorts = view.revision.config.shorts;
341
346
  if (assetId === undefined || shorts === undefined)
342
347
  return undefined;
343
- const fix = "Choose another file under Background music in Edit project → Shorts, or remove the music, then Try again.";
344
- const row = z
345
- .object({ path: z.string() })
346
- .safeParse(deps.db
347
- .prepare("SELECT path FROM project_assets WHERE project_id=? AND id=?")
348
- .get(context.work.projectId, assetId));
349
- if (!row.success)
348
+ const fix = `Choose another file under Background music in ${editSettings.shorts}, or remove the music, then Try again.`;
349
+ const saved = assetPathOrUndefined(deps, context.work.projectId, assetId);
350
+ if (saved === undefined)
350
351
  throw new Error(`The shorts' background music file is no longer in the project. ${fix}`);
351
- const path = outputPath(deps.paths, context.work.projectId, row.data.path);
352
+ const path = outputPath(deps.paths, context.work.projectId, saved);
352
353
  let durationMs;
353
354
  try {
354
355
  durationMs = await probeDurationMs(deps.ffmpeg, path, context.signal, deps.log);
355
356
  }
356
357
  catch (error) {
357
358
  context.signal.throwIfAborted();
358
- throw new Error(`The shorts' background music couldn't be read as audio (${error instanceof Error ? error.message : String(error)}). ${fix}`);
359
+ throw new Error(`The shorts' background music couldn't be read as audio (${errorText(error)}). ${fix}`);
359
360
  }
360
361
  if (durationMs <= 0)
361
362
  throw new Error(`The shorts' background music file holds no sound. ${fix}`);
362
363
  return { path, volume: musicVolumeOf(shorts) };
363
364
  }
364
- export function timingWords(deps, context, view) {
365
- const timing = view.outputs.find((row) => row.selected &&
366
- row.available &&
367
- row.state === "ready" &&
368
- row.workKey === "subtitles:timing" &&
369
- row.output.role === "subtitle_words");
370
- if (timing === undefined)
371
- throw new Error("The narration's word timing, which the shorts are cut and captioned from, is missing. Use More → Render the video again in the Video section, then Try again.");
372
- return wordsSchema.parse(JSON.parse(readFileSync(outputPath(deps.paths, context.work.projectId, timing.output.path), "utf8"))).words;
373
- }
365
+ // What the shorts read the narration's word timing for, in the sentence a missing one fails with.
366
+ const shortsTimingPurpose = "the shorts are cut and captioned from";
374
367
  // The project's text model, with the thinking settings saved for this step's catalogue.
375
368
  function llmCall(deps, context, piece, view, check) {
376
369
  const llm = view.revision.config.llm ?? { provider: "", model: "" };
377
- const row = deps.db
378
- .prepare("SELECT recipe_context FROM revision_work WHERE id=?")
379
- .get(context.work.workId);
380
- const model = savedCatalogue(row?.recipe_context).llm.find((one) => one.provider === llm.provider && one.id === llm.model);
381
- return (messages) => ({
382
- provider: llm.provider,
383
- model: llm.model,
384
- ...(llm.thinking === undefined ? {} : { thinking: llm.thinking }),
385
- thinkingConfig: llm.thinking === undefined ? null : (model?.llm.thinking?.[llm.thinking] ?? null),
386
- messages,
387
- webSearch: false,
388
- previewLabel: piece.key,
389
- check: (value) => check(value.text),
390
- });
370
+ const call = workLlm(deps.db, context.work.workId, llm, piece.key);
371
+ return (messages) => call({ messages, check: (value) => check(value.text) });
391
372
  }
392
373
  function imageIndex(key) {
393
374
  return Number(key.split(":").at(-1));
394
375
  }
395
376
  function setupError() {
396
- return new Error("Slopify hit an internal error (a Shorts step was set up wrongly). Try again; if it happens again, use Download diagnostics in Settings and report it.");
377
+ return new Error(internalError("a Shorts step was set up wrongly"));
397
378
  }
@@ -6,7 +6,7 @@ import { sourceOf } from "../admission/model.js";
6
6
  import { usesShorts, usesYoutubeDescription } from "../admission/rules.js";
7
7
  import { reviewsNarration } from "../reviews/rules.js";
8
8
  import { currentRevisionId } from "../revisions/repo.js";
9
- import { getRevisionView } from "../revisions/view.js";
9
+ import { getRevisionView, isDonePiece, isReadyOutput } from "../revisions/view.js";
10
10
  import { settleScheduleRunsForProject } from "../schedules/repo.js";
11
11
  import { editNeedsTiming } from "../video/edit-settings.js";
12
12
  import { executionPlan, executionView, savedCatalogue } from "./runtime-plan.js";
@@ -146,14 +146,9 @@ export function executionStandings(deps, projectId, derived) {
146
146
  .map((entry) => {
147
147
  const pieces = workPieces(deps.db, entry.work.workId);
148
148
  const retained = pieces.length > 0 &&
149
- pieces.every((piece) => view.outputs.some((output) => output.workKey === piece.key &&
150
- output.selected &&
151
- output.available &&
152
- output.state === "ready") ||
149
+ pieces.every((piece) => view.outputs.some((output) => output.workKey === piece.key && isReadyOutput(output)) ||
153
150
  view.pieces.some((output) => output.key === piece.key &&
154
- output.selected &&
155
- output.available &&
156
- output.piece.state === "done" &&
151
+ isDonePiece(output) &&
157
152
  (output.fingerprint === piece.fingerprint ||
158
153
  madeFrom(deps, projectId, piece.key, output.fingerprint, piece.fingerprint))));
159
154
  // Save can bind an existing asset without granting its placeholder invocation.
@@ -228,7 +223,13 @@ export function projectStandings(deps, projectId) {
228
223
  }, 0);
229
224
  // A failed step, or else one waiting to run again, says why on the stage row. A failed
230
225
  // stage keeps the moment it failed: needs_attention tells a narration retry started after
231
- // the failure (still going) from one started before it (spent) by it.
226
+ // the failure (still going) from one started before it (spent) by it. A stage's start is
227
+ // the moment it went running, kept through a wait to retry, between its steps (one done,
228
+ // the next not claimed yet) and once it ends; a pending start is kept only then, so a
229
+ // stage made again after it was done takes a new one when it runs. A done
230
+ // stage keeps the moment it finished: the project's run length, the time left on a running
231
+ // step and the usual time a step takes (`eta/history.ts`) read them, and a later save that
232
+ // changes nothing in the stage moves neither.
232
233
  const failed = all.find((work) => work.state === "failed") ??
233
234
  (stage.retryAt === undefined || stage.retryAt === null
234
235
  ? undefined
@@ -241,7 +242,16 @@ export function projectStandings(deps, projectId) {
241
242
  deps.db
242
243
  .prepare(`UPDATE stages SET state=:state,failure_reason=:reason,failure_kind=:kind,retry_at=:retryAt,
243
244
  progress_current=:current,progress_total=:total,
244
- finished_at=CASE WHEN :state='done' THEN :now
245
+ started_at=CASE
246
+ WHEN :state='running' THEN CASE
247
+ WHEN started_at IS NOT NULL AND state IN ('running','pending') THEN started_at
248
+ ELSE :now END
249
+ WHEN :state IN ('done','failed') THEN started_at
250
+ WHEN :state='pending' AND :retryAt IS NOT NULL THEN started_at
251
+ WHEN :state='pending' AND state IN ('running','pending') AND :current > 0 THEN started_at
252
+ ELSE NULL END,
253
+ finished_at=CASE
254
+ WHEN :state='done' THEN CASE WHEN state='done' AND finished_at IS NOT NULL THEN finished_at ELSE :now END
245
255
  WHEN :state='failed' THEN CASE WHEN state='failed' AND finished_at IS NOT NULL THEN finished_at ELSE :now END
246
256
  ELSE NULL END
247
257
  WHERE project_id=:projectId AND kind=:stage`)
@@ -1,12 +1,15 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { z } from "zod";
3
+ import { internalError } from "../../kernel/errors.js";
3
4
  import { subtitleModelDir } from "../../kernel/paths.js";
4
5
  import { projectLanguage } from "../../kernel/ports/languages.js";
6
+ import { editSettings } from "../../kernel/ports/settings-places.js";
5
7
  import { SubtitleMismatch } from "../../kernel/ports/subtitles.js";
6
8
  import { narrationAliasesOf } from "../admission/rules.js";
7
9
  import { captionFont, captionFontDeps } from "../fonts/coverage.js";
8
10
  import { resolveFont } from "../fonts/index.js";
9
11
  import { validateCues } from "../revisions/rules.js";
12
+ import { donePiece, isReadyOutput, readyOutput } from "../revisions/view.js";
10
13
  import { discardPreparedAssets, writeAsset } from "../storage/assets.js";
11
14
  import { outputPath } from "../storage/layout.js";
12
15
  import { captionCues, serializeAss, serializeSrt, serializeVtt } from "../subtitles/captions.js";
@@ -47,6 +50,14 @@ const cuesSchema = z.object({
47
50
  })),
48
51
  omissions: wordsSchema.shape.omissions,
49
52
  });
53
+ // The narration's word timing (`subtitles:timing`), read for a step that needs it. `purpose` says
54
+ // what the step uses it for, in the sentence a missing timing fails with.
55
+ export function readTimingWords(deps, context, view, purpose) {
56
+ const timing = readyOutput(view, { workKey: "subtitles:timing", role: "subtitle_words" });
57
+ if (timing === undefined)
58
+ throw new Error(`The narration's word timing, which ${purpose}, is missing. Press Make the video again in the project's Video section, then Try again.`);
59
+ return wordsSchema.parse(JSON.parse(readFileSync(outputPath(deps.paths, context.work.projectId, timing.output.path), "utf8")));
60
+ }
50
61
  export async function executeSubtitleRecipe(deps, context, piece) {
51
62
  if (!context.maySubmit(piece.id))
52
63
  return "held";
@@ -58,15 +69,15 @@ export async function executeSubtitleRecipe(deps, context, piece) {
58
69
  return cues(deps, context, piece, snapshot);
59
70
  if (piece.key === "subtitles:files")
60
71
  return files(deps, context, piece, snapshot);
61
- throw new Error("Slopify hit an internal error (unknown kind of caption step). Try again; if it happens again, use Download diagnostics in Settings and report it.");
72
+ throw new Error(internalError("unknown kind of caption step"));
62
73
  }
63
74
  async function timing(deps, context, piece, snapshot) {
64
75
  const audio = await revisionAudio(deps, context, snapshot.view);
65
76
  if (audio.length === 0)
66
- throw new Error("Captions and the YouTube description need narration audio, but this project has none. Turn captions and the YouTube description off in Edit project, or turn narration on, then Try again.");
77
+ throw new Error(`Captions and the YouTube description need narration audio, but this project has none. Turn captions off in ${editSettings.captions} and the YouTube description off in ${editSettings.extras}, or turn narration on, then Try again.`);
67
78
  const alignSubtitles = deps.alignSubtitles;
68
79
  if (alignSubtitles === undefined)
69
- throw new Error("Slopify hit an internal error (the caption timing tool is missing from this build). Try again; if it happens again, use Download diagnostics in Settings and report it.");
80
+ throw new Error(internalError("the caption timing tool is missing from this build"));
70
81
  if (!context.maySubmit(piece.id))
71
82
  return "held";
72
83
  const words = [];
@@ -111,7 +122,7 @@ async function timing(deps, context, piece, snapshot) {
111
122
  offset += segment.seconds;
112
123
  }
113
124
  if (captionCues(words).length === 0)
114
- throw new Error("None of the narration could be matched to the article text, so captions can't be timed. The narration must be in the project language set in Edit project → Language; if you uploaded your own audio, make sure it reads the article text, then Try again.");
125
+ throw new Error(`None of the narration could be matched to the article text, so captions can't be timed. The narration must be in the project's language (${editSettings.language}); if you uploaded your own audio, make sure it reads the article text, then Try again.`);
115
126
  context.signal.throwIfAborted();
116
127
  const output = preparedText(deps, context, piece, "subtitle_words", "subtitles.json", JSON.stringify({ key: piece.logicalFingerprint ?? piece.fingerprint, words, omissions }));
117
128
  await publishResult(deps, context, piece, [output], { words, omissions }, output.asset);
@@ -123,7 +134,7 @@ async function cues(deps, context, piece, snapshot) {
123
134
  let value;
124
135
  if (manual !== undefined) {
125
136
  if (plan.work.find((one) => one.key === piece.key)?.disposition === "review")
126
- throw new Error("Your edited captions were written for an older version of the narration. Review them in Edit project → Captions and save, then Try again.");
137
+ throw new Error(`Your edited captions were written for an older version of the narration. Look them over in ${editSettings.captions} and save, then Try again.`);
127
138
  const audio = await revisionAudio(deps, context, view);
128
139
  const errors = validateCues(manual.cues, audio.reduce((sum, segment) => sum + segment.seconds, 0));
129
140
  if (errors.length > 0)
@@ -131,12 +142,9 @@ async function cues(deps, context, piece, snapshot) {
131
142
  value = { cues: [...manual.cues], omissions: [] };
132
143
  }
133
144
  else {
134
- const output = view.outputs.find((one) => one.selected &&
135
- one.available &&
136
- one.state === "ready" &&
137
- one.workKey === "subtitles:timing");
145
+ const output = readyOutput(view, { workKey: "subtitles:timing" });
138
146
  if (output === undefined)
139
- throw new Error("Caption timing is missing. Use More → Render the video again in the Video section, then Try again.");
147
+ throw new Error("Caption timing is missing. Press Make the video again in the project's Video section, then Try again.");
140
148
  const timed = wordsSchema.parse(JSON.parse(readFileSync(outputPath(deps.paths, context.work.projectId, output.output.path), "utf8")));
141
149
  value = { cues: [...captionCues(timed.words)], omissions: timed.omissions };
142
150
  }
@@ -150,10 +158,10 @@ async function files(deps, context, piece, snapshot) {
150
158
  const { view } = snapshot;
151
159
  const config = view.revision.config.subtitles;
152
160
  if (config === undefined || config.mode === "off")
153
- throw new Error("Slopify hit an internal error (captions are off for this version of the project). Try again; if it happens again, use Download diagnostics in Settings and report it.");
154
- const row = view.pieces.find((one) => one.key === "subtitles:cues" && one.selected && one.available && one.piece.state === "done");
161
+ throw new Error(internalError("captions are off for this version of the project"));
162
+ const row = donePiece(view, "subtitles:cues");
155
163
  if (row?.piece.payload === null || row?.piece.payload === undefined)
156
- throw new Error("The captions haven't been prepared yet. Use More → Render the video again in the Video section, then Try again.");
164
+ throw new Error("The captions haven't been prepared yet. Press Make the video again in the project's Video section, then Try again.");
157
165
  const value = cuesSchema.parse(JSON.parse(row.piece.payload));
158
166
  const previous = view.outputs.find((one) => one.output.role === "subtitle_font" && one.selected && one.available);
159
167
  const previousFiles = view.pieces.find((one) => one.key === "subtitles:files" &&
@@ -219,9 +227,7 @@ async function files(deps, context, piece, snapshot) {
219
227
  ])
220
228
  prepared.push(preparedText(deps, context, piece, role, filename, text));
221
229
  prepared.push(preparedResult(deps, context, piece, "subtitle_font", writeAsset(deps, context.work.projectId, `selected${font.extension}`, readFileSync(font.path))));
222
- const media = view.outputs.find((one) => one.selected &&
223
- one.available &&
224
- one.state === "ready" &&
230
+ const media = view.outputs.find((one) => isReadyOutput(one) &&
225
231
  (one.output.role === "audio_export" ||
226
232
  (one.output.role === "video" && config.mode === "files")));
227
233
  if (media !== undefined) {
@@ -295,9 +301,6 @@ function mismatchChunks(snapshot, segment) {
295
301
  return [];
296
302
  }
297
303
  }
298
- export function mismatchMessage(snapshot, segment, mismatch) {
299
- return describeMismatch(mismatchChunks(snapshot, segment), segment, mismatch);
300
- }
301
304
  // The chunk that holds the words the audio stopped matching, by index; -1 when none does.
302
305
  export function mismatchChunk(chunks, mismatch) {
303
306
  const probe = comparable(mismatch.expected).split(" ").slice(0, 6).join(" ");
@@ -316,19 +319,19 @@ retry) {
316
319
  : `${clock(mismatch.at)} into the ${segment} narration, in narration chunk ${String(at + 1)} of ${String(chunks.length)} (it starts "${opening(chunk.spokenText)}")`;
317
320
  const expected = mismatch.expected === "" ? "the end of the text" : `"${mismatch.expected}…"`;
318
321
  const heard = mismatch.heard === "" ? "no more speech" : `"${mismatch.heard.toLowerCase()}…"`;
319
- const stopped = `Subtitles stopped matching the audio at ${where}. The text expected ${expected} but the audio has ${heard}`;
322
+ const stopped = `Captions stopped matching the audio at ${where}. The text expected ${expected} but the audio has ${heard}`;
320
323
  if (retry !== undefined) {
321
324
  const n = String(retry.chunk);
322
325
  return retry.try === undefined
323
- ? `${stopped} Slopify already recorded narration chunk ${n} again ${String(narrationRetryLimit)} times and the audio still doesn't match there, so the voice probably reads something its own way (a year, an abbreviation or a name). Listen at ${clock(mismatch.at)}: if words are missing or wrong, reword that sentence in Edit project → Narration → narration chunk ${n}, save, then Continue the run. If it says them right, use Download diagnostics in Settings and report it.`
324
- : `${stopped} The voice probably skipped or garbled words there, so Slopify is recording narration chunk ${n} again by itself (try ${String(retry.try)} of ${String(narrationRetryLimit)}) and carries on with the video when it's done. If this message is still here in a few minutes, regenerate narration chunk ${n} in Edit project → Narration, then Continue the run.`;
326
+ ? `${stopped} Slopify already recorded narration chunk ${n} again ${String(narrationRetryLimit)} times and the audio still doesn't match there, so the voice probably reads something its own way (a year, an abbreviation or a name). Listen at ${clock(mismatch.at)}: if words are missing or wrong, reword that sentence in narration chunk ${n} in ${editSettings.narration}, save, then Continue the run. If it says them right, use Download diagnostics in Settings and report it.`
327
+ : `${stopped} The voice probably skipped or garbled words there, so Slopify is recording narration chunk ${n} again by itself (try ${String(retry.try)} of ${String(narrationRetryLimit)}) and carries on with the video when it's done. If this message is still here in a few minutes, make narration chunk ${n} again in ${editSettings.narration}, then Continue the run.`;
325
328
  }
326
329
  const fix = chunk === undefined
327
- ? `Listen at ${clock(mismatch.at)}: if words are missing or wrong, regenerate that part in Edit project → Narration, then Continue the run.`
328
- : `Listen at ${clock(mismatch.at)}: if words are missing or wrong, regenerate narration chunk ${String(at + 1)} in Edit project → Narration, then Continue the run.`;
330
+ ? `Listen at ${clock(mismatch.at)}: if words are missing or wrong, make that part again in ${editSettings.narration}, then Continue the run.`
331
+ : `Listen at ${clock(mismatch.at)}: if words are missing or wrong, make narration chunk ${String(at + 1)} again in ${editSettings.narration}, then Continue the run.`;
329
332
  // A voice that reads a year, an abbreviation or a name its own way reads it the same way
330
333
  // again, so a remake would cost a chunk and fail here once more.
331
- const same = "If it says them right, only another way (a year, an abbreviation or a name read its own way), regenerating gives the same reading: use Download diagnostics in Settings and report it.";
334
+ const same = "If it says them right, only another way (a year, an abbreviation or a name read its own way), making it again gives the same reading: use Download diagnostics in Settings and report it.";
332
335
  return `${stopped} The recording there probably skips or changes words. ${fix} ${same}`;
333
336
  }
334
337
  function comparable(text) {
@@ -1,10 +1,12 @@
1
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
1
+ import { 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";
5
+ import { createRows, editSettings } from "../../kernel/ports/settings-places.js";
4
6
  import { masterFile, masterReport } from "../loudness/loudnorm.js";
5
7
  import { masterGoal } from "../loudness/model.js";
6
8
  import { allocateAsset, discardPreparedAssets, sealAsset } from "../storage/assets.js";
7
- import { outputPath, projectDir } from "../storage/layout.js";
9
+ import { projectDir } from "../storage/layout.js";
8
10
  import { audioExportArgs } from "../video/audio-export-args.js";
9
11
  import { runFfmpeg } from "../video/ffmpeg.js";
10
12
  import { audioChapters, audioFileArgs, ffmetadata } from "../voices/audio-files.js";
@@ -12,18 +14,17 @@ import { turnStarts } from "../voices/timing.js";
12
14
  import { exportSnapshot, revisionAudio } from "./runtime-export-inputs.js";
13
15
  import { lineLevelledAudio } from "./runtime-lines.js";
14
16
  import { preparedResult, publishResult } from "./runtime-publication.js";
15
- import { wordsSchema } from "./runtime-subtitles.js";
17
+ import { readTimingWords } from "./runtime-subtitles.js";
18
+ import { pieceAlreadyDone } from "./work-records.js";
16
19
  // The listening files of a multi-voice run: the MP3 and the M4B of the narration timeline,
17
20
  // with a chapter at each section of the script where its first turn is heard.
18
21
  export async function executeVoicesRecipe(deps, context, piece) {
19
- if (deps.db
20
- .prepare("SELECT state FROM revision_work_pieces WHERE id=? AND work_id=?")
21
- .get(piece.id, context.work.workId)?.state === "done")
22
+ if (pieceAlreadyDone(deps.db, context.work.workId, piece.id))
22
23
  return "done";
23
24
  if (!context.maySubmit(piece.id))
24
25
  return "held";
25
26
  if (piece.key !== "voices:files" || piece.input.kind !== "local")
26
- throw new Error("Slopify hit an internal error (unknown kind of listening-file step). Try again; if it happens again, use Download diagnostics in Settings and report it.");
27
+ throw new Error(internalError("unknown kind of listening-file step"));
27
28
  context.signal.throwIfAborted();
28
29
  const { view } = exportSnapshot(deps, context, piece);
29
30
  const sections = z
@@ -32,17 +33,10 @@ export async function executeVoicesRecipe(deps, context, piece) {
32
33
  .rest(z.unknown())
33
34
  .parse(piece.input.values)[2]
34
35
  .map(([title, firstTurn]) => ({ title, firstTurn }));
35
- const timing = view.outputs.find((row) => row.selected &&
36
- row.available &&
37
- row.state === "ready" &&
38
- row.workKey === "subtitles:timing" &&
39
- row.output.role === "subtitle_words");
40
- if (timing === undefined)
41
- throw new Error("The narration's word timing, which the chapter markers come from, is missing. Use More → Render the video again in the Video section, then Try again.");
42
- const { words } = wordsSchema.parse(JSON.parse(readFileSync(outputPath(deps.paths, context.work.projectId, timing.output.path), "utf8")));
36
+ const { words } = readTimingWords(deps, context, view, "the chapter markers come from");
43
37
  const audio = await revisionAudio(deps, context, view, { levelled: true });
44
38
  if (audio.length === 0)
45
- throw new Error("The MP3 and M4B files need narration audio, but this project has none. Turn narration on in Edit project, or turn Audio files off under Speakers (Play → Narration, or Edit project → Providers), then Try again.");
39
+ throw new Error(`The MP3 and M4B files need narration audio, but this project has none. Turn narration on in ${editSettings.narration}, or untick Also make MP3 and M4B files under Speakers (${createRows.narration}, or ${editSettings.narration}), then Try again.`);
46
40
  const totalSeconds = audio.reduce((sum, segment) => sum + segment.seconds, 0);
47
41
  const chapters = audioChapters({
48
42
  title: view.revision.config.title,