@gentbajko/slopify 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (366) hide show
  1. package/dist/adapters/account-limits.js +1 -1
  2. package/dist/adapters/alignment/audio.js +1 -1
  3. package/dist/adapters/alignment/cache.js +1 -1
  4. package/dist/adapters/alignment/multilingual.js +2 -1
  5. package/dist/adapters/alignment/runner.js +1 -1
  6. package/dist/adapters/alignment/worker.js +1 -1
  7. package/dist/adapters/explain.js +10 -9
  8. package/dist/adapters/host-cli/index.js +2 -1
  9. package/dist/adapters/host-cli/transport.js +2 -1
  10. package/dist/adapters/image/bytes.js +2 -1
  11. package/dist/adapters/image/codex.js +6 -5
  12. package/dist/adapters/image/fal.js +4 -3
  13. package/dist/adapters/image/google.js +3 -2
  14. package/dist/adapters/image/models.js +2 -2
  15. package/dist/adapters/image/openai.js +2 -1
  16. package/dist/adapters/image/replicate.js +3 -2
  17. package/dist/adapters/image/video.js +2 -1
  18. package/dist/adapters/key-probes.js +1 -1
  19. package/dist/adapters/llm/antigravity-models.js +1 -1
  20. package/dist/adapters/llm/antigravity.js +7 -6
  21. package/dist/adapters/llm/assistant-cli.js +1 -1
  22. package/dist/adapters/llm/claude-code-models.js +1 -1
  23. package/dist/adapters/llm/claude-code.js +3 -2
  24. package/dist/adapters/llm/codex-models.js +1 -1
  25. package/dist/adapters/llm/document-workspace.js +3 -2
  26. package/dist/adapters/llm/image-workspace.js +3 -2
  27. package/dist/adapters/llm/openrouter.js +2 -1
  28. package/dist/adapters/llm/run-cli.js +3 -2
  29. package/dist/adapters/tts/gemini.js +2 -1
  30. package/dist/adapters/tts/inworld-async.js +2 -1
  31. package/dist/adapters/tts/inworld.js +3 -2
  32. package/dist/adapters/tts/system.js +2 -1
  33. package/dist/catalog/registry.js +20 -13
  34. package/dist/catalog/schema.js +2 -1
  35. package/dist/catalog/validate.js +5 -2
  36. package/dist/edge/http/actions.js +7 -7
  37. package/dist/edge/http/app.js +3 -3
  38. package/dist/edge/http/assistant.js +3 -1
  39. package/dist/edge/http/backups.js +1 -1
  40. package/dist/edge/http/channel-memory.js +2 -2
  41. package/dist/edge/http/channels.js +1 -1
  42. package/dist/edge/http/checkpoints.js +7 -5
  43. package/dist/edge/http/design-templates.js +3 -3
  44. package/dist/edge/http/designs.js +1 -1
  45. package/dist/edge/http/document-themes.js +3 -3
  46. package/dist/edge/http/draft-problem.js +1 -1
  47. package/dist/edge/http/extension-auth.js +1 -1
  48. package/dist/edge/http/home.js +9 -1
  49. package/dist/edge/http/model-checks.js +4 -4
  50. package/dist/edge/http/onboarding.js +29 -12
  51. package/dist/edge/http/outputs.js +3 -2
  52. package/dist/edge/http/patch-notes.js +2 -2
  53. package/dist/edge/http/planning.js +1 -1
  54. package/dist/edge/http/project-create.js +1 -1
  55. package/dist/edge/http/project-templates.js +2 -2
  56. package/dist/edge/http/projects.js +22 -4
  57. package/dist/edge/http/prompts.js +2 -2
  58. package/dist/edge/http/providers.js +9 -10
  59. package/dist/edge/http/refusal.js +1 -1
  60. package/dist/edge/http/remote-access.js +4 -4
  61. package/dist/edge/http/remote-guard.js +2 -2
  62. package/dist/edge/http/reviews.js +1 -1
  63. package/dist/edge/http/revision-files.js +1 -1
  64. package/dist/edge/http/revisions.js +4 -4
  65. package/dist/edge/http/schedules.js +13 -11
  66. package/dist/edge/http/settings.js +6 -2
  67. package/dist/edge/http/social.js +5 -4
  68. package/dist/edge/http/staging.js +1 -1
  69. package/dist/edge/http/storage.js +12 -4
  70. package/dist/edge/http/studio.js +71 -19
  71. package/dist/edge/http/style-preview.js +2 -2
  72. package/dist/edge/http/template-studio.js +12 -9
  73. package/dist/edge/http/trash.js +1 -1
  74. package/dist/edge/http/youtube-edits.js +1 -1
  75. package/dist/edge/mcp/excluded-routes.js +4 -3
  76. package/dist/edge/mcp/instructions.js +3 -3
  77. package/dist/edge/mcp/library-tools.js +8 -5
  78. package/dist/edge/mcp/remake-tools.js +7 -4
  79. package/dist/edge/mcp/server.js +1 -1
  80. package/dist/edge/mcp/tools-channels.js +9 -9
  81. package/dist/edge/mcp/tools-designs.js +9 -9
  82. package/dist/edge/mcp/tools-library.js +7 -7
  83. package/dist/edge/mcp/tools-planning.js +18 -18
  84. package/dist/edge/mcp/tools-projects.js +10 -10
  85. package/dist/edge/mcp/tools-publishing.js +9 -9
  86. package/dist/edge/mcp/tools-runs.js +9 -9
  87. package/dist/edge/mcp/tools-schedules.js +6 -6
  88. package/dist/edge/mcp/tools-setup.js +20 -14
  89. package/dist/edge/mcp/tools-social.js +5 -4
  90. package/dist/edge/mcp/tools-storage.js +3 -3
  91. package/dist/edge/mcp/tools-template-studio.js +18 -18
  92. package/dist/edge/remote-access/model.js +1 -1
  93. package/dist/extension/slopify-studio-chrome.zip +0 -0
  94. package/dist/extension/slopify-studio-firefox.zip +0 -0
  95. package/dist/kernel/db/migrations/0078-uploaded-from-links.sql +7 -0
  96. package/dist/kernel/pipeline.js +12 -0
  97. package/dist/kernel/ports/settings-places.js +31 -0
  98. package/dist/kernel/ports/system-speech.js +31 -3
  99. package/dist/kernel/runner/attempt.js +11 -3
  100. package/dist/kernel/runner/providers.js +3 -2
  101. package/dist/kernel/runner/standalone.js +2 -2
  102. package/dist/main.js +5 -3
  103. package/dist/model-catalog.js +20 -0
  104. package/dist/patch-notes/4.0.0.md +13 -13
  105. package/dist/patch-notes/4.1.0.md +42 -0
  106. package/dist/patch-notes/index.json +6 -0
  107. package/dist/slices/admission/listing-facts.js +95 -0
  108. package/dist/slices/admission/repo.js +9 -6
  109. package/dist/slices/admission/rules.js +7 -7
  110. package/dist/slices/admission/state-words.js +34 -5
  111. package/dist/slices/assistant/cost.js +1 -1
  112. package/dist/slices/assistant/model.js +10 -3
  113. package/dist/slices/assistant/turn.js +5 -3
  114. package/dist/slices/autopilot/warnings.js +1 -1
  115. package/dist/slices/backups/service.js +5 -5
  116. package/dist/slices/channels/cast-images.js +1 -1
  117. package/dist/slices/channels/schema.js +1 -1
  118. package/dist/slices/channels/videos.js +1 -1
  119. package/dist/slices/control/providers.js +2 -2
  120. package/dist/slices/designs/budget.js +1 -1
  121. package/dist/slices/designs/calls.js +2 -2
  122. package/dist/slices/designs/check-context.js +4 -4
  123. package/dist/slices/designs/fields.js +4 -4
  124. package/dist/slices/designs/gate.js +7 -5
  125. package/dist/slices/designs/render-input.js +1 -1
  126. package/dist/slices/designs/requests.js +1 -1
  127. package/dist/slices/designs/runner.js +1 -1
  128. package/dist/slices/designs/sample.js +2 -1
  129. package/dist/slices/designs/service.js +1 -1
  130. package/dist/slices/designs/template-checks.js +2 -2
  131. package/dist/slices/designs/template-make.js +3 -3
  132. package/dist/slices/designs/templates.js +3 -3
  133. package/dist/slices/document/fonts.js +1 -1
  134. package/dist/slices/document/model.js +7 -4
  135. package/dist/slices/document/render.js +2 -1
  136. package/dist/slices/document/theme-schema.js +1 -1
  137. package/dist/slices/document/theme.js +49 -3
  138. package/dist/slices/episodes/summarize.js +2 -2
  139. package/dist/slices/estimate/index.js +28 -22
  140. package/dist/slices/estimate/requests.js +4 -4
  141. package/dist/slices/fixes/rules.js +3 -1
  142. package/dist/slices/fonts/catalog.js +2 -1
  143. package/dist/slices/fonts/coverage.js +2 -1
  144. package/dist/slices/images/appearance.js +2 -1
  145. package/dist/slices/images/scenes.js +2 -1
  146. package/dist/slices/keywords/digest.js +2 -1
  147. package/dist/slices/keywords/rules.js +2 -2
  148. package/dist/slices/keywords/specs.js +5 -5
  149. package/dist/slices/keywords/values.js +1 -1
  150. package/dist/slices/library/transfer.js +3 -3
  151. package/dist/slices/loudness/loudnorm.js +2 -1
  152. package/dist/slices/loudness/model.js +3 -2
  153. package/dist/slices/loudness/sentence-level.js +2 -1
  154. package/dist/slices/model-checks/rules.js +13 -0
  155. package/dist/slices/model-upkeep/model.js +1 -1
  156. package/dist/slices/model-upkeep/switch.js +3 -3
  157. package/dist/slices/model-upkeep/usage.js +13 -2
  158. package/dist/slices/narration/breaths.js +2 -1
  159. package/dist/slices/narration/clean-pieces.js +2 -1
  160. package/dist/slices/narration/pauses-model.js +3 -2
  161. package/dist/slices/narration/pronunciation.js +3 -2
  162. package/dist/slices/notifications/subject.js +2 -2
  163. package/dist/slices/onboarding/copy.js +19 -2
  164. package/dist/slices/onboarding/first-run.js +13 -38
  165. package/dist/slices/onboarding/install.js +2 -1
  166. package/dist/slices/onboarding/model.js +26 -21
  167. package/dist/slices/onboarding/preferred-model.js +14 -0
  168. package/dist/slices/onboarding/quick-short.js +103 -64
  169. package/dist/slices/onboarding/sample.js +4 -1
  170. package/dist/slices/onboarding/state.js +7 -0
  171. package/dist/slices/onboarding/template.js +6 -5
  172. package/dist/slices/outputs/add.js +3 -2
  173. package/dist/slices/outputs/service.js +2 -2
  174. package/dist/slices/patch-notes/library.js +1 -1
  175. package/dist/slices/play-drafts/readiness.js +3 -3
  176. package/dist/slices/play-drafts/review-inputs.js +1 -1
  177. package/dist/slices/play-drafts/service.js +17 -7
  178. package/dist/slices/project-templates/next-chapter.js +2 -1
  179. package/dist/slices/project-templates/provider-defaults.js +22 -0
  180. package/dist/slices/project-templates/setup-sections.js +26 -0
  181. package/dist/slices/project-templates/setup.js +6 -1
  182. package/dist/slices/rebuild/preview-details.js +5 -5
  183. package/dist/slices/rebuild/recipe-preparation.js +1 -1
  184. package/dist/slices/rebuild/recipe-save.js +2 -2
  185. package/dist/slices/rebuild/recipe-validation.js +5 -4
  186. package/dist/slices/rebuild/recipe-voices.js +3 -2
  187. package/dist/slices/rebuild/recipe-work.js +4 -4
  188. package/dist/slices/rebuild/recovery.js +6 -6
  189. package/dist/slices/rebuild/remake-outdated.js +5 -5
  190. package/dist/slices/rebuild/review-redo.js +1 -1
  191. package/dist/slices/rebuild/runtime-animate.js +2 -2
  192. package/dist/slices/rebuild/runtime-article.js +4 -3
  193. package/dist/slices/rebuild/runtime-document.js +5 -4
  194. package/dist/slices/rebuild/runtime-export-bed.js +2 -1
  195. package/dist/slices/rebuild/runtime-export-edit.js +2 -1
  196. package/dist/slices/rebuild/runtime-export-inputs.js +5 -4
  197. package/dist/slices/rebuild/runtime-export-short.js +1 -1
  198. package/dist/slices/rebuild/runtime-export.js +4 -3
  199. package/dist/slices/rebuild/runtime-image.js +4 -3
  200. package/dist/slices/rebuild/runtime-local.js +3 -2
  201. package/dist/slices/rebuild/runtime-narration-text.js +3 -2
  202. package/dist/slices/rebuild/runtime-provider.js +7 -6
  203. package/dist/slices/rebuild/runtime-review.js +6 -5
  204. package/dist/slices/rebuild/runtime-shorts.js +6 -5
  205. package/dist/slices/rebuild/runtime-store.js +17 -2
  206. package/dist/slices/rebuild/runtime-subtitles.js +13 -12
  207. package/dist/slices/rebuild/runtime-voices.js +2 -1
  208. package/dist/slices/rebuild/runtime-youtube.js +3 -2
  209. package/dist/slices/rebuild/service-readiness.js +11 -3
  210. package/dist/slices/research/synthesis.js +3 -2
  211. package/dist/slices/reviews/external-model.js +1 -1
  212. package/dist/slices/reviews/holds.js +5 -4
  213. package/dist/slices/reviews/model.js +8 -5
  214. package/dist/slices/reviews/rules.js +3 -3
  215. package/dist/slices/reviews/verdict.js +2 -1
  216. package/dist/slices/revisions/image-plan.js +1 -1
  217. package/dist/slices/revisions/mutation-assets.js +2 -1
  218. package/dist/slices/revisions/mutations.js +3 -2
  219. package/dist/slices/schedules/agenda.js +7 -5
  220. package/dist/slices/schedules/design-check.js +4 -4
  221. package/dist/slices/schedules/design-run.js +3 -3
  222. package/dist/slices/schedules/repo.js +6 -5
  223. package/dist/slices/schedules/schema.js +3 -0
  224. package/dist/slices/schedules/topics.js +5 -5
  225. package/dist/slices/settings/first-run.js +6 -32
  226. package/dist/slices/settings/health.js +30 -15
  227. package/dist/slices/settings/key-test.js +1 -1
  228. package/dist/slices/settings/model.js +4 -2
  229. package/dist/slices/settings/playback.js +2 -2
  230. package/dist/slices/shorts/keep.js +2 -1
  231. package/dist/slices/shorts/pick.js +4 -3
  232. package/dist/slices/shorts/prompts.js +2 -1
  233. package/dist/slices/shorts/render.js +3 -2
  234. package/dist/slices/social/media.js +5 -4
  235. package/dist/slices/social/pack.js +1 -1
  236. package/dist/slices/social/post-prompt.js +2 -1
  237. package/dist/slices/social/reshape.js +2 -2
  238. package/dist/slices/social/serve.js +1 -1
  239. package/dist/slices/social/service.js +3 -2
  240. package/dist/slices/social/state.js +1 -1
  241. package/dist/slices/storage/backup-import.js +2 -2
  242. package/dist/slices/storage/portable.js +1 -1
  243. package/dist/slices/storage/trim.js +23 -13
  244. package/dist/slices/studio/calendar.js +3 -1
  245. package/dist/slices/studio/disclosure.js +4 -4
  246. package/dist/slices/studio/model.js +1 -1
  247. package/dist/slices/studio/pack.js +9 -8
  248. package/dist/slices/studio/plan.js +1 -1
  249. package/dist/slices/studio/settings.js +6 -6
  250. package/dist/slices/studio/videos.js +23 -4
  251. package/dist/slices/style-preview/render.js +3 -3
  252. package/dist/slices/style-preview/schema.js +1 -1
  253. package/dist/slices/subtitles/captions.js +1 -1
  254. package/dist/slices/template-studio/anatomy-view.js +46 -3
  255. package/dist/slices/template-studio/bakeoff.js +3 -3
  256. package/dist/slices/template-studio/budget.js +6 -3
  257. package/dist/slices/template-studio/cards.js +78 -0
  258. package/dist/slices/template-studio/checks.js +99 -50
  259. package/dist/slices/template-studio/for-all.js +5 -5
  260. package/dist/slices/template-studio/free-video.js +1 -1
  261. package/dist/slices/template-studio/free.js +5 -5
  262. package/dist/slices/template-studio/model.js +19 -15
  263. package/dist/slices/template-studio/pack-import.js +14 -14
  264. package/dist/slices/template-studio/pack.js +9 -7
  265. package/dist/slices/template-studio/paid.js +12 -5
  266. package/dist/slices/template-studio/quote.js +18 -11
  267. package/dist/slices/template-studio/ready.js +7 -7
  268. package/dist/slices/template-studio/runner.js +1 -1
  269. package/dist/slices/template-studio/save.js +12 -12
  270. package/dist/slices/template-studio/say-it-as.js +3 -3
  271. package/dist/slices/template-studio/schedule-gate.js +1 -1
  272. package/dist/slices/template-studio/schedule-price.js +3 -1
  273. package/dist/slices/template-studio/schedule-sample.js +5 -1
  274. package/dist/slices/template-studio/schema.js +25 -0
  275. package/dist/slices/template-studio/service.js +3 -1
  276. package/dist/slices/template-studio/status.js +26 -9
  277. package/dist/slices/template-studio/topic.js +3 -3
  278. package/dist/slices/thumbnail/by-llm.js +1 -1
  279. package/dist/slices/uploads/repo.js +37 -1
  280. package/dist/slices/uploads/rules.js +3 -2
  281. package/dist/slices/video/edit-list.js +2 -2
  282. package/dist/slices/video/edit-settings.js +1 -1
  283. package/dist/slices/video/encoding.js +14 -5
  284. package/dist/slices/video/ffmpeg.js +2 -2
  285. package/dist/slices/video/plan.js +2 -1
  286. package/dist/slices/video/slideshow.js +1 -1
  287. package/dist/slices/voices/attribution.js +3 -2
  288. package/dist/slices/voices/model.js +9 -8
  289. package/dist/slices/voices/script.js +2 -1
  290. package/dist/slices/youtube/answer.js +2 -1
  291. package/dist/slices/youtube/edits-repo.js +1 -1
  292. package/dist/tutorials/AI-CLIs.md +25 -25
  293. package/dist/tutorials/Agents-MCP.md +32 -32
  294. package/dist/tutorials/Assistant.md +5 -5
  295. package/dist/tutorials/Backups.md +7 -7
  296. package/dist/tutorials/Calendar.md +17 -25
  297. package/dist/tutorials/Cast-Library.md +19 -19
  298. package/dist/tutorials/Channels.md +40 -35
  299. package/dist/tutorials/Costs-and-Run-Cost.md +38 -40
  300. package/dist/tutorials/Create-Article.md +114 -0
  301. package/dist/tutorials/Create-Extras.md +112 -0
  302. package/dist/tutorials/Create-Images.md +165 -0
  303. package/dist/tutorials/Create-Narration.md +178 -0
  304. package/dist/tutorials/Create-Overview.md +244 -0
  305. package/dist/tutorials/Create-Video-and-Style.md +144 -0
  306. package/dist/tutorials/Docker.md +3 -3
  307. package/dist/tutorials/Document-Themes.md +32 -32
  308. package/dist/tutorials/Editing-a-Project.md +94 -94
  309. package/dist/tutorials/FAQ.md +18 -18
  310. package/dist/tutorials/First-Launch-and-Welcome.md +50 -64
  311. package/dist/tutorials/Home-and-Projects.md +101 -76
  312. package/dist/tutorials/Home.md +33 -33
  313. package/dist/tutorials/Install.md +13 -13
  314. package/dist/tutorials/Intros-and-Outros.md +33 -34
  315. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +62 -58
  316. package/dist/tutorials/Keywords-Slopify-Fills.md +21 -21
  317. package/dist/tutorials/Models.md +27 -27
  318. package/dist/tutorials/Multiple-Voices.md +41 -40
  319. package/dist/tutorials/Narration-Aliases-and-Glossary.md +14 -14
  320. package/dist/tutorials/Notifications.md +21 -17
  321. package/dist/tutorials/Other-Languages.md +15 -15
  322. package/dist/tutorials/PDF-Documents.md +38 -29
  323. package/dist/tutorials/Posting-to-Social-Sites.md +12 -12
  324. package/dist/tutorials/Project-Page.md +118 -120
  325. package/dist/tutorials/Prompts-and-Templates.md +84 -0
  326. package/dist/tutorials/Prompts.md +45 -45
  327. package/dist/tutorials/Providers-and-Keys.md +105 -98
  328. package/dist/tutorials/Publishing-to-YouTube.md +31 -31
  329. package/dist/tutorials/Recovery-and-Retries.md +31 -31
  330. package/dist/tutorials/Remote-Access.md +2 -2
  331. package/dist/tutorials/Reviews-and-Checkpoints.md +90 -93
  332. package/dist/tutorials/Schedules.md +27 -28
  333. package/dist/tutorials/Settings-Reference.md +182 -204
  334. package/dist/tutorials/Shorts.md +34 -33
  335. package/dist/tutorials/Start-at-Login.md +3 -3
  336. package/dist/tutorials/Studio-Extension.md +22 -24
  337. package/dist/tutorials/Template-Studio.md +45 -42
  338. package/dist/tutorials/Templates.md +50 -79
  339. package/dist/tutorials/Trash.md +4 -4
  340. package/dist/tutorials/Troubleshooting.md +44 -44
  341. package/dist/tutorials/Uninstalling-and-Moving.md +5 -5
  342. package/dist/tutorials/Updating-and-Patch-Notes.md +18 -18
  343. package/dist/tutorials/Video-Editing.md +34 -34
  344. package/dist/tutorials/Where-Your-Files-Live.md +10 -10
  345. package/dist/tutorials/YouTube-Description.md +26 -26
  346. package/dist/tutorials/Your-First-Short.md +35 -35
  347. package/dist/tutorials/_Sidebar.md +15 -15
  348. package/dist/web/assets/index-BNftKQrA.css +1 -0
  349. package/dist/web/assets/index-Cpa58woP.js +459 -0
  350. package/dist/web/assets/pdf-BKh8f7R6.js +56 -0
  351. package/dist/web/assets/pdf.worker.min-BmVo14Nb.mjs +28 -0
  352. package/dist/web/assets/pdf.worker.min-D7i8o3wR.js +1 -0
  353. package/dist/web/index.html +2 -2
  354. package/package.json +2 -2
  355. package/dist/tutorials/Library-Overview.md +0 -76
  356. package/dist/tutorials/Play-Images.md +0 -165
  357. package/dist/tutorials/Play-Narration.md +0 -167
  358. package/dist/tutorials/Play-Outputs.md +0 -109
  359. package/dist/tutorials/Play-Overview.md +0 -227
  360. package/dist/tutorials/Play-Title-and-Article.md +0 -136
  361. package/dist/tutorials/Play-Video-and-Style.md +0 -143
  362. package/dist/web/assets/index-CkAI_2E7.css +0 -1
  363. package/dist/web/assets/index-DcD0cdfo.js +0 -334
  364. package/dist/web/assets/pdf-TODRwF31.js +0 -54
  365. package/dist/web/assets/pdf.worker.min-Dswkl-cV.mjs +0 -29
  366. package/dist/web/assets/pdf.worker.min-zC6nfbxG.js +0 -1
@@ -1,7 +1,7 @@
1
1
  import { statfs } from "node:fs/promises";
2
2
  import { z } from "zod";
3
3
  // The autopilot's hourly look at what may stop the schedules: the OpenRouter key's spending
4
- // limit (`GET /api/v1/key`, the same read Settings → Providers' Test makes) and the free disk.
4
+ // limit (`GET /api/v1/key`, the same read Settings → Keys & services' Test makes) and the free disk.
5
5
  // Read-only: neither starts paid work.
6
6
  const keyAnswer = z.object({
7
7
  data: z.object({ limit: z.number().nullable(), limit_remaining: z.number().nullable() }).loose(),
@@ -33,7 +33,7 @@ export async function decodeAudio(ffmpeg, source, output, signal) {
33
33
  child.on("close", (code) => {
34
34
  signal.removeEventListener("abort", abort);
35
35
  if (signal.aborted)
36
- reject(new Error("Subtitle audio preparation was canceled."));
36
+ reject(new Error("Caption audio preparation was canceled."));
37
37
  else if (code !== 0)
38
38
  reject(new Error(`Could not prepare the narration for subtitles. ${errorText}`));
39
39
  else
@@ -170,7 +170,7 @@ async function seedModel(input, seed, target) {
170
170
  await rm(staging, { recursive: true, force: true });
171
171
  }
172
172
  }
173
- const smaller = " To download less, pick a smaller model in Settings → Production defaults → Subtitle timing model.";
173
+ const smaller = " To download less, pick a smaller model in Settings → Production defaults → Caption timing model.";
174
174
  function megabytes(bytes) {
175
175
  return `${String(Math.round(bytes / 1_000_000))} MB`;
176
176
  }
@@ -1,4 +1,5 @@
1
1
  import { languageInfo } from "../../kernel/ports/languages.js";
2
+ import { editSettings } from "../../kernel/ports/settings-places.js";
2
3
  import { englishSpec } from "./spec.js";
3
4
  import { spellers, spokenNumber } from "./spell.js";
4
5
  import { respoken, speechWords } from "./text.js";
@@ -239,7 +240,7 @@ function spell(text, alphabet, raw, language) {
239
240
  }
240
241
  const folded = fold(character);
241
242
  if (folded === undefined)
242
- throw new Error(`Word timing for ${languageInfo(language).name} reads Latin letters only, and "${raw}" has letters from another alphabet. Write that word in Latin letters in the article (open the project, Edit project → Article), then Try again.`);
243
+ throw new Error(`Word timing for ${languageInfo(language).name} reads Latin letters only, and "${raw}" has letters from another alphabet. Write that word in Latin letters in the article (${editSettings.article}), then Try again.`);
243
244
  result += folded;
244
245
  }
245
246
  return result.replace(/\s+/g, " ").trim();
@@ -24,7 +24,7 @@ export async function runAlignmentWorker(input, signal, onProgress, worker = new
24
24
  signal.removeEventListener("abort", abort);
25
25
  child.kill("SIGKILL");
26
26
  };
27
- const abort = () => finish(new Error("Subtitle alignment was canceled."));
27
+ const abort = () => finish(new Error("Caption timing was canceled."));
28
28
  signal.addEventListener("abort", abort, { once: true });
29
29
  child.stderr?.on("data", (data) => {
30
30
  stderr = (stderr + data.toString("utf8")).slice(-2000);
@@ -168,7 +168,7 @@ async function openSession(modelPath) {
168
168
  const reason = nativeError instanceof Error
169
169
  ? (nativeError.message.split("\n")[0] ?? "")
170
170
  : String(nativeError);
171
- throw new Error(`Subtitle timing could not start: neither of its speech engines loaded on this computer (${system}): ${reason}. Reinstall Slopify so npm installs the engine for this system, then try again; if it keeps failing, turn subtitles off for this project.`);
171
+ throw new Error(`Caption timing could not start: neither of its speech engines loaded on this computer (${system}): ${reason}. Reinstall Slopify so npm installs the engine for this system, then try again; if it keeps failing, turn captions off for this project.`);
172
172
  }
173
173
  }
174
174
  const session = await native.InferenceSession.create(modelPath, {
@@ -1,3 +1,4 @@
1
+ import { editSettings } from "../kernel/ports/settings-places.js";
1
2
  // The sentences a failed provider call shows. Whatever an adapter throws becomes the stage's
2
3
  // failure reason on the project page, read by someone who only wants the video, so each one
3
4
  // says what failed, the likely cause and the next step, using the names on screen. The
@@ -6,19 +7,19 @@
6
7
  // A provider's error body can be a whole HTML page; the first few hundred characters say why.
7
8
  const detailMax = 300;
8
9
  export function missingKey(provider) {
9
- return `No ${provider} API key is saved. Add one in Settings → Providers, then use Try again.`;
10
+ return `No ${provider} API key is saved. Add one in Settings → Keys & services, then use Try again.`;
10
11
  }
11
12
  export const refreshList = "refresh the list";
12
- export const checkChoice = "Check the provider and model chosen in the Providers section of Edit project, then use Try again.";
13
+ export const checkChoice = `Check the service and model chosen in ${editSettings.open} (under Written by in Article, in Narration or in Images), then use Try again.`;
13
14
  export function httpFailure(failure) {
14
15
  const { provider, status } = failure;
15
16
  const said = quoted(failure.label ?? `error ${String(status)}`, failure.detail);
16
17
  const next = failure.next ?? "use Try again";
17
18
  if (status === 401) {
18
- return `${provider} did not accept the API key${said}. The key may be wrong, expired or revoked: paste a current key in Settings → Providers, then ${next}.`;
19
+ return `${provider} did not accept the API key${said}. The key may be wrong, expired or revoked: paste a current key in Settings → Keys & services, then ${next}.`;
19
20
  }
20
21
  if (status === 403) {
21
- return `${provider} refused access${said}. The API key may not be allowed to use this model, or the account needs attention: check your ${provider} account and the key in Settings → Providers, then ${next}.`;
22
+ return `${provider} refused access${said}. The API key may not be allowed to use this model, or the account needs attention: check your ${provider} account and the key in Settings → Keys & services, then ${next}.`;
22
23
  }
23
24
  if (status === 402) {
24
25
  return `${provider} says the account is out of credits${said}. Add credits on the ${provider} website, then ${next}.`;
@@ -27,7 +28,7 @@ export function httpFailure(failure) {
27
28
  return `${provider} is limiting requests${said}. Too many requests were sent at once or the plan's quota is used up: wait a few minutes or check your ${provider} usage and billing, then ${next}.`;
28
29
  }
29
30
  if (status >= 400 && status < 500) {
30
- return `${provider} rejected the ${failure.subject ?? "request"}${said}. ${failure.fix ?? (failure.next === undefined ? checkChoice : `Check the key in Settings → Providers, then ${next}.`)}`;
31
+ return `${provider} rejected the ${failure.subject ?? "request"}${said}. ${failure.fix ?? (failure.next === undefined ? checkChoice : `Check the key in Settings → Keys & services, then ${next}.`)}`;
31
32
  }
32
33
  if (status >= 500) {
33
34
  return `${provider} had a problem on its side${said}. This is usually temporary: wait a few minutes, then ${next}.`;
@@ -48,15 +49,15 @@ export function noAudio(provider) {
48
49
  return `${provider} finished without sending any audio. Use Try again; if it keeps happening, check the voice in Settings → Voices.`;
49
50
  }
50
51
  export function noImage(provider) {
51
- return `${provider} finished without sending an image. Use Try again; if it keeps happening, reword the image prompt in the Images section of Edit project or choose another image model in its Providers section.`;
52
+ return `${provider} finished without sending an image. Use Try again; if it keeps happening, reword the image prompt in ${editSettings.images} or choose another image model there.`;
52
53
  }
53
54
  export function refusedImage(provider, detail) {
54
- return `${provider} refused to make this image under its content rules${quoted("", detail)}. Reword the image prompt in the Images section of Edit project, or choose another image provider in its Providers section.`;
55
+ return `${provider} refused to make this image under its content rules${quoted("", detail)}. Reword the image prompt in ${editSettings.images}, or choose another image service there.`;
55
56
  }
56
57
  // A narration request the provider turned down is most often a voice that no longer exists
57
58
  // or a part longer than the model takes.
58
59
  export function voiceFix(voiceId) {
59
- return `Check that voice "${voiceId}" still exists in Settings → Voices and that each narration part fits the model's length limit (Chunking, in the Providers section of Edit project), then use Try again.`;
60
+ return `Check that voice "${voiceId}" still exists in Settings → Voices and that each narration part fits the model's length limit (Chunking, in ${editSettings.narration}), then use Try again.`;
60
61
  }
61
62
  const cliNames = {
62
63
  claude: "Claude Code CLI",
@@ -79,7 +80,7 @@ export function cliReported(binary, detail, next) {
79
80
  return `The ${cliName(binary)} reported an error${quoted("", detail)}. ${next}`;
80
81
  }
81
82
  export function cliCheck(binary) {
82
- return `Use Try again; if it keeps failing, run ${commandOf(binary)} in a terminal to check it works and is signed in, or choose another model in the Providers section of Edit project.`;
83
+ return `Use Try again; if it keeps failing, run ${commandOf(binary)} in a terminal to check it works and is signed in, or choose another model in ${editSettings.writer}.`;
83
84
  }
84
85
  // ` (error 401: "Unauthorized")`, ` (error 401)`, ` ("Unauthorized")`, or nothing.
85
86
  export function quoted(label, detail) {
@@ -2,12 +2,13 @@ import { redact } from "../../kernel/log.js";
2
2
  import { bridgeLimits, decodeHostImageReport, hostFaultSchema, hostFrameSchema, hostHealthSchema, hostImageReportHeader, hostImageSchema, hostLlmSchema, hostModelsSchema, hostOpenedSchema, hostOpenFolderSchema, hostStatusSchema, } from "../../kernel/ports/host-cli.js";
3
3
  import { agentImageTimeoutMs } from "../../kernel/ports/image.js";
4
4
  import { isProviderError, providerError, } from "../../kernel/ports/model.js";
5
+ import { editSettings } from "../../kernel/ports/settings-places.js";
5
6
  import { sniffImage } from "../image/bytes.js";
6
7
  import { claudePlanLimit } from "../llm/claude-code.js";
7
8
  import { codexPlanLimit } from "../llm/codex-limits.js";
8
9
  import { accessField } from "../model-access.js";
9
10
  import { hostRequest, hostUnavailable, readHostBytes, } from "./transport.js";
10
- const tooBigForHost = "Slopify could not send this request to the host helper because it is larger than, or shaped differently from, what the helper accepts. Make the inputs shorter in Edit project, then use Try again; if it happens again, use Download diagnostics in Settings and report it.";
11
+ const tooBigForHost = `Slopify could not send this request to the host helper because it is larger than, or shaped differently from, what the helper accepts. Make the article or its research notes shorter in ${editSettings.article}, then use Try again; if it happens again, use Download diagnostics in Settings and report it.`;
11
12
  export function createHostCliClient(options) {
12
13
  async function request(input) {
13
14
  if (!options.directory)
@@ -5,6 +5,7 @@ import { join } from "node:path";
5
5
  import { bridgeLimits, hostFramesHeader, hostFramesVersion } from "../../kernel/ports/host-cli.js";
6
6
  import { agentImageTimeoutMs } from "../../kernel/ports/image.js";
7
7
  import { providerError } from "../../kernel/ports/model.js";
8
+ import { editSettings } from "../../kernel/ports/settings-places.js";
8
9
  export function hostUnavailable(submitted = false) {
9
10
  return providerError({
10
11
  kind: "unavailable",
@@ -34,7 +35,7 @@ export async function hostRequest(options) {
34
35
  if (options.body && options.body.byteLength > bridgeLimits.request)
35
36
  throw providerError({
36
37
  kind: "unsupported",
37
- message: "This request is larger than the host helper accepts (16 MB), so nothing was sent. Make the inputs shorter in Edit project, or choose an API provider such as OpenRouter in its Providers section, then use Try again.",
38
+ message: `This request is larger than the host helper accepts (16 MB), so nothing was sent. Make the article or its research notes shorter in ${editSettings.article}, or choose an API service such as OpenRouter under Written by there, then use Try again.`,
38
39
  });
39
40
  let token;
40
41
  try {
@@ -1,4 +1,5 @@
1
1
  import { providerError } from "../../kernel/ports/model.js";
2
+ import { editSettings } from "../../kernel/ports/settings-places.js";
2
3
  // Two of the three image providers answer with a link rather than with bytes, so fetching
3
4
  // that link is part of the provider call and belongs inside the attempt with it: a URL
4
5
  // that 404s or hands back an HTML error page is a failed attempt, not a corrupt file on
@@ -48,7 +49,7 @@ export async function downloadImage(download) {
48
49
  if (mime === undefined) {
49
50
  throw providerError({
50
51
  kind: "other",
51
- message: `${download.provider} sent back something that is not a PNG or JPEG image (${describeBytes(bytes)}). Use Try again; if it keeps happening, choose another image model in the Providers section of Edit project.`,
52
+ message: `${download.provider} sent back something that is not a PNG or JPEG image (${describeBytes(bytes)}). Use Try again; if it keeps happening, choose another image model in ${editSettings.images}.`,
52
53
  });
53
54
  }
54
55
  return { bytes, mime };
@@ -5,6 +5,7 @@ import { z } from "zod";
5
5
  import { redact } from "../../kernel/log.js";
6
6
  import { agentImageTimeoutMs, } from "../../kernel/ports/image.js";
7
7
  import { providerError } from "../../kernel/ports/model.js";
8
+ import { editSettings } from "../../kernel/ports/settings-places.js";
8
9
  import { cliReported, quoted, refusedImage } from "../explain.js";
9
10
  import { cliLoginError } from "../llm/cli-login-error.js";
10
11
  import { codexLimitError, readingOf } from "../llm/codex.js";
@@ -212,7 +213,7 @@ export function codexImage(deps) {
212
213
  if (req.model.trim() === "" || req.model.startsWith("-"))
213
214
  throw providerError({
214
215
  kind: "unsupported",
215
- message: "No Codex model is chosen for images. Choose one (or Codex default) for images in the Providers section of Edit project, then use Try again.",
216
+ message: `No Codex model is chosen for images. Choose one (or Codex default) in ${editSettings.images}, then use Try again.`,
216
217
  });
217
218
  req.signal.throwIfAborted();
218
219
  const directory = mkdtempSync(join(tmpdir(), "slopify-codex-image-"));
@@ -253,7 +254,7 @@ export function codexImage(deps) {
253
254
  catch {
254
255
  throw providerError({
255
256
  kind: "unsupported",
256
- message: "The Codex CLI could not be started. Check it is installed and set up in Settings → Providers, then use Try again.",
257
+ message: "The Codex CLI could not be started. Check it is installed and set up in Settings → Keys & services, then use Try again.",
257
258
  });
258
259
  }
259
260
  let completed = false;
@@ -337,7 +338,7 @@ export function codexImage(deps) {
337
338
  if (ended.error !== null)
338
339
  throw providerError({
339
340
  kind: "unsupported",
340
- message: "The Codex CLI could not be started. Check it is installed and set up in Settings → Providers, then use Try again.",
341
+ message: "The Codex CLI could not be started. Check it is installed and set up in Settings → Keys & services, then use Try again.",
341
342
  });
342
343
  const stderrLimit = codexPlanLimit(run.stderr(), new Date());
343
344
  if ((ended.code !== 0 || !completed) && stderrLimit !== undefined)
@@ -397,8 +398,8 @@ async function* bounded(source, maximum) {
397
398
  yield chunk;
398
399
  }
399
400
  }
400
- const imageNext = "Use Try again; if it keeps failing, run codex in a terminal to check it works and is signed in, or choose another image provider in the Providers section of Edit project.";
401
- const cannotDraw = "Update the Codex CLI and check your ChatGPT plan includes image generation, or choose another image provider in the Providers section of Edit project.";
401
+ const imageNext = `Use Try again; if it keeps failing, run codex in a terminal to check it works and is signed in, or choose another image service in ${editSettings.images}.`;
402
+ const cannotDraw = `Update the Codex CLI and check your ChatGPT plan includes image generation, or choose another image service in ${editSettings.images}.`;
402
403
  function imageEnvironment(directory, source) {
403
404
  const env = { PWD: directory };
404
405
  const allowed = [
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
  import { internalError } from "../../kernel/errors.js";
3
3
  import { redact } from "../../kernel/log.js";
4
4
  import { providerError } from "../../kernel/ports/model.js";
5
+ import { createRows, editSettings } from "../../kernel/ports/settings-places.js";
5
6
  import { httpFailure, noImage, providerSaid, unreadable } from "../explain.js";
6
7
  import { failureText, httpProviderError, kindOfStatus, requireKey, safeJson, } from "../http-failure.js";
7
8
  import { modelAccessOf } from "../model-access.js";
@@ -114,7 +115,7 @@ export function falImage(deps) {
114
115
  if (req.reference !== undefined && edit === undefined)
115
116
  throw providerError({
116
117
  kind: "unsupported",
117
- message: "This fal.ai model can't use an establishing image as a reference. Choose FLUX.2 or Nano Banana 2 under Images → Model on Play or in Edit project → Providers, or set Establishing image to Off in the Images section.",
118
+ message: `This fal.ai model can't use an establishing image as a reference. Choose FLUX.2 or Nano Banana 2 as the image model in ${createRows.images} or ${editSettings.images}, or set Establishing image to Off there.`,
118
119
  });
119
120
  const response = await deps.fetch(`${falBase}/${edit ?? req.model}`, {
120
121
  method: "POST",
@@ -177,7 +178,7 @@ export function falImage(deps) {
177
178
  if (current.error)
178
179
  throw providerError({
179
180
  kind: "other",
180
- message: providerSaid("fal.ai", "could not make the video clip", redact(current.error), "Use Try again; if it keeps happening, choose another image-to-video model under Animate images in Edit project → Inputs → Look."),
181
+ message: providerSaid("fal.ai", "could not make the video clip", redact(current.error), `Use Try again; if it keeps happening, choose another image-to-video model under Animate images in ${editSettings.look}.`),
181
182
  });
182
183
  if (current.status === "COMPLETED")
183
184
  break;
@@ -213,7 +214,7 @@ function refused(answer, prompt) {
213
214
  if (flags.length > 0 && flags.every((flagged) => flagged)) {
214
215
  throw providerError({
215
216
  kind: "refusal",
216
- message: `fal.ai's safety checker blocked the image for this prompt: "${redact(prompt)}". Reword the image prompt in the Images section of Edit project, or choose another image provider in its Providers section.`,
217
+ message: `fal.ai's safety checker blocked the image for this prompt: "${redact(prompt)}". Reword the image prompt in ${editSettings.images}, or choose another image service there.`,
217
218
  });
218
219
  }
219
220
  }
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { redact } from "../../kernel/log.js";
3
3
  import { providerError } from "../../kernel/ports/model.js";
4
+ import { editSettings } from "../../kernel/ports/settings-places.js";
4
5
  import { httpFailure, noImage, quoted, refusedImage, unreadable } from "../explain.js";
5
6
  import { failureText, httpProviderError, kindOfStatus, requireKey, safeJson, } from "../http-failure.js";
6
7
  import { modelAccessOf } from "../model-access.js";
@@ -118,7 +119,7 @@ function decode(part) {
118
119
  const claimed = part.mime_type ?? "nothing";
119
120
  throw providerError({
120
121
  kind: "other",
121
- message: `Google sent back something that is not a PNG or JPEG image (labelled ${claimed}, but ${describeBytes(bytes)}). Use Try again; if it keeps happening, choose another image model in the Providers section of Edit project.`,
122
+ message: `Google sent back something that is not a PNG or JPEG image (labelled ${claimed}, but ${describeBytes(bytes)}). Use Try again; if it keeps happening, choose another image model in ${editSettings.images}.`,
122
123
  });
123
124
  }
124
125
  return { bytes, mime };
@@ -158,7 +159,7 @@ async function failure(response) {
158
159
  const detail = message || response.statusText;
159
160
  const refused = kind === "refusal" && refusalWords.test(message);
160
161
  return httpProviderError(response, kind, kind === "unsupported"
161
- ? `Google gives this API key no image quota for this model${quoted(`error ${String(response.status)}`, detail)}. Retrying will not help: turn on billing for the key's project in Google AI Studio, or choose another image provider in the Providers section of Edit project.`
162
+ ? `Google gives this API key no image quota for this model${quoted(`error ${String(response.status)}`, detail)}. Retrying will not help: turn on billing for the key's project in Google AI Studio, or choose another image service in ${editSettings.images}.`
162
163
  : // PERMISSION_DENIED and FAILED_PRECONDITION also cover keys without access, billing
163
164
  // and region limits, so they read as a content refusal only when Google says so.
164
165
  refused
@@ -66,13 +66,13 @@ function requireKey(deps) {
66
66
  if (key === undefined || key === "")
67
67
  throw providerError({
68
68
  kind: "missing_key",
69
- message: "Save this provider's API key in Settings → Providers to load its models.",
69
+ message: "Save this provider's API key in Settings → Keys & services to load its models.",
70
70
  });
71
71
  return key;
72
72
  }
73
73
  function unavailable() {
74
74
  return providerError({
75
75
  kind: "other",
76
- message: "Slopify could not load this provider's model list. Check the API key in Settings → Providers and your internet connection, then refresh the list.",
76
+ message: "Slopify could not load this provider's model list. Check the API key in Settings → Keys & services and your internet connection, then refresh the list.",
77
77
  });
78
78
  }
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { redact } from "../../kernel/log.js";
3
3
  import { providerError } from "../../kernel/ports/model.js";
4
+ import { editSettings } from "../../kernel/ports/settings-places.js";
4
5
  import { httpFailure, noImage, refusedImage, unreadable } from "../explain.js";
5
6
  import { failureText, httpProviderError, kindOfStatus, requireKey, safeJson, } from "../http-failure.js";
6
7
  import { modelAccessOf } from "../model-access.js";
@@ -117,7 +118,7 @@ function decode(b64) {
117
118
  if (mime === undefined) {
118
119
  throw providerError({
119
120
  kind: "other",
120
- message: `OpenAI sent back something that is not a PNG or JPEG image (${describeBytes(bytes)}). Use Try again; if it keeps happening, choose another image model in the Providers section of Edit project.`,
121
+ message: `OpenAI sent back something that is not a PNG or JPEG image (${describeBytes(bytes)}). Use Try again; if it keeps happening, choose another image model in ${editSettings.images}.`,
121
122
  });
122
123
  }
123
124
  return { bytes, mime };
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { redact } from "../../kernel/log.js";
3
3
  import { providerError } from "../../kernel/ports/model.js";
4
+ import { createRows, editSettings } from "../../kernel/ports/settings-places.js";
4
5
  import { httpFailure, noImage, providerSaid, refusedImage, unreadable } from "../explain.js";
5
6
  import { failureText, httpProviderError, kindOfStatus, requireKey, safeJson, } from "../http-failure.js";
6
7
  import { modelAccessOf } from "../model-access.js";
@@ -51,7 +52,7 @@ export function replicateImage(deps) {
51
52
  if (req.reference !== undefined)
52
53
  throw providerError({
53
54
  kind: "unsupported",
54
- message: "Replicate's image models can't use an establishing image as a reference. Choose Codex CLI, OpenAI, Google or fal.ai under Images → Provider on Play or in Edit project → Providers, or set Establishing image to Off in the Images section.",
55
+ message: `Replicate's image models can't use an establishing image as a reference. Choose Codex CLI, OpenAI, Google or fal.ai as the image service in ${createRows.images} or ${editSettings.images}, or set Establishing image to Off there.`,
55
56
  });
56
57
  const response = await deps.fetch(`${replicateBase}/models/${req.model}/predictions`, {
57
58
  method: "POST",
@@ -172,7 +173,7 @@ function declined(current) {
172
173
  kind: refusal ? "refusal" : "other",
173
174
  message: refusal
174
175
  ? refusedImage("Replicate", message)
175
- : providerSaid("Replicate", "could not make the image", message, "Use Try again; if it keeps happening, reword the image prompt in the Images section of Edit project or choose another image model in its Providers section."),
176
+ : providerSaid("Replicate", "could not make the image", message, `Use Try again; if it keeps happening, reword the image prompt in ${editSettings.images} or choose another image model there.`),
176
177
  });
177
178
  }
178
179
  function auth(deps) {
@@ -1,4 +1,5 @@
1
1
  import { providerError } from "../../kernel/ports/model.js";
2
+ import { editSettings } from "../../kernel/ports/settings-places.js";
2
3
  import { describeBytes } from "./bytes.js";
3
4
  // What the two image-to-video adapters share: the still goes up inline as a data URI (the
4
5
  // render sends a small JPEG, `slices/rebuild/runtime-animate.ts`), and the clip comes back
@@ -22,7 +23,7 @@ export async function downloadVideo(download) {
22
23
  if (!sniffVideo(bytes))
23
24
  throw providerError({
24
25
  kind: "other",
25
- message: `${download.provider} sent back something that is not an MP4 video clip (${describeBytes(bytes)}). Use Try again; if it keeps happening, choose another image-to-video model under Animate images in Edit project → Inputs → Look.`,
26
+ message: `${download.provider} sent back something that is not an MP4 video clip (${describeBytes(bytes)}). Use Try again; if it keeps happening, choose another image-to-video model under Animate images in ${editSettings.look}.`,
26
27
  });
27
28
  return { bytes, mime: "video/mp4" };
28
29
  }
@@ -1,6 +1,6 @@
1
1
  import { safeJson } from "./http-failure.js";
2
2
  import { cartesiaVersion } from "./tts/cartesia.js";
3
- // Settings → Providers' Test button, per provider. Some answer a key they do not recognise
3
+ // Settings → Keys & services' Test button, per provider. Some answer a key they do not recognise
4
4
  // with something other than 401: Google with 400 API_KEY_INVALID, ElevenLabs with 400
5
5
  // invalid_api_key, Inworld with 403 "does not exist". Observed on 2026-09-27; their
6
6
  // documentation says 401.
@@ -32,6 +32,6 @@ export async function nodeAntigravityModels(binary, read = nodeReadCliOutput) {
32
32
  }
33
33
  catch {
34
34
  // Do not pass the CLI's raw output or home paths to the browser.
35
- throw new Error('Slopify could not get the model list from the Antigravity CLI (it ran "agy models"). Check agy is installed and signed in (Settings → Providers), then refresh the list, or type a model slug that "agy models" shows.');
35
+ throw new Error('Slopify could not get the model list from the Antigravity CLI (it ran "agy models"). Check agy is installed and signed in (Settings → Keys & services), then refresh the list, or type a model slug that "agy models" shows.');
36
36
  }
37
37
  }
@@ -5,6 +5,7 @@ import { z } from "zod";
5
5
  import { redact } from "../../kernel/log.js";
6
6
  import { documentMessages } from "../../kernel/ports/llm-documents.js";
7
7
  import { providerError } from "../../kernel/ports/model.js";
8
+ import { editSettings } from "../../kernel/ports/settings-places.js";
8
9
  import { cliCheck, cliName, cliReported, commandOf } from "../explain.js";
9
10
  import { accessField } from "../model-access.js";
10
11
  import { nodeAntigravityModels } from "./antigravity-models.js";
@@ -77,7 +78,7 @@ export function antigravityLlm(deps) {
77
78
  if (/^gemini$/i.test(commandOf(binary)))
78
79
  throw providerError({
79
80
  kind: "unsupported",
80
- message: `The saved program path for the Antigravity CLI (${binary}) still points at the Gemini CLI, which Google has retired. In Settings → Providers, clear the Antigravity CLI path or set it to the agy program, then use Try again.`,
81
+ message: `The saved program path for the Antigravity CLI (${binary}) still points at the Gemini CLI, which Google has retired. In Settings → Keys & services, clear the Antigravity CLI path or set it to the agy program, then use Try again.`,
81
82
  });
82
83
  // The documents travel inline, as with OpenRouter: headless agy refuses MCP tools it was
83
84
  // not pre-approved for, so a research reader tool would be silently unavailable.
@@ -160,7 +161,7 @@ export function antigravityLlm(deps) {
160
161
  }
161
162
  req.signal.throwIfAborted();
162
163
  if (run === undefined)
163
- throw failure(binary, req.model, "", `The ${cliName(binary)} could not be started. Check it is installed and set up in Settings → Providers, then use Try again.`);
164
+ throw failure(binary, req.model, "", `The ${cliName(binary)} could not be started. Check it is installed and set up in Settings → Keys & services, then use Try again.`);
164
165
  if (authRequired(run.stderr()))
165
166
  throw authFailure();
166
167
  throw failure(binary, req.model, run.stderr(), ended === undefined ? stuckCli(binary) : endedWithout(binary, ended, run.stderr()));
@@ -191,11 +192,11 @@ function emptyAnswer(binary, stderr) {
191
192
  if (/no output produced|auto-denied|permission/i.test(stderr))
192
193
  return providerError({
193
194
  kind: "other",
194
- message: `The ${cliName(binary)} tried to use a tool Slopify does not allow (it said: ${redact(stderr.trim()).slice(0, 300)}), so it gave no answer. Use Try again; if it keeps happening, choose another model or text provider in the Providers section of Edit project.`,
195
+ message: `The ${cliName(binary)} tried to use a tool Slopify does not allow (it said: ${redact(stderr.trim()).slice(0, 300)}), so it gave no answer. Use Try again; if it keeps happening, choose another service or model in ${editSettings.writer}.`,
195
196
  });
196
197
  return providerError({
197
198
  kind: "other",
198
- message: `The ${cliName(binary)} answered with no text. Use Try again; if it keeps happening, run agy in a terminal to check it works, or choose another model in the Providers section of Edit project.`,
199
+ message: `The ${cliName(binary)} answered with no text. Use Try again; if it keeps happening, run agy in a terminal to check it works, or choose another model in ${editSettings.writer}.`,
199
200
  });
200
201
  }
201
202
  // `message` is what the CLI said; `sentence` replaces the default wording when Slopify
@@ -208,7 +209,7 @@ function failure(binary, model, message, sentence, now = Date.now) {
208
209
  kind: "unsupported",
209
210
  // agy lists models a plan can't run and refuses them with this sentence.
210
211
  access: "no-access",
211
- message: `The ${cliName(binary)} does not offer the model "${model}". Run "agy models" in a terminal to see the ones it has, pick one in the Edit tab (Edit project → Providers), then use Continue the run.`,
212
+ message: `The ${cliName(binary)} does not offer the model "${model}". Run "agy models" in a terminal to see the ones it has, pick one in ${editSettings.writer}, then use Continue the run.`,
212
213
  });
213
214
  if (/quota|resource.?exhausted|capacity/i.test(message) &&
214
215
  /exhaust|exceed|used up|reached|limit/i.test(message)) {
@@ -222,7 +223,7 @@ function failure(binary, model, message, sentence, now = Date.now) {
222
223
  if (waitMs !== undefined && waitMs <= 5 * 60_000)
223
224
  return providerError({
224
225
  kind: "rate_limit",
225
- message: `The ${cliName(binary)} is busy: Google's capacity for this model is used for the moment (it said: ${redact(message)}). Slopify tries again by itself shortly; if it keeps happening, run fewer projects at once or choose another model in the Providers section of Edit project.`,
226
+ message: `The ${cliName(binary)} is busy: Google's capacity for this model is used for the moment (it said: ${redact(message)}). Slopify tries again by itself shortly; if it keeps happening, run fewer projects at once or choose another model in ${editSettings.writer}.`,
226
227
  retryAfterMs: Math.max(1000, waitMs),
227
228
  });
228
229
  return providerError({
@@ -225,7 +225,7 @@ function failed(cli, said) {
225
225
  return { type: "failed", message: login.message };
226
226
  return {
227
227
  type: "failed",
228
- message: `The ${cliName(cli === "claude-code" ? "claude" : "codex")} stopped with an error: ${redact(said).slice(0, 500)}. Send your message again; if it keeps happening, check the CLI in Settings → Providers.`,
228
+ message: `The ${cliName(cli === "claude-code" ? "claude" : "codex")} stopped with an error: ${redact(said).slice(0, 500)}. Send your message again; if it keeps happening, check the CLI in Settings → Keys & services.`,
229
229
  };
230
230
  }
231
231
  // One JSONL line of the CLI's stream as what the chat shows. A stateful reader: Codex reports
@@ -36,7 +36,7 @@ const responseSchema = z.object({
36
36
  });
37
37
  const supportedEfforts = ["low", "medium", "high", "xhigh"];
38
38
  function unavailable() {
39
- return new Error("Slopify could not get the model list from the Claude Code CLI. Check the CLI is installed and signed in (Settings → Providers), then refresh the list, or type an exact model ID.");
39
+ return new Error("Slopify could not get the model list from the Claude Code CLI. Check the CLI is installed and signed in (Settings → Keys & services), then refresh the list, or type an exact model ID.");
40
40
  }
41
41
  function discoveryEnv() {
42
42
  const names = [
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
  import { redact } from "../../kernel/log.js";
3
3
  import { providerError } from "../../kernel/ports/model.js";
4
4
  import { isoOfEpoch } from "../../kernel/ports/plan-limits.js";
5
+ import { editSettings } from "../../kernel/ports/settings-places.js";
5
6
  import { cliCheck, cliReported, commandOf } from "../explain.js";
6
7
  import { nodeClaudeCodeModels } from "./claude-code-models.js";
7
8
  import { cliLoginError } from "./cli-login-error.js";
@@ -284,7 +285,7 @@ export function claudeCodeLlm(deps) {
284
285
  req.signal.throwIfAborted();
285
286
  // The stream ended with no result event at all.
286
287
  if (!run)
287
- throw new Error("The Claude Code CLI could not be started. Check it is installed and set up in Settings → Providers, then use Try again.");
288
+ throw new Error("The Claude Code CLI could not be started. Check it is installed and set up in Settings → Keys & services, then use Try again.");
288
289
  const login = cliLoginError("claude-code", run.stderr());
289
290
  if (login)
290
291
  throw login;
@@ -322,7 +323,7 @@ function nextStep(binary, status) {
322
323
  if (status === 429)
323
324
  return "Your Claude usage limit may be used up: wait until it resets, then use Try again.";
324
325
  if (status === 404)
325
- return "The chosen model may not exist or not be available to your account: choose another in the Providers section of Edit project, then use Try again.";
326
+ return `The chosen model may not exist or not be available to your account: choose another in ${editSettings.writer}, then use Try again.`;
326
327
  return cliCheck(binary);
327
328
  }
328
329
  // `modelUsage` is keyed by model id and a run may touch more than one - a fallback model,
@@ -59,7 +59,7 @@ export async function nodeCodexModels(env = process.env, binary = "codex", timeo
59
59
  }
60
60
  catch {
61
61
  // Do not expose cache contents, home paths or raw JSON errors to the browser.
62
- throw new Error("Slopify could not get the model list from the Codex CLI. Check the CLI is installed and signed in (Settings → Providers), then refresh the list, or type an exact model ID.");
62
+ throw new Error("Slopify could not get the model list from the Codex CLI. Check the CLI is installed and signed in (Settings → Keys & services), then refresh the list, or type an exact model ID.");
63
63
  }
64
64
  }
65
65
  }
@@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url";
6
6
  import { z } from "zod";
7
7
  import { documentIndex, llmDocumentsSchema, } from "../../kernel/ports/llm-documents.js";
8
8
  import { providerError } from "../../kernel/ports/model.js";
9
+ import { editSettings } from "../../kernel/ports/settings-places.js";
9
10
  export const documentServerName = "slopify_research";
10
11
  export const documentTool = "read_document";
11
12
  export const documentToolName = `mcp__${documentServerName}__${documentTool}`;
@@ -16,7 +17,7 @@ export function documentWorkspace(input) {
16
17
  if (!parsed.success)
17
18
  throw providerError({
18
19
  kind: "unsupported",
19
- message: "The research notes are too large, or not in a form the AI model can be given, so nothing was sent. Make the inputs shorter in Edit project, then use Try again; if it keeps happening, use Download diagnostics in Settings and report it.",
20
+ message: `The research notes are too large, or not in a form the AI model can be given, so nothing was sent. Make the research notes shorter in ${editSettings.article}, then use Try again; if it keeps happening, use Download diagnostics in Settings and report it.`,
20
21
  });
21
22
  const documents = parsed.data;
22
23
  const directory = mkdtempSync(join(tmpdir(), "slopify-documents-"));
@@ -82,6 +83,6 @@ export function documentWorkspace(input) {
82
83
  function unread() {
83
84
  return providerError({
84
85
  kind: "unavailable",
85
- message: "The AI model did not read all of the research notes, so its answer was not used (the attempt may still have used your quota). Use Try again; if it keeps happening, choose another model in the Providers section of Edit project.",
86
+ message: `The AI model did not read all of the research notes, so its answer was not used (the attempt may still have used your quota). Use Try again; if it keeps happening, choose another model in ${editSettings.writer}.`,
86
87
  });
87
88
  }
@@ -2,6 +2,7 @@ import { copyFileSync, mkdtempSync, rmSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { basename, extname, join } from "node:path";
4
4
  import { providerError } from "../../kernel/ports/model.js";
5
+ import { editSettings } from "../../kernel/ports/settings-places.js";
5
6
  const extensions = new Set([".png", ".jpg", ".jpeg", ".webp"]);
6
7
  // ceiling: a review shows one image, or a short's handful plus the establishing image.
7
8
  export const imagesMax = 12;
@@ -47,12 +48,12 @@ export function readFileName(path) {
47
48
  export function unseenImages() {
48
49
  return providerError({
49
50
  kind: "unavailable",
50
- message: "The reviewer answered without opening every picture it was given, so its verdict was not used (the attempt may still have used your quota). Use Try again; if it keeps happening, choose Codex as the reviewer in Edit project → Reviews.",
51
+ message: `The reviewer answered without opening every picture it was given, so its verdict was not used (the attempt may still have used your quota). Use Try again; if it keeps happening, choose Codex as the reviewer in ${editSettings.checks}.`,
51
52
  });
52
53
  }
53
54
  export function noImages(provider) {
54
55
  return providerError({
55
56
  kind: "unsupported",
56
- message: `Slopify can't show pictures to ${provider}, so a review that needs them was not sent. In Edit project → Reviews, choose Claude Code or Codex as the reviewer, or set the picture reviews to Off, then use Try again.`,
57
+ message: `Slopify can't show pictures to ${provider}, so a review that needs them was not sent. In ${editSettings.checks}, choose Claude Code or Codex as the reviewer, or set the picture reviews to Off, then use Try again.`,
57
58
  });
58
59
  }
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
  import { redact } from "../../kernel/log.js";
3
3
  import { documentMessages } from "../../kernel/ports/llm-documents.js";
4
4
  import { providerError } from "../../kernel/ports/model.js";
5
+ import { editSettings } from "../../kernel/ports/settings-places.js";
5
6
  import { droppedStream, httpFailure, providerSaid, refreshList, unreadable } from "../explain.js";
6
7
  import { failureText, httpProviderError, kindOfStatus, requireKey, safeJson, } from "../http-failure.js";
7
8
  import { accessField, modelAccessOf } from "../model-access.js";
@@ -164,7 +165,7 @@ async function failure(response, next) {
164
165
  function streamFailure(status, detail) {
165
166
  return status >= 400
166
167
  ? httpFailure({ provider: "OpenRouter", status, detail })
167
- : providerSaid("OpenRouter", "stopped part-way through the answer", detail, "Use Try again; if it keeps happening, choose another model in the Providers section of Edit project.");
168
+ : providerSaid("OpenRouter", "stopped part-way through the answer", detail, `Use Try again; if it keeps happening, choose another model in ${editSettings.writer}.`);
168
169
  }
169
170
  function statusOf(code) {
170
171
  return typeof code === "number" ? code : Number(code ?? 0);
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  import { cliCommand } from "../../kernel/cli-command.js";
4
4
  import { redact } from "../../kernel/log.js";
5
5
  import { providerError } from "../../kernel/ports/model.js";
6
+ import { editSettings } from "../../kernel/ports/settings-places.js";
6
7
  import { cliName, commandOf, quoted } from "../explain.js";
7
8
  import { safeJson } from "../http-failure.js";
8
9
  // ceiling: the last 8 KiB of stderr is kept. A CLI that writes megabytes of progress there
@@ -140,7 +141,7 @@ export function cliInput(text) {
140
141
  if (Buffer.byteLength(text) >= 8 * 1024 * 1024)
141
142
  throw providerError({
142
143
  kind: "unsupported",
143
- message: "This prompt is larger than the CLI accepts (8 MB), so nothing was sent. Make the inputs shorter in Edit project, or choose an API provider such as OpenRouter in its Providers section, then use Try again.",
144
+ message: `This prompt is larger than the CLI accepts (8 MB), so nothing was sent. Make the article or its research notes shorter in ${editSettings.article}, or choose an API service such as OpenRouter under Written by there, then use Try again.`,
144
145
  });
145
146
  return text;
146
147
  }
@@ -169,7 +170,7 @@ export function promptOf(messages) {
169
170
  export function endedWithout(binary, ended, stderr) {
170
171
  const said = redact(stderr.trim());
171
172
  if (ended.error !== null)
172
- return `The ${cliName(binary)} could not be started${quoted("", redact(ended.error.message))}. Check it is installed and set up in Settings → Providers, then use Try again.`;
173
+ return `The ${cliName(binary)} could not be started${quoted("", redact(ended.error.message))}. Check it is installed and set up in Settings → Keys & services, then use Try again.`;
173
174
  const how = ended.code === null ? "killed by the system" : `exit code ${String(ended.code)}`;
174
175
  return `The ${cliName(binary)} stopped without answering${quoted(how, said)}. Run ${commandOf(binary)} in a terminal to check it works and is signed in, then use Try again.`;
175
176
  }