@gentbajko/slopify 2.4.0 → 3.0.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 (469) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +21 -40
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +11 -1
  6. package/dist/adapters/alignment/cache.js +4 -1
  7. package/dist/adapters/alignment/ctc.js +25 -23
  8. package/dist/adapters/alignment/index.js +37 -6
  9. package/dist/adapters/alignment/multilingual.js +267 -0
  10. package/dist/adapters/alignment/protocol.js +11 -0
  11. package/dist/adapters/alignment/quality.js +4 -3
  12. package/dist/adapters/alignment/sentences.js +95 -0
  13. package/dist/adapters/alignment/spec.js +24 -0
  14. package/dist/adapters/alignment/spell.js +769 -0
  15. package/dist/adapters/alignment/text.js +56 -9
  16. package/dist/adapters/alignment/window.js +34 -30
  17. package/dist/adapters/alignment/worker.js +23 -21
  18. package/dist/adapters/explain.js +11 -11
  19. package/dist/adapters/host-cli/index.js +55 -5
  20. package/dist/adapters/host-cli/transport.js +13 -6
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +60 -18
  23. package/dist/adapters/image/codex.js +166 -25
  24. package/dist/adapters/image/fal.js +31 -3
  25. package/dist/adapters/image/google.js +17 -2
  26. package/dist/adapters/image/openai.js +45 -16
  27. package/dist/adapters/image/reference.js +41 -0
  28. package/dist/adapters/image/replicate.js +14 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +36 -0
  31. package/dist/adapters/llm/claude-code.js +168 -32
  32. package/dist/adapters/llm/cli-login-error.js +1 -1
  33. package/dist/adapters/llm/codex-limits.js +157 -0
  34. package/dist/adapters/llm/codex-models.js +2 -1
  35. package/dist/adapters/llm/codex-server-models.js +2 -1
  36. package/dist/adapters/llm/codex.js +99 -12
  37. package/dist/adapters/llm/document-workspace.js +2 -2
  38. package/dist/adapters/llm/gemini-workspace.js +1 -1
  39. package/dist/adapters/llm/gemini.js +16 -3
  40. package/dist/adapters/llm/image-workspace.js +58 -0
  41. package/dist/adapters/llm/openrouter.js +11 -3
  42. package/dist/adapters/llm/run-cli.js +26 -9
  43. package/dist/adapters/tts/cartesia.js +10 -0
  44. package/dist/adapters/tts/elevenlabs.js +31 -9
  45. package/dist/adapters/tts/inworld-async.js +9 -5
  46. package/dist/adapters/tts/inworld.js +17 -3
  47. package/dist/adapters/tts/openai.js +3 -0
  48. package/dist/adapters/tts/voice-languages.js +80 -0
  49. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  50. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/OFL.txt +93 -0
  55. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  56. package/dist/assets/models.yaml +7 -0
  57. package/dist/assets/sample/sample-audiobook.tar +0 -0
  58. package/dist/assets/sample/sample-podcast.tar +0 -0
  59. package/dist/assets/sample/sample-project.tar +0 -0
  60. package/dist/catalog/merge.js +132 -0
  61. package/dist/catalog/registry.js +33 -12
  62. package/dist/catalog/runtime-models.js +1 -1
  63. package/dist/catalog/schema.js +12 -1
  64. package/dist/catalog/store.js +147 -8
  65. package/dist/catalog/validate.js +17 -2
  66. package/dist/compose.yaml +71 -0
  67. package/dist/edge/autostart/docker-record.js +32 -0
  68. package/dist/edge/autostart/docker.js +50 -0
  69. package/dist/edge/autostart/index.js +78 -0
  70. package/dist/edge/autostart/launcher.js +176 -0
  71. package/dist/edge/autostart/model.js +3 -0
  72. package/dist/edge/autostart/native.js +210 -0
  73. package/dist/edge/autostart/prompt.js +66 -0
  74. package/dist/edge/autostart/service.js +81 -0
  75. package/dist/edge/cli.js +57 -51
  76. package/dist/edge/docker-install/activation.js +56 -0
  77. package/dist/edge/docker-install/apply.js +458 -0
  78. package/dist/edge/docker-install/engine.js +362 -0
  79. package/dist/edge/docker-install/run.js +154 -0
  80. package/dist/edge/docker-install/state.js +146 -0
  81. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  82. package/dist/edge/docker.js +14 -22
  83. package/dist/edge/events/hub.js +11 -0
  84. package/dist/edge/host-cli.js +1 -1
  85. package/dist/edge/http/actions.js +32 -10
  86. package/dist/edge/http/app.js +60 -2
  87. package/dist/edge/http/auditions.js +70 -0
  88. package/dist/edge/http/autostart.js +37 -0
  89. package/dist/edge/http/backups.js +40 -0
  90. package/dist/edge/http/channel-memory.js +92 -0
  91. package/dist/edge/http/channels.js +128 -0
  92. package/dist/edge/http/entries.js +22 -3
  93. package/dist/edge/http/files.js +1 -1
  94. package/dist/edge/http/folder-location.js +1 -1
  95. package/dist/edge/http/home.js +39 -0
  96. package/dist/edge/http/host-cli.js +45 -6
  97. package/dist/edge/http/narration-peaks.js +76 -0
  98. package/dist/edge/http/onboarding.js +152 -0
  99. package/dist/edge/http/open-folder.js +1 -1
  100. package/dist/edge/http/patch-notes.js +45 -0
  101. package/dist/edge/http/planning.js +4 -2
  102. package/dist/edge/http/problem.js +1 -0
  103. package/dist/edge/http/project-create.js +87 -0
  104. package/dist/edge/http/projects.js +31 -99
  105. package/dist/edge/http/prompts.js +40 -3
  106. package/dist/edge/http/pronunciations.js +23 -4
  107. package/dist/edge/http/providers.js +86 -0
  108. package/dist/edge/http/reviews.js +121 -0
  109. package/dist/edge/http/revision-files.js +1 -1
  110. package/dist/edge/http/revisions.js +1 -1
  111. package/dist/edge/http/run-cost.js +25 -0
  112. package/dist/edge/http/schedules.js +111 -6
  113. package/dist/edge/http/settings.js +128 -6
  114. package/dist/edge/http/storage-files.js +50 -0
  115. package/dist/edge/http/storage.js +29 -2
  116. package/dist/edge/http/studio.js +207 -0
  117. package/dist/edge/http/style-preview.js +100 -0
  118. package/dist/edge/http/subtitles.js +1 -1
  119. package/dist/edge/http/trash.js +69 -0
  120. package/dist/edge/http/update.js +24 -3
  121. package/dist/edge/http/whats-new.js +15 -0
  122. package/dist/edge/http/youtube-edits.js +83 -0
  123. package/dist/edge/native-update.js +62 -0
  124. package/dist/host-cli/open-folder.js +10 -10
  125. package/dist/host-cli/runtime.js +12 -3
  126. package/dist/kernel/db/migrations/0022-drop-duplicate-held-work.sql +56 -0
  127. package/dist/kernel/db/migrations/0024-reference-attachments.sql +18 -0
  128. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  129. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  130. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  131. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  132. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  133. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  134. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  135. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  136. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  137. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  138. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  139. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  140. package/dist/kernel/paths.js +27 -6
  141. package/dist/kernel/pipeline.js +4 -1
  142. package/dist/kernel/ports/host-cli.js +139 -16
  143. package/dist/kernel/ports/image.js +3 -1
  144. package/dist/kernel/ports/key-probe.js +1 -0
  145. package/dist/kernel/ports/languages.js +157 -0
  146. package/dist/kernel/ports/llm.js +3 -1
  147. package/dist/kernel/ports/model.js +5 -2
  148. package/dist/kernel/ports/narration-aliases.js +60 -0
  149. package/dist/kernel/ports/plan-limits.js +36 -0
  150. package/dist/kernel/runner/attempt.js +18 -9
  151. package/dist/kernel/runner/audition.js +36 -0
  152. package/dist/kernel/runner/graph.js +24 -5
  153. package/dist/kernel/runner/index.js +35 -5
  154. package/dist/kernel/runner/meter.js +1 -0
  155. package/dist/kernel/runner/providers.js +164 -9
  156. package/dist/kernel/runner/retry-policy.js +43 -0
  157. package/dist/kernel/runner/standalone.js +134 -0
  158. package/dist/kernel/runner/work-authority.js +28 -2
  159. package/dist/main.js +321 -20
  160. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  161. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  162. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  163. package/dist/patch-notes/3.0.0.md +70 -0
  164. package/dist/patch-notes/index.json +26 -0
  165. package/dist/slices/admission/model.js +35 -0
  166. package/dist/slices/admission/repo.js +24 -4
  167. package/dist/slices/admission/rules.js +126 -1
  168. package/dist/slices/admission/schema.js +68 -1
  169. package/dist/slices/admission/short-mode.js +47 -0
  170. package/dist/slices/admission/start.js +90 -16
  171. package/dist/slices/article/continuation.js +2 -2
  172. package/dist/slices/article/provided-entries.js +2 -2
  173. package/dist/slices/article/run.js +6 -2
  174. package/dist/slices/article/segments.js +3 -3
  175. package/dist/slices/backups/files.js +135 -0
  176. package/dist/slices/backups/folder.js +43 -0
  177. package/dist/slices/backups/model.js +65 -0
  178. package/dist/slices/backups/repo.js +28 -0
  179. package/dist/slices/backups/schedule.js +60 -0
  180. package/dist/slices/backups/service.js +311 -0
  181. package/dist/slices/batch/index.js +9 -2
  182. package/dist/slices/cancel/index.js +1 -1
  183. package/dist/slices/channels/cast-images.js +110 -0
  184. package/dist/slices/channels/cast-match.js +32 -0
  185. package/dist/slices/channels/images.js +19 -0
  186. package/dist/slices/channels/model.js +5 -0
  187. package/dist/slices/channels/rebrand.js +100 -0
  188. package/dist/slices/channels/repo.js +218 -0
  189. package/dist/slices/channels/runs.js +129 -0
  190. package/dist/slices/channels/schema.js +129 -0
  191. package/dist/slices/channels/service.js +180 -0
  192. package/dist/slices/channels/studio-csv.js +110 -0
  193. package/dist/slices/channels/videos.js +120 -0
  194. package/dist/slices/checkpoints/recovery.js +10 -0
  195. package/dist/slices/control/index.js +2 -1
  196. package/dist/slices/document/fonts.js +1 -1
  197. package/dist/slices/document/render.js +1 -1
  198. package/dist/slices/episodes/related.js +90 -0
  199. package/dist/slices/episodes/repo.js +60 -0
  200. package/dist/slices/episodes/service.js +58 -0
  201. package/dist/slices/episodes/summarize.js +170 -0
  202. package/dist/slices/estimate/index.js +113 -24
  203. package/dist/slices/estimate/requests.js +60 -2
  204. package/dist/slices/fixes/rules.js +73 -0
  205. package/dist/slices/fonts/coverage.js +82 -0
  206. package/dist/slices/fonts/sfnt.js +74 -0
  207. package/dist/slices/images/run.js +8 -5
  208. package/dist/slices/images/scale.js +84 -0
  209. package/dist/slices/library/diff.js +122 -0
  210. package/dist/slices/library/history.js +100 -0
  211. package/dist/slices/library/model.js +3 -0
  212. package/dist/slices/library/photorealistic.js +49 -0
  213. package/dist/slices/library/repo.js +19 -14
  214. package/dist/slices/library/save.js +62 -12
  215. package/dist/slices/library/slots.js +15 -3
  216. package/dist/slices/library/snapshot.js +8 -2
  217. package/dist/slices/library/used-by.js +153 -0
  218. package/dist/slices/loudness/level-pieces.js +41 -0
  219. package/dist/slices/loudness/loudnorm.js +164 -0
  220. package/dist/slices/loudness/model.js +153 -0
  221. package/dist/slices/model-upkeep/model.js +10 -0
  222. package/dist/slices/model-upkeep/switch.js +217 -0
  223. package/dist/slices/model-upkeep/usage.js +183 -0
  224. package/dist/slices/narration/aliases-library.js +31 -0
  225. package/dist/slices/narration/aliases-schema.js +53 -0
  226. package/dist/slices/narration/aliases.js +49 -0
  227. package/dist/slices/narration/blocks.js +352 -0
  228. package/dist/slices/narration/chunk.js +19 -7
  229. package/dist/slices/narration/concat.js +2 -2
  230. package/dist/slices/narration/describe.js +45 -0
  231. package/dist/slices/narration/pauses-model.js +63 -0
  232. package/dist/slices/narration/pauses.js +269 -0
  233. package/dist/slices/narration/peaks-model.js +1 -0
  234. package/dist/slices/narration/peaks.js +94 -0
  235. package/dist/slices/narration/preparation.js +21 -3
  236. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  237. package/dist/slices/narration/pronunciation.js +72 -25
  238. package/dist/slices/narration/run.js +6 -6
  239. package/dist/slices/narration/shared-glossary.js +1 -1
  240. package/dist/slices/narration/steering.js +15 -4
  241. package/dist/slices/notifications/notifier.js +94 -0
  242. package/dist/slices/notifications/rules.js +102 -0
  243. package/dist/slices/notifications/send.js +40 -0
  244. package/dist/slices/notifications/settings.js +34 -0
  245. package/dist/slices/onboarding/copy.js +143 -0
  246. package/dist/slices/onboarding/first-run.js +46 -0
  247. package/dist/slices/onboarding/install.js +119 -0
  248. package/dist/slices/onboarding/model.js +64 -0
  249. package/dist/slices/onboarding/packs.js +491 -0
  250. package/dist/slices/onboarding/quick-short.js +129 -0
  251. package/dist/slices/onboarding/sample.js +132 -0
  252. package/dist/slices/onboarding/state.js +68 -0
  253. package/dist/slices/onboarding/template.js +73 -0
  254. package/dist/slices/patch-notes/library.js +73 -0
  255. package/dist/slices/patch-notes/seen.js +77 -0
  256. package/dist/slices/play-drafts/convert.js +160 -9
  257. package/dist/slices/play-drafts/readiness.js +3 -0
  258. package/dist/slices/play-drafts/repo.js +9 -1
  259. package/dist/slices/play-drafts/review-inputs.js +22 -3
  260. package/dist/slices/play-drafts/schema.js +109 -2
  261. package/dist/slices/play-drafts/service.js +10 -1
  262. package/dist/slices/play-drafts/start.js +43 -18
  263. package/dist/slices/play-drafts/uploads.js +2 -1
  264. package/dist/slices/project-templates/from-project.js +70 -4
  265. package/dist/slices/project-templates/one-off.js +26 -0
  266. package/dist/slices/project-templates/repo.js +2 -1
  267. package/dist/slices/project-templates/service.js +15 -4
  268. package/dist/slices/project-templates/setup.js +29 -2
  269. package/dist/slices/rebuild/admission-repo.js +1 -1
  270. package/dist/slices/rebuild/model.js +6 -0
  271. package/dist/slices/rebuild/preview-details.js +67 -2
  272. package/dist/slices/rebuild/preview-plan.js +13 -6
  273. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  274. package/dist/slices/rebuild/recipe-audio.js +66 -12
  275. package/dist/slices/rebuild/recipe-build.js +15 -5
  276. package/dist/slices/rebuild/recipe-cast.js +18 -0
  277. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  278. package/dist/slices/rebuild/recipe-describe.js +93 -0
  279. package/dist/slices/rebuild/recipe-edit.js +13 -1
  280. package/dist/slices/rebuild/recipe-exports.js +62 -8
  281. package/dist/slices/rebuild/recipe-input-schema.js +43 -1
  282. package/dist/slices/rebuild/recipe-legacy.js +12 -0
  283. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  284. package/dist/slices/rebuild/recipe-model.js +25 -6
  285. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  286. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  287. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  288. package/dist/slices/rebuild/recipe-reference.js +58 -0
  289. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  290. package/dist/slices/rebuild/recipe-save.js +4 -1
  291. package/dist/slices/rebuild/recipe-shorts.js +32 -5
  292. package/dist/slices/rebuild/recipe-text.js +114 -19
  293. package/dist/slices/rebuild/recipe-validation.js +29 -3
  294. package/dist/slices/rebuild/recipe-visual.js +104 -29
  295. package/dist/slices/rebuild/recipe-voices.js +286 -0
  296. package/dist/slices/rebuild/recipe-work.js +77 -6
  297. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  298. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  299. package/dist/slices/rebuild/recovery-model.js +3 -0
  300. package/dist/slices/rebuild/recovery-selection.js +28 -2
  301. package/dist/slices/rebuild/recovery.js +35 -17
  302. package/dist/slices/rebuild/repo.js +3 -3
  303. package/dist/slices/rebuild/retry.js +18 -0
  304. package/dist/slices/rebuild/review-redo.js +110 -0
  305. package/dist/slices/rebuild/runtime-actions.js +14 -2
  306. package/dist/slices/rebuild/runtime-admission.js +4 -1
  307. package/dist/slices/rebuild/runtime-animate.js +4 -4
  308. package/dist/slices/rebuild/runtime-article.js +5 -5
  309. package/dist/slices/rebuild/runtime-document.js +4 -4
  310. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  311. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  312. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  313. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  314. package/dist/slices/rebuild/runtime-export.js +84 -19
  315. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  316. package/dist/slices/rebuild/runtime-image.js +69 -0
  317. package/dist/slices/rebuild/runtime-local.js +154 -27
  318. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  319. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  320. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  321. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  322. package/dist/slices/rebuild/runtime-piece-label.js +29 -0
  323. package/dist/slices/rebuild/runtime-provider.js +134 -19
  324. package/dist/slices/rebuild/runtime-publication.js +21 -5
  325. package/dist/slices/rebuild/runtime-review.js +237 -0
  326. package/dist/slices/rebuild/runtime-run.js +17 -11
  327. package/dist/slices/rebuild/runtime-shorts.js +48 -23
  328. package/dist/slices/rebuild/runtime-store.js +39 -10
  329. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  330. package/dist/slices/rebuild/runtime-voices.js +103 -0
  331. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  332. package/dist/slices/rebuild/service-readiness.js +15 -1
  333. package/dist/slices/rebuild/soften.js +49 -0
  334. package/dist/slices/rebuild/transition-repo.js +10 -3
  335. package/dist/slices/research/run.js +4 -4
  336. package/dist/slices/research/synthesis.js +2 -2
  337. package/dist/slices/reviews/model.js +105 -0
  338. package/dist/slices/reviews/outcome.js +22 -0
  339. package/dist/slices/reviews/repo.js +106 -0
  340. package/dist/slices/reviews/rules.js +63 -0
  341. package/dist/slices/reviews/verdict.js +71 -0
  342. package/dist/slices/reviews/view.js +30 -0
  343. package/dist/slices/revisions/adopt-content.js +12 -2
  344. package/dist/slices/revisions/adopt.js +9 -2
  345. package/dist/slices/revisions/downloads.js +7 -1
  346. package/dist/slices/revisions/image-plan.js +183 -0
  347. package/dist/slices/revisions/mutation-assets.js +43 -9
  348. package/dist/slices/revisions/mutation-cues.js +71 -0
  349. package/dist/slices/revisions/mutation-prepare.js +8 -10
  350. package/dist/slices/revisions/mutation-request.js +2 -1
  351. package/dist/slices/revisions/mutations.js +97 -3
  352. package/dist/slices/revisions/schema.js +16 -2
  353. package/dist/slices/run-cost/limits.js +128 -0
  354. package/dist/slices/run-cost/meter.js +47 -0
  355. package/dist/slices/run-cost/panel.js +234 -0
  356. package/dist/slices/run-cost/pricing.js +92 -0
  357. package/dist/slices/run-cost/week.js +108 -0
  358. package/dist/slices/schedules/agenda.js +158 -0
  359. package/dist/slices/schedules/repo.js +58 -18
  360. package/dist/slices/schedules/scheduler.js +67 -16
  361. package/dist/slices/schedules/schema.js +138 -1
  362. package/dist/slices/schedules/service.js +48 -4
  363. package/dist/slices/schedules/similar.js +90 -0
  364. package/dist/slices/schedules/topic-list.js +163 -0
  365. package/dist/slices/schedules/topics.js +422 -0
  366. package/dist/slices/settings/first-run.js +102 -0
  367. package/dist/slices/settings/health.js +190 -0
  368. package/dist/slices/settings/key-guides.js +128 -0
  369. package/dist/slices/settings/key-test.js +85 -0
  370. package/dist/slices/settings/model.js +11 -1
  371. package/dist/slices/settings/playback.js +15 -0
  372. package/dist/slices/settings/repo.js +17 -3
  373. package/dist/slices/settings/voices.js +64 -4
  374. package/dist/slices/settings/whats-new.js +64 -0
  375. package/dist/slices/shorts/captions.js +9 -0
  376. package/dist/slices/shorts/pick.js +3 -3
  377. package/dist/slices/shorts/prompts.js +1 -1
  378. package/dist/slices/shorts/render.js +54 -5
  379. package/dist/slices/storage/asset-name.js +3 -0
  380. package/dist/slices/storage/backup-export.js +48 -9
  381. package/dist/slices/storage/backup-format.js +38 -1
  382. package/dist/slices/storage/backup-import.js +191 -19
  383. package/dist/slices/storage/delete-project.js +7 -4
  384. package/dist/slices/storage/documents.js +79 -0
  385. package/dist/slices/storage/files-location.js +531 -0
  386. package/dist/slices/storage/layout.js +20 -0
  387. package/dist/slices/storage/model.js +14 -0
  388. package/dist/slices/storage/portable.js +95 -12
  389. package/dist/slices/storage/reconcile.js +19 -2
  390. package/dist/slices/storage/repo.js +4 -1
  391. package/dist/slices/storage/schema.js +11 -0
  392. package/dist/slices/storage/trim.js +146 -0
  393. package/dist/slices/studio/disclosure.js +74 -0
  394. package/dist/slices/studio/model.js +28 -0
  395. package/dist/slices/studio/pack.js +209 -0
  396. package/dist/slices/studio/settings.js +133 -0
  397. package/dist/slices/style-preview/images.js +61 -0
  398. package/dist/slices/style-preview/render.js +161 -0
  399. package/dist/slices/style-preview/schema.js +45 -0
  400. package/dist/slices/style-preview/service.js +93 -0
  401. package/dist/slices/style-preview/settings.js +89 -0
  402. package/dist/slices/subtitles/captions.js +42 -9
  403. package/dist/slices/subtitles/model.js +22 -0
  404. package/dist/slices/subtitles/prepare.js +27 -16
  405. package/dist/slices/subtitles/transcript.js +1 -1
  406. package/dist/slices/thumbnail/run.js +7 -7
  407. package/dist/slices/trash/model.js +25 -0
  408. package/dist/slices/trash/service.js +273 -0
  409. package/dist/slices/uploads/repo.js +20 -0
  410. package/dist/slices/video/ambient-bed-schema.js +15 -0
  411. package/dist/slices/video/ambient-bed.js +95 -0
  412. package/dist/slices/video/ambient-mix.js +72 -0
  413. package/dist/slices/video/audio-inputs.js +2 -2
  414. package/dist/slices/video/cards.js +5 -2
  415. package/dist/slices/video/edit-list.js +23 -2
  416. package/dist/slices/video/edit-settings.js +13 -3
  417. package/dist/slices/video/ffmpeg.js +63 -9
  418. package/dist/slices/video/figure-card.js +492 -0
  419. package/dist/slices/video/figure-spans.js +51 -0
  420. package/dist/slices/video/plan.js +125 -13
  421. package/dist/slices/video/run.js +14 -12
  422. package/dist/slices/video/slideshow.js +39 -4
  423. package/dist/slices/voices/attribution.js +69 -0
  424. package/dist/slices/voices/audio-files.js +82 -0
  425. package/dist/slices/voices/audition.js +25 -0
  426. package/dist/slices/voices/cast.js +56 -0
  427. package/dist/slices/voices/grouping.js +49 -0
  428. package/dist/slices/voices/join.js +35 -0
  429. package/dist/slices/voices/languages.js +25 -0
  430. package/dist/slices/voices/model.js +179 -0
  431. package/dist/slices/voices/palette.js +26 -0
  432. package/dist/slices/voices/panel.js +91 -0
  433. package/dist/slices/voices/portraits.js +55 -0
  434. package/dist/slices/voices/script.js +136 -0
  435. package/dist/slices/voices/timing.js +58 -0
  436. package/dist/slices/youtube/answer.js +1 -1
  437. package/dist/slices/youtube/chapters.js +126 -0
  438. package/dist/slices/youtube/edits-repo.js +69 -0
  439. package/dist/slices/youtube/edits.js +89 -0
  440. package/dist/slices/youtube/placeholders.js +98 -0
  441. package/dist/slices/youtube/transcript.js +11 -4
  442. package/dist/updater/service.js +66 -21
  443. package/dist/web/assets/index-CUA2sUxU.css +1 -0
  444. package/dist/web/assets/index-CX590kE-.js +303 -0
  445. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  446. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  447. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  448. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  449. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  450. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  451. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  452. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  453. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  454. package/dist/web/assets/{pdf-CAJNT254.js → pdf-BG8n_uc5.js} +1 -1
  455. package/dist/web/index.html +2 -2
  456. package/package.json +4 -4
  457. package/dist/edge/docker-launch.js +0 -81
  458. package/dist/edge/docker-projects/activation.js +0 -35
  459. package/dist/edge/docker-projects/claims.js +0 -85
  460. package/dist/edge/docker-projects/committed.js +0 -40
  461. package/dist/edge/docker-projects/engine.js +0 -461
  462. package/dist/edge/docker-projects/install.js +0 -254
  463. package/dist/edge/docker-projects/prune.js +0 -66
  464. package/dist/edge/docker-projects/recover.js +0 -100
  465. package/dist/edge/docker-projects/state.js +0 -225
  466. package/dist/web/assets/index-BGgYMm2u.css +0 -1
  467. package/dist/web/assets/index-BVVgtntL.js +0 -171
  468. package/scripts/docker-run.sh +0 -3
  469. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -23,7 +23,7 @@ export async function* inworldAsync(deps, request, key) {
23
23
  if (saved === undefined && request.text.length > 100_000) {
24
24
  throw providerError({
25
25
  kind: "unsupported",
26
- message: "This narration part is longer than the 100,000 characters Inworld takes at once (10,000 on On-Demand accounts). Set Chunking to Paragraph in the Providers section of Edit project, then use Retry stage.",
26
+ message: "This narration part is longer than the 100,000 characters Inworld takes at once (10,000 on On-Demand accounts). Set Chunking to Paragraph in the Providers section of Edit project, then use Try again.",
27
27
  });
28
28
  }
29
29
  const headers = { Authorization: `Basic ${key}`, "Content-Type": "application/json" };
@@ -68,7 +68,9 @@ export async function* inworldAsync(deps, request, key) {
68
68
  ? "rate_limit"
69
69
  : code === 3 || code === 5 || code === 9
70
70
  ? "unsupported"
71
- : "other",
71
+ : code === 14
72
+ ? "dropped"
73
+ : "other",
72
74
  message: streamFailure(code, clean(message ?? "", key), request.voiceId),
73
75
  });
74
76
  }
@@ -141,7 +143,9 @@ async function check(response, key, voiceId) {
141
143
  ? "rate_limit"
142
144
  : response.status === 400 || response.status === 404
143
145
  ? "unsupported"
144
- : "other",
146
+ : response.status >= 500
147
+ ? "dropped"
148
+ : "other",
145
149
  message: httpFailure({
146
150
  provider: "Inworld",
147
151
  status: response.status,
@@ -160,7 +164,7 @@ function clean(message, key) {
160
164
  function downloadFailed(status) {
161
165
  return providerError({
162
166
  kind: "other",
163
- message: `Inworld made the narration, but Slopify could not download it${status}. Check your internet connection, then use Retry stage.`,
167
+ message: `Inworld made the narration, but Slopify could not download it${status}. Check your internet connection, then use Try again.`,
164
168
  });
165
169
  }
166
170
  function unreadableAnswer() {
@@ -177,5 +181,5 @@ export function streamFailure(code, detail, voiceId) {
177
181
  return httpFailure({ provider: "Inworld", status: 429, detail, label });
178
182
  if (code === 3 || code === 5 || code === 9)
179
183
  return providerSaid("Inworld", "could not narrate this text", detail, voiceFix(voiceId));
180
- return providerSaid("Inworld", "stopped before the narration was finished", detail, "This is usually temporary: wait a few minutes, then use Retry stage.");
184
+ return providerSaid("Inworld", "stopped before the narration was finished", detail, "This is usually temporary: wait a few minutes, then use Try again.");
181
185
  }
@@ -4,6 +4,7 @@ import { providerError } from "../../kernel/ports/model.js";
4
4
  import { httpFailure, missingKey, noAudio, unreadable, voiceFix } from "../explain.js";
5
5
  import { retryAfter } from "../retry-after.js";
6
6
  import { inworldAsync, streamFailure } from "./inworld-async.js";
7
+ import { inworldLanguages, lookUp } from "./voice-languages.js";
7
8
  export const inworldModels = [
8
9
  { id: "inworld-tts-2", name: "Realtime TTS-2" },
9
10
  { id: "inworld-tts-2-flash", name: "Realtime TTS-2 Flash" },
@@ -16,6 +17,15 @@ export function inworldTts(deps) {
16
17
  return {
17
18
  id: "inworld",
18
19
  capabilities: { streams: true },
20
+ voiceLanguages: async (voiceId, signal) => {
21
+ const key = deps
22
+ .key()
23
+ ?.trim()
24
+ .replace(/^Basic\s+/i, "");
25
+ if (!key)
26
+ return undefined;
27
+ return inworldLanguages(await lookUp(deps.fetch, `https://api.inworld.ai/voices/v1/voices/${encodeURIComponent(voiceId)}`, { Authorization: `Basic ${key}` }, signal));
28
+ },
19
29
  models: async () => inworldModels,
20
30
  synthesize: async (request) => {
21
31
  const key = deps
@@ -59,7 +69,7 @@ async function* synthesizeRequest(deps, request, key) {
59
69
  if (request.text.trim().length === 0)
60
70
  throw providerError({
61
71
  kind: "unsupported",
62
- message: "Inworld was sent an empty piece of narration. Check the article has text in the Article section of Edit project, then use Retry stage.",
72
+ message: "Inworld was sent an empty piece of narration. Check the article has text in the Article section of Edit project, then use Try again.",
63
73
  });
64
74
  if ((request.model ?? "inworld-tts-2") === "inworld-tts-2" && request.text.length > 4000) {
65
75
  yield* inworldAsync(deps, request, key);
@@ -93,7 +103,9 @@ async function* synthesizeRequest(deps, request, key) {
93
103
  ? "rate_limit"
94
104
  : response.status === 400 || response.status === 404
95
105
  ? "unsupported"
96
- : "other",
106
+ : response.status >= 500
107
+ ? "dropped"
108
+ : "other",
97
109
  message: clean(httpFailure({
98
110
  provider: "Inworld",
99
111
  status: response.status,
@@ -128,7 +140,9 @@ async function* synthesizeRequest(deps, request, key) {
128
140
  ? "rate_limit"
129
141
  : code === 3 || code === 5
130
142
  ? "unsupported"
131
- : "other",
143
+ : code === 14
144
+ ? "dropped"
145
+ : "other",
132
146
  message: streamFailure(code, clean(message ?? "", key), request.voiceId),
133
147
  });
134
148
  }
@@ -95,6 +95,9 @@ function kindOf(status) {
95
95
  }
96
96
  // ceiling: as in the OpenRouter adapter, a 400 is retried three more times because the
97
97
  // port's error contract has no terminal kind for a request that cannot be repaired.
98
+ // The provider's own server failed; the same request may well succeed later.
99
+ if (status >= 500)
100
+ return "dropped";
98
101
  return "other";
99
102
  }
100
103
  async function failure(response, voiceId, next) {
@@ -0,0 +1,80 @@
1
+ import { z } from "zod";
2
+ // What each provider's voice API says a voice speaks, reduced to lower-case primary language
3
+ // subtags ("es-MX" and "ES_MX" are both "es"). Undefined when the provider said nothing
4
+ // usable: an unknown voice speaks every language as far as the app is concerned.
5
+ export function primaryLanguages(values) {
6
+ const codes = values.flatMap((value) => {
7
+ const code = value?.trim().split(/[-_]/)[0]?.toLowerCase();
8
+ return code !== undefined && /^[a-z]{2,3}$/.test(code) ? [code] : [];
9
+ });
10
+ return codes.length === 0 ? undefined : [...new Set(codes)];
11
+ }
12
+ // Asks once, briefly: the answer only fills a hint in Settings → Voices, so a slow or failed
13
+ // lookup is simply no answer.
14
+ export async function lookUp(fetcher, url, headers, signal) {
15
+ try {
16
+ const response = await fetcher(url, {
17
+ headers,
18
+ signal: AbortSignal.any([signal, AbortSignal.timeout(8000)]),
19
+ });
20
+ if (!response.ok) {
21
+ await response.body?.cancel();
22
+ return undefined;
23
+ }
24
+ return await response.json();
25
+ }
26
+ catch {
27
+ signal.throwIfAborted();
28
+ return undefined;
29
+ }
30
+ }
31
+ // GET /v1/voices/{id}: `verified_languages[].language` (or `.locale`), and the older
32
+ // `labels.language` and `fine_tuning.language`.
33
+ const elevenLabsVoice = z.object({
34
+ verified_languages: z
35
+ .array(z.object({ language: z.string().nullish(), locale: z.string().nullish() }))
36
+ .nullish(),
37
+ labels: z.object({ language: z.string().nullish() }).partial().nullish(),
38
+ fine_tuning: z.object({ language: z.string().nullish() }).partial().nullish(),
39
+ });
40
+ export function elevenLabsLanguages(body) {
41
+ const parsed = elevenLabsVoice.safeParse(body);
42
+ if (!parsed.success)
43
+ return undefined;
44
+ return primaryLanguages([
45
+ ...(parsed.data.verified_languages ?? []).flatMap((one) => [one.language, one.locale]),
46
+ parsed.data.labels?.language,
47
+ parsed.data.fine_tuning?.language,
48
+ ]);
49
+ }
50
+ // GET /voices/{id}: `language` (ISO 639-1) and `accents[].locale`.
51
+ const cartesiaVoice = z.object({
52
+ language: z.string().nullish(),
53
+ accents: z.array(z.object({ locale: z.string().nullish() })).nullish(),
54
+ });
55
+ export function cartesiaLanguages(body) {
56
+ const parsed = cartesiaVoice.safeParse(body);
57
+ if (!parsed.success)
58
+ return undefined;
59
+ return primaryLanguages([
60
+ parsed.data.language,
61
+ ...(parsed.data.accents ?? []).map((one) => one.locale),
62
+ ]);
63
+ }
64
+ // GET /voices/v1/voices/{id}: `languageCode` ("en-US"), `langCode` ("EN_US") and, for a
65
+ // multilingual voice, `promptLanguages`.
66
+ const inworldVoice = z.object({
67
+ languageCode: z.string().nullish(),
68
+ langCode: z.string().nullish(),
69
+ promptLanguages: z.array(z.string()).nullish(),
70
+ });
71
+ export function inworldLanguages(body) {
72
+ const parsed = inworldVoice.safeParse(typeof body === "object" && body !== null && "voice" in body ? body.voice : body);
73
+ if (!parsed.success)
74
+ return undefined;
75
+ return primaryLanguages([
76
+ parsed.data.languageCode,
77
+ parsed.data.langCode,
78
+ ...(parsed.data.promptLanguages ?? []),
79
+ ]);
80
+ }
@@ -0,0 +1,93 @@
1
+ Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ https://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,18 @@
1
+ Noto Sans (Latin, Greek, Cyrillic), Noto Sans Arabic, Noto Sans Hebrew, Noto Sans Devanagari
2
+ and Noto Sans Thai, Regular. Caption fallbacks for project languages the chosen caption font
3
+ cannot draw (src/slices/fonts/coverage.ts).
4
+
5
+ Source: the Noto Project (https://github.com/notofonts), static Regular instances as packaged
6
+ by Arch Linux noto-fonts 1:2026.09.01-1 (/usr/share/fonts/noto/). Noto Sans reports
7
+ "Version 2.015".
8
+ Licence: SIL Open Font License 1.1, OFL.txt beside this file, from
9
+ https://raw.githubusercontent.com/notofonts/latin-greek-cyrillic/main/OFL.txt
10
+ (SHA-256 cee9892f9f0cc8fe882c9e9537ee6a89621d86ee7ceaf70b02e2b2b1c25c061a). Each family is
11
+ "Copyright 2022 The Noto Project Authors" under the same licence.
12
+
13
+ SHA-256:
14
+ 478c558ea716033cd60c03438f628dfa75694dcf6b5f6d505a2f05fd2b4f3823 NotoSans-Regular.ttf
15
+ bdff3e5659d67e67def05b33f749683b9376ae819d65d3dd62ac4640b3aaef48 NotoSansArabic-Regular.ttf
16
+ cdefaf8efd47045f6820928eba84db5bed7557539328952b5f828315485e02ee NotoSansHebrew-Regular.ttf
17
+ 306b53ecfb182a504dd8a7446093c316387d2fd8dc350d0792ed1753fe0996cd NotoSansDevanagari-Regular.ttf
18
+ 61cf814eec46b294d6ea4401ac295d0cecd5207bd2331dcc5a15e7301d30ee44 NotoSansThai-Regular.ttf
@@ -141,6 +141,7 @@ image:
141
141
  source: https://developers.openai.com/api/docs/guides/image-generation
142
142
  keywords:
143
143
  - image
144
+ - reference
144
145
  pricing:
145
146
  note: Automatic quality and generated image tokens determine the charge; a fixed
146
147
  image price is unavailable.
@@ -154,6 +155,7 @@ image:
154
155
  source: https://developers.openai.com/api/docs/guides/image-generation
155
156
  keywords:
156
157
  - image
158
+ - reference
157
159
  pricing:
158
160
  note: Automatic quality and generated image tokens determine the charge; a fixed
159
161
  image price is unavailable.
@@ -164,6 +166,7 @@ image:
164
166
  source: https://developers.openai.com/api/docs/guides/image-generation
165
167
  keywords:
166
168
  - image
169
+ - reference
167
170
  pricing:
168
171
  note: Automatic quality and generated image tokens determine the charge; a fixed
169
172
  image price is unavailable.
@@ -174,6 +177,7 @@ image:
174
177
  source: https://ai.google.dev/gemini-api/docs/pricing
175
178
  keywords:
176
179
  - image
180
+ - reference
177
181
  pricing:
178
182
  perImage: 0.101
179
183
  note: 2K image output price; prompt, thinking and other input tokens are
@@ -187,6 +191,7 @@ image:
187
191
  source: https://ai.google.dev/gemini-api/docs/models
188
192
  keywords:
189
193
  - image
194
+ - reference
190
195
  pricing:
191
196
  note: Price depends on your account or generated output; estimate unavailable.
192
197
  image:
@@ -198,6 +203,7 @@ image:
198
203
  source: https://fal.ai/models/fal-ai/flux-2
199
204
  keywords:
200
205
  - image
206
+ - reference
201
207
  pricing:
202
208
  note: Price depends on your account or generated output; estimate unavailable.
203
209
  image: *a4
@@ -207,6 +213,7 @@ image:
207
213
  source: https://fal.ai/models/fal-ai/nano-banana-2
208
214
  keywords:
209
215
  - image
216
+ - reference
210
217
  pricing:
211
218
  note: Price depends on your account or generated output; estimate unavailable.
212
219
  image: *a4
@@ -0,0 +1,132 @@
1
+ // What an automatic catalogue check found. Every change is reported, none is applied to a
2
+ // template, schedule, draft or project: a retired model is only flagged.
3
+ export const catalogueFamilies = ["llm", "image", "tts"];
4
+ export const noChanges = { added: [], retired: [], priced: [] };
5
+ export function modelKey(family, provider, id) {
6
+ return `${family}:${provider}:${id}`;
7
+ }
8
+ function ref(family, model) {
9
+ return { family, provider: model.provider, id: model.id, name: model.name };
10
+ }
11
+ function samePricing(a, b) {
12
+ const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
13
+ for (const key of keys) {
14
+ if (key === "note")
15
+ continue;
16
+ const x = a[key];
17
+ const y = b[key];
18
+ // A price read back from per-token figures differs from the curated one only in rounding.
19
+ if (typeof x === "number" && typeof y === "number") {
20
+ if (Math.abs(x - y) > 1e-9 * Math.max(1, Math.abs(x), Math.abs(y)))
21
+ return false;
22
+ }
23
+ else if (x !== y)
24
+ return false;
25
+ }
26
+ return true;
27
+ }
28
+ // Folds the published catalogue into the local one instead of replacing it:
29
+ // - a published model replaces the local row (prices, names, capabilities), except that a model
30
+ // the user switched off stays off and a speech model keeps its local character limit, which
31
+ // decides how narration is split and so what existing projects count as up to date;
32
+ // - a published model the local file lacks is added;
33
+ // - a local model that was published before and no longer is, or that the published file marks
34
+ // deprecated, is kept and marked deprecated, so pickers hide it and the places that use it can
35
+ // be listed; a model the user added themselves (never published) is left alone.
36
+ export function mergeCatalogue(local, published, knownPublished) {
37
+ const added = [];
38
+ const retired = [];
39
+ const priced = [];
40
+ const known = new Set(knownPublished);
41
+ const merge = (family, localRows, publishedRows) => {
42
+ const localByKey = new Map(localRows.map((row) => [modelKey(family, row.provider, row.id), row]));
43
+ const publishedKeys = new Set();
44
+ const rows = [];
45
+ for (const row of publishedRows) {
46
+ const key = modelKey(family, row.provider, row.id);
47
+ publishedKeys.add(key);
48
+ known.add(key);
49
+ const before = localByKey.get(key);
50
+ if (before === undefined) {
51
+ added.push(ref(family, row));
52
+ rows.push(row);
53
+ continue;
54
+ }
55
+ let next = before.enabled ? row : { ...row, enabled: false };
56
+ if ("tts" in before && "tts" in next)
57
+ next = { ...next, tts: { ...next.tts, maxCharacters: before.tts.maxCharacters } };
58
+ if (!before.deprecated && next.deprecated)
59
+ retired.push(ref(family, next));
60
+ if (!samePricing(before.pricing, next.pricing))
61
+ priced.push({ ...ref(family, next), before: before.pricing, after: next.pricing });
62
+ rows.push(next);
63
+ }
64
+ for (const row of localRows) {
65
+ const key = modelKey(family, row.provider, row.id);
66
+ if (publishedKeys.has(key))
67
+ continue;
68
+ if (knownPublished.has(key) && !row.deprecated) {
69
+ retired.push(ref(family, row));
70
+ rows.push({ ...row, deprecated: true });
71
+ }
72
+ else
73
+ rows.push(row);
74
+ }
75
+ return rows;
76
+ };
77
+ const catalogue = {
78
+ schemaVersion: 1,
79
+ updatedAt: published.updatedAt > local.updatedAt ? published.updatedAt : local.updatedAt,
80
+ providers: { ...local.providers, ...published.providers },
81
+ llm: merge("llm", local.llm, published.llm),
82
+ image: merge("image", local.image, published.image),
83
+ tts: merge("tts", local.tts, published.tts),
84
+ };
85
+ return { catalogue, changes: { added, retired, priced }, known };
86
+ }
87
+ export function applyOpenRouterListing(catalogue, listing) {
88
+ if (listing.length === 0)
89
+ return { catalogue, changes: noChanges };
90
+ const byId = new Map(listing.map((row) => [row.id, row]));
91
+ const retired = [];
92
+ const priced = [];
93
+ const llm = catalogue.llm.map((row) => {
94
+ if (row.provider !== "openrouter" || row.deprecated)
95
+ return row;
96
+ const live = byId.get(row.id);
97
+ if (live === undefined) {
98
+ retired.push(ref("llm", row));
99
+ return { ...row, deprecated: true };
100
+ }
101
+ const pricing = {
102
+ ...row.pricing,
103
+ ...(live.promptPerToken === undefined
104
+ ? {}
105
+ : { inputPerMillionTokens: perMillion(live.promptPerToken) }),
106
+ ...(live.completionPerToken === undefined
107
+ ? {}
108
+ : { outputPerMillionTokens: perMillion(live.completionPerToken) }),
109
+ };
110
+ if (samePricing(row.pricing, pricing))
111
+ return row;
112
+ priced.push({ ...ref("llm", row), before: row.pricing, after: pricing });
113
+ return { ...row, pricing };
114
+ });
115
+ return { catalogue: { ...catalogue, llm }, changes: { added: [], retired, priced } };
116
+ }
117
+ function perMillion(perToken) {
118
+ return Math.round(perToken * 1_000_000 * 1_000_000) / 1_000_000;
119
+ }
120
+ export function combineChanges(a, b) {
121
+ const priced = new Map();
122
+ for (const row of [...a.priced, ...b.priced]) {
123
+ const key = modelKey(row.family, row.provider, row.id);
124
+ const earlier = priced.get(key);
125
+ priced.set(key, earlier === undefined ? row : { ...row, before: earlier.before });
126
+ }
127
+ return {
128
+ added: [...a.added, ...b.added],
129
+ retired: [...a.retired, ...b.retired],
130
+ priced: [...priced.values()],
131
+ };
132
+ }
@@ -1,5 +1,7 @@
1
+ import { thinkingModes } from "../kernel/ports/llm.js";
1
2
  import { providerError } from "../kernel/ports/model.js";
2
3
  import { isLocalCliProvider } from "../slices/settings/model.js";
4
+ import { referenceRefusal, takesReferenceImage } from "./schema.js";
3
5
  export function curateRegistry(registry, catalogue) {
4
6
  const requireModel = (provider, family, id) => {
5
7
  const models = catalogue.models(provider, family);
@@ -7,7 +9,7 @@ export function curateRegistry(registry, catalogue) {
7
9
  if (!model)
8
10
  throw providerError({
9
11
  kind: "unsupported",
10
- message: `The chosen ${provider} model is no longer in Slopify's model list. In the Edit tab, choose Edit project and change it under Providers, then use Resume.`,
12
+ message: `The chosen ${provider} model is no longer in Slopify's model list. In the Edit tab, choose Edit project and change it under Providers, then use Continue the run.`,
11
13
  });
12
14
  return model;
13
15
  };
@@ -23,7 +25,7 @@ export function curateRegistry(registry, catalogue) {
23
25
  if (request.model.trim() === "")
24
26
  throw providerError({
25
27
  kind: "unsupported",
26
- message: "No model is chosen for this step. In the Edit tab, choose Edit project and change it under Providers, then use Resume.",
28
+ message: "No model is chosen for this step. In the Edit tab, choose Edit project and change it under Providers, then use Continue the run.",
27
29
  });
28
30
  let models;
29
31
  try {
@@ -37,19 +39,19 @@ export function curateRegistry(registry, catalogue) {
37
39
  if (models !== undefined && selected === undefined)
38
40
  throw providerError({
39
41
  kind: "unsupported",
40
- message: "The chosen model is not available in the AI command-line tool on your computer. Update the tool, or pick another model: in the Edit tab, choose Edit project and change it under Providers, then use Resume.",
42
+ message: "The chosen model is not available in the AI command-line tool on your computer. Update the tool, or pick another model: in the Edit tab, choose Edit project and change it under Providers, then use Continue the run.",
41
43
  });
42
44
  if (request.thinking !== undefined &&
43
45
  selected !== undefined &&
44
46
  !selected.thinkingModes?.includes(request.thinking))
45
47
  throw providerError({
46
48
  kind: "unsupported",
47
- message: "The chosen model does not support this thinking setting. In the Edit tab, choose Edit project and change it under Providers, then use Resume.",
49
+ message: "The chosen model does not support this thinking setting. In the Edit tab, choose Edit project and change it under Providers, then use Continue the run.",
48
50
  });
49
51
  if (request.webSearch && !port.capabilities.webSearch)
50
52
  throw providerError({
51
53
  kind: "unsupported",
52
- message: "This command-line tool cannot search the web for research. Choose another text provider or turn Research off in the Edit tab under Edit project, then use Resume.",
54
+ message: "This command-line tool cannot search the web for research. Choose another text provider or turn Research off in the Edit tab under Edit project, then use Continue the run.",
53
55
  });
54
56
  const thinkingConfig = id === "codex" && request.thinking !== undefined
55
57
  ? { effort: request.thinking === "off" ? "none" : request.thinking }
@@ -67,7 +69,7 @@ export function curateRegistry(registry, catalogue) {
67
69
  ...m,
68
70
  ...("llm" in m && m.llm.thinking
69
71
  ? {
70
- thinkingModes: Object.keys(m.llm.thinking).filter((mode) => ["off", "low", "medium", "high", "xhigh"].includes(mode)),
72
+ thinkingModes: Object.keys(m.llm.thinking).filter((mode) => thinkingModes.includes(mode)),
71
73
  }
72
74
  : {}),
73
75
  })),
@@ -78,13 +80,13 @@ export function curateRegistry(registry, catalogue) {
78
80
  if (request.webSearch && !model.llm.webSearch)
79
81
  throw providerError({
80
82
  kind: "unsupported",
81
- message: "The chosen model cannot search the web for research. Choose another model or turn Research off in the Edit tab under Edit project, then use Resume.",
83
+ message: "The chosen model cannot search the web for research. Choose another model or turn Research off in the Edit tab under Edit project, then use Continue the run.",
82
84
  });
83
85
  const thinkingConfig = request.thinking === undefined ? undefined : model.llm.thinking?.[request.thinking];
84
86
  if (request.thinking !== undefined && !thinkingConfig)
85
87
  throw providerError({
86
88
  kind: "unsupported",
87
- message: "The chosen model does not support this thinking setting. In the Edit tab, choose Edit project and change it under Providers, then use Resume.",
89
+ message: "The chosen model does not support this thinking setting. In the Edit tab, choose Edit project and change it under Providers, then use Continue the run.",
88
90
  });
89
91
  yield* port.complete({
90
92
  ...request,
@@ -100,10 +102,19 @@ export function curateRegistry(registry, catalogue) {
100
102
  ...port,
101
103
  models: () => port.models(),
102
104
  generate: async (request) => {
103
- if (!(await port.models()).some((model) => model.id === request.model))
105
+ const selected = (await port.models()).find((model) => model.id === request.model);
106
+ if (selected === undefined)
104
107
  throw providerError({
105
108
  kind: "unsupported",
106
- message: "Image generation is not available in the Codex CLI on your computer. Update Codex CLI, or choose another image provider in the Edit tab under Edit project, then use Resume.",
109
+ message: request.model === "codex-imagegen"
110
+ ? "Image generation is not available in the Codex CLI on your computer. Update Codex CLI, or choose another image provider in the Edit tab under Edit project, then use Continue the run."
111
+ : "The chosen Codex model for images is not in the Codex CLI's model list on your computer. Update Codex CLI, or pick another model for images: in the Edit tab, choose Edit project and change it under Providers, then use Continue the run.",
112
+ });
113
+ if (request.thinking !== undefined &&
114
+ !selected.thinkingModes?.includes(request.thinking))
115
+ throw providerError({
116
+ kind: "unsupported",
117
+ message: "The chosen Codex model does not support this effort for images. In the Edit tab, choose Edit project and change the images' Effort under Providers, then use Continue the run.",
107
118
  });
108
119
  return port.generate(request);
109
120
  },
@@ -117,7 +128,17 @@ export function curateRegistry(registry, catalogue) {
117
128
  if (!("image" in model) || !model.image.aspectRatios.includes(request.aspect))
118
129
  throw providerError({
119
130
  kind: "unsupported",
120
- message: "The chosen image model cannot make images in this video's shape. In the Edit tab, choose Edit project and change it under Providers, then use Resume.",
131
+ message: "The chosen image model cannot make images in this video's shape. In the Edit tab, choose Edit project and change it under Providers, then use Continue the run.",
132
+ });
133
+ if (request.thinking !== undefined)
134
+ throw providerError({
135
+ kind: "unsupported",
136
+ message: "The chosen image model has no effort setting. In the Edit tab, choose Edit project and set the images' Effort back to Model default under Providers, then use Continue the run.",
137
+ });
138
+ if (request.reference !== undefined && !takesReferenceImage(model))
139
+ throw providerError({
140
+ kind: "unsupported",
141
+ message: referenceRefusal,
121
142
  });
122
143
  return port.generate(request);
123
144
  },
@@ -138,7 +159,7 @@ export function curateRegistry(registry, catalogue) {
138
159
  if (request.text.length > model.tts.maxCharacters)
139
160
  throw providerError({
140
161
  kind: "unsupported",
141
- message: `A piece of narration is longer than this voice model's ${model.tts.maxCharacters}-character limit. Use Re-run section on Audio so Slopify splits it into shorter pieces.`,
162
+ message: `A piece of narration is longer than this voice model's ${model.tts.maxCharacters}-character limit. Use More → make it again in its section on Audio so Slopify splits it into shorter pieces.`,
142
163
  });
143
164
  return port.synthesize({ ...request, model: model.id });
144
165
  },
@@ -6,7 +6,7 @@ export async function checkRuntimeModel(modelsFor, provider, family, id, thinkin
6
6
  const model = (await modelsFor(provider, family)).find((row) => row.id === id);
7
7
  if (model === undefined)
8
8
  return "missing";
9
- if (family === "llm" && thinking !== undefined && !model.thinkingModes?.includes(thinking))
9
+ if (family !== "tts" && thinking !== undefined && !model.thinkingModes?.includes(thinking))
10
10
  return "thinking";
11
11
  return "available";
12
12
  }