@gentbajko/slopify 4.0.0 → 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 (366) hide show
  1. package/dist/adapters/account-limits.js +1 -1
  2. package/dist/adapters/alignment/audio.js +1 -1
  3. package/dist/adapters/alignment/cache.js +1 -1
  4. package/dist/adapters/alignment/multilingual.js +2 -1
  5. package/dist/adapters/alignment/runner.js +1 -1
  6. package/dist/adapters/alignment/worker.js +1 -1
  7. package/dist/adapters/explain.js +10 -9
  8. package/dist/adapters/host-cli/index.js +2 -1
  9. package/dist/adapters/host-cli/transport.js +2 -1
  10. package/dist/adapters/image/bytes.js +2 -1
  11. package/dist/adapters/image/codex.js +6 -5
  12. package/dist/adapters/image/fal.js +4 -3
  13. package/dist/adapters/image/google.js +3 -2
  14. package/dist/adapters/image/models.js +2 -2
  15. package/dist/adapters/image/openai.js +2 -1
  16. package/dist/adapters/image/replicate.js +3 -2
  17. package/dist/adapters/image/video.js +2 -1
  18. package/dist/adapters/key-probes.js +1 -1
  19. package/dist/adapters/llm/antigravity-models.js +1 -1
  20. package/dist/adapters/llm/antigravity.js +7 -6
  21. package/dist/adapters/llm/assistant-cli.js +1 -1
  22. package/dist/adapters/llm/claude-code-models.js +1 -1
  23. package/dist/adapters/llm/claude-code.js +3 -2
  24. package/dist/adapters/llm/codex-models.js +1 -1
  25. package/dist/adapters/llm/document-workspace.js +3 -2
  26. package/dist/adapters/llm/image-workspace.js +3 -2
  27. package/dist/adapters/llm/openrouter.js +2 -1
  28. package/dist/adapters/llm/run-cli.js +3 -2
  29. package/dist/adapters/tts/gemini.js +2 -1
  30. package/dist/adapters/tts/inworld-async.js +2 -1
  31. package/dist/adapters/tts/inworld.js +3 -2
  32. package/dist/adapters/tts/system.js +2 -1
  33. package/dist/catalog/registry.js +20 -13
  34. package/dist/catalog/schema.js +2 -1
  35. package/dist/catalog/validate.js +5 -2
  36. package/dist/edge/http/actions.js +7 -7
  37. package/dist/edge/http/app.js +3 -3
  38. package/dist/edge/http/assistant.js +3 -1
  39. package/dist/edge/http/backups.js +1 -1
  40. package/dist/edge/http/channel-memory.js +2 -2
  41. package/dist/edge/http/channels.js +1 -1
  42. package/dist/edge/http/checkpoints.js +7 -5
  43. package/dist/edge/http/design-templates.js +3 -3
  44. package/dist/edge/http/designs.js +1 -1
  45. package/dist/edge/http/document-themes.js +3 -3
  46. package/dist/edge/http/draft-problem.js +1 -1
  47. package/dist/edge/http/extension-auth.js +1 -1
  48. package/dist/edge/http/home.js +9 -1
  49. package/dist/edge/http/model-checks.js +4 -4
  50. package/dist/edge/http/onboarding.js +29 -12
  51. package/dist/edge/http/outputs.js +3 -2
  52. package/dist/edge/http/patch-notes.js +2 -2
  53. package/dist/edge/http/planning.js +1 -1
  54. package/dist/edge/http/project-create.js +1 -1
  55. package/dist/edge/http/project-templates.js +2 -2
  56. package/dist/edge/http/projects.js +22 -4
  57. package/dist/edge/http/prompts.js +2 -2
  58. package/dist/edge/http/providers.js +9 -10
  59. package/dist/edge/http/refusal.js +1 -1
  60. package/dist/edge/http/remote-access.js +4 -4
  61. package/dist/edge/http/remote-guard.js +2 -2
  62. package/dist/edge/http/reviews.js +1 -1
  63. package/dist/edge/http/revision-files.js +1 -1
  64. package/dist/edge/http/revisions.js +4 -4
  65. package/dist/edge/http/schedules.js +13 -11
  66. package/dist/edge/http/settings.js +6 -2
  67. package/dist/edge/http/social.js +5 -4
  68. package/dist/edge/http/staging.js +1 -1
  69. package/dist/edge/http/storage.js +12 -4
  70. package/dist/edge/http/studio.js +71 -19
  71. package/dist/edge/http/style-preview.js +2 -2
  72. package/dist/edge/http/template-studio.js +12 -9
  73. package/dist/edge/http/trash.js +1 -1
  74. package/dist/edge/http/youtube-edits.js +1 -1
  75. package/dist/edge/mcp/excluded-routes.js +4 -3
  76. package/dist/edge/mcp/instructions.js +3 -3
  77. package/dist/edge/mcp/library-tools.js +8 -5
  78. package/dist/edge/mcp/remake-tools.js +7 -4
  79. package/dist/edge/mcp/server.js +1 -1
  80. package/dist/edge/mcp/tools-channels.js +9 -9
  81. package/dist/edge/mcp/tools-designs.js +9 -9
  82. package/dist/edge/mcp/tools-library.js +7 -7
  83. package/dist/edge/mcp/tools-planning.js +18 -18
  84. package/dist/edge/mcp/tools-projects.js +10 -10
  85. package/dist/edge/mcp/tools-publishing.js +9 -9
  86. package/dist/edge/mcp/tools-runs.js +9 -9
  87. package/dist/edge/mcp/tools-schedules.js +6 -6
  88. package/dist/edge/mcp/tools-setup.js +20 -14
  89. package/dist/edge/mcp/tools-social.js +5 -4
  90. package/dist/edge/mcp/tools-storage.js +3 -3
  91. package/dist/edge/mcp/tools-template-studio.js +18 -18
  92. package/dist/edge/remote-access/model.js +1 -1
  93. package/dist/extension/slopify-studio-chrome.zip +0 -0
  94. package/dist/extension/slopify-studio-firefox.zip +0 -0
  95. package/dist/kernel/db/migrations/0078-uploaded-from-links.sql +7 -0
  96. package/dist/kernel/pipeline.js +12 -0
  97. package/dist/kernel/ports/settings-places.js +31 -0
  98. package/dist/kernel/ports/system-speech.js +31 -3
  99. package/dist/kernel/runner/attempt.js +11 -3
  100. package/dist/kernel/runner/providers.js +3 -2
  101. package/dist/kernel/runner/standalone.js +2 -2
  102. package/dist/main.js +5 -3
  103. package/dist/model-catalog.js +20 -0
  104. package/dist/patch-notes/4.0.0.md +13 -13
  105. package/dist/patch-notes/4.1.0.md +42 -0
  106. package/dist/patch-notes/index.json +6 -0
  107. package/dist/slices/admission/listing-facts.js +95 -0
  108. package/dist/slices/admission/repo.js +9 -6
  109. package/dist/slices/admission/rules.js +7 -7
  110. package/dist/slices/admission/state-words.js +34 -5
  111. package/dist/slices/assistant/cost.js +1 -1
  112. package/dist/slices/assistant/model.js +10 -3
  113. package/dist/slices/assistant/turn.js +5 -3
  114. package/dist/slices/autopilot/warnings.js +1 -1
  115. package/dist/slices/backups/service.js +5 -5
  116. package/dist/slices/channels/cast-images.js +1 -1
  117. package/dist/slices/channels/schema.js +1 -1
  118. package/dist/slices/channels/videos.js +1 -1
  119. package/dist/slices/control/providers.js +2 -2
  120. package/dist/slices/designs/budget.js +1 -1
  121. package/dist/slices/designs/calls.js +2 -2
  122. package/dist/slices/designs/check-context.js +4 -4
  123. package/dist/slices/designs/fields.js +4 -4
  124. package/dist/slices/designs/gate.js +7 -5
  125. package/dist/slices/designs/render-input.js +1 -1
  126. package/dist/slices/designs/requests.js +1 -1
  127. package/dist/slices/designs/runner.js +1 -1
  128. package/dist/slices/designs/sample.js +2 -1
  129. package/dist/slices/designs/service.js +1 -1
  130. package/dist/slices/designs/template-checks.js +2 -2
  131. package/dist/slices/designs/template-make.js +3 -3
  132. package/dist/slices/designs/templates.js +3 -3
  133. package/dist/slices/document/fonts.js +1 -1
  134. package/dist/slices/document/model.js +7 -4
  135. package/dist/slices/document/render.js +2 -1
  136. package/dist/slices/document/theme-schema.js +1 -1
  137. package/dist/slices/document/theme.js +49 -3
  138. package/dist/slices/episodes/summarize.js +2 -2
  139. package/dist/slices/estimate/index.js +28 -22
  140. package/dist/slices/estimate/requests.js +4 -4
  141. package/dist/slices/fixes/rules.js +3 -1
  142. package/dist/slices/fonts/catalog.js +2 -1
  143. package/dist/slices/fonts/coverage.js +2 -1
  144. package/dist/slices/images/appearance.js +2 -1
  145. package/dist/slices/images/scenes.js +2 -1
  146. package/dist/slices/keywords/digest.js +2 -1
  147. package/dist/slices/keywords/rules.js +2 -2
  148. package/dist/slices/keywords/specs.js +5 -5
  149. package/dist/slices/keywords/values.js +1 -1
  150. package/dist/slices/library/transfer.js +3 -3
  151. package/dist/slices/loudness/loudnorm.js +2 -1
  152. package/dist/slices/loudness/model.js +3 -2
  153. package/dist/slices/loudness/sentence-level.js +2 -1
  154. package/dist/slices/model-checks/rules.js +13 -0
  155. package/dist/slices/model-upkeep/model.js +1 -1
  156. package/dist/slices/model-upkeep/switch.js +3 -3
  157. package/dist/slices/model-upkeep/usage.js +13 -2
  158. package/dist/slices/narration/breaths.js +2 -1
  159. package/dist/slices/narration/clean-pieces.js +2 -1
  160. package/dist/slices/narration/pauses-model.js +3 -2
  161. package/dist/slices/narration/pronunciation.js +3 -2
  162. package/dist/slices/notifications/subject.js +2 -2
  163. package/dist/slices/onboarding/copy.js +19 -2
  164. package/dist/slices/onboarding/first-run.js +13 -38
  165. package/dist/slices/onboarding/install.js +2 -1
  166. package/dist/slices/onboarding/model.js +26 -21
  167. package/dist/slices/onboarding/preferred-model.js +14 -0
  168. package/dist/slices/onboarding/quick-short.js +103 -64
  169. package/dist/slices/onboarding/sample.js +4 -1
  170. package/dist/slices/onboarding/state.js +7 -0
  171. package/dist/slices/onboarding/template.js +6 -5
  172. package/dist/slices/outputs/add.js +3 -2
  173. package/dist/slices/outputs/service.js +2 -2
  174. package/dist/slices/patch-notes/library.js +1 -1
  175. package/dist/slices/play-drafts/readiness.js +3 -3
  176. package/dist/slices/play-drafts/review-inputs.js +1 -1
  177. package/dist/slices/play-drafts/service.js +17 -7
  178. package/dist/slices/project-templates/next-chapter.js +2 -1
  179. package/dist/slices/project-templates/provider-defaults.js +22 -0
  180. package/dist/slices/project-templates/setup-sections.js +26 -0
  181. package/dist/slices/project-templates/setup.js +6 -1
  182. package/dist/slices/rebuild/preview-details.js +5 -5
  183. package/dist/slices/rebuild/recipe-preparation.js +1 -1
  184. package/dist/slices/rebuild/recipe-save.js +2 -2
  185. package/dist/slices/rebuild/recipe-validation.js +5 -4
  186. package/dist/slices/rebuild/recipe-voices.js +3 -2
  187. package/dist/slices/rebuild/recipe-work.js +4 -4
  188. package/dist/slices/rebuild/recovery.js +6 -6
  189. package/dist/slices/rebuild/remake-outdated.js +5 -5
  190. package/dist/slices/rebuild/review-redo.js +1 -1
  191. package/dist/slices/rebuild/runtime-animate.js +2 -2
  192. package/dist/slices/rebuild/runtime-article.js +4 -3
  193. package/dist/slices/rebuild/runtime-document.js +5 -4
  194. package/dist/slices/rebuild/runtime-export-bed.js +2 -1
  195. package/dist/slices/rebuild/runtime-export-edit.js +2 -1
  196. package/dist/slices/rebuild/runtime-export-inputs.js +5 -4
  197. package/dist/slices/rebuild/runtime-export-short.js +1 -1
  198. package/dist/slices/rebuild/runtime-export.js +4 -3
  199. package/dist/slices/rebuild/runtime-image.js +4 -3
  200. package/dist/slices/rebuild/runtime-local.js +3 -2
  201. package/dist/slices/rebuild/runtime-narration-text.js +3 -2
  202. package/dist/slices/rebuild/runtime-provider.js +7 -6
  203. package/dist/slices/rebuild/runtime-review.js +6 -5
  204. package/dist/slices/rebuild/runtime-shorts.js +6 -5
  205. package/dist/slices/rebuild/runtime-store.js +17 -2
  206. package/dist/slices/rebuild/runtime-subtitles.js +13 -12
  207. package/dist/slices/rebuild/runtime-voices.js +2 -1
  208. package/dist/slices/rebuild/runtime-youtube.js +3 -2
  209. package/dist/slices/rebuild/service-readiness.js +11 -3
  210. package/dist/slices/research/synthesis.js +3 -2
  211. package/dist/slices/reviews/external-model.js +1 -1
  212. package/dist/slices/reviews/holds.js +5 -4
  213. package/dist/slices/reviews/model.js +8 -5
  214. package/dist/slices/reviews/rules.js +3 -3
  215. package/dist/slices/reviews/verdict.js +2 -1
  216. package/dist/slices/revisions/image-plan.js +1 -1
  217. package/dist/slices/revisions/mutation-assets.js +2 -1
  218. package/dist/slices/revisions/mutations.js +3 -2
  219. package/dist/slices/schedules/agenda.js +7 -5
  220. package/dist/slices/schedules/design-check.js +4 -4
  221. package/dist/slices/schedules/design-run.js +3 -3
  222. package/dist/slices/schedules/repo.js +6 -5
  223. package/dist/slices/schedules/schema.js +3 -0
  224. package/dist/slices/schedules/topics.js +5 -5
  225. package/dist/slices/settings/first-run.js +6 -32
  226. package/dist/slices/settings/health.js +30 -15
  227. package/dist/slices/settings/key-test.js +1 -1
  228. package/dist/slices/settings/model.js +4 -2
  229. package/dist/slices/settings/playback.js +2 -2
  230. package/dist/slices/shorts/keep.js +2 -1
  231. package/dist/slices/shorts/pick.js +4 -3
  232. package/dist/slices/shorts/prompts.js +2 -1
  233. package/dist/slices/shorts/render.js +3 -2
  234. package/dist/slices/social/media.js +5 -4
  235. package/dist/slices/social/pack.js +1 -1
  236. package/dist/slices/social/post-prompt.js +2 -1
  237. package/dist/slices/social/reshape.js +2 -2
  238. package/dist/slices/social/serve.js +1 -1
  239. package/dist/slices/social/service.js +3 -2
  240. package/dist/slices/social/state.js +1 -1
  241. package/dist/slices/storage/backup-import.js +2 -2
  242. package/dist/slices/storage/portable.js +1 -1
  243. package/dist/slices/storage/trim.js +23 -13
  244. package/dist/slices/studio/calendar.js +3 -1
  245. package/dist/slices/studio/disclosure.js +4 -4
  246. package/dist/slices/studio/model.js +1 -1
  247. package/dist/slices/studio/pack.js +9 -8
  248. package/dist/slices/studio/plan.js +1 -1
  249. package/dist/slices/studio/settings.js +6 -6
  250. package/dist/slices/studio/videos.js +23 -4
  251. package/dist/slices/style-preview/render.js +3 -3
  252. package/dist/slices/style-preview/schema.js +1 -1
  253. package/dist/slices/subtitles/captions.js +1 -1
  254. package/dist/slices/template-studio/anatomy-view.js +46 -3
  255. package/dist/slices/template-studio/bakeoff.js +3 -3
  256. package/dist/slices/template-studio/budget.js +6 -3
  257. package/dist/slices/template-studio/cards.js +78 -0
  258. package/dist/slices/template-studio/checks.js +99 -50
  259. package/dist/slices/template-studio/for-all.js +5 -5
  260. package/dist/slices/template-studio/free-video.js +1 -1
  261. package/dist/slices/template-studio/free.js +5 -5
  262. package/dist/slices/template-studio/model.js +19 -15
  263. package/dist/slices/template-studio/pack-import.js +14 -14
  264. package/dist/slices/template-studio/pack.js +9 -7
  265. package/dist/slices/template-studio/paid.js +12 -5
  266. package/dist/slices/template-studio/quote.js +18 -11
  267. package/dist/slices/template-studio/ready.js +7 -7
  268. package/dist/slices/template-studio/runner.js +1 -1
  269. package/dist/slices/template-studio/save.js +12 -12
  270. package/dist/slices/template-studio/say-it-as.js +3 -3
  271. package/dist/slices/template-studio/schedule-gate.js +1 -1
  272. package/dist/slices/template-studio/schedule-price.js +3 -1
  273. package/dist/slices/template-studio/schedule-sample.js +5 -1
  274. package/dist/slices/template-studio/schema.js +25 -0
  275. package/dist/slices/template-studio/service.js +3 -1
  276. package/dist/slices/template-studio/status.js +26 -9
  277. package/dist/slices/template-studio/topic.js +3 -3
  278. package/dist/slices/thumbnail/by-llm.js +1 -1
  279. package/dist/slices/uploads/repo.js +37 -1
  280. package/dist/slices/uploads/rules.js +3 -2
  281. package/dist/slices/video/edit-list.js +2 -2
  282. package/dist/slices/video/edit-settings.js +1 -1
  283. package/dist/slices/video/encoding.js +14 -5
  284. package/dist/slices/video/ffmpeg.js +2 -2
  285. package/dist/slices/video/plan.js +2 -1
  286. package/dist/slices/video/slideshow.js +1 -1
  287. package/dist/slices/voices/attribution.js +3 -2
  288. package/dist/slices/voices/model.js +9 -8
  289. package/dist/slices/voices/script.js +2 -1
  290. package/dist/slices/youtube/answer.js +2 -1
  291. package/dist/slices/youtube/edits-repo.js +1 -1
  292. package/dist/tutorials/AI-CLIs.md +25 -25
  293. package/dist/tutorials/Agents-MCP.md +32 -32
  294. package/dist/tutorials/Assistant.md +5 -5
  295. package/dist/tutorials/Backups.md +7 -7
  296. package/dist/tutorials/Calendar.md +17 -25
  297. package/dist/tutorials/Cast-Library.md +19 -19
  298. package/dist/tutorials/Channels.md +40 -35
  299. package/dist/tutorials/Costs-and-Run-Cost.md +38 -40
  300. package/dist/tutorials/Create-Article.md +114 -0
  301. package/dist/tutorials/Create-Extras.md +112 -0
  302. package/dist/tutorials/Create-Images.md +165 -0
  303. package/dist/tutorials/Create-Narration.md +178 -0
  304. package/dist/tutorials/Create-Overview.md +244 -0
  305. package/dist/tutorials/Create-Video-and-Style.md +144 -0
  306. package/dist/tutorials/Docker.md +3 -3
  307. package/dist/tutorials/Document-Themes.md +32 -32
  308. package/dist/tutorials/Editing-a-Project.md +94 -94
  309. package/dist/tutorials/FAQ.md +18 -18
  310. package/dist/tutorials/First-Launch-and-Welcome.md +50 -64
  311. package/dist/tutorials/Home-and-Projects.md +101 -76
  312. package/dist/tutorials/Home.md +33 -33
  313. package/dist/tutorials/Install.md +13 -13
  314. package/dist/tutorials/Intros-and-Outros.md +33 -34
  315. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +62 -58
  316. package/dist/tutorials/Keywords-Slopify-Fills.md +21 -21
  317. package/dist/tutorials/Models.md +27 -27
  318. package/dist/tutorials/Multiple-Voices.md +41 -40
  319. package/dist/tutorials/Narration-Aliases-and-Glossary.md +14 -14
  320. package/dist/tutorials/Notifications.md +21 -17
  321. package/dist/tutorials/Other-Languages.md +15 -15
  322. package/dist/tutorials/PDF-Documents.md +38 -29
  323. package/dist/tutorials/Posting-to-Social-Sites.md +12 -12
  324. package/dist/tutorials/Project-Page.md +118 -120
  325. package/dist/tutorials/Prompts-and-Templates.md +84 -0
  326. package/dist/tutorials/Prompts.md +45 -45
  327. package/dist/tutorials/Providers-and-Keys.md +105 -98
  328. package/dist/tutorials/Publishing-to-YouTube.md +31 -31
  329. package/dist/tutorials/Recovery-and-Retries.md +31 -31
  330. package/dist/tutorials/Remote-Access.md +2 -2
  331. package/dist/tutorials/Reviews-and-Checkpoints.md +90 -93
  332. package/dist/tutorials/Schedules.md +27 -28
  333. package/dist/tutorials/Settings-Reference.md +182 -204
  334. package/dist/tutorials/Shorts.md +34 -33
  335. package/dist/tutorials/Start-at-Login.md +3 -3
  336. package/dist/tutorials/Studio-Extension.md +22 -24
  337. package/dist/tutorials/Template-Studio.md +45 -42
  338. package/dist/tutorials/Templates.md +50 -79
  339. package/dist/tutorials/Trash.md +4 -4
  340. package/dist/tutorials/Troubleshooting.md +44 -44
  341. package/dist/tutorials/Uninstalling-and-Moving.md +5 -5
  342. package/dist/tutorials/Updating-and-Patch-Notes.md +18 -18
  343. package/dist/tutorials/Video-Editing.md +34 -34
  344. package/dist/tutorials/Where-Your-Files-Live.md +10 -10
  345. package/dist/tutorials/YouTube-Description.md +26 -26
  346. package/dist/tutorials/Your-First-Short.md +35 -35
  347. package/dist/tutorials/_Sidebar.md +15 -15
  348. package/dist/web/assets/index-BNftKQrA.css +1 -0
  349. package/dist/web/assets/index-Cpa58woP.js +459 -0
  350. package/dist/web/assets/pdf-BKh8f7R6.js +56 -0
  351. package/dist/web/assets/pdf.worker.min-BmVo14Nb.mjs +28 -0
  352. package/dist/web/assets/pdf.worker.min-D7i8o3wR.js +1 -0
  353. package/dist/web/index.html +2 -2
  354. package/package.json +2 -2
  355. package/dist/tutorials/Library-Overview.md +0 -76
  356. package/dist/tutorials/Play-Images.md +0 -165
  357. package/dist/tutorials/Play-Narration.md +0 -167
  358. package/dist/tutorials/Play-Outputs.md +0 -109
  359. package/dist/tutorials/Play-Overview.md +0 -227
  360. package/dist/tutorials/Play-Title-and-Article.md +0 -136
  361. package/dist/tutorials/Play-Video-and-Style.md +0 -143
  362. package/dist/web/assets/index-CkAI_2E7.css +0 -1
  363. package/dist/web/assets/index-DcD0cdfo.js +0 -334
  364. package/dist/web/assets/pdf-TODRwF31.js +0 -54
  365. package/dist/web/assets/pdf.worker.min-Dswkl-cV.mjs +0 -29
  366. package/dist/web/assets/pdf.worker.min-zC6nfbxG.js +0 -1
@@ -5,33 +5,33 @@ import { markReadySchema } from "../../slices/template-studio/ready.js";
5
5
  import { makeSampleSchema, saveStudioSchema } from "../../slices/template-studio/save.js";
6
6
  import { sayItAsSchema } from "../../slices/template-studio/say-it-as.js";
7
7
  import { fullScriptSchema, topicSchema } from "../../slices/template-studio/topic.js";
8
- // The template studio (Library → Templates → Open studio). Not the YouTube Studio tools.
8
+ // Edit template (Templates → a template → Edit template). Not the YouTube Studio tools.
9
9
  export const templateStudioTools = [
10
10
  {
11
11
  name: "get_template_studio",
12
12
  route: "GET /api/template-studio/:id",
13
- title: "Read a template's studio",
13
+ title: "Read a template in Edit template",
14
14
  description: "The template's sections with each sample's state (current, making, waiting, needs-fix, stale, off, provided), the sample topic, and which version schedules run. Its checks are read with get_template_checks.",
15
15
  },
16
16
  {
17
17
  name: "get_template_checks",
18
18
  route: "GET /api/template-studio/:id/checks",
19
19
  title: "Read a template's checks",
20
- description: "The free checks the template's current version passes before Mark ready, each failing one with what is wrong and where it is fixed: keywords filled, providers set up, a voice that is one of yours (Settings → Voices), release slots, a known monthly cost, someone to answer each checkpoint.",
20
+ description: "The free checks the template's current version passes before Approve for schedules, each failing one with what is wrong and where it is fixed: keywords filled, providers set up, a voice that is one of yours (Settings → Voices), release times, a known monthly cost, someone to answer each pause.",
21
21
  },
22
22
  {
23
23
  name: "mark_template_ready",
24
24
  route: "POST /api/template-studio/:id/ready",
25
25
  body: markReadySchema,
26
- title: "Mark a template ready for its schedules",
27
- description: "Marks the template's newest version (pass it as version) ready, so its schedules run it from their next run on. Refused, marking nothing, while a check fails or a section's sample isn't current, with each thing to fix (problems). A version already ready is answered again. Until a template's first Mark ready its schedules run its newest version; from then on a schedule runs only a version marked ready, saved before the studio, or carried over from one by Slopify, and each version saved later waits until it is marked ready.",
28
- confirm: "Schedules run this version from their next run, and each version saved later waits until it is marked ready.",
26
+ title: "Approve a template for its schedules",
27
+ description: "Approves the template's newest version (pass it as version) for its schedules, so they run it from their next run on: Edit template's Approve for schedules. Refused, approving nothing, while a check fails or a section's sample isn't current, with each thing to fix (problems). A version already approved is answered again. Until a template's first approval its schedules run its newest version; from then on a schedule runs only a version approved for schedules, saved before Edit template, or carried over from one by Slopify, and each version saved later waits until it is approved.",
28
+ confirm: "Schedules run this version from their next run, and each version saved later waits until it is approved.",
29
29
  },
30
30
  {
31
31
  name: "get_prompt_anatomy",
32
32
  route: "GET /api/template-studio/:id/anatomy",
33
33
  title: "Read a section's prompt anatomy",
34
- description: "One of the prompts a template section sends (section: subject, script, voice, pictures, thumbnail, shorts, upload, reviews; item: the prompt's key from items), cut into segments: the person's own words (yours), their keywords' values (keyword), what Slopify fills itself (slopify) and what settings add (setting). Read from the prompt the sample sent when it was made from this setup.",
34
+ description: "One of the prompts a template section sends (section: subject, script, voice, pictures, thumbnail, shorts, upload, reviews; item: the prompt's key from items), cut into segments: the person's own words (yours), their keywords' values (keyword), what Slopify fills itself (slopify) and what settings add (setting). Read from the prompt the sample sent when it was made from these settings.",
35
35
  },
36
36
  {
37
37
  name: "quote_template_save",
@@ -44,7 +44,7 @@ export const templateStudioTools = [
44
44
  name: "save_template_studio",
45
45
  route: "POST /api/template-studio/:id/save",
46
46
  body: saveStudioSchema,
47
- title: "Save a template in its studio",
47
+ title: "Save a template in Edit template",
48
48
  description: 'Saves the template document as its next version. With sample "make" it also makes the paid sample of what changed, which spends money: price it first with quote_template_save and pass that quote\'s high as acknowledgedHigh (and acknowledgedUnknown: true when the quote counts steps with no price). A higher price, an unaccepted unknown one or a sample past today\'s budget is refused with the new quote and nothing is saved. With sample "skip" only the free samples are made. Send the same mutationId to retry the very same Save.',
49
49
  },
50
50
  {
@@ -72,7 +72,7 @@ export const templateStudioTools = [
72
72
  name: "get_sample_pronunciations",
73
73
  route: "GET /api/template-studio/:id/pronunciations",
74
74
  title: "Read the names a template's voice may say wrong",
75
- description: "The names the template's sample script says that a voice may get wrong (its Pronunciation Glossary, and words Library → Narration aliases already change), each with its IPA, the first line it is said in and the alias it has (spokenAs). aliasesOn says whether the template's voice uses Library → Narration aliases.",
75
+ description: "The names the template's sample script says that a voice may get wrong (its Pronunciation Glossary, and words the Pronunciation list (Settings → Voices → Pronunciation) already changes), each with its IPA, the first line it is said in and the alias it has (spokenAs). aliasesOn says whether the template's voice uses the Pronunciation list.",
76
76
  },
77
77
  {
78
78
  name: "quote_say_it_as",
@@ -86,20 +86,20 @@ export const templateStudioTools = [
86
86
  route: "POST /api/template-studio/:id/say-it-as",
87
87
  body: sayItAsSchema,
88
88
  title: "Say a word another way",
89
- description: "Writes a Library → Narration aliases row (written said as spoken, whole word, any case; it replaces one for the same word) and voices again only the template sample's lines that say it, which spends money: price it first with quote_say_it_as and pass its high as acknowledgedHigh. The alias applies to every project started after it. Refused, writing nothing, at a higher price, with unaccepted unknown prices, past today's budget, or when the template's voice doesn't use aliases.",
89
+ description: "Writes a row of the Pronunciation list (Settings → Voices → Pronunciation) (written said as spoken, whole word, any case; it replaces one for the same word) and voices again only the template sample's lines that say it, which spends money: price it first with quote_say_it_as and pass its high as acknowledgedHigh. The alias applies to every project started after it. Refused, writing nothing, at a higher price, with unaccepted unknown prices, past today's budget, or when the template's voice doesn't use aliases.",
90
90
  },
91
91
  {
92
92
  name: "get_template_studio_settings",
93
93
  route: "GET /api/template-studio/settings",
94
- title: "Read the template studio's settings",
95
- description: "The most the studio's samples may spend in one day (dailyBudget, US dollars), whether they may run steps on a CLI plan (samplesUsePlans; off by default, such a sample waits; it applies to designed pieces' samples too), the most designed pieces' template samples may spend in one day (designDailyBudget, $1 by default), and what each spent today and has left (today, designToday).",
94
+ title: "Read the template samples' budgets",
95
+ description: "The most Edit template's samples may spend in one day (dailyBudget, US dollars), whether they may run steps on a CLI plan (samplesUsePlans; off by default, such a sample waits; it applies to designed pieces' samples too), the most designed pieces' template samples may spend in one day (designDailyBudget, $1 by default), and what each spent today and has left (today, designToday).",
96
96
  },
97
97
  {
98
98
  name: "update_template_studio_settings",
99
99
  route: "PUT /api/template-studio/settings",
100
100
  body: studioSettingsSchema,
101
- title: "Change the template studio's daily budgets",
102
- description: "Sets any of: dailyBudget, the most the studio's samples may spend in one day, in US dollars (0 to 1000); designDailyBudget, the same for designed pieces' template samples (0 to 1000); samplesUsePlans, whether either may run steps on a Claude Code or Codex plan, which the dollar budgets can't limit. A field left out keeps what it was. A sample that would go over its budget, or needs a plan while plans are off, waits.",
101
+ title: "Change the template samples' daily budgets",
102
+ description: "Sets any of: dailyBudget, the most Edit template's samples may spend in one day, in US dollars (0 to 1000); designDailyBudget, the same for designed pieces' template samples (0 to 1000); samplesUsePlans, whether either may run steps on a Claude Code or Codex plan, which the dollar budgets can't limit. A field left out keeps what it was. A sample that would go over its budget, or needs a plan while plans are off, waits.",
103
103
  },
104
104
  {
105
105
  name: "quote_samples_for_all",
@@ -125,15 +125,15 @@ export const templateStudioTools = [
125
125
  {
126
126
  name: "import_template_pack",
127
127
  route: "POST /api/template-studio/packs/import",
128
- title: "Import a template pack",
129
- description: "Makes a new template from a template pack (the .zip a template studio's Export pack made, at most 30 MB): its prompts and intros join the Library (a name taken by other text comes in as \"<name> (2)\" and the template names that one), its voice is used as it is, and it is not checked in the studio yet: look its samples over and mark it ready (mark_template_ready). A pack holding anything that looks like a key, a file a pack never has or a newer format is refused, writing nothing. Answers templateId, the name it got, how many Library items it added and which were renamed, whether Settings → Voices has its voice (voiceKnown; when false, get_template_checks' voice check says what to do) and how many samples it brought.",
128
+ title: "Import a template file",
129
+ description: "Makes a new template from a template file (the .zip Edit template's Export as a file (.zip) made, at most 30 MB): its prompts and intros join Prompts (a name taken by other text comes in as \"<name> (2)\" and the template names that one), its voice is used as it is, and it is not checked in Edit template yet: look its samples over and approve it for schedules (mark_template_ready). A file holding anything that looks like a key, a file a template file never has or a newer format is refused, writing nothing. Answers templateId, the name it got, how many prompts and intros it added and which were renamed, whether Settings → Voices has its voice (voiceKnown; when false, get_template_checks' voice check says what to do) and how many samples it brought.",
130
130
  upload: "multipart",
131
131
  },
132
132
  {
133
133
  name: "get_template_pack_samples",
134
134
  route: "GET /api/template-studio/:id/pack-samples",
135
- title: "List the samples a template's pack brought",
136
- description: "The samples an imported pack came with (role: thumbnail, picture or narration; name; url), shown in the studio until the template makes its own. Empty for a template that didn't come from a pack.",
135
+ title: "List the samples a template's file brought",
136
+ description: "The samples an imported template file came with (role: thumbnail, picture or narration; name; url), shown in Edit template until the template makes its own. Empty for a template that didn't come from a file.",
137
137
  },
138
138
  {
139
139
  name: "quote_bakeoff",
@@ -1,4 +1,4 @@
1
- // What Settings → General → Remote access shows. The web client imports this file, so it stays
1
+ // What Settings → Remote access shows. The web client imports this file, so it stays
2
2
  // free of Node imports.
3
3
  export const remoteAccessStates = [
4
4
  // The switch is off: Slopify answers only on this computer.
@@ -0,0 +1,7 @@
1
+ -- The long video on YouTube and the project's "uploaded" mark are one fact
2
+ -- (`slices/studio/videos.ts`): saving or confirming the long video's link marks the project
3
+ -- uploaded. A long video already on YouTube before that marks its project now, from when its
4
+ -- link was recorded; a mark made by hand keeps its own time.
5
+ INSERT INTO project_uploads (project_id, uploaded_at)
6
+ SELECT project_id, recorded_at FROM youtube_videos WHERE short = 0 AND upload_state = 'done'
7
+ ON CONFLICT(project_id) DO NOTHING;
@@ -19,6 +19,18 @@ export const stageKinds = [
19
19
  "video",
20
20
  "document",
21
21
  ];
22
+ // One name per stage, everywhere a stage is named, on screen (the web app reads it through
23
+ // `@/lib/stage-words`) and in the server's sentences. The pipeline calls the narration stage
24
+ // `audio` and the PDF stage `document`; a person never sees those words.
25
+ export const stageWords = {
26
+ research: "Research",
27
+ article: "Article",
28
+ audio: "Narration",
29
+ images: "Images",
30
+ thumbnail: "Thumbnail",
31
+ video: "Video",
32
+ document: "PDF",
33
+ };
22
34
  export const stageStates = [
23
35
  "pending",
24
36
  "running",
@@ -0,0 +1,31 @@
1
+ // Where a server sentence sends a person to change something: a project's Edit settings and its
2
+ // sections (the web app's project/revision-form.tsx), and Create's rows (play/setup-rows.ts).
3
+ // Named once here, so every refusal and failure says the same path, and a section that is
4
+ // renamed on screen is renamed in its sentences by changing one line. Browser-safe.
5
+ export const editSettings = {
6
+ open: "Edit settings",
7
+ title: "Edit settings → Title and keywords",
8
+ article: "Edit settings → Article",
9
+ // The one text model a project's AI writing uses: the article, its research, the narration's
10
+ // preparation, the image prompts, the description, the shorts' picks and the speaker split.
11
+ writer: "Edit settings → Article → Written by",
12
+ narration: "Edit settings → Narration",
13
+ images: "Edit settings → Images",
14
+ video: "Edit settings → Video and style",
15
+ look: "Edit settings → Video and style → Look",
16
+ sound: "Edit settings → Video and style → Sound polish",
17
+ captions: "Edit settings → Captions",
18
+ // The thumbnail, the YouTube description, the shorts and the PDF, together as in Create.
19
+ extras: "Edit settings → Extras",
20
+ shorts: "Edit settings → Extras → Shorts",
21
+ checks: "Edit settings → Pauses and checks",
22
+ channel: "Edit settings → Channel",
23
+ language: "Edit settings → Channel → Language",
24
+ };
25
+ export const createRows = {
26
+ article: "Create → Article",
27
+ narration: "Create → Narration",
28
+ images: "Create → Images",
29
+ video: "Create → Video and style",
30
+ extras: "Create → Extras",
31
+ };
@@ -30,7 +30,30 @@ const picoVoices = [
30
30
  { id: "fr-FR", name: "French", language: "fr-FR" },
31
31
  { id: "it-IT", name: "Italian", language: "it-IT" },
32
32
  ];
33
- export const noSpeechFix = "or add an ElevenLabs or OpenAI key in Settings → Providers for a better voice.";
33
+ // OpenAI is named alone: the voices the starter templates set up are OpenAI voices, so its key
34
+ // narrates at once, while ElevenLabs, Cartesia and Inworld also need a voice saved first.
35
+ export const noSpeechFix = "or add an OpenAI key in Settings → Keys & services: its voices work at once and sound better.";
36
+ // How to get a speech program onto this computer from a terminal, for a screen that lists the
37
+ // commands apart from the sentence (the first-run checklist), or undefined where none is
38
+ // installed that way: macOS and Windows have their own, which only a broken setup lacks. The
39
+ // issue sentences below name the fix in words and leave the commands to this.
40
+ export function speechInstall(host) {
41
+ if (host.platform === "darwin" || host.platform === "win32")
42
+ return undefined;
43
+ return host.env.SLOPIFY_CONTAINER === "1"
44
+ ? {
45
+ text: "Update Slopify's Docker image, which has one built in:",
46
+ commands: ["docker compose pull", "docker compose up -d"],
47
+ }
48
+ : {
49
+ text: "Install espeak-ng with one of these:",
50
+ commands: [
51
+ "sudo apt install espeak-ng",
52
+ "sudo pacman -S espeak-ng",
53
+ "sudo dnf install espeak-ng",
54
+ ],
55
+ };
56
+ }
34
57
  export async function detectSpeech(given, host) {
35
58
  // A probe that throws (a host bridge that cannot run local programs) found nothing.
36
59
  const probe = (binary, args, timeoutMs) => Promise.resolve()
@@ -84,8 +107,8 @@ export async function detectSpeech(given, host) {
84
107
  return {
85
108
  engines,
86
109
  issue: host.env.SLOPIFY_CONTAINER === "1"
87
- ? `Slopify runs in Docker and this image has no speech program, so the system voice can't narrate. Update the image (docker compose pull, then docker compose up -d), ${noSpeechFix}`
88
- : `No speech program was found on this computer, so the system voice can't narrate. Install espeak-ng (for example sudo apt install espeak-ng, sudo pacman -S espeak-ng or sudo dnf install espeak-ng) and reload Settings → Providers, ${noSpeechFix}`,
110
+ ? `Slopify runs in Docker and this image has no speech program, so the system voice can't narrate. Update Slopify's Docker image, which has one built in, ${noSpeechFix}`
111
+ : `No speech program was found on this computer, so the system voice can't narrate. Install the free program eSpeak NG, ${noSpeechFix}`,
89
112
  };
90
113
  }
91
114
  async function onPath(probe, binary) {
@@ -185,6 +208,11 @@ export function primaryLanguage(voice) {
185
208
  // processes each time is wasted work for something that changes when a program is installed.
186
209
  const cacheMs = 60_000;
187
210
  const cached = new WeakMap();
211
+ // "Check again" after installing a speech program: the next look runs the programs again
212
+ // instead of answering from the last minute's.
213
+ export function forgetDetectedSpeech(probe) {
214
+ cached.delete(probe);
215
+ }
188
216
  export function detectSpeechCached(probe, host) {
189
217
  const now = performance.now();
190
218
  const key = [
@@ -1,6 +1,7 @@
1
1
  import { errorText } from "../errors.js";
2
2
  import { redact } from "../log.js";
3
3
  import { isProviderError, providerError } from "../ports/model.js";
4
+ import { editSettings } from "../ports/settings-places.js";
4
5
  // The retry policy, in one place. A stage slice is handed the wrapped calls of
5
6
  // `providers.ts` and never an adapter, so there is no way round it.
6
7
  // Four attempts - the first plus 3 retries - waiting 2 s, 8 s, 30 s.
@@ -112,7 +113,7 @@ function classify(error, expired, opts, limit) {
112
113
  kind: "timeout",
113
114
  message: opts.streaming === true
114
115
  ? `The ${callee[opts.kind]} sent nothing for ${seconds} seconds, so Slopify stopped waiting. It may be overloaded: wait a few minutes, then use Try again.`
115
- : `The ${callee[opts.kind]} did not answer within ${seconds} seconds. It may be overloaded: wait a few minutes, then use Try again, or choose another model in the Providers section of Edit project.`,
116
+ : `The ${callee[opts.kind]} did not answer within ${seconds} seconds. It may be overloaded: wait a few minutes, then use Try again, or choose another model in ${modelPlace[opts.kind]}.`,
116
117
  });
117
118
  }
118
119
  if (isProviderError(error)) {
@@ -142,10 +143,17 @@ function classify(error, expired, opts, limit) {
142
143
  // What the user calls the other end of each kind of call.
143
144
  const callee = {
144
145
  llm: "AI model",
145
- tts: "narration provider",
146
- image: "image provider",
146
+ tts: "voice service",
147
+ image: "image service",
147
148
  video: "image-to-video model",
148
149
  };
150
+ // Where a project's settings choose the model of each kind of call.
151
+ const modelPlace = {
152
+ llm: editSettings.writer,
153
+ tts: editSettings.narration,
154
+ image: editSettings.images,
155
+ video: editSettings.look,
156
+ };
149
157
  // Node's `fetch` rejects a request that never reached the server with this TypeError.
150
158
  function unreachable(error) {
151
159
  return error instanceof TypeError && error.message === "fetch failed";
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { errorText } from "../errors.js";
3
3
  import { isProviderError, providerError } from "../ports/model.js";
4
4
  import { planAccountOf } from "../ports/plan-limits.js";
5
+ import { createRows, editSettings } from "../ports/settings-places.js";
5
6
  import { attempt } from "./attempt.js";
6
7
  import { observed } from "./model-access.js";
7
8
  export function stageProviders(deps, context, pieceId) {
@@ -183,7 +184,7 @@ export function stageProviders(deps, context, pieceId) {
183
184
  if (call.dialogue !== undefined && port.capabilities.dialogue !== true)
184
185
  throw providerError({
185
186
  kind: "unsupported",
186
- message: `${call.provider} can't speak several voices in one request. Turn off Native multi-speaker under Speakers (Play → Narration, or Edit project → Providers), then Try again.`,
187
+ message: `${call.provider} can't speak several voices in one request. Turn off One request for consecutive turns under Speakers (${createRows.narration}, or ${editSettings.narration}), then Try again.`,
187
188
  });
188
189
  const spoken = await port.synthesize({
189
190
  model: call.model,
@@ -303,7 +304,7 @@ export function stageProviders(deps, context, pieceId) {
303
304
  if (animate === undefined)
304
305
  return Promise.reject(providerError({
305
306
  kind: "unsupported",
306
- message: `${call.provider} can't turn images into video clips. Choose fal.ai or Replicate as the image provider in Edit project → Providers, or turn Animate images off in Edit project → Inputs → Look.`,
307
+ message: `${call.provider} can't turn images into video clips. Choose fal.ai or Replicate as the image service in ${editSettings.images}, or turn Animate images off in ${editSettings.look}.`,
307
308
  }));
308
309
  const started = deps.clock.now().getTime();
309
310
  const animated = learn({ provider: call.provider, family: "image", model: call.model }, schedule(call.provider, () => attempt(ctx, (signal) => animate({
@@ -105,9 +105,9 @@ function standaloneError(error) {
105
105
  if (!isProviderError(error))
106
106
  return error;
107
107
  const kept = error.message
108
- .replace(/,? then use (?:Retry stage|Try again)(?:, or choose another model in the Providers section of Edit project)?/g, "")
108
+ .replace(/,? then use (?:Retry stage|Try again)(?:, or choose another model in Edit settings[^.,;]*)?/g, "")
109
109
  .split(/(?<=\.)\s+/)
110
- .filter((sentence) => !/Retry stage|Try again|Edit project/.test(sentence))
110
+ .filter((sentence) => !/Retry stage|Try again|Edit settings/.test(sentence))
111
111
  .join(" ")
112
112
  .trim();
113
113
  if (kept === error.message || kept === "")
package/dist/main.js CHANGED
@@ -45,7 +45,7 @@ import { stageProviders } from "./kernel/runner/providers.js";
45
45
  import { createProviderQueue } from "./kernel/runner/queue.js";
46
46
  import { standaloneImage, standaloneLlm } from "./kernel/runner/standalone.js";
47
47
  import { readVersion } from "./kernel/version.js";
48
- import { modelSources } from "./model-catalog.js";
48
+ import { keptModels, modelSources } from "./model-catalog.js";
49
49
  import { cliWatcher, lookAtModels, wireModelChecks } from "./model-checks.js";
50
50
  import { projectPaused, projectTrashed } from "./slices/admission/repo.js";
51
51
  import { createBackupService } from "./slices/backups/service.js";
@@ -493,7 +493,9 @@ export async function boot(config, options = {}) {
493
493
  uuid: randomUUID,
494
494
  resolveFont: (fontId) => resolveFont(paths, fontId),
495
495
  providers: rebuild.providers,
496
- modelsFor: rebuild.modelsFor,
496
+ // Create checks the draft after every pause in typing; the model lists it checks against
497
+ // are read once a minute at most, not each time (`keptModels`).
498
+ modelsFor: keptModels(rebuild.modelsFor, 60_000),
497
499
  emit: (event) => hub.emitGlobal(event),
498
500
  recordStarted: (projectIds) => {
499
501
  for (const _projectId of projectIds)
@@ -632,7 +634,7 @@ export async function boot(config, options = {}) {
632
634
  version,
633
635
  env: process.env,
634
636
  });
635
- // Settings → General → Remote access. It proxies to the port the server really got, read
637
+ // Settings → Remote access. It proxies to the port the server really got, read
636
638
  // when it applies; nothing applies before the server listens.
637
639
  const remoteAccess = createRemoteAccessService({
638
640
  db,
@@ -31,3 +31,23 @@ export function modelSources(registry) {
31
31
  fallbackModelsFor: (provider) => Object.hasOwn(fallbacks, provider) ? (fallbacks[provider] ?? []) : [],
32
32
  };
33
33
  }
34
+ // A model list kept for a short while. Create checks a draft again after every pause in typing,
35
+ // and Claude Code lists its models by running its CLI (about a second each time), so the check
36
+ // and the start that follows read one list. A list that failed is asked for again.
37
+ export function keptModels(modelsFor, keepMs, now = Date.now) {
38
+ const kept = new Map();
39
+ return (provider, family) => {
40
+ const key = `${family}:${provider}`;
41
+ const at = now();
42
+ const held = kept.get(key);
43
+ if (held !== undefined && at - held.at < keepMs)
44
+ return held.models;
45
+ const models = modelsFor(provider, family);
46
+ kept.set(key, { at, models });
47
+ models.catch(() => {
48
+ if (kept.get(key)?.models === models)
49
+ kept.delete(key);
50
+ });
51
+ return models;
52
+ };
53
+ }
@@ -2,21 +2,21 @@
2
2
 
3
3
  Released 18 October 2026.
4
4
 
5
- ## Template Studio
5
+ ## Edit template
6
6
 
7
- - **Set a template up once, with real samples.** Library → Templates → Open studio shows a template section by section: Subject, Script, Voice, Pictures, Video & captions, Thumbnail & title, Shorts, Document, Reviews & checkpoints, Upload pack and Schedule. Each Save makes a short sample of the sections it changed with your own providers. The sample is the opening of the script, about 30 seconds of narration, 4 pictures, a 30-second clip, a short, the PDF and the upload text, so you see and hear the result before a schedule uses it.
8
- - Every paid Save shows its price on the button. A daily sample budget (Settings → Template studio, $2 by default) holds samples that would go over it. CLI plans are used only when you turn on "Samples may use my CLI plans".
9
- - **Mark ready.** Once you mark a version ready in the studio, schedules run the newest ready version and later edits wait until you mark them ready. Templates you never mark ready keep following their newest version, as before.
10
- - **Compare models** makes one section's sample with 2–3 models side by side, with cost and time. **Say it as…** fixes a name the voice mispronounces. **Export pack** and **Import pack** share a ready template with its samples.
11
- - Create opens on template + topic + Start. More settings opens the full form.
7
+ - **Set a template up once, with real samples.** Templates → **Edit template** shows a template section by section: Title and keywords, Article, Narration, Images, Video and style, Thumbnail, Shorts, PDF, Pauses and checks, YouTube description and posts, and Schedule. Each Save makes a short sample of the sections it changed with your own services. The sample is the opening of the article, about 30 seconds of narration, 4 images, a 30-second clip, a short, the PDF and the description, so you see and hear the result before a schedule uses it.
8
+ - Every paid Save shows its price on the button. A daily sample budget (Settings → Production defaults → Template samples, $2 by default) holds samples that would go over it. Claude Code and Codex plans are used only when you turn on "Let samples use my Claude Code or Codex plan".
9
+ - **Approve for schedules.** Once you approve a version in Edit template, schedules run the newest approved version and later edits wait until you approve them. Templates you never approve keep following their newest version, as before.
10
+ - **Compare models** makes one section's sample with 2–3 models side by side, with cost and time. **Say it as…** fixes a name the voice mispronounces. **Export as a file (.zip)** and **Import a template file (.zip)** share a template with its samples.
11
+ - Create opens on a template and a topic; **Make the video** starts it.
12
12
 
13
13
  ## Posting to X, LinkedIn, Instagram, TikTok, Bluesky and Reddit
14
14
 
15
- - Prepare upload has a **Where** choice: YouTube plus each site the project posts to. Each post shows its text, its media in the site's shape, a live character count and plain warnings. Templates set the defaults in Template Studio → Upload pack.
15
+ - **Copy for YouTube Studio** has a **Where** choice: YouTube plus each site the project posts to. Each post shows its text, its media in the site's shape, a live character count and plain warnings. Templates set the defaults in Edit template → YouTube description and posts.
16
16
  - **Fill in <site>** opens the site and the Slopify Studio extension fills the post: text, media, the site's AI label, and its own scheduler where it has one (X, LinkedIn, TikTok). You press Post. On Instagram, Bluesky and Reddit, which have no scheduler, the extension fills the post at its release time and notifies you.
17
17
  - The extension never presses Post, Share or Publish, and it only fills tabs Slopify opened. Allow each site in the extension's options with "Allow posting on …". Reddit has not been checked live yet.
18
18
 
19
- ## Pictures and designed pieces
19
+ ## Images and designed pieces
20
20
 
21
21
  - **Edit a picture** you upload with an instruction, and keep editing the result. Every step is a version you can go back to.
22
22
  - **Design posters and flyers.** The AI makes the artwork and Slopify sets the names, dates and prices in real fonts, so they are always spelled right. Download PNG or a print-ready PDF in A4, A3, A5 and social sizes. A design template with fields (event, date, venue…) makes a piece per row ahead of its date, or one at a time with Make a piece.
@@ -28,15 +28,15 @@ Released 18 October 2026.
28
28
  ## Assistant, health and checks
29
29
 
30
30
  - **Assistant.** A chat panel that runs on your Claude Code or Codex and can only use Slopify's own tools. It asks before anything that costs money, deletes or sends something, and shows what it will do. Its changes show as "by assistant".
31
- - **Autopilot on Home** says how many days of videos are ready, when a schedule's topics run out, and warns before something breaks: a CLI signed out, a plan near its limit, disk filling, a model being retired. A weekly report can go to your notification URL (off by default).
31
+ - **Coming up on Home** says how many days of videos are ready, when a schedule's topics run out, and warns before something breaks: Claude Code or Codex signed out, a plan near its limit, disk filling, a model being retired. A weekly report can go to your notification URL (off by default).
32
32
  - **Models that work for you.** Model pickers show which models your key or plan can actually run, with Check models and a weekly free check. Models that are being retired show their replacement.
33
33
  - **Promise check.** An optional cheap review before narration: does the opening deliver what the title and thumbnail promise.
34
34
  - **Topic ranking.** Schedules can rank new topics by what your library already covers, or also by public search interest (sends topic titles to Google, DuckDuckGo and Wikipedia; off by default).
35
35
 
36
- ## Remote access and rendering
36
+ ## Remote access and encoding
37
37
 
38
- - **Open Slopify on your phone or laptop** through Tailscale (Settings → General → Remote access). Only devices on your own tailnet can open it, never the public internet. Keys, imports and updates can only be changed on this computer.
39
- - **Render settings** (Settings → Production defaults → Rendering): video encoder (processor or NVIDIA graphics card), clip encoding speed and render priority.
38
+ - **Open Slopify on your phone or laptop** through Tailscale (Settings → Remote access). Only devices on your own tailnet can open it, never the public internet. Keys, imports and updates can only be changed on this computer.
39
+ - **Encoding settings** (Settings → Production defaults → Encoding): video encoder (processor or NVIDIA graphics card), clip encoding speed and encoding priority.
40
40
 
41
41
  ## Fixes
42
42
 
@@ -46,6 +46,6 @@ Released 18 October 2026.
46
46
 
47
47
  ## Upgrading from 3.25.9
48
48
 
49
- - Your projects, templates and schedules carry over unchanged; existing projects are not remade. Every template version you had counts as "Not checked in the studio" and keeps running.
49
+ - Your projects, templates and schedules carry over unchanged; existing projects are not remade. Every template version you had keeps running, as before.
50
50
  - The extension goes to 2.0.0. Posting needs its new optional permissions, granted in its options; YouTube works as before without them.
51
51
  - `/mcp` now answers only on this computer: connect agents to `http://127.0.0.1:<port>/mcp`, not through a proxy.
@@ -0,0 +1,42 @@
1
+ # Slopify 4.1.0
2
+
3
+ Released 7 November 2026.
4
+
5
+ This release is about making Slopify easy to use without reading anything first: every button says what it does, each screen shows more and wastes less space, and the same thing has the same name everywhere.
6
+
7
+ ## Finding your way around
8
+
9
+ - **A new rail.** The left rail is narrow, with a word under every icon: Create, Home, Projects, Prompts, Templates, Calendar, Channels (once you have two or more) and Settings, then Search, Assistant, Help and Support Slopify at the foot. On a phone the bottom bar has Create in the middle.
10
+ - **Library is split in two.** **Prompts** holds your prompts and intros and outros, with every kind listed down the left. **Templates** holds video and poster templates. PDF looks are under Settings → Production defaults and Create's PDF choice, Pronunciation is under Settings → Voices, and What worked on YouTube is on the channel page. Old addresses still open the right page.
11
+ - The channel picker reads **Showing: All channels** in the header of Home, Projects and Calendar, and only appears with two or more channels.
12
+
13
+ ## Your first video
14
+
15
+ - The welcome is a checklist of what a short needs (Article, Narration, Images), each with its state and one button to fix it. The usage notice is a single line at the bottom of the window instead of a dialog, and start at login is asked once.
16
+ - **Create is one screen.** Pick what you're making, type the topic (the box has the cursor already) and press **Make the video**. Each part of the run is one sentence with Change, and anything missing is named once, with Fix. On a phone the Make button stays pinned within reach.
17
+ - **New prompt** opens beside Create and picks the prompt when you save, so you never leave the page. If you skipped the starter templates, Create offers them.
18
+ - **Everything this run will do** shows the whole run in plain sentences before you start.
19
+
20
+ ## A finished project
21
+
22
+ - The video, its downloads, the chapters, the shorts and every stage's status are on the first screen. Each section (Article, Narration, Images, Video, Shorts, YouTube, PDF, All files) leads with what it made and a named download.
23
+ - **Edit settings** uses Create's sections and names. **Versions** replaces History.
24
+ - Every action is a visible button: Mark uploaded, Rename, Save as template and Delete. Paid work says so on its button ("Make this image again").
25
+
26
+ ## Lists and settings
27
+
28
+ - **Home** shows your finished videos first, with Watch and Download, then what needs you and what's running, step by step.
29
+ - **Projects** rows show a picture, the length, what was made and when, with Open and Download.
30
+ - **Templates** are cards that say what each one makes, with **Make a video** and **Edit template**. Edit template's sections match Create's.
31
+ - **Prompts** searches every kind at once. The editor reads like text, **Insert a keyword** shows exactly what goes in, and every kind has examples to start from.
32
+ - **Settings** is one list of pages. **Keys & services** opens on three lines (who writes, who reads, who draws), each Ready or with a button to set it up. Trash has its own page, and Projects and Prompts show **Trash (n)** when something is in it.
33
+
34
+ ## One word per thing
35
+
36
+ - Captions (not subtitles), Narration, Voice, Images, Make … again (not Remake or Regenerate), Copy for YouTube Studio, Pauses and checks, PDF looks, and US spelling throughout.
37
+ - Info buttons are gone where a short line under the setting says it better. A status dot always has its word next to it, and each area has at most one lime button.
38
+
39
+ ## Fixes
40
+
41
+ - Typing the topic the moment Create opens no longer drops the starter template's writer and image service.
42
+ - Make the video is ready about a second after you stop typing, instead of three or four.
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "id": "4.1.0",
4
+ "title": "Slopify 4.1.0",
5
+ "version": "4.1.0",
6
+ "date": "2026-11-07"
7
+ },
2
8
  {
3
9
  "id": "4.0.0",
4
10
  "title": "Slopify 4.0.0",
@@ -0,0 +1,95 @@
1
+ import { z } from "zod";
2
+ import { stageKinds } from "../../kernel/pipeline.js";
3
+ const outputRow = z.object({
4
+ project_id: z.string(),
5
+ role: z.string(),
6
+ count: z.number(),
7
+ duration_ms: z.number().nullable(),
8
+ });
9
+ const stageRow = z.object({
10
+ project_id: z.string(),
11
+ kind: z.enum(stageKinds),
12
+ state: z.string(),
13
+ finished_at: z.string().nullable(),
14
+ });
15
+ const outdatedRow = z.object({ project_id: z.string() });
16
+ // The roles a row names. A sound-only run's length comes from its export.
17
+ const namedRoles = [
18
+ "video",
19
+ "short_video",
20
+ "document_pdf",
21
+ "audio_mp3",
22
+ "audio_m4b",
23
+ "audio_export",
24
+ ];
25
+ const audioRoles = new Set(["audio_mp3", "audio_m4b", "audio_export"]);
26
+ export function listingFacts(db) {
27
+ const gathered = new Map();
28
+ const of = (projectId) => {
29
+ let one = gathered.get(projectId);
30
+ if (one === undefined) {
31
+ one = {
32
+ outputs: { video: false, shorts: 0, pdf: false, audio: false },
33
+ videoMs: null,
34
+ audioMs: null,
35
+ finishedAt: null,
36
+ failed: [],
37
+ };
38
+ gathered.set(projectId, one);
39
+ }
40
+ return one;
41
+ };
42
+ const roles = namedRoles.map(() => "?").join(",");
43
+ for (const row of db
44
+ .prepare(`SELECT project_id, role, COUNT(*) AS count, MAX(duration_ms) AS duration_ms FROM outputs WHERE role IN (${roles}) GROUP BY project_id, role`)
45
+ .all(...namedRoles)) {
46
+ const parsed = outputRow.parse(row);
47
+ const one = of(parsed.project_id);
48
+ if (parsed.role === "video") {
49
+ one.outputs.video = true;
50
+ one.videoMs = parsed.duration_ms;
51
+ }
52
+ else if (parsed.role === "short_video")
53
+ one.outputs.shorts = parsed.count;
54
+ else if (parsed.role === "document_pdf")
55
+ one.outputs.pdf = true;
56
+ else if (audioRoles.has(parsed.role)) {
57
+ one.outputs.audio = true;
58
+ if (parsed.duration_ms !== null)
59
+ one.audioMs = Math.max(one.audioMs ?? 0, parsed.duration_ms);
60
+ }
61
+ }
62
+ for (const row of db
63
+ .prepare("SELECT project_id, kind, state, finished_at FROM stages WHERE state IN ('done','failed','provided') ORDER BY rowid")
64
+ .all()) {
65
+ const parsed = stageRow.parse(row);
66
+ const one = of(parsed.project_id);
67
+ if (parsed.finished_at !== null &&
68
+ (one.finishedAt === null || parsed.finished_at > one.finishedAt))
69
+ one.finishedAt = parsed.finished_at;
70
+ if (parsed.state === "failed")
71
+ one.failed.push(parsed.kind);
72
+ }
73
+ const outdated = new Set(db
74
+ .prepare("SELECT DISTINCT revision_outputs.project_id FROM revision_outputs JOIN project_heads ON project_heads.project_id = revision_outputs.project_id AND project_heads.revision_id = revision_outputs.revision_id WHERE revision_outputs.selected = 1 AND revision_outputs.state = 'outdated'")
75
+ .all()
76
+ .map((row) => outdatedRow.parse(row).project_id));
77
+ const facts = new Map();
78
+ for (const [projectId, one] of gathered) {
79
+ const made = one.outputs;
80
+ const ms = one.videoMs ?? one.audioMs;
81
+ facts.set(projectId, {
82
+ ...(one.finishedAt === null ? {} : { finishedAt: one.finishedAt }),
83
+ ...(ms === null || ms <= 0 ? {} : { durationSeconds: Math.round(ms / 1000) }),
84
+ ...(made.video || made.shorts > 0 || made.pdf || made.audio
85
+ ? { outputs: { ...made } }
86
+ : {}),
87
+ ...(one.failed.length === 0 ? {} : { failedSteps: one.failed }),
88
+ ...(outdated.has(projectId) ? { outdated: true } : {}),
89
+ });
90
+ }
91
+ for (const projectId of outdated)
92
+ if (!facts.has(projectId))
93
+ facts.set(projectId, { outdated: true });
94
+ return facts;
95
+ }