@gentbajko/slopify 4.0.0 → 4.2.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 (375) 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 +5 -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/pronunciation-check.js +133 -0
  59. package/dist/edge/http/pronunciations.js +26 -0
  60. package/dist/edge/http/providers.js +9 -10
  61. package/dist/edge/http/refusal.js +1 -1
  62. package/dist/edge/http/remote-access.js +4 -4
  63. package/dist/edge/http/remote-guard.js +2 -2
  64. package/dist/edge/http/reviews.js +1 -1
  65. package/dist/edge/http/revision-files.js +1 -1
  66. package/dist/edge/http/revisions.js +4 -4
  67. package/dist/edge/http/schedules.js +13 -11
  68. package/dist/edge/http/settings.js +6 -2
  69. package/dist/edge/http/social.js +5 -4
  70. package/dist/edge/http/staging.js +1 -1
  71. package/dist/edge/http/storage.js +12 -4
  72. package/dist/edge/http/studio.js +71 -19
  73. package/dist/edge/http/style-preview.js +2 -2
  74. package/dist/edge/http/template-studio.js +12 -9
  75. package/dist/edge/http/trash.js +1 -1
  76. package/dist/edge/http/youtube-edits.js +1 -1
  77. package/dist/edge/mcp/excluded-routes.js +4 -3
  78. package/dist/edge/mcp/instructions.js +3 -3
  79. package/dist/edge/mcp/library-tools.js +8 -5
  80. package/dist/edge/mcp/remake-tools.js +7 -4
  81. package/dist/edge/mcp/server.js +1 -1
  82. package/dist/edge/mcp/tools-channels.js +9 -9
  83. package/dist/edge/mcp/tools-designs.js +9 -9
  84. package/dist/edge/mcp/tools-library.js +31 -7
  85. package/dist/edge/mcp/tools-planning.js +18 -18
  86. package/dist/edge/mcp/tools-projects.js +10 -10
  87. package/dist/edge/mcp/tools-publishing.js +9 -9
  88. package/dist/edge/mcp/tools-runs.js +9 -9
  89. package/dist/edge/mcp/tools-schedules.js +6 -6
  90. package/dist/edge/mcp/tools-setup.js +20 -14
  91. package/dist/edge/mcp/tools-social.js +5 -4
  92. package/dist/edge/mcp/tools-storage.js +3 -3
  93. package/dist/edge/mcp/tools-template-studio.js +18 -18
  94. package/dist/edge/remote-access/model.js +1 -1
  95. package/dist/extension/slopify-studio-chrome.zip +0 -0
  96. package/dist/extension/slopify-studio-firefox.zip +0 -0
  97. package/dist/kernel/db/migrations/0078-uploaded-from-links.sql +7 -0
  98. package/dist/kernel/pipeline.js +12 -0
  99. package/dist/kernel/ports/settings-places.js +31 -0
  100. package/dist/kernel/ports/system-speech.js +31 -3
  101. package/dist/kernel/runner/attempt.js +11 -3
  102. package/dist/kernel/runner/providers.js +3 -2
  103. package/dist/kernel/runner/standalone.js +2 -2
  104. package/dist/main.js +5 -3
  105. package/dist/model-catalog.js +20 -0
  106. package/dist/patch-notes/4.0.0.md +13 -13
  107. package/dist/patch-notes/4.1.0.md +42 -0
  108. package/dist/patch-notes/4.2.0.md +28 -0
  109. package/dist/patch-notes/index.json +12 -0
  110. package/dist/slices/admission/listing-facts.js +95 -0
  111. package/dist/slices/admission/repo.js +9 -6
  112. package/dist/slices/admission/rules.js +7 -7
  113. package/dist/slices/admission/schema.js +2 -0
  114. package/dist/slices/admission/start.js +7 -0
  115. package/dist/slices/admission/state-words.js +34 -5
  116. package/dist/slices/assistant/cost.js +1 -1
  117. package/dist/slices/assistant/gate.js +1 -0
  118. package/dist/slices/assistant/model.js +10 -3
  119. package/dist/slices/assistant/turn.js +5 -3
  120. package/dist/slices/autopilot/warnings.js +1 -1
  121. package/dist/slices/backups/service.js +5 -5
  122. package/dist/slices/channels/cast-images.js +1 -1
  123. package/dist/slices/channels/schema.js +1 -1
  124. package/dist/slices/channels/videos.js +1 -1
  125. package/dist/slices/control/providers.js +2 -2
  126. package/dist/slices/designs/budget.js +1 -1
  127. package/dist/slices/designs/calls.js +2 -2
  128. package/dist/slices/designs/check-context.js +4 -4
  129. package/dist/slices/designs/fields.js +4 -4
  130. package/dist/slices/designs/gate.js +7 -5
  131. package/dist/slices/designs/render-input.js +1 -1
  132. package/dist/slices/designs/requests.js +1 -1
  133. package/dist/slices/designs/runner.js +1 -1
  134. package/dist/slices/designs/sample.js +2 -1
  135. package/dist/slices/designs/service.js +1 -1
  136. package/dist/slices/designs/template-checks.js +2 -2
  137. package/dist/slices/designs/template-make.js +3 -3
  138. package/dist/slices/designs/templates.js +3 -3
  139. package/dist/slices/document/fonts.js +1 -1
  140. package/dist/slices/document/model.js +7 -4
  141. package/dist/slices/document/render.js +2 -1
  142. package/dist/slices/document/theme-schema.js +1 -1
  143. package/dist/slices/document/theme.js +49 -3
  144. package/dist/slices/episodes/summarize.js +2 -2
  145. package/dist/slices/estimate/index.js +28 -22
  146. package/dist/slices/estimate/requests.js +4 -4
  147. package/dist/slices/fixes/rules.js +3 -1
  148. package/dist/slices/fonts/catalog.js +2 -1
  149. package/dist/slices/fonts/coverage.js +2 -1
  150. package/dist/slices/images/appearance.js +2 -1
  151. package/dist/slices/images/scenes.js +2 -1
  152. package/dist/slices/keywords/digest.js +2 -1
  153. package/dist/slices/keywords/rules.js +2 -2
  154. package/dist/slices/keywords/specs.js +5 -5
  155. package/dist/slices/keywords/values.js +1 -1
  156. package/dist/slices/library/transfer.js +3 -3
  157. package/dist/slices/loudness/loudnorm.js +2 -1
  158. package/dist/slices/loudness/model.js +3 -2
  159. package/dist/slices/loudness/sentence-level.js +2 -1
  160. package/dist/slices/model-checks/rules.js +13 -0
  161. package/dist/slices/model-upkeep/model.js +1 -1
  162. package/dist/slices/model-upkeep/switch.js +3 -3
  163. package/dist/slices/model-upkeep/usage.js +13 -2
  164. package/dist/slices/narration/breaths.js +2 -1
  165. package/dist/slices/narration/clean-pieces.js +2 -1
  166. package/dist/slices/narration/pauses-model.js +3 -2
  167. package/dist/slices/narration/pronunciation-check.js +226 -0
  168. package/dist/slices/narration/pronunciation.js +7 -2
  169. package/dist/slices/narration/pronunciations-library.js +94 -0
  170. package/dist/slices/notifications/subject.js +2 -2
  171. package/dist/slices/onboarding/copy.js +19 -2
  172. package/dist/slices/onboarding/first-run.js +13 -38
  173. package/dist/slices/onboarding/install.js +2 -1
  174. package/dist/slices/onboarding/model.js +26 -21
  175. package/dist/slices/onboarding/preferred-model.js +14 -0
  176. package/dist/slices/onboarding/quick-short.js +103 -64
  177. package/dist/slices/onboarding/sample.js +4 -1
  178. package/dist/slices/onboarding/state.js +7 -0
  179. package/dist/slices/onboarding/template.js +6 -5
  180. package/dist/slices/outputs/add.js +3 -2
  181. package/dist/slices/outputs/service.js +2 -2
  182. package/dist/slices/patch-notes/library.js +1 -1
  183. package/dist/slices/play-drafts/readiness.js +3 -3
  184. package/dist/slices/play-drafts/review-inputs.js +1 -1
  185. package/dist/slices/play-drafts/service.js +17 -7
  186. package/dist/slices/project-templates/next-chapter.js +2 -1
  187. package/dist/slices/project-templates/provider-defaults.js +22 -0
  188. package/dist/slices/project-templates/setup-sections.js +26 -0
  189. package/dist/slices/project-templates/setup.js +6 -1
  190. package/dist/slices/rebuild/preview-details.js +5 -5
  191. package/dist/slices/rebuild/recipe-preparation.js +1 -1
  192. package/dist/slices/rebuild/recipe-save.js +2 -2
  193. package/dist/slices/rebuild/recipe-text.js +16 -4
  194. package/dist/slices/rebuild/recipe-validation.js +5 -4
  195. package/dist/slices/rebuild/recipe-voices.js +3 -2
  196. package/dist/slices/rebuild/recipe-work.js +4 -4
  197. package/dist/slices/rebuild/recovery.js +6 -6
  198. package/dist/slices/rebuild/remake-outdated.js +5 -5
  199. package/dist/slices/rebuild/review-redo.js +1 -1
  200. package/dist/slices/rebuild/runtime-animate.js +2 -2
  201. package/dist/slices/rebuild/runtime-article.js +4 -3
  202. package/dist/slices/rebuild/runtime-document.js +5 -4
  203. package/dist/slices/rebuild/runtime-export-bed.js +2 -1
  204. package/dist/slices/rebuild/runtime-export-edit.js +2 -1
  205. package/dist/slices/rebuild/runtime-export-inputs.js +5 -4
  206. package/dist/slices/rebuild/runtime-export-short.js +1 -1
  207. package/dist/slices/rebuild/runtime-export.js +4 -3
  208. package/dist/slices/rebuild/runtime-image.js +4 -3
  209. package/dist/slices/rebuild/runtime-local.js +3 -2
  210. package/dist/slices/rebuild/runtime-narration-text.js +3 -2
  211. package/dist/slices/rebuild/runtime-provider.js +7 -6
  212. package/dist/slices/rebuild/runtime-review.js +6 -5
  213. package/dist/slices/rebuild/runtime-shorts.js +6 -5
  214. package/dist/slices/rebuild/runtime-store.js +17 -2
  215. package/dist/slices/rebuild/runtime-subtitles.js +13 -12
  216. package/dist/slices/rebuild/runtime-voices.js +2 -1
  217. package/dist/slices/rebuild/runtime-youtube.js +3 -2
  218. package/dist/slices/rebuild/service-readiness.js +11 -3
  219. package/dist/slices/research/synthesis.js +3 -2
  220. package/dist/slices/reviews/external-model.js +1 -1
  221. package/dist/slices/reviews/holds.js +5 -4
  222. package/dist/slices/reviews/model.js +8 -5
  223. package/dist/slices/reviews/rules.js +3 -3
  224. package/dist/slices/reviews/verdict.js +2 -1
  225. package/dist/slices/revisions/image-plan.js +1 -1
  226. package/dist/slices/revisions/mutation-assets.js +2 -1
  227. package/dist/slices/revisions/mutations.js +3 -2
  228. package/dist/slices/schedules/agenda.js +7 -5
  229. package/dist/slices/schedules/design-check.js +4 -4
  230. package/dist/slices/schedules/design-run.js +3 -3
  231. package/dist/slices/schedules/repo.js +6 -5
  232. package/dist/slices/schedules/schema.js +3 -0
  233. package/dist/slices/schedules/topics.js +5 -5
  234. package/dist/slices/settings/first-run.js +6 -32
  235. package/dist/slices/settings/health.js +30 -15
  236. package/dist/slices/settings/key-test.js +1 -1
  237. package/dist/slices/settings/model.js +4 -2
  238. package/dist/slices/settings/playback.js +2 -2
  239. package/dist/slices/shorts/keep.js +2 -1
  240. package/dist/slices/shorts/pick.js +4 -3
  241. package/dist/slices/shorts/prompts.js +2 -1
  242. package/dist/slices/shorts/render.js +3 -2
  243. package/dist/slices/social/media.js +5 -4
  244. package/dist/slices/social/pack.js +1 -1
  245. package/dist/slices/social/post-prompt.js +2 -1
  246. package/dist/slices/social/reshape.js +2 -2
  247. package/dist/slices/social/serve.js +1 -1
  248. package/dist/slices/social/service.js +3 -2
  249. package/dist/slices/social/state.js +1 -1
  250. package/dist/slices/storage/backup-import.js +2 -2
  251. package/dist/slices/storage/portable.js +1 -1
  252. package/dist/slices/storage/trim.js +23 -13
  253. package/dist/slices/studio/calendar.js +3 -1
  254. package/dist/slices/studio/disclosure.js +4 -4
  255. package/dist/slices/studio/model.js +1 -1
  256. package/dist/slices/studio/pack.js +9 -8
  257. package/dist/slices/studio/plan.js +1 -1
  258. package/dist/slices/studio/settings.js +6 -6
  259. package/dist/slices/studio/videos.js +23 -4
  260. package/dist/slices/style-preview/render.js +3 -3
  261. package/dist/slices/style-preview/schema.js +1 -1
  262. package/dist/slices/subtitles/captions.js +1 -1
  263. package/dist/slices/template-studio/anatomy-view.js +46 -3
  264. package/dist/slices/template-studio/bakeoff.js +3 -3
  265. package/dist/slices/template-studio/budget.js +6 -3
  266. package/dist/slices/template-studio/cards.js +78 -0
  267. package/dist/slices/template-studio/checks.js +99 -50
  268. package/dist/slices/template-studio/for-all.js +5 -5
  269. package/dist/slices/template-studio/free-video.js +1 -1
  270. package/dist/slices/template-studio/free.js +5 -5
  271. package/dist/slices/template-studio/model.js +19 -15
  272. package/dist/slices/template-studio/pack-import.js +14 -14
  273. package/dist/slices/template-studio/pack.js +9 -7
  274. package/dist/slices/template-studio/paid.js +12 -5
  275. package/dist/slices/template-studio/quote.js +18 -11
  276. package/dist/slices/template-studio/ready.js +7 -7
  277. package/dist/slices/template-studio/runner.js +1 -1
  278. package/dist/slices/template-studio/save.js +12 -12
  279. package/dist/slices/template-studio/say-it-as.js +3 -3
  280. package/dist/slices/template-studio/schedule-gate.js +1 -1
  281. package/dist/slices/template-studio/schedule-price.js +3 -1
  282. package/dist/slices/template-studio/schedule-sample.js +5 -1
  283. package/dist/slices/template-studio/schema.js +25 -0
  284. package/dist/slices/template-studio/service.js +3 -1
  285. package/dist/slices/template-studio/status.js +26 -9
  286. package/dist/slices/template-studio/topic.js +3 -3
  287. package/dist/slices/thumbnail/by-llm.js +1 -1
  288. package/dist/slices/uploads/repo.js +37 -1
  289. package/dist/slices/uploads/rules.js +3 -2
  290. package/dist/slices/video/edit-list.js +2 -2
  291. package/dist/slices/video/edit-settings.js +1 -1
  292. package/dist/slices/video/encoding.js +14 -5
  293. package/dist/slices/video/ffmpeg.js +2 -2
  294. package/dist/slices/video/plan.js +2 -1
  295. package/dist/slices/video/slideshow.js +1 -1
  296. package/dist/slices/voices/attribution.js +3 -2
  297. package/dist/slices/voices/model.js +9 -8
  298. package/dist/slices/voices/script.js +2 -1
  299. package/dist/slices/youtube/answer.js +2 -1
  300. package/dist/slices/youtube/edits-repo.js +1 -1
  301. package/dist/tutorials/AI-CLIs.md +25 -25
  302. package/dist/tutorials/Agents-MCP.md +32 -32
  303. package/dist/tutorials/Assistant.md +5 -5
  304. package/dist/tutorials/Backups.md +7 -7
  305. package/dist/tutorials/Calendar.md +17 -25
  306. package/dist/tutorials/Cast-Library.md +19 -19
  307. package/dist/tutorials/Channels.md +40 -35
  308. package/dist/tutorials/Costs-and-Run-Cost.md +38 -40
  309. package/dist/tutorials/Create-Article.md +114 -0
  310. package/dist/tutorials/Create-Extras.md +112 -0
  311. package/dist/tutorials/Create-Images.md +165 -0
  312. package/dist/tutorials/Create-Narration.md +178 -0
  313. package/dist/tutorials/Create-Overview.md +244 -0
  314. package/dist/tutorials/Create-Video-and-Style.md +144 -0
  315. package/dist/tutorials/Docker.md +3 -3
  316. package/dist/tutorials/Document-Themes.md +32 -32
  317. package/dist/tutorials/Editing-a-Project.md +95 -94
  318. package/dist/tutorials/FAQ.md +18 -18
  319. package/dist/tutorials/First-Launch-and-Welcome.md +50 -64
  320. package/dist/tutorials/Home-and-Projects.md +101 -76
  321. package/dist/tutorials/Home.md +33 -33
  322. package/dist/tutorials/Install.md +13 -13
  323. package/dist/tutorials/Intros-and-Outros.md +33 -34
  324. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +62 -58
  325. package/dist/tutorials/Keywords-Slopify-Fills.md +21 -21
  326. package/dist/tutorials/Models.md +27 -27
  327. package/dist/tutorials/Multiple-Voices.md +41 -40
  328. package/dist/tutorials/Narration-Aliases-and-Glossary.md +101 -19
  329. package/dist/tutorials/Notifications.md +21 -17
  330. package/dist/tutorials/Other-Languages.md +15 -15
  331. package/dist/tutorials/PDF-Documents.md +38 -29
  332. package/dist/tutorials/Posting-to-Social-Sites.md +12 -12
  333. package/dist/tutorials/Project-Page.md +118 -120
  334. package/dist/tutorials/Prompts-and-Templates.md +84 -0
  335. package/dist/tutorials/Prompts.md +45 -45
  336. package/dist/tutorials/Providers-and-Keys.md +105 -98
  337. package/dist/tutorials/Publishing-to-YouTube.md +31 -31
  338. package/dist/tutorials/Recovery-and-Retries.md +31 -31
  339. package/dist/tutorials/Remote-Access.md +2 -2
  340. package/dist/tutorials/Reviews-and-Checkpoints.md +90 -93
  341. package/dist/tutorials/Schedules.md +27 -28
  342. package/dist/tutorials/Settings-Reference.md +183 -204
  343. package/dist/tutorials/Shorts.md +34 -33
  344. package/dist/tutorials/Start-at-Login.md +3 -3
  345. package/dist/tutorials/Studio-Extension.md +22 -24
  346. package/dist/tutorials/Template-Studio.md +45 -42
  347. package/dist/tutorials/Templates.md +50 -79
  348. package/dist/tutorials/Trash.md +4 -4
  349. package/dist/tutorials/Troubleshooting.md +44 -44
  350. package/dist/tutorials/Uninstalling-and-Moving.md +5 -5
  351. package/dist/tutorials/Updating-and-Patch-Notes.md +18 -18
  352. package/dist/tutorials/Video-Editing.md +34 -34
  353. package/dist/tutorials/Where-Your-Files-Live.md +10 -10
  354. package/dist/tutorials/YouTube-Description.md +26 -26
  355. package/dist/tutorials/Your-First-Short.md +35 -35
  356. package/dist/tutorials/_Sidebar.md +15 -15
  357. package/dist/web/assets/index-BNftKQrA.css +1 -0
  358. package/dist/web/assets/index-CQASqwjT.js +460 -0
  359. package/dist/web/assets/pdf-BKh8f7R6.js +56 -0
  360. package/dist/web/assets/pdf.worker.min-BmVo14Nb.mjs +28 -0
  361. package/dist/web/assets/pdf.worker.min-D7i8o3wR.js +1 -0
  362. package/dist/web/index.html +2 -2
  363. package/package.json +2 -2
  364. package/dist/tutorials/Library-Overview.md +0 -76
  365. package/dist/tutorials/Play-Images.md +0 -165
  366. package/dist/tutorials/Play-Narration.md +0 -167
  367. package/dist/tutorials/Play-Outputs.md +0 -109
  368. package/dist/tutorials/Play-Overview.md +0 -227
  369. package/dist/tutorials/Play-Title-and-Article.md +0 -136
  370. package/dist/tutorials/Play-Video-and-Style.md +0 -143
  371. package/dist/web/assets/index-CkAI_2E7.css +0 -1
  372. package/dist/web/assets/index-DcD0cdfo.js +0 -334
  373. package/dist/web/assets/pdf-TODRwF31.js +0 -54
  374. package/dist/web/assets/pdf.worker.min-Dswkl-cV.mjs +0 -29
  375. package/dist/web/assets/pdf.worker.min-zC6nfbxG.js +0 -1
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { readinessIsUsable } from "../../kernel/ports/model.js";
3
+ import { costPhrase } from "../../slices/model-checks/rules.js";
3
4
  import { quoteBody } from "../http/auditions.js";
4
5
  import { checkBody } from "../http/model-checks.js";
5
6
  import { switchBody } from "../http/providers.js";
@@ -56,7 +57,7 @@ const settings = [
56
57
  name: "get_remote_access",
57
58
  route: "GET /api/settings/remote-access",
58
59
  title: "Read remote access",
59
- description: "Settings → General → Remote access: whether Slopify's page is open on the person's other devices through Tailscale serve (their tailnet only, never the public internet), its https address, and any problem with the exact fix. Turning it on or off is the person's; agents can't.",
60
+ description: "Settings → Remote access: whether Slopify's page is open on the person's other devices through Tailscale serve (their tailnet only, never the public internet), its https address, and any problem with the exact fix. Turning it on or off is the person's; agents can't.",
60
61
  },
61
62
  {
62
63
  name: "get_settings",
@@ -80,7 +81,7 @@ const settings = [
80
81
  name: "get_default_channel_links",
81
82
  route: "GET /api/settings/channel-links",
82
83
  title: "Read the default channel links",
83
- description: "Settings → Channel links: the links a description's {{Name}} placeholders fill from.",
84
+ description: "Settings → YouTube Studio and posting: the links a description's {{Name}} placeholders fill from.",
84
85
  },
85
86
  {
86
87
  name: "set_default_channel_links",
@@ -147,7 +148,7 @@ const settings = [
147
148
  route: "POST /api/auditions/quote",
148
149
  body: quoteBody,
149
150
  title: "Price a voice sample",
150
- description: "Try a sample's price, as Play shows it before playing one. Body: {lines: [{speaker, provider, model, text}]}.",
151
+ description: "Try a sample's price, as Create shows it before playing one. Body: {lines: [{speaker, provider, model, text}]}.",
151
152
  unrecorded: true,
152
153
  },
153
154
  {
@@ -166,7 +167,7 @@ const settings = [
166
167
  name: "get_studio_settings",
167
168
  route: "GET /api/studio/settings",
168
169
  title: "Read YouTube Studio settings",
169
- description: "Settings → YouTube Studio: the playlists upload packs name, and whether the comment is posted.",
170
+ description: "Settings → YouTube Studio and posting: the playlists upload packs name, and whether the comment is posted.",
170
171
  shape: withoutExtensionSecrets,
171
172
  },
172
173
  {
@@ -186,14 +187,14 @@ const providers = [
186
187
  {
187
188
  name: "list_providers",
188
189
  route: "GET /api/providers",
189
- title: "List providers",
190
- description: "Each provider as configured or missing.",
190
+ title: "List the services",
191
+ description: "Each service on Settings → Keys & services, as configured or missing.",
191
192
  shape: configuredOrMissing,
192
193
  },
193
194
  {
194
195
  name: "list_models",
195
196
  route: "GET /api/providers/:id/models",
196
- title: "List a provider's models",
197
+ title: "List a service's models",
197
198
  description: "The models Slopify offers for the provider, with prices where known, and each model's standing for the saved key or CLI under standings: works (a check or a run succeeded), listed (the key's own model list names it), known (only Slopify's model list does), no-access (the provider said it is not on the plan, with its sentence), and retiring with its replacement when one is due. Standings never stop a run: a model chosen anywhere keeps working as before.",
198
199
  },
199
200
  {
@@ -205,26 +206,31 @@ const providers = [
205
206
  {
206
207
  name: "estimate_model_checks",
207
208
  route: "GET /api/providers/:id/models/check/estimate",
208
- title: "Estimate checking a provider's models",
209
+ title: "Estimate checking a service's models",
209
210
  description: "What Check models in Settings → Models would check for this provider and what it costs at list price, before anything is sent. Query: models (optional, comma-separated ids).",
210
211
  },
211
212
  {
212
213
  name: "check_models",
213
214
  route: "POST /api/providers/:id/models/check",
214
215
  body: checkBody,
215
- title: "Check a provider's models",
216
+ title: "Check a service's models",
216
217
  description: "Settings → Models' Check models: sends each model the smallest request (one word, a spoken Hi, one small image) to learn whether it works for the saved key or CLI, in the background. A check that costs money is refused with its estimate until the body carries confirmedDollars of at least the estimate's totalDollars (read estimate_model_checks first). Body: {models?: [ids], confirmedDollars?: number}.",
217
218
  confirm: "Each model checked is sent one small paid request at list price (a CLI's checks use its plan's allowance instead).",
218
219
  confirmFrom: {
219
220
  route: "/api/providers/:id/models/check/estimate",
220
221
  text: (value) => {
221
222
  const estimate = z
222
- .object({ count: z.number(), text: z.string(), planAccounts: z.array(z.string()) })
223
+ .object({
224
+ count: z.number(),
225
+ totalDollars: z.number(),
226
+ unpriced: z.number(),
227
+ planAccounts: z.array(z.string()),
228
+ })
223
229
  .safeParse(value);
224
230
  if (!estimate.success)
225
231
  return undefined;
226
- const { count, text, planAccounts } = estimate.data;
227
- return `Checks ${String(count)} model${count === 1 ? "" : "s"}, one small request each: ${text} at list price${planAccounts.length > 0 ? ", using the CLI plan's allowance" : ""}.`;
232
+ const { count, totalDollars, unpriced, planAccounts } = estimate.data;
233
+ return `Checks ${String(count)} model${count === 1 ? "" : "s"}, one small request each: ${costPhrase(totalDollars, unpriced)}${planAccounts.length > 0 ? ", using the CLI plan's allowance" : ""}.`;
228
234
  },
229
235
  },
230
236
  reachesOut: "asks",
@@ -244,8 +250,8 @@ const providers = [
244
250
  {
245
251
  name: "check_providers",
246
252
  route: "POST /api/providers/health",
247
- title: "Check providers",
248
- description: "Settings → Providers' Check all: which providers answer and which CLIs are signed in.",
253
+ title: "Check every service",
254
+ description: "Settings → Keys & services' Check every service: which services answer and which CLIs are signed in.",
249
255
  unrecorded: true,
250
256
  reachesOut: "asks",
251
257
  },
@@ -1,18 +1,19 @@
1
+ import { setupSectionNames } from "../../slices/project-templates/setup-sections.js";
1
2
  import { choiceBody, linkBody, redditBody, textBody } from "../../slices/social/schema.js";
2
- // A project's posts to X, LinkedIn, Instagram, TikTok, Reddit and Bluesky, as Prepare upload
3
- // shows them. Agents prepare them; Fill in <site> and posting are the person's.
3
+ // A project's posts to X, LinkedIn, Instagram, TikTok, Reddit and Bluesky, as Copy for YouTube
4
+ // Studio shows them. Agents prepare them; Fill in <site> and posting are the person's.
4
5
  export const socialTools = [
5
6
  {
6
7
  name: "get_post_packs",
7
8
  route: "GET /api/social/packs/:projectId",
8
9
  title: "Read the posts for other sites",
9
- description: "Prepare upload's posts: for each site the project posts to, the text as it goes in (with its count and limit), hashtags, link, media, AI label, when it goes out, warnings and where the post stands. The agent's work ends here: filling a site's form and posting are the person's.",
10
+ description: "Copy for YouTube Studio's posts: for each site the project posts to, the text as it goes in (with its count and limit), hashtags, link, media, AI label, when it goes out, warnings and where the post stands. The agent's work ends here: filling a site's form and posting are the person's.",
10
11
  },
11
12
  {
12
13
  name: "set_post_choice",
13
14
  route: "PUT /api/social/packs/:projectId/:platform/choice",
14
15
  title: "Change what a post carries",
15
- description: "The project's own settings for one site (platform: x, linkedin, instagram, tiktok, reddit or bluesky): its text source, media, hashtags and link; {off: true} to not post there; null to go back to the template's. A post prompt (text \"prompt\") is a paid step picked only in the template (Template Studio → Upload pack) or on Play, where Save shows its cost. Body: {choice}.",
16
+ description: `The project's own settings for one site (platform: x, linkedin, instagram, tiktok, reddit or bluesky): its text source, media, hashtags and link; {off: true} to not post there; null to go back to the template's. A post prompt (text "prompt") is a paid step picked only in the template (Edit template → ${setupSectionNames.youtube}) or in Create, where Save shows its cost. Body: {choice}.`,
16
17
  body: choiceBody,
17
18
  },
18
19
  {
@@ -63,13 +63,13 @@ const onboarding = [
63
63
  name: "get_onboarding",
64
64
  route: "GET /api/onboarding",
65
65
  title: "Read the welcome state",
66
- description: "Starter packs and what the welcome offers.",
66
+ description: "Starter templates and what the welcome offers.",
67
67
  },
68
68
  {
69
69
  name: "install_starter_pack",
70
70
  route: "POST /api/onboarding/packs/:id",
71
- title: "Install a starter pack",
72
- description: "Adds a starter pack's prompts and templates.",
71
+ title: "Install a starter template",
72
+ description: "Adds a starter template's prompts and templates.",
73
73
  },
74
74
  {
75
75
  name: "make_quick_short",
@@ -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.