@gentbajko/slopify 2.5.0 → 3.0.1

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 (542) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +24 -41
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +31 -0
  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 +44 -5
  20. package/dist/adapters/host-cli/transport.js +7 -5
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +1 -1
  23. package/dist/adapters/image/codex.js +87 -22
  24. package/dist/adapters/image/fal.js +13 -5
  25. package/dist/adapters/image/google.js +11 -8
  26. package/dist/adapters/image/openai.js +14 -7
  27. package/dist/adapters/image/reference.js +34 -0
  28. package/dist/adapters/image/replicate.js +7 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +85 -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.js +99 -12
  35. package/dist/adapters/llm/document-workspace.js +2 -2
  36. package/dist/adapters/llm/gemini-workspace.js +1 -1
  37. package/dist/adapters/llm/gemini.js +16 -3
  38. package/dist/adapters/llm/image-workspace.js +58 -0
  39. package/dist/adapters/llm/openrouter.js +11 -3
  40. package/dist/adapters/llm/run-cli.js +12 -7
  41. package/dist/adapters/tts/cartesia.js +10 -0
  42. package/dist/adapters/tts/elevenlabs.js +31 -9
  43. package/dist/adapters/tts/gemini.js +206 -0
  44. package/dist/adapters/tts/inworld-async.js +9 -5
  45. package/dist/adapters/tts/inworld.js +17 -3
  46. package/dist/adapters/tts/openai.js +3 -0
  47. package/dist/adapters/tts/pcm-mp3.js +62 -0
  48. package/dist/adapters/tts/system.js +147 -0
  49. package/dist/adapters/tts/voice-languages.js +80 -0
  50. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  55. package/dist/assets/fonts/noto/OFL.txt +93 -0
  56. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  57. package/dist/assets/models.yaml +38 -0
  58. package/dist/assets/sample/sample-audiobook.tar +0 -0
  59. package/dist/assets/sample/sample-podcast.tar +0 -0
  60. package/dist/assets/sample/sample-project.tar +0 -0
  61. package/dist/assets/style-preview/narration.mp3 +0 -0
  62. package/dist/assets/style-preview/tall-1.jpg +0 -0
  63. package/dist/assets/style-preview/tall-2.jpg +0 -0
  64. package/dist/assets/style-preview/tall-3.jpg +0 -0
  65. package/dist/assets/style-preview/wide-1.jpg +0 -0
  66. package/dist/assets/style-preview/wide-2.jpg +0 -0
  67. package/dist/assets/style-preview/wide-3.jpg +0 -0
  68. package/dist/catalog/merge.js +132 -0
  69. package/dist/catalog/registry.js +17 -14
  70. package/dist/catalog/schema.js +2 -0
  71. package/dist/catalog/store.js +147 -8
  72. package/dist/catalog/validate.js +2 -2
  73. package/dist/compose.yaml +71 -0
  74. package/dist/edge/autostart/docker-record.js +32 -0
  75. package/dist/edge/autostart/docker.js +118 -0
  76. package/dist/edge/autostart/index.js +78 -0
  77. package/dist/edge/autostart/launcher.js +176 -0
  78. package/dist/edge/autostart/model.js +3 -0
  79. package/dist/edge/autostart/native.js +210 -0
  80. package/dist/edge/autostart/prompt.js +72 -0
  81. package/dist/edge/autostart/service.js +90 -0
  82. package/dist/edge/cli-args.js +71 -0
  83. package/dist/edge/cli.js +64 -63
  84. package/dist/edge/docker-install/activation.js +56 -0
  85. package/dist/edge/docker-install/apply.js +458 -0
  86. package/dist/edge/docker-install/engine.js +362 -0
  87. package/dist/edge/docker-install/run.js +160 -0
  88. package/dist/edge/docker-install/state.js +146 -0
  89. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  90. package/dist/edge/docker.js +14 -22
  91. package/dist/edge/events/hub.js +3 -1
  92. package/dist/edge/host-cli.js +1 -1
  93. package/dist/edge/http/actions.js +38 -11
  94. package/dist/edge/http/app.js +60 -2
  95. package/dist/edge/http/auditions.js +70 -0
  96. package/dist/edge/http/autostart.js +37 -0
  97. package/dist/edge/http/byte-range.js +42 -0
  98. package/dist/edge/http/channel-memory.js +92 -0
  99. package/dist/edge/http/channels.js +128 -0
  100. package/dist/edge/http/diagnostics.js +1 -0
  101. package/dist/edge/http/draft-files.js +2 -4
  102. package/dist/edge/http/entries.js +22 -3
  103. package/dist/edge/http/files.js +11 -7
  104. package/dist/edge/http/folder-location.js +1 -1
  105. package/dist/edge/http/home.js +39 -0
  106. package/dist/edge/http/host-cli.js +29 -5
  107. package/dist/edge/http/narration-peaks.js +76 -0
  108. package/dist/edge/http/onboarding.js +212 -0
  109. package/dist/edge/http/open-folder.js +1 -1
  110. package/dist/edge/http/patch-notes.js +45 -0
  111. package/dist/edge/http/planning.js +4 -2
  112. package/dist/edge/http/project-create.js +87 -0
  113. package/dist/edge/http/project-templates.js +19 -2
  114. package/dist/edge/http/projects.js +37 -100
  115. package/dist/edge/http/prompts.js +40 -3
  116. package/dist/edge/http/pronunciations.js +23 -4
  117. package/dist/edge/http/providers.js +119 -3
  118. package/dist/edge/http/reviews.js +131 -0
  119. package/dist/edge/http/revision-files.js +3 -5
  120. package/dist/edge/http/revisions.js +1 -1
  121. package/dist/edge/http/run-cost.js +25 -0
  122. package/dist/edge/http/schedules.js +115 -6
  123. package/dist/edge/http/settings.js +84 -6
  124. package/dist/edge/http/storage-files.js +50 -0
  125. package/dist/edge/http/storage.js +43 -2
  126. package/dist/edge/http/studio.js +287 -0
  127. package/dist/edge/http/style-preview.js +61 -0
  128. package/dist/edge/http/subtitles.js +1 -1
  129. package/dist/edge/http/trash.js +69 -0
  130. package/dist/edge/http/tutorials.js +59 -0
  131. package/dist/edge/http/update.js +24 -3
  132. package/dist/edge/http/whats-new.js +15 -0
  133. package/dist/edge/http/youtube-edits.js +92 -0
  134. package/dist/edge/native-update.js +62 -0
  135. package/dist/extension/slopify-studio-chrome.zip +0 -0
  136. package/dist/extension/slopify-studio-firefox.zip +0 -0
  137. package/dist/host-cli/gemini-login.js +83 -0
  138. package/dist/host-cli/open-folder.js +10 -10
  139. package/dist/host-cli/runtime.js +9 -1
  140. package/dist/host-cli/status.js +4 -2
  141. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  142. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  143. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  144. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  145. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  146. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  147. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  148. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  149. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  150. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  151. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  152. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  153. package/dist/kernel/db/migrations/0041-cast-hosts.sql +3 -0
  154. package/dist/kernel/paths.js +27 -6
  155. package/dist/kernel/pipeline.js +4 -1
  156. package/dist/kernel/ports/host-cli.js +122 -14
  157. package/dist/kernel/ports/key-probe.js +1 -0
  158. package/dist/kernel/ports/languages.js +157 -0
  159. package/dist/kernel/ports/model.js +10 -5
  160. package/dist/kernel/ports/narration-aliases.js +60 -0
  161. package/dist/kernel/ports/plan-limits.js +36 -0
  162. package/dist/kernel/ports/system-speech.js +203 -0
  163. package/dist/kernel/runner/attempt.js +17 -8
  164. package/dist/kernel/runner/audition.js +36 -0
  165. package/dist/kernel/runner/graph.js +40 -5
  166. package/dist/kernel/runner/index.js +39 -9
  167. package/dist/kernel/runner/meter.js +1 -0
  168. package/dist/kernel/runner/providers.js +142 -7
  169. package/dist/kernel/runner/retry-policy.js +43 -0
  170. package/dist/kernel/runner/standalone.js +134 -0
  171. package/dist/kernel/runner/work-authority.js +28 -2
  172. package/dist/main.js +277 -16
  173. package/dist/model-catalog.js +2 -0
  174. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  175. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  176. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  177. package/dist/patch-notes/3.0.0.md +70 -0
  178. package/dist/patch-notes/3.0.1.md +105 -0
  179. package/dist/patch-notes/index.json +32 -0
  180. package/dist/slices/admission/model.js +27 -0
  181. package/dist/slices/admission/repo.js +24 -4
  182. package/dist/slices/admission/rules.js +80 -2
  183. package/dist/slices/admission/schema.js +58 -1
  184. package/dist/slices/admission/short-mode.js +47 -0
  185. package/dist/slices/admission/start.js +42 -6
  186. package/dist/slices/article/continuation.js +2 -2
  187. package/dist/slices/article/provided-entries.js +2 -2
  188. package/dist/slices/article/run.js +6 -2
  189. package/dist/slices/article/segments.js +3 -3
  190. package/dist/slices/backups/folder.js +14 -6
  191. package/dist/slices/batch/index.js +7 -2
  192. package/dist/slices/cancel/index.js +1 -1
  193. package/dist/slices/channels/cast-images.js +110 -0
  194. package/dist/slices/channels/cast-match.js +32 -0
  195. package/dist/slices/channels/images.js +19 -0
  196. package/dist/slices/channels/model.js +5 -0
  197. package/dist/slices/channels/rebrand.js +100 -0
  198. package/dist/slices/channels/repo.js +221 -0
  199. package/dist/slices/channels/runs.js +129 -0
  200. package/dist/slices/channels/schema.js +152 -0
  201. package/dist/slices/channels/service.js +180 -0
  202. package/dist/slices/channels/studio-csv.js +110 -0
  203. package/dist/slices/channels/videos.js +120 -0
  204. package/dist/slices/checkpoints/recovery.js +10 -0
  205. package/dist/slices/control/index.js +5 -2
  206. package/dist/slices/control/providers.js +4 -1
  207. package/dist/slices/document/fonts.js +1 -1
  208. package/dist/slices/document/render.js +1 -1
  209. package/dist/slices/episodes/related.js +90 -0
  210. package/dist/slices/episodes/repo.js +60 -0
  211. package/dist/slices/episodes/service.js +58 -0
  212. package/dist/slices/episodes/summarize.js +170 -0
  213. package/dist/slices/estimate/index.js +109 -28
  214. package/dist/slices/estimate/requests.js +68 -2
  215. package/dist/slices/eta/history.js +56 -0
  216. package/dist/slices/eta/model.js +63 -0
  217. package/dist/slices/eta/view.js +25 -0
  218. package/dist/slices/fixes/rules.js +73 -0
  219. package/dist/slices/fonts/coverage.js +82 -0
  220. package/dist/slices/fonts/sfnt.js +74 -0
  221. package/dist/slices/images/run.js +8 -5
  222. package/dist/slices/images/scale.js +84 -0
  223. package/dist/slices/library/diff.js +122 -0
  224. package/dist/slices/library/history.js +100 -0
  225. package/dist/slices/library/model.js +3 -0
  226. package/dist/slices/library/photorealistic.js +49 -0
  227. package/dist/slices/library/repo.js +19 -14
  228. package/dist/slices/library/save.js +62 -12
  229. package/dist/slices/library/slots.js +7 -1
  230. package/dist/slices/library/used-by.js +153 -0
  231. package/dist/slices/loudness/level-pieces.js +41 -0
  232. package/dist/slices/loudness/loudnorm.js +164 -0
  233. package/dist/slices/loudness/model.js +153 -0
  234. package/dist/slices/model-upkeep/model.js +10 -0
  235. package/dist/slices/model-upkeep/switch.js +217 -0
  236. package/dist/slices/model-upkeep/usage.js +185 -0
  237. package/dist/slices/narration/aliases-library.js +31 -0
  238. package/dist/slices/narration/aliases-schema.js +53 -0
  239. package/dist/slices/narration/aliases.js +49 -0
  240. package/dist/slices/narration/blocks.js +352 -0
  241. package/dist/slices/narration/chunk.js +19 -7
  242. package/dist/slices/narration/concat.js +2 -2
  243. package/dist/slices/narration/describe.js +45 -0
  244. package/dist/slices/narration/pauses-model.js +63 -0
  245. package/dist/slices/narration/pauses.js +269 -0
  246. package/dist/slices/narration/peaks-model.js +1 -0
  247. package/dist/slices/narration/peaks.js +94 -0
  248. package/dist/slices/narration/preparation.js +21 -3
  249. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  250. package/dist/slices/narration/pronunciation.js +72 -25
  251. package/dist/slices/narration/run.js +6 -6
  252. package/dist/slices/narration/shared-glossary.js +1 -1
  253. package/dist/slices/narration/steering.js +15 -4
  254. package/dist/slices/notifications/notifier.js +47 -3
  255. package/dist/slices/notifications/rules.js +48 -3
  256. package/dist/slices/onboarding/copy.js +143 -0
  257. package/dist/slices/onboarding/first-run.js +72 -0
  258. package/dist/slices/onboarding/full-video.js +81 -0
  259. package/dist/slices/onboarding/install.js +119 -0
  260. package/dist/slices/onboarding/model.js +85 -0
  261. package/dist/slices/onboarding/packs.js +491 -0
  262. package/dist/slices/onboarding/quick-short.js +144 -0
  263. package/dist/slices/onboarding/sample.js +132 -0
  264. package/dist/slices/onboarding/state.js +68 -0
  265. package/dist/slices/onboarding/template.js +73 -0
  266. package/dist/slices/patch-notes/library.js +73 -0
  267. package/dist/slices/patch-notes/seen.js +77 -0
  268. package/dist/slices/play-drafts/convert.js +127 -8
  269. package/dist/slices/play-drafts/readiness.js +4 -1
  270. package/dist/slices/play-drafts/repo.js +2 -0
  271. package/dist/slices/play-drafts/review-inputs.js +20 -3
  272. package/dist/slices/play-drafts/schema.js +89 -0
  273. package/dist/slices/play-drafts/service.js +4 -1
  274. package/dist/slices/play-drafts/start.js +43 -18
  275. package/dist/slices/project-templates/from-project.js +59 -6
  276. package/dist/slices/project-templates/next-chapter.js +94 -0
  277. package/dist/slices/project-templates/one-off.js +26 -0
  278. package/dist/slices/project-templates/repo.js +2 -1
  279. package/dist/slices/project-templates/schema.js +7 -1
  280. package/dist/slices/project-templates/service.js +15 -4
  281. package/dist/slices/project-templates/setup.js +18 -2
  282. package/dist/slices/rebuild/admission-repo.js +1 -1
  283. package/dist/slices/rebuild/model.js +6 -0
  284. package/dist/slices/rebuild/preview-details.js +46 -3
  285. package/dist/slices/rebuild/preview-plan.js +13 -6
  286. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  287. package/dist/slices/rebuild/recipe-audio.js +66 -12
  288. package/dist/slices/rebuild/recipe-build.js +8 -4
  289. package/dist/slices/rebuild/recipe-cast.js +18 -0
  290. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  291. package/dist/slices/rebuild/recipe-describe.js +93 -0
  292. package/dist/slices/rebuild/recipe-edit.js +13 -1
  293. package/dist/slices/rebuild/recipe-exports.js +67 -8
  294. package/dist/slices/rebuild/recipe-input-schema.js +35 -0
  295. package/dist/slices/rebuild/recipe-legacy.js +9 -0
  296. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  297. package/dist/slices/rebuild/recipe-model.js +25 -6
  298. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  299. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  300. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  301. package/dist/slices/rebuild/recipe-reference.js +15 -1
  302. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  303. package/dist/slices/rebuild/recipe-save.js +4 -1
  304. package/dist/slices/rebuild/recipe-shorts.js +23 -3
  305. package/dist/slices/rebuild/recipe-text.js +114 -19
  306. package/dist/slices/rebuild/recipe-validation.js +14 -2
  307. package/dist/slices/rebuild/recipe-visual.js +96 -33
  308. package/dist/slices/rebuild/recipe-voices.js +286 -0
  309. package/dist/slices/rebuild/recipe-work.js +77 -6
  310. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  311. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  312. package/dist/slices/rebuild/recovery-model.js +3 -0
  313. package/dist/slices/rebuild/recovery-selection.js +28 -2
  314. package/dist/slices/rebuild/recovery.js +35 -17
  315. package/dist/slices/rebuild/repo.js +3 -3
  316. package/dist/slices/rebuild/retry.js +18 -0
  317. package/dist/slices/rebuild/review-redo.js +125 -0
  318. package/dist/slices/rebuild/runtime-actions.js +5 -0
  319. package/dist/slices/rebuild/runtime-admission.js +4 -1
  320. package/dist/slices/rebuild/runtime-animate.js +4 -4
  321. package/dist/slices/rebuild/runtime-article.js +5 -5
  322. package/dist/slices/rebuild/runtime-document.js +4 -4
  323. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  324. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  325. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  326. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  327. package/dist/slices/rebuild/runtime-export.js +84 -19
  328. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  329. package/dist/slices/rebuild/runtime-image.js +21 -2
  330. package/dist/slices/rebuild/runtime-local.js +148 -24
  331. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  332. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  333. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  334. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  335. package/dist/slices/rebuild/runtime-piece-label.js +27 -0
  336. package/dist/slices/rebuild/runtime-provider.js +126 -18
  337. package/dist/slices/rebuild/runtime-publication.js +21 -5
  338. package/dist/slices/rebuild/runtime-review.js +267 -0
  339. package/dist/slices/rebuild/runtime-run.js +17 -11
  340. package/dist/slices/rebuild/runtime-shorts.js +44 -17
  341. package/dist/slices/rebuild/runtime-store.js +62 -11
  342. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  343. package/dist/slices/rebuild/runtime-voices.js +103 -0
  344. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  345. package/dist/slices/rebuild/service-readiness.js +3 -3
  346. package/dist/slices/rebuild/soften.js +49 -0
  347. package/dist/slices/rebuild/transition-repo.js +9 -2
  348. package/dist/slices/research/run.js +4 -4
  349. package/dist/slices/research/synthesis.js +2 -2
  350. package/dist/slices/reviews/model.js +105 -0
  351. package/dist/slices/reviews/outcome.js +22 -0
  352. package/dist/slices/reviews/repo.js +118 -0
  353. package/dist/slices/reviews/rules.js +63 -0
  354. package/dist/slices/reviews/verdict.js +71 -0
  355. package/dist/slices/reviews/view.js +30 -0
  356. package/dist/slices/revisions/adopt-content.js +5 -2
  357. package/dist/slices/revisions/adopt.js +3 -1
  358. package/dist/slices/revisions/downloads.js +7 -1
  359. package/dist/slices/revisions/image-plan.js +183 -0
  360. package/dist/slices/revisions/mutation-assets.js +10 -0
  361. package/dist/slices/revisions/mutation-cues.js +71 -0
  362. package/dist/slices/revisions/mutation-request.js +2 -1
  363. package/dist/slices/revisions/mutations.js +97 -3
  364. package/dist/slices/revisions/schema.js +11 -1
  365. package/dist/slices/run-cost/limits.js +128 -0
  366. package/dist/slices/run-cost/meter.js +47 -0
  367. package/dist/slices/run-cost/panel.js +256 -0
  368. package/dist/slices/run-cost/pricing.js +95 -0
  369. package/dist/slices/run-cost/week.js +108 -0
  370. package/dist/slices/schedules/agenda.js +222 -0
  371. package/dist/slices/schedules/repo.js +58 -18
  372. package/dist/slices/schedules/scheduler.js +68 -17
  373. package/dist/slices/schedules/schema.js +179 -1
  374. package/dist/slices/schedules/service.js +43 -4
  375. package/dist/slices/schedules/similar.js +128 -0
  376. package/dist/slices/schedules/topic-list.js +163 -0
  377. package/dist/slices/schedules/topics.js +523 -0
  378. package/dist/slices/settings/first-run.js +106 -0
  379. package/dist/slices/settings/health.js +247 -0
  380. package/dist/slices/settings/key-guides.js +138 -0
  381. package/dist/slices/settings/key-test.js +93 -0
  382. package/dist/slices/settings/keys.js +10 -5
  383. package/dist/slices/settings/model-reach.js +52 -0
  384. package/dist/slices/settings/model.js +55 -1
  385. package/dist/slices/settings/models.js +5 -1
  386. package/dist/slices/settings/playback.js +15 -0
  387. package/dist/slices/settings/readiness.js +14 -2
  388. package/dist/slices/settings/repo.js +17 -3
  389. package/dist/slices/settings/tutorial-schema.js +5 -0
  390. package/dist/slices/settings/voices.js +64 -4
  391. package/dist/slices/settings/whats-new.js +64 -0
  392. package/dist/slices/shorts/captions.js +9 -0
  393. package/dist/slices/shorts/pick.js +3 -3
  394. package/dist/slices/shorts/prompts.js +1 -1
  395. package/dist/slices/shorts/render.js +63 -13
  396. package/dist/slices/storage/asset-name.js +3 -0
  397. package/dist/slices/storage/backup-export.js +48 -9
  398. package/dist/slices/storage/backup-format.js +38 -1
  399. package/dist/slices/storage/backup-import.js +191 -19
  400. package/dist/slices/storage/delete-project.js +7 -4
  401. package/dist/slices/storage/documents.js +87 -0
  402. package/dist/slices/storage/downloads.js +33 -1
  403. package/dist/slices/storage/files-location.js +531 -0
  404. package/dist/slices/storage/layout.js +14 -4
  405. package/dist/slices/storage/model.js +11 -0
  406. package/dist/slices/storage/portable.js +94 -12
  407. package/dist/slices/storage/reconcile.js +14 -1
  408. package/dist/slices/storage/repo.js +4 -1
  409. package/dist/slices/storage/schema.js +11 -0
  410. package/dist/slices/storage/trim.js +146 -0
  411. package/dist/slices/studio/disclosure.js +74 -0
  412. package/dist/slices/studio/model.js +30 -0
  413. package/dist/slices/studio/pack.js +211 -0
  414. package/dist/slices/studio/queue.js +69 -0
  415. package/dist/slices/studio/settings.js +170 -0
  416. package/dist/slices/style-preview/images.js +61 -0
  417. package/dist/slices/style-preview/narration.js +33 -0
  418. package/dist/slices/style-preview/render.js +148 -0
  419. package/dist/slices/style-preview/schema.js +59 -0
  420. package/dist/slices/style-preview/service.js +92 -0
  421. package/dist/slices/style-preview/settings.js +139 -0
  422. package/dist/slices/subtitles/captions.js +42 -9
  423. package/dist/slices/subtitles/model.js +22 -0
  424. package/dist/slices/subtitles/prepare.js +27 -16
  425. package/dist/slices/subtitles/transcript.js +1 -1
  426. package/dist/slices/thumbnail/run.js +7 -7
  427. package/dist/slices/trash/model.js +25 -0
  428. package/dist/slices/trash/service.js +273 -0
  429. package/dist/slices/tutorials/anchors.js +57 -0
  430. package/dist/slices/tutorials/library.js +185 -0
  431. package/dist/slices/uploads/repo.js +20 -0
  432. package/dist/slices/video/ambient-bed-schema.js +15 -0
  433. package/dist/slices/video/ambient-bed.js +95 -0
  434. package/dist/slices/video/ambient-mix.js +72 -0
  435. package/dist/slices/video/audio-inputs.js +2 -2
  436. package/dist/slices/video/cards.js +5 -2
  437. package/dist/slices/video/edit-list.js +23 -2
  438. package/dist/slices/video/edit-settings.js +13 -3
  439. package/dist/slices/video/ffmpeg.js +63 -9
  440. package/dist/slices/video/figure-card.js +492 -0
  441. package/dist/slices/video/figure-spans.js +51 -0
  442. package/dist/slices/video/plan.js +125 -13
  443. package/dist/slices/video/run.js +14 -12
  444. package/dist/slices/video/slideshow.js +39 -4
  445. package/dist/slices/voices/attribution.js +69 -0
  446. package/dist/slices/voices/audio-files.js +87 -0
  447. package/dist/slices/voices/audition.js +25 -0
  448. package/dist/slices/voices/cast.js +74 -0
  449. package/dist/slices/voices/grouping.js +59 -0
  450. package/dist/slices/voices/join.js +35 -0
  451. package/dist/slices/voices/languages.js +25 -0
  452. package/dist/slices/voices/model.js +218 -0
  453. package/dist/slices/voices/palette.js +26 -0
  454. package/dist/slices/voices/panel.js +91 -0
  455. package/dist/slices/voices/portraits.js +55 -0
  456. package/dist/slices/voices/script.js +136 -0
  457. package/dist/slices/voices/timing.js +58 -0
  458. package/dist/slices/youtube/answer.js +1 -1
  459. package/dist/slices/youtube/chapters.js +126 -0
  460. package/dist/slices/youtube/edits-repo.js +84 -0
  461. package/dist/slices/youtube/edits.js +89 -0
  462. package/dist/slices/youtube/placeholders.js +98 -0
  463. package/dist/slices/youtube/transcript.js +11 -4
  464. package/dist/tutorials/AI-CLIs.md +146 -0
  465. package/dist/tutorials/Backups.md +121 -0
  466. package/dist/tutorials/Calendar.md +103 -0
  467. package/dist/tutorials/Cast-Library.md +112 -0
  468. package/dist/tutorials/Channels.md +148 -0
  469. package/dist/tutorials/Costs-and-Run-Cost.md +136 -0
  470. package/dist/tutorials/Docker.md +171 -0
  471. package/dist/tutorials/Document-Themes.md +207 -0
  472. package/dist/tutorials/Editing-a-Project.md +204 -0
  473. package/dist/tutorials/FAQ.md +134 -0
  474. package/dist/tutorials/First-Launch-and-Welcome.md +153 -0
  475. package/dist/tutorials/Home-and-Projects.md +174 -0
  476. package/dist/tutorials/Home.md +119 -0
  477. package/dist/tutorials/Install.md +154 -0
  478. package/dist/tutorials/Intros-and-Outros.md +86 -0
  479. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +215 -0
  480. package/dist/tutorials/Library-Overview.md +75 -0
  481. package/dist/tutorials/Models.md +133 -0
  482. package/dist/tutorials/Multiple-Voices.md +161 -0
  483. package/dist/tutorials/Narration-Aliases-and-Glossary.md +129 -0
  484. package/dist/tutorials/Notifications.md +86 -0
  485. package/dist/tutorials/Other-Languages.md +110 -0
  486. package/dist/tutorials/PDF-Documents.md +100 -0
  487. package/dist/tutorials/Play-Images.md +135 -0
  488. package/dist/tutorials/Play-Narration.md +167 -0
  489. package/dist/tutorials/Play-Outputs.md +109 -0
  490. package/dist/tutorials/Play-Overview.md +216 -0
  491. package/dist/tutorials/Play-Title-and-Article.md +135 -0
  492. package/dist/tutorials/Play-Video-and-Style.md +143 -0
  493. package/dist/tutorials/Project-Page.md +228 -0
  494. package/dist/tutorials/Prompts.md +136 -0
  495. package/dist/tutorials/Providers-and-Keys.md +290 -0
  496. package/dist/tutorials/Publishing-to-YouTube.md +135 -0
  497. package/dist/tutorials/Recovery-and-Retries.md +141 -0
  498. package/dist/tutorials/Reviews-and-Checkpoints.md +143 -0
  499. package/dist/tutorials/Schedules.md +154 -0
  500. package/dist/tutorials/Settings-Reference.md +258 -0
  501. package/dist/tutorials/Shorts.md +116 -0
  502. package/dist/tutorials/Start-at-Login.md +126 -0
  503. package/dist/tutorials/Studio-Extension.md +146 -0
  504. package/dist/tutorials/Templates.md +146 -0
  505. package/dist/tutorials/Trash.md +87 -0
  506. package/dist/tutorials/Troubleshooting.md +149 -0
  507. package/dist/tutorials/Uninstalling-and-Moving.md +113 -0
  508. package/dist/tutorials/Updating-and-Patch-Notes.md +120 -0
  509. package/dist/tutorials/Video-Editing.md +135 -0
  510. package/dist/tutorials/Where-Your-Files-Live.md +149 -0
  511. package/dist/tutorials/YouTube-Description.md +132 -0
  512. package/dist/tutorials/Your-First-Short.md +125 -0
  513. package/dist/tutorials/_Footer.md +1 -0
  514. package/dist/tutorials/_Sidebar.md +69 -0
  515. package/dist/updater/service.js +66 -21
  516. package/dist/web/assets/index-DBLSfqA-.css +1 -0
  517. package/dist/web/assets/index-DFdIhmm0.js +307 -0
  518. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  519. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  520. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  521. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  522. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  523. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  524. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  525. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  526. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  527. package/dist/web/assets/{pdf-BLF2i8c2.js → pdf-B9OY1ioX.js} +1 -1
  528. package/dist/web/index.html +2 -2
  529. package/package.json +5 -5
  530. package/dist/edge/docker-launch.js +0 -81
  531. package/dist/edge/docker-projects/activation.js +0 -35
  532. package/dist/edge/docker-projects/claims.js +0 -85
  533. package/dist/edge/docker-projects/committed.js +0 -40
  534. package/dist/edge/docker-projects/engine.js +0 -461
  535. package/dist/edge/docker-projects/install.js +0 -254
  536. package/dist/edge/docker-projects/prune.js +0 -66
  537. package/dist/edge/docker-projects/recover.js +0 -100
  538. package/dist/edge/docker-projects/state.js +0 -225
  539. package/dist/web/assets/index-CktpxlJd.js +0 -171
  540. package/dist/web/assets/index-DB-vOnGU.css +0 -1
  541. package/scripts/docker-run.sh +0 -3
  542. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -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
@@ -14,6 +14,8 @@ providers:
14
14
  maxConcurrent: 3
15
15
  inworld:
16
16
  maxConcurrent: 5
17
+ google-tts:
18
+ maxConcurrent: 3
17
19
  fal:
18
20
  maxConcurrent: 3
19
21
  replicate:
@@ -435,3 +437,39 @@ tts:
435
437
  tts:
436
438
  maxCharacters: 4096
437
439
  streaming: true
440
+ - provider: google-tts
441
+ id: gemini-2.5-flash-preview-tts
442
+ name: Gemini 2.5 Flash TTS (preview)
443
+ source: https://ai.google.dev/gemini-api/docs/speech-generation
444
+ keywords:
445
+ - tts
446
+ pricing:
447
+ note: Billed per text token in and audio token out on your Gemini API key; estimate
448
+ unavailable.
449
+ tts:
450
+ maxCharacters: 4000
451
+ streaming: false
452
+ - provider: google-tts
453
+ id: gemini-2.5-pro-preview-tts
454
+ name: Gemini 2.5 Pro TTS (preview)
455
+ source: https://ai.google.dev/gemini-api/docs/speech-generation
456
+ keywords:
457
+ - tts
458
+ pricing:
459
+ note: Billed per text token in and audio token out on your Gemini API key; estimate
460
+ unavailable.
461
+ tts:
462
+ maxCharacters: 4000
463
+ streaming: false
464
+ - provider: google-tts
465
+ id: gemini-3.1-flash-tts-preview
466
+ name: Gemini 3.1 Flash TTS (preview)
467
+ source: https://ai.google.dev/gemini-api/docs/speech-generation
468
+ keywords:
469
+ - tts
470
+ pricing:
471
+ note: Billed per text token in and audio token out on your Gemini API key; estimate
472
+ unavailable.
473
+ tts:
474
+ maxCharacters: 4000
475
+ streaming: false
@@ -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,6 +1,6 @@
1
1
  import { thinkingModes } from "../kernel/ports/llm.js";
2
2
  import { providerError } from "../kernel/ports/model.js";
3
- import { isLocalCliProvider } from "../slices/settings/model.js";
3
+ import { isLocalCliProvider, systemVoiceProvider } from "../slices/settings/model.js";
4
4
  import { referenceRefusal, takesReferenceImage } from "./schema.js";
5
5
  export function curateRegistry(registry, catalogue) {
6
6
  const requireModel = (provider, family, id) => {
@@ -9,7 +9,7 @@ export function curateRegistry(registry, catalogue) {
9
9
  if (!model)
10
10
  throw providerError({
11
11
  kind: "unsupported",
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 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.`,
13
13
  });
14
14
  return model;
15
15
  };
@@ -25,7 +25,7 @@ export function curateRegistry(registry, catalogue) {
25
25
  if (request.model.trim() === "")
26
26
  throw providerError({
27
27
  kind: "unsupported",
28
- 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.",
29
29
  });
30
30
  let models;
31
31
  try {
@@ -39,19 +39,19 @@ export function curateRegistry(registry, catalogue) {
39
39
  if (models !== undefined && selected === undefined)
40
40
  throw providerError({
41
41
  kind: "unsupported",
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 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.",
43
43
  });
44
44
  if (request.thinking !== undefined &&
45
45
  selected !== undefined &&
46
46
  !selected.thinkingModes?.includes(request.thinking))
47
47
  throw providerError({
48
48
  kind: "unsupported",
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 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.",
50
50
  });
51
51
  if (request.webSearch && !port.capabilities.webSearch)
52
52
  throw providerError({
53
53
  kind: "unsupported",
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 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.",
55
55
  });
56
56
  const thinkingConfig = id === "codex" && request.thinking !== undefined
57
57
  ? { effort: request.thinking === "off" ? "none" : request.thinking }
@@ -80,13 +80,13 @@ export function curateRegistry(registry, catalogue) {
80
80
  if (request.webSearch && !model.llm.webSearch)
81
81
  throw providerError({
82
82
  kind: "unsupported",
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 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.",
84
84
  });
85
85
  const thinkingConfig = request.thinking === undefined ? undefined : model.llm.thinking?.[request.thinking];
86
86
  if (request.thinking !== undefined && !thinkingConfig)
87
87
  throw providerError({
88
88
  kind: "unsupported",
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 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.",
90
90
  });
91
91
  yield* port.complete({
92
92
  ...request,
@@ -107,14 +107,14 @@ export function curateRegistry(registry, catalogue) {
107
107
  throw providerError({
108
108
  kind: "unsupported",
109
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 Resume."
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 Resume.",
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
112
  });
113
113
  if (request.thinking !== undefined &&
114
114
  !selected.thinkingModes?.includes(request.thinking))
115
115
  throw providerError({
116
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 Resume.",
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.",
118
118
  });
119
119
  return port.generate(request);
120
120
  },
@@ -128,12 +128,12 @@ export function curateRegistry(registry, catalogue) {
128
128
  if (!("image" in model) || !model.image.aspectRatios.includes(request.aspect))
129
129
  throw providerError({
130
130
  kind: "unsupported",
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 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
132
  });
133
133
  if (request.thinking !== undefined)
134
134
  throw providerError({
135
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 Resume.",
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
137
  });
138
138
  if (request.reference !== undefined && !takesReferenceImage(model))
139
139
  throw providerError({
@@ -146,6 +146,9 @@ export function curateRegistry(registry, catalogue) {
146
146
  },
147
147
  tts: (id) => {
148
148
  const port = registry.tts(id);
149
+ // The system voice's models are the speech programs on this computer, not list rows.
150
+ if (id === systemVoiceProvider)
151
+ return port;
149
152
  return {
150
153
  ...port,
151
154
  models: async () => catalogue.models(id, "tts"),
@@ -159,7 +162,7 @@ export function curateRegistry(registry, catalogue) {
159
162
  if (request.text.length > model.tts.maxCharacters)
160
163
  throw providerError({
161
164
  kind: "unsupported",
162
- 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.`,
165
+ 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.`,
163
166
  });
164
167
  return port.synthesize({ ...request, model: model.id });
165
168
  },
@@ -14,6 +14,8 @@ const base = z.object({
14
14
  .object({
15
15
  inputPerMillionTokens: money.optional(),
16
16
  outputPerMillionTokens: money.optional(),
17
+ // Input read from the provider's prompt cache. Absent, cached input is priced as input.
18
+ cachedInputPerMillionTokens: money.optional(),
17
19
  perMillionCharacters: money.optional(),
18
20
  perImage: money.optional(),
19
21
  perMinute: money.optional(),
@@ -1,9 +1,14 @@
1
1
  import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { parse } from "yaml";
3
+ import { parse, stringify } from "yaml";
4
4
  import { isLocalCliProvider } from "../slices/settings/model.js";
5
+ import { applyOpenRouterListing, catalogueFamilies, combineChanges, mergeCatalogue, modelKey, noChanges, } from "./merge.js";
5
6
  import { catalogueSchema, isVideoModel } from "./schema.js";
6
7
  export const catalogueSource = "https://raw.githubusercontent.com/GentBajko/slopify/main/packages/app/src/assets/models.yaml";
8
+ // OpenRouter's public model list: live ids and per-token prices, no key needed.
9
+ export const openRouterModelsSource = "https://openrouter.ai/api/v1/models";
10
+ // How often the automatic check runs; it also runs once at every start.
11
+ export const catalogueCheckIntervalMs = 24 * 60 * 60 * 1000;
7
12
  export function parseCatalogue(text) {
8
13
  if (Buffer.byteLength(text) > 1024 * 1024)
9
14
  throw new Error("Model catalogue exceeds 1 MB");
@@ -26,6 +31,9 @@ export function createCatalogueStore(deps) {
26
31
  let stamp = "";
27
32
  let warning = null;
28
33
  let refreshing;
34
+ let syncing;
35
+ const syncPath = join(deps.dataDir, "models-sync.json");
36
+ const now = deps.now ?? (() => new Date());
29
37
  function read() {
30
38
  try {
31
39
  const file = statSync(path);
@@ -40,12 +48,12 @@ export function createCatalogueStore(deps) {
40
48
  }
41
49
  catch {
42
50
  warning =
43
- "Your models.yaml file is missing or has a mistake, so Slopify is still using the last working model list. Fix the file, or use Refresh published catalogue in Settings → Models to replace it.";
51
+ "Your models.yaml file is missing or has a mistake, so Slopify is still using the last working model list. Fix the file, or use Replace with published file in Settings → Models.";
44
52
  }
45
53
  return current;
46
54
  }
47
- async function refresh() {
48
- const response = await deps.fetch(catalogueSource, {
55
+ async function download(url, maxBytes) {
56
+ const response = await deps.fetch(url, {
49
57
  signal: AbortSignal.timeout(15000),
50
58
  redirect: "error",
51
59
  });
@@ -62,8 +70,8 @@ export function createCatalogueStore(deps) {
62
70
  if (next.done)
63
71
  break;
64
72
  size += next.value.byteLength;
65
- if (size > 1024 * 1024)
66
- throw new Error("Catalogue exceeds 1 MB");
73
+ if (size > maxBytes)
74
+ throw new Error("Download is too large");
67
75
  chunks.push(next.value);
68
76
  }
69
77
  }
@@ -71,7 +79,10 @@ export function createCatalogueStore(deps) {
71
79
  await reader.cancel().catch(() => { });
72
80
  reader.releaseLock();
73
81
  }
74
- const text = Buffer.concat(chunks).toString("utf8");
82
+ return Buffer.concat(chunks).toString("utf8");
83
+ }
84
+ async function refresh() {
85
+ const text = await download(catalogueSource, 1024 * 1024);
75
86
  const next = parseCatalogue(text);
76
87
  mkdirSync(deps.dataDir, { recursive: true, mode: 0o700 });
77
88
  // A manual refresh replaces the local catalogue; retain the previous text for recovery.
@@ -84,10 +95,91 @@ export function createCatalogueStore(deps) {
84
95
  warning = null;
85
96
  read();
86
97
  }
98
+ function readSync() {
99
+ try {
100
+ const parsed = JSON.parse(readFileSync(syncPath, "utf8"));
101
+ if (isSyncFile(parsed))
102
+ return parsed;
103
+ }
104
+ catch {
105
+ // Absent or damaged: the first check starts from the bundled list below.
106
+ }
107
+ return { checkedAt: null, known: null, changes: noChanges, warning: null };
108
+ }
109
+ function writeSync(value) {
110
+ writeFileSync(`${syncPath}.next`, JSON.stringify(value), { mode: 0o600 });
111
+ renameSync(`${syncPath}.next`, syncPath);
112
+ }
113
+ async function openRouterListing() {
114
+ try {
115
+ return parseOpenRouterListing(JSON.parse(await download(openRouterModelsSource, 16 * 1024 * 1024)));
116
+ }
117
+ catch {
118
+ // Optional: the published catalogue still carries OpenRouter's curated prices.
119
+ return [];
120
+ }
121
+ }
122
+ async function sync() {
123
+ const saved = readSync();
124
+ const local = read();
125
+ if (warning !== null) {
126
+ // Never overwrite a file the user is in the middle of fixing.
127
+ writeSync({ ...saved, warning });
128
+ return { checkedAt: saved.checkedAt, changes: saved.changes, warning };
129
+ }
130
+ let published;
131
+ try {
132
+ published = parseCatalogue(await download(catalogueSource, 1024 * 1024));
133
+ }
134
+ catch {
135
+ const failed = "Slopify could not download the latest model list, so it is still using the one it has. Check your internet connection, then choose Check now in Settings → Models.";
136
+ writeSync({ ...saved, warning: failed });
137
+ return { checkedAt: saved.checkedAt, changes: saved.changes, warning: failed };
138
+ }
139
+ // Before the first check the models shipped with this version count as published, so a
140
+ // model the user added to the file is never mistaken for a retired one.
141
+ const known = new Set(saved.known ?? keysOf(parseCatalogue(bundled)));
142
+ const merged = mergeCatalogue(local, published, known);
143
+ const live = applyOpenRouterListing(merged.catalogue, await openRouterListing());
144
+ const changes = combineChanges(merged.changes, live.changes);
145
+ const text = `${syncedHeader}${stringify(live.catalogue, { aliasDuplicateObjects: false })}`;
146
+ const next = parseCatalogue(text);
147
+ if (JSON.stringify(next) !== JSON.stringify(local)) {
148
+ if (existsSync(path))
149
+ writeFileSync(`${path}.previous`, readFileSync(path), { mode: 0o600 });
150
+ writeFileSync(`${path}.next`, text, { mode: 0o600 });
151
+ renameSync(`${path}.next`, path);
152
+ current = next;
153
+ stamp = "";
154
+ read();
155
+ }
156
+ const status = { checkedAt: now().toISOString(), changes, warning: null };
157
+ writeSync({ ...status, known: [...merged.known].sort() });
158
+ return status;
159
+ }
87
160
  return {
161
+ sync: () => {
162
+ syncing ??= sync().finally(() => {
163
+ syncing = undefined;
164
+ });
165
+ return syncing;
166
+ },
167
+ syncDue: (at) => {
168
+ const last = readSync().checkedAt;
169
+ return last === null || at - Date.parse(last) >= catalogueCheckIntervalMs;
170
+ },
88
171
  read,
89
172
  models: (provider, family) => read()[family].filter((m) => m.provider === provider && m.enabled && !m.deprecated && !isVideoModel(m)),
90
- status: () => ({ updatedAt: read().updatedAt, path, warning, source: catalogueSource }),
173
+ status: () => {
174
+ const saved = readSync();
175
+ return {
176
+ updatedAt: read().updatedAt,
177
+ path,
178
+ warning,
179
+ source: catalogueSource,
180
+ sync: { checkedAt: saved.checkedAt, changes: saved.changes, warning: saved.warning },
181
+ };
182
+ },
91
183
  refresh: () => {
92
184
  refreshing ??= refresh().finally(() => {
93
185
  refreshing = undefined;
@@ -96,3 +188,50 @@ export function createCatalogueStore(deps) {
96
188
  },
97
189
  };
98
190
  }
191
+ const syncedHeader = "# Slopify's model list, kept up to date automatically from the published catalogue.\n" +
192
+ "# Your edits are kept; retired models stay listed with deprecated: true.\n";
193
+ function isSyncFile(value) {
194
+ if (typeof value !== "object" || value === null)
195
+ return false;
196
+ const row = value;
197
+ const changes = row.changes;
198
+ return ((row.checkedAt === null || typeof row.checkedAt === "string") &&
199
+ (row.known === null || Array.isArray(row.known)) &&
200
+ (row.warning === null || typeof row.warning === "string") &&
201
+ typeof changes === "object" &&
202
+ changes !== null &&
203
+ Array.isArray(changes.added) &&
204
+ Array.isArray(changes.retired) &&
205
+ Array.isArray(changes.priced));
206
+ }
207
+ function keysOf(catalogue) {
208
+ return catalogueFamilies.flatMap((family) => catalogue[family].map((row) => modelKey(family, row.provider, row.id)));
209
+ }
210
+ // `{ data: [{ id, pricing: { prompt, completion } }] }`, prices in USD per token as strings.
211
+ // Rows that do not read cleanly are skipped; a negative price means "varies" and is ignored.
212
+ export function parseOpenRouterListing(value) {
213
+ if (typeof value !== "object" || value === null)
214
+ return [];
215
+ const data = value.data;
216
+ if (!Array.isArray(data))
217
+ return [];
218
+ const price = (raw) => {
219
+ const number = typeof raw === "string" || typeof raw === "number" ? Number(raw) : Number.NaN;
220
+ return Number.isFinite(number) && number >= 0 ? number : undefined;
221
+ };
222
+ const rows = [];
223
+ for (const row of data) {
224
+ if (typeof row !== "object" || row === null)
225
+ continue;
226
+ const { id, pricing } = row;
227
+ if (typeof id !== "string" || id === "")
228
+ continue;
229
+ const prices = typeof pricing === "object" && pricing !== null ? pricing : {};
230
+ rows.push({
231
+ id,
232
+ promptPerToken: price(prices.prompt),
233
+ completionPerToken: price(prices.completion),
234
+ });
235
+ }
236
+ return rows;
237
+ }