@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
@@ -0,0 +1,218 @@
1
+ import { z } from "zod";
2
+ import { transact } from "../../kernel/db/tx.js";
3
+ import { defaultChannelId } from "../channels/model.js";
4
+ import { designDocumentSchema } from "./schema.js";
5
+ import { mutationHash, reusedMessage } from "./service.js";
6
+ import { documentChecks } from "./template-checks.js";
7
+ // Design templates: a designed piece saved with fields to make again by hand or from a
8
+ // schedule. Every Save is a new version; a version is marked ready only when its checks pass,
9
+ // and only a ready version is what a schedule makes (`runnableDesignVersion`).
10
+ const id = z
11
+ .string()
12
+ .min(1)
13
+ .max(64)
14
+ .regex(/^[A-Za-z0-9_-]+$/);
15
+ const name = z.string().trim().min(1).max(200);
16
+ export const createTemplateSchema = z
17
+ .object({
18
+ id,
19
+ mutationId: z.string().min(8).max(128),
20
+ name,
21
+ document: designDocumentSchema,
22
+ channelId: id.optional(),
23
+ })
24
+ .strict();
25
+ export const saveTemplateSchema = z
26
+ .object({
27
+ baseVersion: z.number().int().min(1),
28
+ name,
29
+ document: designDocumentSchema,
30
+ // Whether this Save redraws the editor's sample (`sample.ts`); "skip" saves only.
31
+ sample: z.enum(["make", "skip"]).optional(),
32
+ acknowledgedHigh: z.number().min(0).max(1000).optional(),
33
+ })
34
+ .strict();
35
+ export const markReadySchema = z.object({ version: z.number().int().min(1) }).strict();
36
+ const revisionRow = z.object({
37
+ version: z.number(),
38
+ name: z.string(),
39
+ document_json: z.string(),
40
+ gate: z.enum(["unchecked", "ready"]),
41
+ checks_json: z.string().nullable(),
42
+ marked_at: z.string().nullable(),
43
+ created_at: z.string(),
44
+ });
45
+ const checksSchema = z
46
+ .array(z.object({
47
+ id: z.string(),
48
+ ok: z.boolean(),
49
+ sentence: z.string(),
50
+ fix: z.object({ label: z.string(), href: z.string() }).nullable(),
51
+ }))
52
+ .catch([]);
53
+ export function templateRevision(db, templateId, version) {
54
+ const raw = db
55
+ .prepare("SELECT * FROM design_template_revisions WHERE template_id=? AND version=?")
56
+ .get(templateId, version);
57
+ if (raw === undefined)
58
+ return undefined;
59
+ const row = revisionRow.parse(raw);
60
+ return {
61
+ version: row.version,
62
+ name: row.name,
63
+ gate: row.gate,
64
+ markedAt: row.marked_at,
65
+ createdAt: row.created_at,
66
+ document: designDocumentSchema.parse(JSON.parse(row.document_json)),
67
+ };
68
+ }
69
+ export function getTemplate(db, templateId) {
70
+ const head = db
71
+ .prepare("SELECT id, channel_id, head_version, created_at FROM design_templates WHERE id=? AND deleted_at IS NULL")
72
+ .get(templateId);
73
+ if (head === undefined)
74
+ return undefined;
75
+ const revisions = db
76
+ .prepare("SELECT * FROM design_template_revisions WHERE template_id=? ORDER BY version")
77
+ .all(templateId)
78
+ .map((raw) => revisionRow.parse(raw));
79
+ const current = revisions.find((row) => row.version === head.head_version);
80
+ if (current === undefined)
81
+ return undefined;
82
+ const ready = revisions.filter((row) => row.gate === "ready").at(-1);
83
+ return {
84
+ id: String(head.id),
85
+ channelId: head.channel_id === null ? defaultChannelId : String(head.channel_id),
86
+ headVersion: current.version,
87
+ name: current.name,
88
+ document: designDocumentSchema.parse(JSON.parse(current.document_json)),
89
+ gate: current.gate,
90
+ checks: current.checks_json === null ? [] : checksSchema.parse(JSON.parse(current.checks_json)),
91
+ readyVersion: ready?.version ?? null,
92
+ versions: revisions.map((row) => ({
93
+ version: row.version,
94
+ name: row.name,
95
+ gate: row.gate,
96
+ markedAt: row.marked_at,
97
+ createdAt: row.created_at,
98
+ })),
99
+ createdAt: String(head.created_at),
100
+ updatedAt: current.created_at,
101
+ };
102
+ }
103
+ export function listTemplates(db) {
104
+ return db
105
+ .prepare("SELECT id FROM design_templates WHERE deleted_at IS NULL ORDER BY created_at DESC, id")
106
+ .all()
107
+ .flatMap((row) => getTemplate(db, String(row.id)) ?? []);
108
+ }
109
+ const notFound = {
110
+ ok: false,
111
+ reason: "not-found",
112
+ message: "This design template no longer exists. Open Templates → Poster templates to choose another.",
113
+ };
114
+ function invalid(error) {
115
+ return {
116
+ ok: false,
117
+ reason: "invalid",
118
+ message: `The template can't be saved: ${error.issues[0]?.message ?? "a field is missing"}.`,
119
+ };
120
+ }
121
+ export function createTemplate(deps, raw) {
122
+ const parsed = createTemplateSchema.safeParse(raw);
123
+ if (!parsed.success)
124
+ return invalid(parsed.error);
125
+ const { mutationId, ...input } = parsed.data;
126
+ const hash = mutationHash(input);
127
+ const replay = deps.db
128
+ .prepare("SELECT id, mutation_hash FROM design_templates WHERE mutation_id=?")
129
+ .get(mutationId);
130
+ if (replay !== undefined) {
131
+ if (replay.mutation_hash !== hash)
132
+ return { ok: false, reason: "mutation-reused", message: reusedMessage };
133
+ const template = getTemplate(deps.db, String(replay.id));
134
+ return template === undefined ? notFound : { ok: true, value: template };
135
+ }
136
+ if (deps.db.prepare("SELECT 1 FROM design_templates WHERE id=?").get(input.id) !== undefined)
137
+ return { ok: false, reason: "mutation-reused", message: reusedMessage };
138
+ const at = deps.clock.now().toISOString();
139
+ transact(deps.db, () => {
140
+ deps.db
141
+ .prepare("INSERT INTO design_templates (id,channel_id,head_version,created_at,mutation_id,mutation_hash) VALUES (?,?,1,?,?,?)")
142
+ .run(input.id, input.channelId ?? null, at, mutationId, hash);
143
+ deps.db
144
+ .prepare("INSERT INTO design_template_revisions (template_id,version,name,document_json,created_at) VALUES (?,1,?,?,?)")
145
+ .run(input.id, input.name, JSON.stringify(input.document), at);
146
+ });
147
+ const template = getTemplate(deps.db, input.id);
148
+ return template === undefined ? notFound : { ok: true, value: template };
149
+ }
150
+ // A Save on the version it was made on becomes the next version; one made on an older version
151
+ // is refused with the current template. Saving exactly what the head already holds is a no-op.
152
+ export function saveTemplate(deps, templateId, raw) {
153
+ const parsed = saveTemplateSchema.safeParse(raw);
154
+ if (!parsed.success)
155
+ return invalid(parsed.error);
156
+ const current = getTemplate(deps.db, templateId);
157
+ if (current === undefined)
158
+ return notFound;
159
+ const input = parsed.data;
160
+ const same = input.name === current.name &&
161
+ JSON.stringify(input.document) === JSON.stringify(current.document);
162
+ if (same)
163
+ return { ok: true, value: { template: current, saved: false, before: current.document } };
164
+ if (input.baseVersion !== current.headVersion)
165
+ return {
166
+ ok: false,
167
+ reason: "changed",
168
+ message: "This template was saved in another tab or by an agent. Reload it to see that version, then make your change again.",
169
+ current,
170
+ };
171
+ const version = current.headVersion + 1;
172
+ transact(deps.db, () => {
173
+ deps.db
174
+ .prepare("INSERT INTO design_template_revisions (template_id,version,name,document_json,created_at) VALUES (?,?,?,?,?)")
175
+ .run(templateId, version, input.name, JSON.stringify(input.document), deps.clock.now().toISOString());
176
+ deps.db
177
+ .prepare("UPDATE design_templates SET head_version=? WHERE id=?")
178
+ .run(version, templateId);
179
+ });
180
+ const template = getTemplate(deps.db, templateId);
181
+ return template === undefined
182
+ ? notFound
183
+ : { ok: true, value: { template, saved: true, before: current.document } };
184
+ }
185
+ // Mark ready: the version's checks are run and kept; it is marked only when every one passes,
186
+ // and otherwise refused with the list.
187
+ export async function markTemplateReady(deps, templateId, raw, more = async () => []) {
188
+ const parsed = markReadySchema.safeParse(raw);
189
+ if (!parsed.success)
190
+ return { ok: false, reason: "invalid", message: "Say which version to approve for schedules." };
191
+ if (getTemplate(deps.db, templateId) === undefined)
192
+ return notFound;
193
+ const revision = templateRevision(deps.db, templateId, parsed.data.version);
194
+ if (revision === undefined)
195
+ return {
196
+ ok: false,
197
+ reason: "not-found",
198
+ message: "That version of the template doesn't exist. Reload the template.",
199
+ };
200
+ const checks = [
201
+ ...documentChecks(revision.document, templateId),
202
+ ...(await more(revision.document, parsed.data.version)),
203
+ ];
204
+ const passed = checks.every((check) => check.ok);
205
+ deps.db
206
+ .prepare("UPDATE design_template_revisions SET checks_json=?, gate=?, marked_at=? WHERE template_id=? AND version=?")
207
+ .run(JSON.stringify(checks), passed ? "ready" : revision.gate, passed ? deps.clock.now().toISOString() : revision.markedAt, templateId, parsed.data.version);
208
+ const failed = checks.filter((check) => !check.ok);
209
+ if (failed.length > 0)
210
+ return {
211
+ ok: false,
212
+ reason: "not-ready",
213
+ message: `Version ${String(parsed.data.version)} can't be approved for schedules yet: ${failed.map((check) => check.sentence).join(" ")}`,
214
+ current: failed,
215
+ };
216
+ const template = getTemplate(deps.db, templateId);
217
+ return template === undefined ? notFound : { ok: true, value: template };
218
+ }
@@ -1,4 +1,5 @@
1
1
  import { readFileSync } from "node:fs";
2
+ import { errorText } from "../../kernel/errors.js";
2
3
  // The bundled Cinzel weights and Literata styles (SIL OFL 1.1, assets/document/fonts) and the
3
4
  // page texture. Read on every render rather than kept in memory: eight fonts and one image
4
5
  // are about a megabyte and a half, and a document is made once per article.
@@ -32,7 +33,7 @@ export function readDocumentAssets() {
32
33
  };
33
34
  }
34
35
  catch (error) {
35
- throw new Error(`Slopify couldn't read the fonts and page texture it makes documents with (${error instanceof Error ? error.message : String(error)}). Its installation is incomplete: reinstall or update Slopify, then use Try again on Document.`, { cause: error });
36
+ throw new Error(`Slopify couldn't read the fonts and page texture it makes documents with (${errorText(error)}). Its installation is incomplete: reinstall or update Slopify, then use Try again on PDF.`, { cause: error });
36
37
  }
37
38
  }
38
39
  export function registerFonts(doc, fonts) {
@@ -1,6 +1,7 @@
1
- // The built-in looks a project's PDF can be given. Browser-safe: Play, Edit project and the
2
- // server all offer the same names from here.
3
- export const documentThemes = ["plain"];
1
+ // The ready looks a project's PDF can be given, so a PDF looks right without a theme of one's
2
+ // own: Plain, Book (book type on parchment) and Modern (a sans-serif on white). Browser-safe:
3
+ // Create, Edit template, Edit settings and the server all offer the same names from here.
4
+ export const documentThemes = ["plain", "book", "modern"];
4
5
  // DiceMaster was built in before 2.3.0 and is one brand's look, not Slopify's. A project
5
6
  // saved with it keeps it (see legacy-dicemaster.ts), so the name is still read everywhere a
6
7
  // config is parsed, but it is never offered.
@@ -11,6 +12,8 @@ export const documentThemeNames = [...documentThemes, legacyDocumentTheme];
11
12
  export const defaultDocumentTheme = "plain";
12
13
  export const documentThemeLabels = {
13
14
  plain: "Plain",
15
+ book: "Book",
16
+ modern: "Modern",
14
17
  dicemaster: "DiceMaster",
15
18
  };
16
19
  // The built-in a saved project config is drawn with. A project saved before the Document stage
@@ -34,7 +37,7 @@ export const documentThemeNameMax = 80;
34
37
  export function documentThemeNameProblems(name) {
35
38
  const trimmed = name.trim();
36
39
  if (trimmed === "")
37
- return [{ field: "name", message: "Give the theme a name." }];
40
+ return [{ field: "name", message: "Give the look a name." }];
38
41
  if (trimmed.length > documentThemeNameMax)
39
42
  return [
40
43
  {
@@ -1,4 +1,5 @@
1
1
  import { jsPDF } from "jspdf";
2
+ import { editSettings } from "../../kernel/ports/settings-places.js";
2
3
  import { runsText } from "./blocks.js";
3
4
  import { drawLine, wrapRuns } from "./flow.js";
4
5
  import { faceWidth, registerFonts, useFace } from "./fonts.js";
@@ -10,7 +11,7 @@ export function renderDocument(input) {
10
11
  const text = documentText(input.articleMarkdown, input.researchNotes);
11
12
  const blocks = withoutTitle(text.blocks, input.title);
12
13
  if (blocks.length === 0)
13
- throw new Error("The article has no text to put in the document. Write or regenerate the article (Edit project → Article), then use Try again on Document.");
14
+ throw new Error(`The article has no text to put in the PDF. Write it in ${editSettings.article}, or press Write the article again in the project's Article section, then use Try again on PDF.`);
14
15
  const words = blocks
15
16
  .map((block) => block.kind === "heading" ? block.text : "runs" in block ? runsText(block.runs) : "")
16
17
  .join(" ")
@@ -8,7 +8,7 @@ const mm = (min, max) => {
8
8
  const range = { error: `Use a value from ${String(min)} to ${String(max)}.` };
9
9
  return z.number({ error: "Enter a number." }).finite(range).min(min, range).max(max, range);
10
10
  };
11
- const color = z.string().regex(/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i, "Use a colour like #8c1e14.");
11
+ const color = z.string().regex(/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i, "Use a color like #8c1e14.");
12
12
  const line = z.string().max(300, { error: "Keep this under 300 characters." });
13
13
  const nullableLine = line.nullable();
14
14
  const url = z.string().max(500, { error: "Keep this under 500 characters." }).nullable();
@@ -9,8 +9,11 @@ const cinzel = (style, letterSpacing) => ({
9
9
  style,
10
10
  letterSpacing,
11
11
  });
12
- // The one built-in: an unbranded flat page. Its layout is the one Slopify has always drawn,
13
- // written out in full rather than as changes to another theme.
12
+ const literata = (style) => ({ family: "Literata", style, letterSpacing: 0 });
13
+ const sans = (style) => ({ family: "helvetica", style, letterSpacing: 0 });
14
+ // The first built-in, and the default: an unbranded flat page. Its layout is the one Slopify
15
+ // has always drawn, written out in full rather than as changes to another theme. The other
16
+ // built-ins are changes to it.
14
17
  const plain = {
15
18
  page: { format: "a4", margin: 22, contentTop: 32 },
16
19
  background: { image: null, color: "#fdfaf3" },
@@ -91,10 +94,53 @@ const plain = {
91
94
  closing: null,
92
95
  },
93
96
  };
97
+ // Book: the article set like a novel, in Literata on the parchment texture, with Plain's Cinzel
98
+ // headings and drop caps in a warm brown.
99
+ const book = {
100
+ background: { image: "parchment", color: "#f5ebd7" },
101
+ colors: { heading: "#5a3214", text: "#231a12", muted: "#5e5246", faint: "#857767" },
102
+ fonts: {
103
+ body: literata("normal"),
104
+ strong: literata("bold"),
105
+ emphasis: literata("italic"),
106
+ footer: literata("italic"),
107
+ },
108
+ sizes: { body: 11 },
109
+ spacing: { bodyLine: 6.5, paragraphGap: 0.6 },
110
+ // A paragraph of a line or two starts plainly: a three-line initial beside it looks lost.
111
+ dropCap: { minLength: 120 },
112
+ };
113
+ // Modern: a sans-serif on a white page, blue headings and no drop caps, like a report.
114
+ const modern = {
115
+ background: { image: null, color: "#ffffff" },
116
+ colors: { heading: "#1d4e89", text: "#1c1c1c", muted: "#5a5a5a", faint: "#8a8a8a" },
117
+ fonts: {
118
+ body: sans("normal"),
119
+ strong: sans("bold"),
120
+ emphasis: sans("italic"),
121
+ heading: sans("bold"),
122
+ dramatic: sans("bold"),
123
+ decorative: sans("normal"),
124
+ dropCap: sans("bold"),
125
+ footer: sans("normal"),
126
+ },
127
+ sizes: { brand: 28, title: 24, section: 18, heading: 15, subheading: 12, body: 10.5 },
128
+ spacing: { bodyLine: 6, headingLine: 9, subheadingLine: 7, paragraphGap: 0.7 },
129
+ dropCap: { enabled: false },
130
+ };
94
131
  // A built-in by name. "dicemaster" is still read, from projects saved while it was built in,
95
132
  // and gives exactly the values it gave then.
96
133
  export function builtInTheme(name) {
97
- return resolveTheme({}, name === legacyDocumentTheme ? legacyDiceMasterTheme : plain);
134
+ switch (name) {
135
+ case "book":
136
+ return resolveTheme(book, plain);
137
+ case "modern":
138
+ return resolveTheme(modern, plain);
139
+ case legacyDocumentTheme:
140
+ return resolveTheme({}, legacyDiceMasterTheme);
141
+ default:
142
+ return resolveTheme({}, plain);
143
+ }
98
144
  }
99
145
  // The theme a project's document is drawn with: its copy of a Library theme, or a built-in.
100
146
  export function resolvedDocumentTheme(settings) {
@@ -1,11 +1,13 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
+ import { errorText } from "../../kernel/errors.js";
3
4
  import { projectById } from "../admission/repo.js";
4
5
  import { castMentions } from "../channels/cast-match.js";
5
6
  import { castOfChannel, projectChannelId } from "../channels/repo.js";
6
7
  import { reviewedByAgent } from "../reviews/model.js";
7
8
  import { outputPath } from "../storage/layout.js";
8
9
  import { outputsOf } from "../storage/repo.js";
10
+ import { isTemplateSample } from "../template-studio/repo.js";
9
11
  import { episodeMemoryOn, memoryOfProject, saveGeneratedMemory } from "./repo.js";
10
12
  // When a project finishes, a small LLM call writes what the episode covered (at most
11
13
  // `summaryWordsMax` words) for its channel's episode memory. It never fails the project: every
@@ -51,7 +53,7 @@ export async function summarizeEpisode(deps, projectId, signal) {
51
53
  return "failed";
52
54
  deps.log.write("warn", "episode.memory", {
53
55
  projectId,
54
- detail: `The episode summary wasn't saved: ${error instanceof Error ? error.message : String(error)}. The project is unaffected; only later episodes won't be reminded of this one. Check the project's text model in Settings → Providers.`,
56
+ detail: `The episode summary wasn't saved: ${errorText(error)}. The project is unaffected; only later episodes won't be reminded of this one. Check the service it writes with in Settings → Keys & services.`,
55
57
  });
56
58
  return "failed";
57
59
  }
@@ -60,6 +62,8 @@ async function summarize(deps, projectId, signal) {
60
62
  const project = projectById(deps.db, projectId);
61
63
  if (project === undefined)
62
64
  return "gone";
65
+ if (isTemplateSample(deps.db, projectId))
66
+ return "sample";
63
67
  const channelId = projectChannelId(deps.db, projectId);
64
68
  if (!episodeMemoryOn(deps.db, channelId))
65
69
  return "off";
@@ -78,7 +82,7 @@ async function summarize(deps, projectId, signal) {
78
82
  if (choice === undefined) {
79
83
  deps.log.write("info", "episode.memory", {
80
84
  projectId,
81
- detail: "No episode summary: this project has no text model chosen, so later episodes won't be reminded of it.",
85
+ detail: "No episode summary: this project has no AI model chosen, so later episodes won't be reminded of it.",
82
86
  });
83
87
  return "no-llm";
84
88
  }
@@ -3,7 +3,10 @@ import { sourceOf, thumbnailCountOf } from "../admission/model.js";
3
3
  import { imageSecondsProblem, usesDescribedNarration, usesNarrationPreparation, usesReference, usesShorts, usesYoutubeDescription, } from "../admission/rules.js";
4
4
  import { plainText } from "../article/plain.js";
5
5
  import { splitEndMatter } from "../article/split.js";
6
+ import { usesAppearance } from "../images/appearance.js";
6
7
  import { narrationMinutes, plannedImageCount, scalesImages } from "../images/scale.js";
8
+ import { usesSlopifyScene } from "../images/scenes.js";
9
+ import { keywordUsesOf, placeOfKey } from "../keywords/rules.js";
7
10
  import { describedBlocks, narrationBlocks } from "../narration/blocks.js";
8
11
  import { chunkNarration, defaultChunking } from "../narration/chunk.js";
9
12
  import { describeMessages } from "../narration/describe.js";
@@ -13,12 +16,15 @@ import { defaultReviewPrompts, reviewedByAgent, reviewPromptKey, reviewRetriesOf
13
16
  import { activeReviewStages } from "../reviews/rules.js";
14
17
  import { isLocalCliProvider } from "../settings/model.js";
15
18
  import { defaultShortsImagePrompt, defaultShortsPrompt, shortsImageUpperBound, shortsSettingsProblems, } from "../shorts/model.js";
19
+ import { socialPlatforms } from "../social/model.js";
20
+ import { defaultPostPrompt } from "../social/post-prompt.js";
16
21
  import { animatedClipSeconds, animatedImageIndexes, usesAnimation, videoEditProblems, } from "../video/edit-settings.js";
17
- import { usesVoices } from "../voices/model.js";
22
+ import { usesScriptPrompt, usesVoices } from "../voices/model.js";
18
23
  import { defaultDescriptionPrompt } from "../youtube/model.js";
19
24
  // ceiling: the most chapters "Chapter openers" is charged for before the article exists, the
20
25
  // top of the 5-12 chapters the built-in YouTube description prompt asks for.
21
26
  const chapterOpenersMax = 12;
27
+ import { sampleSpokenWords } from "../template-studio/scope.js";
22
28
  import { estimateRequests, groupEstimateRows } from "./requests.js";
23
29
  export { estimateRequests } from "./requests.js";
24
30
  export function estimateRun(draft, rendered, expectedWords, catalogue) {
@@ -36,14 +42,13 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
36
42
  const tts = draft.audio ?? { provider: "", model: "" };
37
43
  const image = draft.images ?? { provider: "", model: "" };
38
44
  const textNote = isLocalCliProvider(llm.provider)
39
- ? "Runs on your CLI plan, so it adds no charge; the API figure is what the same tokens would cost through the API."
45
+ ? "Runs on your subscription at no extra charge; the API figure is what the same work would cost through the API."
40
46
  : (data.llm.find((model) => model.provider === llm.provider &&
41
47
  model.id === llm.model &&
42
48
  model.enabled &&
43
- !model.deprecated)?.pricing.note ??
44
- "CLI subscription or model pricing is unavailable; usage may be billed by your account.");
49
+ !model.deprecated)?.pricing.note ?? "No price is listed for this model; your account may be billed for it.");
45
50
  const imageNote = isLocalCliProvider(image.provider)
46
- ? "Runs on your Codex plan, so it adds no charge; no API price is given for agent-drawn images."
51
+ ? "Runs on your Codex subscription at no extra charge."
47
52
  : (data.image.find((model) => model.provider === image.provider &&
48
53
  model.id === image.model &&
49
54
  model.enabled &&
@@ -55,7 +60,8 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
55
60
  key === "description" ||
56
61
  key === "shorts" ||
57
62
  key === "shortsImage" ||
58
- key.startsWith("review.")
63
+ key.startsWith("review.") ||
64
+ key.startsWith("social.")
59
65
  ? 0
60
66
  : value.length), 0);
61
67
  const local = (stage, detail) => {
@@ -91,15 +97,18 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
91
97
  code: draft.audio?.skipCode === true ? "skip" : "describe",
92
98
  language: draft.language,
93
99
  }));
94
- if (blocks === undefined)
100
+ // On a subscription the count does not change the price.
101
+ if (blocks === undefined && isLocalCliProvider(llm.provider))
102
+ local("Describing tables and figures", "One AI call per table, figure, equation or code block in the article, on your subscription at no extra charge.");
103
+ else if (blocks === undefined)
95
104
  requests.push({
96
105
  kind: "unknown",
97
- stage: "Narration descriptions",
98
- detail: "One LLM call per table, figure, equation or code block in the article; how many is known once the article is written.",
106
+ stage: "Describing tables and figures",
107
+ detail: "One AI call per table, figure, equation or code block in the article; how many is known once the article is written.",
99
108
  });
100
109
  else
101
110
  for (const block of blocks)
102
- text("Narration descriptions", describeMessages(block, rendered.narration ?? "", draft.language).reduce((n, message) => n + message.content.length, 0), 600, `${String(blocks.length)} LLM ${blocks.length === 1 ? "call" : "calls"}: one per table, figure, equation or code block.`);
111
+ text("Describing tables and figures", describeMessages(block, rendered.narration ?? "", draft.language).reduce((n, message) => n + message.content.length, 0), 600, `${String(blocks.length)} AI ${blocks.length === 1 ? "call" : "calls"}: one per table, figure, equation or code block.`);
103
112
  }
104
113
  if (draft.sources.audio === "generate") {
105
114
  if (usesNarrationPreparation(draft)) {
@@ -115,18 +124,20 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
115
124
  if (unknown)
116
125
  requests.push({
117
126
  kind: "unknown",
118
- stage: "Narration Preparation",
119
- detail: "One LLM call per future logical narration chunk and enabled entry; generated source length is not yet known.",
127
+ stage: "Narration preparation",
128
+ detail: "One AI call per narration piece and per intro or outro written for it; how many is known once the text is written.",
120
129
  });
121
130
  else
122
131
  for (const source of known)
123
- text("Narration Preparation", preparationMessages(rendered.narration ?? "", source, [], draft.language).reduce((n, message) => n + message.content.length, 0), 2400, `${known.length} LLM calls: one per logical narration chunk or entry.`);
132
+ text("Narration preparation", preparationMessages(rendered.narration ?? "", source, [], draft.language).reduce((n, message) => n + message.content.length, 0), 2400, `${known.length} AI calls: one per narration piece or intro or outro.`);
124
133
  requests.push({
125
134
  kind: "unknown",
126
- stage: "Delivery cue overhead",
127
- detail: "TTS character charges include delivery tags; their length is known after preparation.",
135
+ stage: "Delivery cues",
136
+ detail: "The voice service charges per character, delivery tags included; their length is known after preparation.",
128
137
  });
129
138
  }
139
+ // A template studio sample says only the narration's opening (`template-studio/scope.ts`).
140
+ const spokenChars = draft.sample === undefined ? articleChars : Math.min(articleChars, sampleSpokenWords * 6);
130
141
  const extras = ["intro", "outro"].reduce((n, key) => n + (rendered[key]?.length ?? 0), 0);
131
142
  const llmExtras = (draft.intro?.mode === "llm" ? 1200 : 0) + (draft.outro?.mode === "llm" ? 1200 : 0);
132
143
  const choice = { stage: "Narration", provider: tts.provider, model: tts.model };
@@ -140,19 +151,19 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
140
151
  stage: "Narration",
141
152
  provider: speaker.voice.provider,
142
153
  model: speaker.voice.model,
143
- characters: Math.round(articleChars / speakers.length),
154
+ characters: Math.round(spokenChars / speakers.length),
144
155
  detail: `${speaker.name.trim() || "A speaker"}: assumes every speaker says about the same.`,
145
156
  });
146
157
  if (extras + llmExtras > 0)
147
158
  requests.push({ ...choice, kind: "tts-estimate", characters: extras + llmExtras });
148
159
  if (draft.voices?.source === "attribute")
149
- text("Speaker split", articleChars + 1500, Math.round(articleChars * 1.1), "One LLM call hands the text's passages to the speakers.");
160
+ text("Speaker split", articleChars + 1500, Math.round(articleChars * 1.1), "One AI call hands the text's passages to the speakers.");
150
161
  if (draft.voices?.source === "adapt")
151
- text("Conversation script", articleChars + 1500, Math.round(articleChars * 1.2), "One LLM call rewrites the text as a conversation between the speakers; the article stays as written.");
162
+ text("Conversation script", articleChars + 1500, Math.round(articleChars * 1.2), "One AI call rewrites the text as a conversation between the speakers; the article stays as written.");
152
163
  }
153
164
  else
154
165
  requests.push(generatedArticle || llmExtras > 0
155
- ? { ...choice, kind: "tts-estimate", characters: articleChars + extras + llmExtras }
166
+ ? { ...choice, kind: "tts-estimate", characters: spokenChars + extras + llmExtras }
156
167
  : {
157
168
  ...choice,
158
169
  kind: "tts",
@@ -178,9 +189,29 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
178
189
  });
179
190
  if (images === 0)
180
191
  local("Images", "Provided or off.");
181
- // Scenes from the article: one call reads the article and writes every image's scene.
182
- if (images > 0 && draft.imageScenes === true)
192
+ // Scenes from the article: one call reads the article and writes every image's scene. A
193
+ // `Slopify_` scene keyword turns it on for the pictures that use it, switch or no switch.
194
+ const pictureKeys = Object.keys(rendered).filter((key) => /^imagePrompts\.\d+$/.test(key));
195
+ if (images > 0 &&
196
+ (draft.imageScenes === true || pictureKeys.some((key) => usesSlopifyScene(rendered[key]))))
183
197
  text("Image scenes", articleChars + 1500, 120 * images);
198
+ if (draft.sources.thumbnail === "from_prompt" && usesSlopifyScene(rendered.thumbnailPrompt))
199
+ text("Thumbnail scenes", articleChars + 1500, 120 * thumbnailCountOf(draft));
200
+ // The keyword digest: one call reads the article and writes every digest keyword.
201
+ const placed = Object.entries(rendered).flatMap(([key, body]) => {
202
+ const place = placeOfKey(key, { script: usesScriptPrompt(draft) });
203
+ return place === undefined ? [] : [{ field: key, place, body }];
204
+ });
205
+ if (keywordUsesOf(placed).digest !== undefined)
206
+ text("Keyword digest", articleChars + 1500, 600, "One call reads the article and writes every digest keyword the run uses.");
207
+ // `{{Appearance}}` (or `{{Slopify_Appearance}}`): one call with web search looks up the looks.
208
+ if ([
209
+ ...(images > 0 ? pictureKeys : []),
210
+ ...(draft.sources.thumbnail === "from_prompt" ? ["thumbnailPrompt"] : []),
211
+ "referencePrompt",
212
+ "shortsImage",
213
+ ].some((key) => usesAppearance(rendered[key])))
214
+ text("Appearance lookup", articleChars + 1500, 1500, "One call with web search looks up how the subject and characters look.");
184
215
  // The establishing image is one more image when it is made from a prompt.
185
216
  if (usesReference(draft) && draft.reference?.source === "prompt")
186
217
  requests.push({
@@ -201,7 +232,11 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
201
232
  local("Thumbnail", "Provided or off.");
202
233
  if (draft.sources.thumbnail === "prompt_by_llm")
203
234
  text("Thumbnail prompt", promptChars + articleChars, 1200);
204
- local("Export / subtitles", "Local processing; no API fee.");
235
+ // Putting it together on this computer: the video (with its captions), or the sound alone.
236
+ if (draft.sources.video === "generate")
237
+ local("Video", "Made on this computer; no API fee.");
238
+ else if (draft.sources.audio !== "off")
239
+ local("Audio export", "Made on this computer; no API fee.");
205
240
  // The timed transcript is the narration with a time before every passage of about 20 s,
206
241
  // plus the fixed rules of the answer.
207
242
  if (usesYoutubeDescription(draft))
@@ -209,7 +244,14 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
209
244
  ? rendered.description.length
210
245
  : defaultDescriptionPrompt.length) +
211
246
  Math.round(articleChars * 1.05) +
212
- 1500, 2400, "One LLM call on the timed transcript.");
247
+ 1500, 2400, "One AI call on the timed transcript.");
248
+ // Each site whose post is written by a post prompt: one call that reads the article.
249
+ for (const platform of socialPlatforms) {
250
+ if (draft.social?.[platform]?.text !== "prompt")
251
+ continue;
252
+ const own = rendered[`social.${platform}`]?.trim() ?? "";
253
+ text("Posts", (own === "" ? defaultPostPrompt.length : own.length) + articleChars + 400, 600, "One AI call per site whose post a post prompt writes, reading the article.");
254
+ }
213
255
  // Shorts: one call picks the clips from the numbered transcript, one per clip writes its
214
256
  // image prompts, and the images are charged at the most the step can ask for, every clip
215
257
  // at the longest length allowed.
@@ -221,12 +263,12 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
221
263
  imageSecondsProblem(draft.imageSeconds) === undefined) {
222
264
  text("Shorts", (rendered.shorts?.trim() ? rendered.shorts.length : defaultShortsPrompt.length) +
223
265
  Math.round(articleChars * 1.15) +
224
- 1500, 400 * shorts.count, "One LLM call on the numbered transcript picks the clips.");
266
+ 1500, 400 * shorts.count, "One AI call on the numbered transcript picks the clips.");
225
267
  const style = rendered.shortsImage?.trim()
226
268
  ? rendered.shortsImage.length
227
269
  : defaultShortsImagePrompt.length;
228
270
  for (let clip = 0; clip < shorts.count; clip++)
229
- text("Shorts", style + 3000, 2400, "One LLM call per short writes its image prompts.");
271
+ text("Shorts", style + 3000, 2400, "One AI call per short writes its image prompts.");
230
272
  const count = shortsImageUpperBound(shorts, draft.imageSeconds);
231
273
  for (let index = 0; index < count; index++)
232
274
  requests.push({
@@ -262,7 +304,7 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
262
304
  const reviews = draft.reviews;
263
305
  if (reviews !== undefined && reviewedByAgent(reviews)) {
264
306
  if (activeReviewStages(draft).length > 0)
265
- local("Reviews", "An external agent answers each review through MCP; no API fee.");
307
+ local("Reviews", "Your own AI agent answers each check; no API fee.");
266
308
  }
267
309
  else if (reviews !== undefined)
268
310
  for (const stage of activeReviewStages(draft)) {
@@ -273,7 +315,9 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
273
315
  ? articleChars + promptChars
274
316
  : stage === "narration"
275
317
  ? Math.round(articleChars * 2.1)
276
- : 1500;
318
+ : stage === "promise"
319
+ ? articleChars + 1500
320
+ : 1500;
277
321
  for (let index = 0; index < items; index++)
278
322
  requests.push({
279
323
  kind: "llm",
@@ -286,7 +330,7 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
286
330
  });
287
331
  }
288
332
  if (sourceOf(draft.sources, "document") === "generate")
289
- local("Document", "Laid out locally from the article; no API fee.");
333
+ local("PDF", "Laid out on this computer from the article; no API fee.");
290
334
  return {
291
335
  ...groupEstimateRows(estimateRequests(requests, data)),
292
336
  expectedWords,