@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
@@ -13,12 +13,13 @@ export const reviewStages = [
13
13
  "shorts",
14
14
  "promise",
15
15
  ];
16
- // The stages in the order a run reaches them, as the Reviews rows list them.
16
+ // The stages in the order a run reaches them, as the Reviews rows list them: the narration is
17
+ // made before the images (`kernel/pipeline.ts`), as the pause ticks say.
17
18
  export const reviewStagesInRunOrder = [
18
19
  "article",
19
20
  "promise",
20
- "images",
21
21
  "narration",
22
+ "images",
22
23
  "thumbnail",
23
24
  "shorts",
24
25
  ];
@@ -30,9 +31,11 @@ export const reviewers = ["model", "agent"];
30
31
  // Who gave a verdict: the reviewer model, an external agent, the person (through the verdict
31
32
  // route), or nobody when the review passed at its deadline.
32
33
  export const reviewAnswerers = ["model", "agent", "person", "nobody"];
34
+ // Said to a person who never heard of MCP: a model Slopify pays per check, or an AI agent they
35
+ // connected themselves (Settings → AI agents), which answers for free.
33
36
  export const reviewerLabels = {
34
- model: "Model",
35
- agent: "External agent (MCP)",
37
+ model: "An AI model",
38
+ agent: "Your own AI agent",
36
39
  };
37
40
  // ceiling: the owner's numbers. Two redos is what a person would try before looking at it
38
41
  // themselves; five is already six paid images for one slot.
@@ -123,7 +126,7 @@ export function reviewSettingsFromForm(form, makes) {
123
126
  ...(valid
124
127
  ? {}
125
128
  : {
126
- retriesProblem: `Enter a number of redos between ${String(reviewRetriesMin)} and ${String(reviewRetriesMax)} in the Reviews section.`,
129
+ retriesProblem: `Enter a number of redos between ${String(reviewRetriesMin)} and ${String(reviewRetriesMax)} under Pauses and checks → Checker and stages.`,
127
130
  }),
128
131
  };
129
132
  }
@@ -44,14 +44,14 @@ export function reviewFields(draft) {
44
44
  if (!byAgent && (reviews.provider.trim() === "" || reviews.model.trim() === ""))
45
45
  fields.push({
46
46
  field: "reviews.provider",
47
- message: "Automatic reviews need a reviewer model. Choose one in the Reviews section, or set every review to Off.",
47
+ message: "AI checks need a model to check with. Choose one under Pauses and checks → Checker and stages → Checked by, or set every check to Off.",
48
48
  });
49
49
  const retries = reviews.retries;
50
50
  if (retries !== undefined &&
51
51
  (!Number.isInteger(retries) || retries < reviewRetriesMin || retries > reviewRetriesMax))
52
52
  fields.push({
53
53
  field: "reviews.retries",
54
- message: `Enter a number of redos between ${reviewRetriesMin} and ${reviewRetriesMax} in the Reviews section.`,
54
+ message: `Enter a number of redos between ${reviewRetriesMin} and ${reviewRetriesMax} under Pauses and checks → Checker and stages.`,
55
55
  });
56
56
  const pictures = active.filter((stage) => visionStages.includes(stage));
57
57
  if (!byAgent &&
@@ -60,7 +60,7 @@ export function reviewFields(draft) {
60
60
  !reviewerSeesImages(reviews.provider))
61
61
  fields.push({
62
62
  field: "reviews.provider",
63
- message: `The ${pictures.map((stage) => reviewStageLabels[stage]).join(", ")} ${pictures.length === 1 ? "review looks" : "reviews look"} at pictures, and Slopify can't show pictures to this reviewer. In the Reviews section, choose Claude Code or Codex as the reviewer, or set ${pictures.length === 1 ? "that review" : "those reviews"} to Off.`,
63
+ message: `The ${pictures.map((stage) => reviewStageLabels[stage]).join(", ")} ${pictures.length === 1 ? "check looks" : "checks look"} at images, and Slopify can't show images to this checker. Under Pauses and checks → Checker and stages → Checked by, choose Claude Code or Codex, or set ${pictures.length === 1 ? "that check" : "those checks"} to Off.`,
64
64
  });
65
65
  return fields;
66
66
  }
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { editSettings } from "../../kernel/ports/settings-places.js";
2
3
  // The whole answer the reviewer may give: one JSON object, nothing else. A failed item names
3
4
  // at least one reason, since a reason is what the person reads beside it.
4
5
  export const reviewReasonMax = 500;
@@ -46,7 +47,7 @@ export function reviewMessages(material) {
46
47
  },
47
48
  ];
48
49
  }
49
- const retryHint = "Slopify asks again while attempts remain; if it keeps happening, choose another reviewer model in Edit project → Reviews, then use Try again.";
50
+ const retryHint = `Slopify asks again while attempts remain; if it keeps happening, choose another reviewer model in ${editSettings.checks}, then use Try again.`;
50
51
  // Strict: one JSON object in the shape above, optionally inside a single ```json fence (the
51
52
  // one wrapping models add unasked). Anything else is refused with what was wrong with it.
52
53
  export function parseVerdict(text) {
@@ -86,7 +86,7 @@ export function replanImagePrompts(input) {
86
86
  if (body === undefined) {
87
87
  fields.push({
88
88
  field: `imagePrompts.${String(index)}.name`,
89
- message: `The image prompt "${prompt.name}" is not in the Library any more. Untick it under Images, or add it back on Library → Prompts.`,
89
+ message: `The image prompt "${prompt.name}" is not in Prompts any more. Untick it under Images, or add it back in Prompts → Image.`,
90
90
  });
91
91
  continue;
92
92
  }
@@ -1,5 +1,6 @@
1
1
  import { statSync } from "node:fs";
2
2
  import { z } from "zod";
3
+ import { editSettings } from "../../kernel/ports/settings-places.js";
3
4
  import { referenceKey, thumbnailCountOf } from "../admission/model.js";
4
5
  import { usesReference } from "../admission/rules.js";
5
6
  import { outputPath, stagingPath } from "../storage/layout.js";
@@ -59,7 +60,7 @@ export function validateUploads(deps, edit) {
59
60
  if (to.kind === "shortsMusic" && edit.config.shorts?.enabled !== true)
60
61
  fields.push({
61
62
  field,
62
- message: "Background music is only used by the shorts. Turn Shorts on in Edit project → Shorts, then add the music.",
63
+ message: `Background music is only used by the shorts. Turn Shorts on in ${editSettings.shorts}, then add the music.`,
63
64
  });
64
65
  const destination = JSON.stringify(to);
65
66
  if (staged.has(upload.stagedFileId) || destinations.has(destination))
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { transact } from "../../kernel/db/tx.js";
3
+ import { editSettings } from "../../kernel/ports/settings-places.js";
3
4
  import { rebrandedEdit } from "../channels/rebrand.js";
4
5
  import { projectChannelId, resolveChannelId, setProjectChannel } from "../channels/repo.js";
5
6
  import { carryCheckpointGates } from "../checkpoints/recovery.js";
@@ -204,14 +205,14 @@ function settingProblems(edit) {
204
205
  ? []
205
206
  : ambientBedProblems(bed).map((problem) => ({
206
207
  field: `ambientBed.${problem.field}`,
207
- message: `${problem.message} Change it in Edit project → Inputs → Ambient sound.`,
208
+ message: `${problem.message} Change it under Ambient sound in ${editSettings.sound}.`,
208
209
  }))),
209
210
  ...(scaleProblem === undefined
210
211
  ? []
211
212
  : [
212
213
  {
213
214
  field: "imageScale",
214
- message: `${scaleProblem} Change it in Edit project → Images → More images for long videos.`,
215
+ message: `${scaleProblem} Change it under More images for long videos in ${editSettings.images}.`,
215
216
  },
216
217
  ]),
217
218
  ];
@@ -1,14 +1,14 @@
1
1
  import { z } from "zod";
2
2
  import { derive, progressOf } from "../../kernel/runner/graph.js";
3
3
  import { liveProject, stageStandingsByProject } from "../admission/repo.js";
4
- import { readsByWaitBesideFailure } from "../admission/state-words.js";
4
+ import { readsByWaitBesideFailure, retryingAt } from "../admission/state-words.js";
5
5
  import { queueEntries } from "../batch/index.js";
6
6
  import { sampleIds } from "../onboarding/model.js";
7
7
  import { readSampleRecord } from "../onboarding/state.js";
8
8
  import { projectHolds } from "../reviews/holds.js";
9
9
  import { limitWaitsByProject, listingWait } from "../run-cost/panel.js";
10
10
  import { notTemplateSample } from "../template-studio/repo.js";
11
- import { setAsideProjects, uploadedAndSettled, uploadedProjects } from "../uploads/repo.js";
11
+ import { setAsideProjects, settledProjects, uploadedProjects } from "../uploads/repo.js";
12
12
  import { settledStatus } from "../uploads/rules.js";
13
13
  import { nextOccurrence } from "./calendar.js";
14
14
  import { upcomingDesignRuns } from "./design-agenda.js";
@@ -151,8 +151,8 @@ function sampleProjectIds(db) {
151
151
  // What a project on the calendar asks of the person, if anything. The same readings Home's
152
152
  // Needs you uses: a failed run, a paused run, one a restart stopped (Continue the run), or one
153
153
  // waiting for a review. A step failed beside a wait reads by that wait and is listed apart
154
- // (`stepFailed`). One Kept as is asks nothing, nor does an uploaded one that reads Done
155
- // (`uploadedAndSettled`).
154
+ // (`stepFailed`). One Kept as is asks nothing, nor does an uploaded or edited one that reads
155
+ // Done (`settledProjects`).
156
156
  function needsOf(state, ask) {
157
157
  if (state === "failed")
158
158
  return "failed";
@@ -170,7 +170,7 @@ function needsOf(state, ask) {
170
170
  // the range, with what each needs from the person and whether its video is ready to upload.
171
171
  function projectsIn(deps, from, to) {
172
172
  const standings = stageStandingsByProject(deps.db);
173
- const settled = uploadedAndSettled(deps.db);
173
+ const settled = settledProjects(deps.db);
174
174
  const holds = projectHolds(deps.db, { standings, settled });
175
175
  const reviewing = awaitingReview(deps.db, holds);
176
176
  const uploads = uploadedProjects(deps.db);
@@ -224,6 +224,7 @@ function projectsIn(deps, from, to) {
224
224
  !uploads.has(project.id) &&
225
225
  !samples.has(project.id);
226
226
  const waiting = waits.get(project.id);
227
+ const retry = state === "running" ? retryingAt(standings.get(project.id) ?? []) : undefined;
227
228
  return [
228
229
  {
229
230
  id: project.id,
@@ -242,6 +243,7 @@ function projectsIn(deps, from, to) {
242
243
  ...(readsByWaitBesideFailure(state, standings.get(project.id) ?? [])
243
244
  ? { stepFailed: true }
244
245
  : {}),
246
+ ...(retry === undefined ? {} : { retryAt: retry }),
245
247
  ...(waiting === undefined ? {} : { limitWaits: waiting.map(listingWait) }),
246
248
  },
247
249
  ];
@@ -11,25 +11,25 @@ export function checkDesign(deps, input) {
11
11
  return {
12
12
  ok: false,
13
13
  reason: "invalid-input",
14
- message: "A design schedule makes each row's piece ahead of its date: it has no cadence, topic generation or release days. Choose Ahead of each date.",
14
+ message: "A poster schedule makes each row's poster ahead of its date: it has no cadence, topic generation or release days. Choose Ahead of each date.",
15
15
  };
16
16
  const template = getTemplate(deps.db, input.templateId);
17
17
  if (template === undefined)
18
18
  return { ok: false, reason: "missing-template" };
19
19
  const plan = designPlanOf(deps.db, input.templateId);
20
20
  if (plan === undefined)
21
- return refuse(`The schedule wasn't saved. ${template.name} doesn't say which field holds each date. Open it in Library → Designs → Templates, choose the date field under Schedule, save it, then save the schedule again.`);
21
+ return refuse(`The schedule wasn't saved. ${template.name} doesn't say which keyword holds each date. Open it from Templates → Poster templates, choose the date keyword under Schedule, save it, then save the schedule again.`);
22
22
  const fields = designFields(plan.document);
23
23
  const named = fields.length === 0 ? "none" : fields.map((one) => `{{${one}}}`).join(", ");
24
24
  const problems = [];
25
25
  for (const keyword of Object.keys(input.values))
26
26
  if (!fields.includes(keyword))
27
- problems.push(`{{${keyword}}} (for every row) is not a field of this template (its fields are ${named}). Remove it.`);
27
+ problems.push(`{{${keyword}}} (for every row) is not a keyword of this template (its keywords are ${named}). Remove it.`);
28
28
  for (const [index, row] of input.items.entries()) {
29
29
  const where = `Row ${String(index + 1)} (${row.title})`;
30
30
  for (const keyword of Object.keys(row.values))
31
31
  if (!fields.includes(keyword))
32
- problems.push(`${where} fills {{${keyword}}}, which this template doesn't use (its fields are ${named}). Remove that column.`);
32
+ problems.push(`${where} fills {{${keyword}}}, which this template doesn't use (its keywords are ${named}). Remove that column.`);
33
33
  if (rowDate(row, plan.dateField) === undefined)
34
34
  problems.push(`${where} has no date in {{${plan.dateField}}}. Write it as YYYY-MM-DD, for example 2026-11-14.`);
35
35
  }
@@ -72,11 +72,11 @@ export function claimDesignRun(deps, schedule, now) {
72
72
  // missed runs. The row stays and is tried again at the same time the next day.
73
73
  const scheduledFor = schedule.nextRunAt === null ? now : new Date(schedule.nextRunAt);
74
74
  if (current.missedPolicy === "skip" && now.valueOf() - scheduledFor.valueOf() > missedGraceMs)
75
- return keep("skipped", `Skipped: Slopify wasn't open at ${time} when ${row.title}'s piece was due, and this schedule skips the runs it misses. It tries again at ${time} tomorrow; to make a missed piece as soon as Slopify opens, set Missed run to Run once when Slopify reopens in Library → Designs → Templates → ${plan.name} → Schedule.`);
75
+ return keep("skipped", `Skipped: Slopify wasn't open at ${time} when ${row.title}'s poster was due, and this schedule skips the runs it misses. It tries again at ${time} tomorrow; to make a missed poster as soon as Slopify opens, set Missed run to Run once when Slopify reopens in Templates → Poster templates → ${plan.name} → Schedule.`);
76
76
  if (designs === undefined)
77
77
  return keep("failed", "Not started: designs aren't available in this copy of Slopify. Restart Slopify; if it keeps happening, reinstall it.");
78
78
  if (plan.readyVersion === null)
79
- return keep("skipped", `Not started: ${plan.name} has no version marked ready. Open it in Library → Designs → Templates and press Mark ready.`);
79
+ return keep("skipped", `Not started: ${plan.name} has no version approved for schedules. Open it in Templates → Poster templates and press Approve for schedules.`);
80
80
  const missing = missingValues(plan.document, row.values);
81
81
  if (missing.length > 0) {
82
82
  const list = missing.map((name) => `{{${name}}}`).join(", ");
@@ -85,7 +85,7 @@ export function claimDesignRun(deps, schedule, now) {
85
85
  const quote = quoteDocument(designs.catalogue(), plan.document);
86
86
  if (current.spendLimitCents !== null &&
87
87
  (quote.unknown > 0 || quote.high * 100 > current.spendLimitCents))
88
- return keep("failed", `Not started: ${row.title}'s piece costs up to $${quote.high.toFixed(2)}${quote.unknown > 0 ? " and some prices are unknown" : ""}, over this schedule's spend limit. Raise the spend limit or choose an image model with a known, lower price.`);
88
+ return keep("failed", `Not started: ${row.title}'s poster costs up to $${quote.high.toFixed(2)}${quote.unknown > 0 ? " and some prices are unknown" : ""}, over this schedule's spend limit. Raise the spend limit or choose an image model with a known, lower price.`);
89
89
  const started = run("running", null);
90
90
  if (!insertRun(deps.db, started))
91
91
  return;
@@ -4,6 +4,7 @@ import { transact } from "../../kernel/db/tx.js";
4
4
  import { derive } from "../../kernel/runner/graph.js";
5
5
  import { projectExists, projectPaused, stagesOf } from "../admission/repo.js";
6
6
  import { readStartReceipt } from "../play-drafts/start-repo.js";
7
+ import { setupSectionNames } from "../project-templates/setup-sections.js";
7
8
  import { scheduleRunSchema, scheduleSummarySchema, } from "./schema.js";
8
9
  const rowSchema = z.object({
9
10
  id: z.string(),
@@ -361,12 +362,12 @@ function parseRun(row) {
361
362
  const internalRunError = "Slopify hit an internal error while starting this run. The next run will try again; if it keeps happening, use Download diagnostics in Settings and report it.";
362
363
  const runErrors = {
363
364
  "missing-template": "The template this schedule uses was deleted or changed. Edit the schedule and choose a template again.",
364
- "unsupported-media": "This template uses audio, images, a thumbnail, shorts background music or an ambient sound file you supplied, which scheduled runs cannot use. Pick a template that generates these instead, or on Play remove the music under Outputs → More shorts options (or pick Rain, Fireplace or Wind under Video and style → Ambient sound) and save the template again.",
365
+ "unsupported-media": `This template uses audio, images, a thumbnail, shorts background music or an ambient sound file you supplied, which scheduled runs cannot use. Pick a template that generates these instead, or open this one in Edit template, remove the music under ${setupSectionNames.shorts} (or pick Rain, Fireplace or Wind as the Ambient sound under ${setupSectionNames.video}) and save it again.`,
365
366
  "spend-limit": "Not started: the estimated cost was above this schedule's spend limit, or some prices were unknown. Raise the spend limit or choose models with known prices.",
366
- readiness: "Not started: a provider was not ready, for example a missing API key, model or voice. Check Settings → Providers; the next run will try again.",
367
- "not-found": "The template's settings are no longer valid, for example a prompt or voice it uses was deleted. Fix the template in Library > Templates and save it.",
368
- "invalid-draft": "The template's settings are no longer valid, for example a prompt or voice it uses was deleted. Fix the template in Library > Templates and save it.",
369
- "invalid-edit": "The template's settings are no longer valid, for example a prompt or voice it uses was deleted. Fix the template in Library > Templates and save it.",
367
+ readiness: "Not started: a provider was not ready, for example a missing API key, model or voice. Check Settings → Keys & services; the next run will try again.",
368
+ "not-found": "The template's settings are no longer valid, for example a prompt or voice it uses was deleted. Fix the template in Templates → Edit template and save it.",
369
+ "invalid-draft": "The template's settings are no longer valid, for example a prompt or voice it uses was deleted. Fix the template in Templates → Edit template and save it.",
370
+ "invalid-edit": "The template's settings are no longer valid, for example a prompt or voice it uses was deleted. Fix the template in Templates → Edit template and save it.",
370
371
  conflict: internalRunError,
371
372
  "pending-start": internalRunError,
372
373
  "already-started": internalRunError,
@@ -309,6 +309,9 @@ export const calendarProjectSchema = z
309
309
  // A step failed while the run still reads by a wait (`readsByWaitBesideFailure`): listed as a
310
310
  // failed item beside what it waits for, as on Home. Older servers leave it out.
311
311
  stepFailed: z.literal(true).optional(),
312
+ // When a step that failed on something time can fix runs again by itself, while no step
313
+ // runs (`admission/state-words.ts` retryingAt): it reads Waiting to try again, as on Home.
314
+ retryAt: z.string().optional(),
312
315
  // Stages waiting for a CLI plan's limits to reset.
313
316
  limitWaits: z
314
317
  .array(z
@@ -108,9 +108,9 @@ async function ask(deps, schedule, options) {
108
108
  const template = deps.template(schedule.templateId);
109
109
  const llm = schedule.topicGeneration.llm ?? template?.document.form.llm;
110
110
  if (llm === undefined)
111
- return fail("the template this schedule uses was deleted, so there is no LLM to ask. Edit the schedule and choose a template, or pick an LLM under Topic generation.");
111
+ return fail("the template this schedule uses was deleted, so there is no AI model to ask. Edit the schedule and choose a template, or pick a Service and Model under Topic generation.");
112
112
  if (deps.topicLlm === undefined)
113
- return fail("no LLM is available in this Slopify. Restart Slopify and try again.");
113
+ return fail("no AI model is available in this Slopify. Restart Slopify and try again.");
114
114
  const wanted = Math.max(generationWanted(schedule), options.force ? spare : 0);
115
115
  const known = knownTitles(deps, schedule.id);
116
116
  const signal = AbortSignal.any([
@@ -134,11 +134,11 @@ async function ask(deps, schedule, options) {
134
134
  if (options.signal?.aborted === true)
135
135
  return { ok: false, reason: "failed", message: "Slopify was closing." };
136
136
  const detail = errorText(error);
137
- return fail(`${llm.provider} (${llm.model}) did not answer: ${detail.trim().slice(0, 300)}. Check it in Settings → Providers, or pick another LLM under Edit → Topic generation.`);
137
+ return fail(`${llm.provider} (${llm.model}) did not answer: ${detail.trim().slice(0, 300)}. Check it in Settings → Keys & services, or pick another Service or Model under Edit → Topic generation.`);
138
138
  }
139
139
  const suggested = parseTopics(answer);
140
140
  if (suggested.length === 0)
141
- return fail(`${llm.provider} (${llm.model}) answered without a list of topics. Try again, or pick another LLM under Edit → Topic generation.`);
141
+ return fail(`${llm.provider} (${llm.model}) answered without a list of topics. Try again, or pick another Service or Model under Edit → Topic generation.`);
142
142
  // Scored outside the transaction: search may take seconds, and never holds the database.
143
143
  const scores = schedule.topicScoring === "off"
144
144
  ? undefined
@@ -154,7 +154,7 @@ async function ask(deps, schedule, options) {
154
154
  ? candidates
155
155
  : candidates.toSorted((a, b) => (scores.get(b)?.score ?? -1) - (scores.get(a)?.score ?? -1))).slice(0, Math.max(0, Math.min(wanted, queueMax - current.items.length)));
156
156
  if (fresh.length === 0)
157
- return fail("every topic the LLM suggested was already made, queued or turned down for this schedule. Add more detail to the series brief under Edit, so it has new ground to cover.");
157
+ return fail("every topic the AI model suggested was already made, queued or turned down for this schedule. Add more detail to the series brief under Edit, so it has new ground to cover.");
158
158
  const at = deps.clock.now().toISOString();
159
159
  const mode = current.topicGeneration.mode === "hold" ? "hold" : "queue";
160
160
  if (scores !== undefined)
@@ -9,17 +9,17 @@ export const providerDefaultsSchema = z
9
9
  .strict();
10
10
  export const providerDefaultsKey = "provider.defaults";
11
11
  const defaultsKey = providerDefaultsKey;
12
+ // Set by the first-launch callout's "Got it" in older versions. Nothing writes it now; an
13
+ // install that pressed it keeps its first launch behind it.
12
14
  const doneKey = "first-run.done";
13
15
  // The order a text tool is preferred in when several are found.
14
16
  const textPreference = ["claude-code", "codex", "gemini"];
15
17
  export function readProviderDefaults(db) {
16
18
  return readJsonSetting(db, defaultsKey, providerDefaultsSchema, {});
17
19
  }
18
- export function dismissFirstRun(db) {
19
- writeSetting(db, doneKey, "1");
20
- }
21
- // First launch: find Claude Code, Codex and Gemini, pick the found ones as Play's defaults, and
22
- // say a video can be made without any API key. Later launches only report what was found.
20
+ // First launch: find Claude Code, Codex and Gemini and pick the found ones as Create's defaults.
21
+ // Later launches only report what was found. The first-run screen (slices/onboarding) says what
22
+ // was found in its own words.
23
23
  export async function firstRunStatus(deps, statuses) {
24
24
  const detected = statuses
25
25
  .filter((status) => status.readiness.kind === "cli")
@@ -58,28 +58,7 @@ export async function firstRunStatus(deps, statuses) {
58
58
  };
59
59
  writeSetting(deps.db, defaultsKey, JSON.stringify(defaults));
60
60
  }
61
- const names = detected
62
- .filter((cli) => cli.usable && cli.id !== "codex-image")
63
- .map((cli) => cli.displayName);
64
- const systemVoice = statuses.some((status) => status.readiness.kind === "local" && status.readiness.available);
65
- const narration = systemVoice
66
- ? "Narration can use your computer's built-in voice (System voice in Settings → Voices); add an ElevenLabs or OpenAI key later for a better one."
67
- : "Narration still needs a voice provider key, or turn Audio off or upload your own.";
68
- return {
69
- firstRun,
70
- detected,
71
- defaults,
72
- message: !firstRun
73
- ? null
74
- : names.length > 0
75
- ? "You can make a video now, no API keys needed."
76
- : "No AI command-line tool was found on this computer.",
77
- detail: !firstRun
78
- ? null
79
- : names.length > 0
80
- ? `Slopify found ${list(names)} and picked ${names.length === 1 ? "it" : "them"} on Play. ${narration} Keys are optional extras in Settings → Providers.`
81
- : "Install Claude Code, Codex or Antigravity CLI and sign in once in a terminal, then reload this page. Or paste an API key in Settings → Providers.",
82
- };
61
+ return { firstRun, detected, defaults };
83
62
  }
84
63
  async function firstModel(deps, provider, family) {
85
64
  try {
@@ -90,8 +69,3 @@ async function firstModel(deps, provider, family) {
90
69
  return "";
91
70
  }
92
71
  }
93
- function list(names) {
94
- return names.length <= 1
95
- ? (names[0] ?? "")
96
- : `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
97
- }
@@ -1,10 +1,15 @@
1
1
  import { choiceSites, retiredModelUsage, slotLabels, } from "../model-upkeep/usage.js";
2
2
  import { cliPathStatus } from "./cli-paths.js";
3
- import { keyGuides } from "./key-guides.js";
4
3
  import { testProviderKey } from "./key-test.js";
5
4
  import { isLocalCliProvider } from "./model.js";
6
5
  import { modelReach } from "./model-reach.js";
7
6
  import { providerStatuses } from "./readiness.js";
7
+ // The kinds as Keys & services heads its lists.
8
+ const familyTitles = {
9
+ llm: "Writing services",
10
+ tts: "Voice services",
11
+ image: "Image services",
12
+ };
8
13
  const signInCommand = {
9
14
  "claude-code": "claude auth login",
10
15
  codex: "codex login",
@@ -13,7 +18,16 @@ const signInCommand = {
13
18
  function loginSource(id) {
14
19
  return id === "codex-image" ? "codex" : id;
15
20
  }
16
- // Where each provider's models are chosen, as "Text model in template “Weekly”".
21
+ // Where a model is chosen, as a person finds it: "template “Weekly”", "the draft “Moon” on
22
+ // Create".
23
+ function siteWords(kind, name) {
24
+ if (kind !== "draft")
25
+ return `${kind} “${name}”`;
26
+ return name === "Untitled draft"
27
+ ? "an untitled draft on Create"
28
+ : `the draft “${name}” on Create`;
29
+ }
30
+ // Where each provider's models are chosen, as "Writing model in template “Weekly”".
17
31
  function uses(db) {
18
32
  const found = new Map();
19
33
  for (const site of choiceSites(db))
@@ -24,7 +38,7 @@ function uses(db) {
24
38
  const list = found.get(choice.provider) ?? [];
25
39
  list.push({
26
40
  model: choice.model,
27
- where: `${slotLabels[slot]} in ${site.kind} “${site.name}”`,
41
+ where: `${slotLabels[slot]} in ${siteWords(site.kind, site.name)}`,
28
42
  });
29
43
  found.set(choice.provider, list);
30
44
  }
@@ -37,7 +51,7 @@ function overall(checks, used, setUp) {
37
51
  return "warning";
38
52
  return setUp || used ? "ok" : "unused";
39
53
  }
40
- // "Check all" on Settings → Providers: each command-line tool found and signed in, each saved
54
+ // "Check all" on Settings → Keys & services: each command-line tool found and signed in, each saved
41
55
  // key accepted by its provider, and each chosen model still offered and answering for the key.
42
56
  // `only` checks one provider: its row's Check again, or a sign-in fix-it's.
43
57
  export async function checkProviderHealth(deps, only) {
@@ -85,7 +99,7 @@ async function cliHealth(deps, status, used) {
85
99
  label: "Installed",
86
100
  state: used.length > 0 ? "problem" : "skipped",
87
101
  detail: readiness.issue ??
88
- `${name} was not found on this computer. Install it, sign in once in a terminal, then choose Check all again. If it is installed somewhere unusual, set its path in Settings → Providers.`,
102
+ `${name} was not found on this computer. Install it, sign in once in a terminal, then press Check again. If it is installed somewhere unusual, set its path in Settings → Keys & services.`,
89
103
  });
90
104
  return { ...base, state: used.length > 0 ? "problem" : "unused", checks };
91
105
  }
@@ -93,7 +107,7 @@ async function cliHealth(deps, status, used) {
93
107
  label: "Installed",
94
108
  state: readiness.issueKind === "version" ? "problem" : "ok",
95
109
  detail: readiness.issueKind === "version"
96
- ? (readiness.issue ?? `Update ${name}, then choose Check all again.`)
110
+ ? (readiness.issue ?? `Update ${name}, then press Check again.`)
97
111
  : `Found ${command}${readiness.version === undefined ? "" : ` (version ${readiness.version})`}.`,
98
112
  });
99
113
  const source = loginSource(status.id);
@@ -115,7 +129,7 @@ async function cliHealth(deps, status, used) {
115
129
  ? {
116
130
  label: "Signed in",
117
131
  state: "problem",
118
- detail: `${name} is not signed in. Open a terminal on the computer running it, run "${signInCommand[source]}" and sign in, then choose Check all again.`,
132
+ detail: `${name} is not signed in. Open a terminal on the computer running it, run "${signInCommand[source]}" and sign in, then press Check again.`,
119
133
  }
120
134
  : {
121
135
  label: "Signed in",
@@ -141,7 +155,7 @@ async function cliModels(deps, status, used) {
141
155
  return {
142
156
  label: "Chosen models",
143
157
  state: "warning",
144
- detail: `Slopify could not read ${status.displayName}'s model list, so it could not check the chosen models. Make sure it is signed in and up to date, then choose Check all again.`,
158
+ detail: `Slopify could not read ${status.displayName}'s model list, so it could not check the chosen models. Make sure it is signed in and up to date, then press Check again.`,
145
159
  };
146
160
  }
147
161
  const ids = new Set(offered.map((model) => model.id));
@@ -174,14 +188,14 @@ async function reachable(deps, status, used) {
174
188
  return {
175
189
  label: "Model reachable",
176
190
  state: "problem",
177
- detail: `${name} does not offer ${describe(missing)} to this key: the model id is wrong, retired, or the account has no access to it. Pick another model there and save, or enable the model for your account, then choose Check all again.`,
191
+ detail: `${name} does not offer ${describe(missing)} to this key: the model id is wrong, retired, or the account has no access to it. Pick another model there and save, or enable the model for your account, then press Check again.`,
178
192
  };
179
193
  const unknown = [...found].filter(([, reach]) => reach === "unknown").map(([model]) => model);
180
194
  if (unknown.length > 0)
181
195
  return {
182
196
  label: "Model reachable",
183
197
  state: "warning",
184
- detail: `${name} didn't answer for ${unknown.join(", ")}, so Slopify couldn't confirm the key can use ${unknown.length === 1 ? "it" : "them"}. Check your internet connection, then choose Check all again.`,
198
+ detail: `${name} didn't answer for ${unknown.join(", ")}, so Slopify couldn't confirm the key can use ${unknown.length === 1 ? "it" : "them"}. Check your internet connection, then press Check again.`,
185
199
  };
186
200
  return {
187
201
  label: "Model reachable",
@@ -198,16 +212,17 @@ function describe(missing) {
198
212
  async function keyedHealth(deps, status, used, retired) {
199
213
  const { readiness } = status;
200
214
  const base = { id: status.id, displayName: status.displayName, family: status.family };
201
- const guide = keyGuides[status.id];
202
215
  const checks = [];
203
216
  const hasKey = readiness.kind === "keyed" && readiness.hasKey;
204
217
  if (!hasKey) {
218
+ // Said where it is read, Keys & services: the service's own setup, under its kind, has
219
+ // the key box and where to get a key.
205
220
  checks.push({
206
- label: "Key saved",
221
+ label: "Key",
207
222
  state: used.length > 0 ? "problem" : "skipped",
208
- detail: `No key is saved.${used.length > 0 ? ` It is chosen for ${describe(used)}.` : ""} Make one at ${guide?.keyPage.url ?? "the provider's website"}, paste it in Settings → Providers → ${status.displayName} and choose Save.`,
223
+ detail: `${used.length > 0 ? `None is saved, and ${status.displayName} is chosen for ${describe(used)}.` : "None is saved."} Open ${status.displayName} under ${familyTitles[status.family]}, paste a key and press Save.`,
209
224
  });
210
- return { ...base, state: used.length > 0 ? "problem" : "unused", checks };
225
+ return { ...base, state: used.length > 0 ? "no-key" : "unused", checks };
211
226
  }
212
227
  const test = await testProviderKey(deps, status.id);
213
228
  checks.push({
@@ -228,7 +243,7 @@ async function keyedHealth(deps, status, used, retired) {
228
243
  state: "problem",
229
244
  detail: `${describe(mine.map((usage) => ({
230
245
  model: usage.model,
231
- where: `${slotLabels[usage.slot]} in ${usage.kind} “${usage.name}”`,
246
+ where: `${slotLabels[usage.slot]} in ${siteWords(usage.kind, usage.name)}`,
232
247
  })))} ${mine.length === 1 ? "is" : "are"} no longer offered. Use Switch in Settings → Models → Retired models in use.`,
233
248
  });
234
249
  else if (used.length > 0)
@@ -21,7 +21,7 @@ export function keyTestOutcome(provider, answer, checkedAt, badKey) {
21
21
  const guide = keyGuides[provider];
22
22
  const keyPage = guide === undefined ? "the provider's website" : guide.keyPage.url;
23
23
  const billing = guide?.billing?.url;
24
- const place = `Settings → Providers → ${name}`;
24
+ const place = `Settings → Keys & services → ${name}`;
25
25
  const done = (result, message) => ({
26
26
  provider,
27
27
  result,
@@ -11,8 +11,8 @@ export const providerIds = [
11
11
  "claude-code",
12
12
  "codex",
13
13
  "gemini",
14
- "elevenlabs",
15
14
  "openai-tts",
15
+ "elevenlabs",
16
16
  "cartesia",
17
17
  "inworld",
18
18
  "system-voice",
@@ -70,8 +70,10 @@ export const providers = [
70
70
  binary: "agy",
71
71
  versionArgs: ["--version"],
72
72
  },
73
- { id: "elevenlabs", family: "tts", displayName: "ElevenLabs", auth: "key" },
73
+ // OpenAI first among the voices: the voices Slopify sets up are OpenAI's, so its key narrates
74
+ // at once, while ElevenLabs, Cartesia and Inworld also need a voice saved under Voices.
74
75
  { id: "openai-tts", family: "tts", displayName: "OpenAI", auth: "key" },
76
+ { id: "elevenlabs", family: "tts", displayName: "ElevenLabs", auth: "key" },
75
77
  { id: "cartesia", family: "tts", displayName: "Cartesia", auth: "key" },
76
78
  { id: "inworld", family: "tts", displayName: "Inworld", auth: "key" },
77
79
  { id: "system-voice", family: "tts", displayName: "System voice", auth: "local" },
@@ -56,7 +56,7 @@ export function saveSettings(deps, settings) {
56
56
  if (!subtitleModelValue.safeParse(settings.subtitleModel).success) {
57
57
  fields.push({
58
58
  field: "subtitleModel",
59
- message: "Choose Most accurate, Balanced or Smallest in Settings → Production defaults → Subtitle timing model.",
59
+ message: "Choose Most accurate, Balanced or Smallest in Settings → Production defaults → Caption timing model.",
60
60
  });
61
61
  }
62
62
  if (!videoEncoderValue.safeParse(settings.videoEncoder).success) {
@@ -74,7 +74,7 @@ export function saveSettings(deps, settings) {
74
74
  if (!renderPriorityValue.safeParse(settings.renderPriority).success) {
75
75
  fields.push({
76
76
  field: "renderPriority",
77
- message: "Choose Normal or Background in Settings → Production defaults → Render priority.",
77
+ message: "Choose Normal or Background in Settings → Production defaults → Encoding priority.",
78
78
  });
79
79
  }
80
80
  if (fields.length > 0) {
@@ -1,3 +1,4 @@
1
+ import { editSettings } from "../../kernel/ports/settings-places.js";
1
2
  import { clipBounds } from "./pick.js";
2
3
  // Shorts kept through a change of their moments: a count raised or lowered, a new intro or a
3
4
  // narration edit that moves the timing. A kept short is remembered by its sentences' text, so
@@ -87,5 +88,5 @@ export function lostMessage(lost, kept) {
87
88
  return short === undefined ? `short ${String(at)}` : `short ${String(at)} ("${short.title}")`;
88
89
  })
89
90
  .join(", ");
90
- return `The narration of ${names} changed, so its moment can't be kept. Open Edit project → Shorts and choose Pick a new moment for it or drop it, then Remake.`;
91
+ return `The narration of ${names} changed, so its moment can't be kept. Untick it under Keep finished shorts in ${editSettings.shorts}, so a new moment is picked for it, save, then use Choose what to make again (in the project's More menu).`;
91
92
  }
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { jsonBetween } from "../../kernel/json-answer.js";
3
+ import { editSettings } from "../../kernel/ports/settings-places.js";
3
4
  import { youtubeTimestamp } from "../youtube/timestamps.js";
4
5
  import { shortHashtagsMax, shortHashtagsMin, shortTitleMax } from "./model.js";
5
6
  export const shortPickSchema = z.object({
@@ -121,7 +122,7 @@ const answerSchema = z.array(z.object({
121
122
  hashtags: z.array(z.string()),
122
123
  why: z.string().optional(),
123
124
  }));
124
- const fix = "Try again, or choose another model in Edit project → Providers.";
125
+ const fix = `Try again, or choose another model in ${editSettings.writer}.`;
125
126
  // Timing noise: the length rules are checked to the hundredth of a second.
126
127
  const tolerance = 0.01;
127
128
  // Reads the model's answer and keeps the clips that follow the rules: in range, the right
@@ -221,8 +222,8 @@ export function checkPicks(text, sentences, limits) {
221
222
  export function noPicksMessage(problems, limits) {
222
223
  const shortVideo = limits.durationSeconds < limits.minSeconds;
223
224
  if (shortVideo)
224
- return `The narration is ${String(Math.round(limits.durationSeconds))} seconds long, shorter than the ${String(limits.minSeconds)}-second minimum for a short, so there is nothing to cut. Lower the shortest length in Edit project → Shorts, then Try again.`;
225
- return `The AI model didn't pick any clip Slopify could use as a short, twice (${problems[0] ?? "no clips were listed"}). Try again; if it keeps happening, widen the length range in Edit project → Shorts, or choose another model in Edit project → Providers.`;
225
+ return `The narration is ${String(Math.round(limits.durationSeconds))} seconds long, shorter than the ${String(limits.minSeconds)}-second minimum for a short, so there is nothing to cut. Lower the shortest Length in ${editSettings.shorts}, then Try again.`;
226
+ return `The AI model didn't pick any clip Slopify could use as a short, twice (${problems[0] ?? "no clips were listed"}). Try again; if it keeps happening, widen the Length range in ${editSettings.shorts}, or choose another model in ${editSettings.writer}.`;
226
227
  }
227
228
  function round(seconds) {
228
229
  return Math.round(seconds * 1000) / 1000;