@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
package/dist/main.js CHANGED
@@ -11,13 +11,15 @@ import { createHostCliClient } from "./adapters/host-cli/index.js";
11
11
  import { nodeRunCli } from "./adapters/llm/run-cli.js";
12
12
  import { curateRegistry } from "./catalog/registry.js";
13
13
  import { createCatalogueStore } from "./catalog/store.js";
14
- import { dockerActivationCommitted, dockerFolderConfiguration, } from "./edge/docker-projects/activation.js";
14
+ import { createAutostart } from "./edge/autostart/index.js";
15
+ import { dockerActivationCommitted, dockerFilesLayout, dockerFolderConfiguration, } from "./edge/docker-install/activation.js";
15
16
  import { createHub, observedHub } from "./edge/events/hub.js";
16
17
  import { currentProjectEvent } from "./edge/events/visibility.js";
17
18
  import { createApp } from "./edge/http/app.js";
18
19
  import { createMutationLifecycle, drainMutationsWithDeadline } from "./edge/http/mutations.js";
19
20
  import { limitRequestTimes } from "./edge/http/timeouts.js";
20
21
  import { openFolder } from "./edge/open-folder.js";
22
+ import { readHostLogin } from "./host-cli/status.js";
21
23
  import { createAudioPreviewStore } from "./kernel/audio-preview.js";
22
24
  import { systemClock } from "./kernel/clock.js";
23
25
  import { openDb } from "./kernel/db/index.js";
@@ -27,38 +29,58 @@ import { causedBy } from "./kernel/errors.js";
27
29
  import { ulidIds } from "./kernel/ids.js";
28
30
  import { acquireInstanceLock } from "./kernel/lock.js";
29
31
  import { openLog } from "./kernel/log.js";
30
- import { ensureDirs, layout, subtitleModelDir } from "./kernel/paths.js";
32
+ import { ensureDataDirs, ensureDirs, layout, repoint, subtitleModelDir } from "./kernel/paths.js";
31
33
  import { stageKinds } from "./kernel/pipeline.js";
32
34
  import { sqliteAttempts } from "./kernel/runner/attempt-repo.js";
35
+ import { auditionVoice } from "./kernel/runner/audition.js";
33
36
  import { createCheckpointAuthority, } from "./kernel/runner/checkpoint-authority.js";
34
37
  import { createRunner } from "./kernel/runner/index.js";
35
38
  import { stageProviders } from "./kernel/runner/providers.js";
36
39
  import { createProviderQueue } from "./kernel/runner/queue.js";
40
+ import { standaloneImage, standaloneLlm } from "./kernel/runner/standalone.js";
37
41
  import { readVersion } from "./kernel/version.js";
38
42
  import { modelSources } from "./model-catalog.js";
39
- import { projectById, projectPaused } from "./slices/admission/repo.js";
43
+ import { projectById, projectPaused, projectTrashed } from "./slices/admission/repo.js";
40
44
  import { createBackupService } from "./slices/backups/service.js";
41
45
  import { pumpQueue, queueWaiting } from "./slices/batch/index.js";
46
+ import { settleInterruptedCastImages } from "./slices/channels/cast-images.js";
42
47
  import { approveCheckpoint } from "./slices/checkpoints/index.js";
43
48
  import { checkpointDecisionForWork, recoverCheckpointWork, settleReleasedCheckpoints, } from "./slices/checkpoints/recovery.js";
49
+ import { createEpisodeMemoryWatcher, } from "./slices/episodes/summarize.js";
44
50
  import { resolveFont } from "./slices/fonts/index.js";
51
+ import { decodePeaks } from "./slices/narration/peaks.js";
45
52
  import { createRunNotifier } from "./slices/notifications/notifier.js";
53
+ import { projectLink } from "./slices/notifications/rules.js";
46
54
  import { createNotificationSender } from "./slices/notifications/send.js";
47
55
  import { readNotificationUrl } from "./slices/notifications/settings.js";
56
+ import { seedSamples } from "./slices/onboarding/sample.js";
48
57
  import { templateById } from "./slices/project-templates/repo.js";
58
+ import { recoverProject } from "./slices/rebuild/recovery.js";
49
59
  import { claimWork, finishWork, maySubmit } from "./slices/rebuild/repo.js";
60
+ import { waitToRetry, wakeRetries } from "./slices/rebuild/retry.js";
61
+ import { createReviewRedos, reviewHold } from "./slices/rebuild/review-redo.js";
50
62
  import { materializeAdmittedWork } from "./slices/rebuild/runtime-materialize.js";
51
63
  import { runRevisionInvocation } from "./slices/rebuild/runtime-run.js";
52
64
  import { executionStages, executionStandings, invocationReady, projectStandings, recordWorkProgress, } from "./slices/rebuild/runtime-store.js";
65
+ import { currentRevisionId } from "./slices/revisions/repo.js";
66
+ import { createLimitGate, resumeAfterRestart } from "./slices/run-cost/limits.js";
67
+ import { createStandaloneMeter, createUsageMeter } from "./slices/run-cost/meter.js";
53
68
  import { settleTerminalScheduleRuns } from "./slices/schedules/repo.js";
54
69
  import { createScheduleRunner } from "./slices/schedules/scheduler.js";
55
70
  import { nodeCliProbe } from "./slices/settings/cli-status.js";
56
71
  import { isLocalCliProvider, localCliConcurrency } from "./slices/settings/model.js";
57
72
  import { providerStatuses } from "./slices/settings/readiness.js";
73
+ import { busyProjects } from "./slices/storage/backup-export.js";
74
+ import { documentsDir, nodeDocumentsHost } from "./slices/storage/documents.js";
75
+ import { createFilesService, ensureFilesFolders, filesLayoutOf, settleFilesLocation, } from "./slices/storage/files-location.js";
58
76
  import { reconcileStorage } from "./slices/storage/reconcile.js";
77
+ import { previewPictures } from "./slices/style-preview/images.js";
78
+ import { ffmpegStylePreview } from "./slices/style-preview/render.js";
79
+ import { createStylePreviews, stylePreviewDir } from "./slices/style-preview/service.js";
59
80
  import { collectorEndpoint, httpPostEvents } from "./slices/telemetry/collector-client.js";
60
81
  import { createFlusher } from "./slices/telemetry/flush.js";
61
82
  import { record } from "./slices/telemetry/record.js";
83
+ import { createTrashPurge } from "./slices/trash/service.js";
62
84
  import { probeDurationMs } from "./slices/video/ffmpeg.js";
63
85
  import { watchActivation } from "./updater/candidate.js";
64
86
  import { launchUpdate } from "./updater/install.js";
@@ -125,11 +147,12 @@ export async function boot(config, options = {}) {
125
147
  if (dockerState !== undefined &&
126
148
  (process.env.SLOPIFY_CONTAINER !== "1" ||
127
149
  dockerState !== "/opt/slopify-install/activation.json"))
128
- throw new Error("This container was started with Slopify's Docker settings in the wrong place (SLOPIFY_DOCKER_INSTALL_STATE). Start it with the launcher instead: npx @gentbajko/slopify --docker");
150
+ throw new Error("This container was started with Slopify's Docker settings in the wrong place (SLOPIFY_DOCKER_INSTALL_STATE must be /opt/slopify-install/activation.json). Start it with Slopify's compose.yaml, or install it with npx @gentbajko/slopify --docker");
129
151
  const candidateToken = process.env.SLOPIFY_UPDATE_TOKEN ?? "";
130
152
  const pendingActivation = isUpdateToken(candidateToken) && process.env.SLOPIFY_UPDATE_PENDING === "1";
131
- ensureDirs(paths, { mode: 0o700 });
153
+ ensureDataDirs(paths, { mode: 0o700 });
132
154
  const lock = acquireInstanceLock(paths.lock);
155
+ const freshInstall = !existsSync(paths.db);
133
156
  let db;
134
157
  try {
135
158
  const ffmpeg = await prepareFfmpeg({
@@ -140,10 +163,29 @@ export async function boot(config, options = {}) {
140
163
  });
141
164
  db = openDb(paths.db);
142
165
  migrate(db, clock);
166
+ // Where the projects, backups and exports live. A container's are the installer's mounts;
167
+ // a native install decides once and remembers (slices/storage/files-location.ts).
168
+ const container = process.env.SLOPIFY_CONTAINER === "1";
169
+ const files = container
170
+ ? dockerFilesLayout(process.env, paths.dataDir)
171
+ : filesLayoutOf(await settleFilesLocation({
172
+ db,
173
+ dataDir: paths.dataDir,
174
+ fresh: freshInstall,
175
+ documents: options.filesInDocuments === true
176
+ ? () => documentsDir(nodeDocumentsHost())
177
+ : undefined,
178
+ }), paths.dataDir);
179
+ repoint(paths, files);
180
+ if (files.exports === null)
181
+ ensureDirs(paths, { mode: 0o700 });
182
+ else
183
+ await ensureFilesFolders(files);
143
184
  const runtimeDb = db;
144
185
  const interrupted = markInterruptedStages(db, clock);
145
186
  recoverCheckpointWork(db);
146
187
  const settledSchedules = settleTerminalScheduleRuns(db, clock.now().toISOString());
188
+ settleInterruptedCastImages({ db });
147
189
  // The updater can restore the database after a failed candidate boot, but it cannot
148
190
  // restore files deleted by reconciliation. Leave the filesystem untouched until the
149
191
  // candidate's committed activation pointer has been verified.
@@ -164,14 +206,22 @@ export async function boot(config, options = {}) {
164
206
  ? undefined
165
207
  : { title: project.title, makesVideo: project.config.sources.video !== "off" };
166
208
  },
209
+ // Read when a notification goes out, by which time the server is listening.
210
+ link: (projectId) => projectLink(config.host, listeningPort, projectId),
167
211
  send: sendNotification,
168
212
  log,
169
213
  });
214
+ // Episode memory summarises a project that has just finished; wired once the providers
215
+ // exist, below.
216
+ let episodes;
170
217
  const hub = observedHub(createHub({
171
218
  ids,
172
219
  log,
173
220
  acceptEvent: (event) => currentProjectEvent(eventDb, event),
174
- }), notifier.observe);
221
+ }), (event) => {
222
+ notifier.observe(event);
223
+ episodes?.observe(event);
224
+ });
175
225
  const version = readVersion();
176
226
  const telemetry = { db, ids, clock, log, appVersion: version };
177
227
  const flusher = createFlusher({
@@ -191,9 +241,28 @@ export async function boot(config, options = {}) {
191
241
  clock,
192
242
  probe: nodeCliProbe,
193
243
  hostCli,
244
+ ffmpeg,
194
245
  }), catalogue);
246
+ // Calls that belong to no project (topic generation, episode summaries, cast pictures)
247
+ // still go through the attempt wrapper, and are metered against their schedule or channel.
248
+ const standalone = {
249
+ registry,
250
+ clock,
251
+ log,
252
+ meter: createStandaloneMeter({ db, ids, clock, catalogue: () => catalogue.read() }),
253
+ };
254
+ episodes = createEpisodeMemoryWatcher({
255
+ db,
256
+ paths,
257
+ clock,
258
+ log,
259
+ uuid: randomUUID,
260
+ llm: (call) => standaloneLlm(standalone, call),
261
+ });
195
262
  const audioPreviews = createAudioPreviewStore();
263
+ const reviewRedos = createReviewRedos();
196
264
  const runner = wireRunner({
265
+ onFinished: (work) => reviewRedos.kick(work.projectId),
197
266
  db,
198
267
  paths,
199
268
  clock,
@@ -257,7 +326,7 @@ export async function boot(config, options = {}) {
257
326
  ? Promise.resolve(version)
258
327
  : publishedVersion(globalThis.fetch),
259
328
  unsupported: () => process.env.SLOPIFY_DISABLE_UPDATES === "1"
260
- ? "This container is updated by pulling a new image and recreating it."
329
+ ? "Slopify in Docker is updated from the terminal: npx @gentbajko/slopify@latest update. It waits for running work, keeps a recovery copy of your data and puts the previous version back if the new one doesn't start."
261
330
  : !existsSync(oldEntry) || !existsSync(workerEntry)
262
331
  ? "Run Slopify from its installed package to use in-app updates."
263
332
  : npm === undefined
@@ -269,6 +338,18 @@ export async function boot(config, options = {}) {
269
338
  .prepare("SELECT id FROM projects")
270
339
  .all()
271
340
  .some((row) => typeof row.id === "string" && runner.hasInflight?.(row.id) === true),
341
+ busyWith: () => {
342
+ const running = updateDb
343
+ .prepare("SELECT p.title FROM stages s JOIN projects p ON p.id=s.project_id WHERE s.state='running' LIMIT 1")
344
+ .get();
345
+ if (typeof running?.title === "string")
346
+ return running.title;
347
+ const inflight = updateDb
348
+ .prepare("SELECT id,title FROM projects")
349
+ .all()
350
+ .find((row) => typeof row.id === "string" && runner.hasInflight?.(row.id) === true);
351
+ return typeof inflight?.title === "string" ? inflight.title : undefined;
352
+ },
272
353
  report: (message) => log.write("warn", "update", { detail: message }),
273
354
  install: async (next, restarting) => {
274
355
  if (npm === undefined)
@@ -302,6 +383,21 @@ export async function boot(config, options = {}) {
302
383
  modelsFor: modelSources(registry).modelsFor,
303
384
  emit: (projectId, event) => hub.emit(projectId, event),
304
385
  };
386
+ // Redos a review asked for before the last shutdown start now.
387
+ reviewRedos.bind(rebuild);
388
+ reviewRedos.kick();
389
+ // A stage that was waiting for a CLI's plan limits when the app stopped carries on waiting.
390
+ void resumeAfterRestart(db, async (projectId) => {
391
+ const baseRevisionId = currentRevisionId(runtimeDb, projectId);
392
+ if (baseRevisionId === undefined)
393
+ return false;
394
+ const result = await recoverProject(rebuild, projectId, {
395
+ baseRevisionId,
396
+ idempotencyKey: randomUUID(),
397
+ action: { kind: "resume" },
398
+ });
399
+ return result.ok;
400
+ }, log);
305
401
  const draftDeps = {
306
402
  db,
307
403
  paths,
@@ -321,11 +417,20 @@ export async function boot(config, options = {}) {
321
417
  flusher.soon();
322
418
  },
323
419
  };
324
- const scheduleDeps = {
420
+ const scheduleRunnerDeps = {
325
421
  ...draftDeps,
326
422
  template: (id, templateVersion) => templateById(runtimeDb, id, templateVersion),
423
+ topicLlm: (call) => standaloneLlm(standalone, call),
424
+ topicsWaiting: (event) => {
425
+ hub.emitGlobal(event);
426
+ notifier.observeTopics(event);
427
+ },
428
+ };
429
+ const scheduleRunner = createScheduleRunner(scheduleRunnerDeps);
430
+ const scheduleDeps = {
431
+ ...scheduleRunnerDeps,
432
+ requestTopics: scheduleRunner.requestTopics,
327
433
  };
328
- const scheduleRunner = createScheduleRunner(scheduleDeps);
329
434
  scheduleRunner.recover(clock.now());
330
435
  const scheduleTicks = createScheduleTickLifecycle({
331
436
  beginMutation: updater.beginMutation,
@@ -336,6 +441,23 @@ export async function boot(config, options = {}) {
336
441
  });
337
442
  const mutations = createMutationLifecycle();
338
443
  const folderConfiguration = await dockerFolderConfiguration(process.env, paths.projects);
444
+ const filesService = createFilesService({
445
+ db,
446
+ paths,
447
+ documents: () => documentsDir(nodeDocumentsHost()),
448
+ busy: () => busyProjects({ db: runtimeDb, hasInflight: runner.hasInflight }),
449
+ openFolder,
450
+ beginMutation: updater.beginMutation,
451
+ ...(container
452
+ ? {
453
+ docker: {
454
+ hostProjects: folderConfiguration.hostProjects,
455
+ hostBackups: folderConfiguration.hostBackups,
456
+ ...(hostCli === undefined ? {} : { openOnHost: hostCli.openFolder }),
457
+ },
458
+ }
459
+ : {}),
460
+ });
339
461
  const backups = createBackupService({
340
462
  db,
341
463
  paths,
@@ -348,12 +470,34 @@ export async function boot(config, options = {}) {
348
470
  bootedAt: clock.now(),
349
471
  beginMutation: updater.beginMutation,
350
472
  });
473
+ // Before the server answers, so the first page already lists the samples. A failure costs
474
+ // only the samples: the log says why and Settings → Restore samples tries again.
475
+ if (options.seedSample === true && !pendingActivation)
476
+ await seedSamples({ db, paths, clock, ids, log, appVersion: version }).catch((error) => {
477
+ log.write("warn", "sample.seed", { detail: causedBy(error) });
478
+ });
479
+ const autostart = await createAutostart({
480
+ db,
481
+ dataDir: paths.dataDir,
482
+ logs: paths.logs,
483
+ port: config.port,
484
+ host: config.host,
485
+ version,
486
+ env: process.env,
487
+ });
488
+ if (options.refreshAutostart === true)
489
+ void autostart.refresh().catch((error) => {
490
+ log.write("warn", "autostart.refresh", { detail: causedBy(error) });
491
+ });
351
492
  const app = createApp({
493
+ autostart,
352
494
  rebuild,
353
495
  drafts: draftDeps,
354
496
  schedules: scheduleDeps,
355
497
  backups,
498
+ files: filesService,
356
499
  ...(rebuild.measureAudio === undefined ? {} : { measureAudio: rebuild.measureAudio }),
500
+ decodePeaks: (path, signal) => decodePeaks(ffmpeg, path, signal),
357
501
  openFolder,
358
502
  folderLocation: {
359
503
  ...folderConfiguration,
@@ -368,16 +512,39 @@ export async function boot(config, options = {}) {
368
512
  mutations,
369
513
  sendNotification,
370
514
  audioPreviews,
515
+ stylePreviews: createStylePreviews({
516
+ dir: stylePreviewDir(paths.dataDir),
517
+ render: ffmpegStylePreview({ ffmpeg, paths, log, dir: stylePreviewDir(paths.dataDir) }),
518
+ pictures: previewPictures({ db, paths }),
519
+ log,
520
+ }),
371
521
  ...modelSources(registry),
522
+ audition: (call, signal) => auditionVoice({ registry, clock, log }, call, signal),
523
+ voiceLanguages: async (provider, voiceId, signal) => {
524
+ try {
525
+ return await registry.tts(provider).voiceLanguages?.(voiceId, signal);
526
+ }
527
+ catch {
528
+ return undefined;
529
+ }
530
+ },
372
531
  catalogue,
373
532
  clock,
374
533
  ids,
375
534
  log,
376
535
  version,
377
536
  webDist: fileURLToPath(new URL("../dist/web", import.meta.url)),
537
+ extensionDist: fileURLToPath(new URL("../dist/extension", import.meta.url)),
378
538
  flushSoon: flusher.soon,
379
539
  probe: nodeCliProbe,
380
540
  hostCliStatus: hostCli?.status,
541
+ generateCastImage: ({ channelId, ...call }) => standaloneImage(standalone, {
542
+ ...call,
543
+ owner: { kind: "channel", id: channelId },
544
+ purpose: "cast-image",
545
+ }),
546
+ fetch: globalThis.fetch,
547
+ ...(hostCli === undefined ? { cliLogin: readHostLogin } : {}),
381
548
  });
382
549
  const server = await listen(app, config, log);
383
550
  const queueTimer = setInterval(() => {
@@ -397,14 +564,70 @@ export async function boot(config, options = {}) {
397
564
  const scheduleTimer = setInterval(() => {
398
565
  void scheduleTicks.tick();
399
566
  }, 15_000);
567
+ // A step waiting out a rate limit or a timeout runs again once its wait is over; the wait
568
+ // is in the database, so this also picks up the ones a restart left waiting.
569
+ const retryTimer = setInterval(() => {
570
+ const release = updater.beginMutation();
571
+ if (!release)
572
+ return;
573
+ try {
574
+ wakeRetries(updateDb, clock, runner);
575
+ }
576
+ catch (error) {
577
+ log.write("error", "stage.retry", { detail: causedBy(error) });
578
+ }
579
+ finally {
580
+ release();
581
+ }
582
+ }, 5_000);
400
583
  void scheduleTicks.tick();
401
584
  // Once a minute is plenty for a daily slot; the decision itself (slices/backups/schedule)
402
585
  // holds the first run back for a couple of minutes after a start.
586
+ // The model catalogue keeps itself current: once at start, then whenever a day has passed
587
+ // since the last check (looked at hourly, so a laptop that slept catches up).
588
+ const syncModels = () => {
589
+ if (!options.refreshModels || !catalogue.sync)
590
+ return;
591
+ catalogue.sync().then((status) => {
592
+ if (status.warning !== null)
593
+ log.write("warn", "model-catalog.sync", { detail: status.warning });
594
+ }, (error) => log.write("warn", "model-catalog.sync", { detail: causedBy(error) }));
595
+ };
596
+ syncModels();
597
+ const modelTimer = setInterval(() => {
598
+ if (catalogue.syncDue?.(Date.now()) === true)
599
+ syncModels();
600
+ }, 60 * 60_000);
403
601
  const backupTimer = setInterval(() => {
404
602
  backups.tick().catch((error) => {
405
603
  log.write("error", "backups.tick", { detail: causedBy(error) });
406
604
  });
407
605
  }, 60_000);
606
+ // Settings → Trash keeps deleted items 30 days; once a day (looked at hourly, and once at
607
+ // start) whatever is older goes for good, a project's folder with it.
608
+ const trashPurge = createTrashPurge({
609
+ db: updateDb,
610
+ paths,
611
+ clock,
612
+ log,
613
+ hasInflight: runner.hasInflight,
614
+ });
615
+ const purgeTrash = () => {
616
+ const release = updater.beginMutation();
617
+ if (!release)
618
+ return;
619
+ try {
620
+ trashPurge.tick();
621
+ }
622
+ catch (error) {
623
+ log.write("error", "trash.purge", { detail: causedBy(error) });
624
+ }
625
+ finally {
626
+ release();
627
+ }
628
+ };
629
+ purgeTrash();
630
+ const trashTimer = setInterval(purgeTrash, 60 * 60_000);
408
631
  listeningPort = portOf(server) ?? config.port;
409
632
  // Whatever last run left queued goes out at start. Nothing waits for
410
633
  // it, and an unreachable collector costs one refused socket.
@@ -427,11 +650,16 @@ export async function boot(config, options = {}) {
427
650
  shutdown = () => {
428
651
  stopping ??= (async () => {
429
652
  notifier.close();
653
+ episodes?.close();
430
654
  clearInterval(queueTimer);
431
655
  clearInterval(scheduleTimer);
656
+ clearInterval(retryTimer);
432
657
  clearInterval(backupTimer);
658
+ clearInterval(trashTimer);
659
+ clearInterval(modelTimer);
433
660
  const mutationDrain = mutations.stop();
434
661
  const scheduleDrain = scheduleTicks.stop();
662
+ const topicDrain = scheduleRunner.stop();
435
663
  // A backup being written is stopped and its partial file removed, not waited for.
436
664
  const backupDrain = backups.stop();
437
665
  const serverClose = beginServerClose(server);
@@ -443,8 +671,10 @@ export async function boot(config, options = {}) {
443
671
  // database until their response settles; anything racing shutdown receives 503.
444
672
  await drainMutationsWithDeadline(mutationDrain, serverClose.terminate, mutationDrainTimeoutMs);
445
673
  await scheduleDrain;
674
+ await topicDrain;
446
675
  await backupDrain;
447
676
  await runner.abortAll();
677
+ await episodes?.settled();
448
678
  }
449
679
  finally {
450
680
  serverClose.terminate();
@@ -468,7 +698,7 @@ export async function boot(config, options = {}) {
468
698
  };
469
699
  if (pendingActivation)
470
700
  stopActivation = watchActivation(updater, candidateToken, shutdown, (message) => log.write("warn", "update", { detail: message }));
471
- return { paths, url: urlOf(config.host, listeningPort), stop: shutdown };
701
+ return { paths, url: urlOf(config.host, listeningPort), stop: shutdown, autostart };
472
702
  }
473
703
  catch (error) {
474
704
  db?.close();
@@ -476,7 +706,7 @@ export async function boot(config, options = {}) {
476
706
  throw error;
477
707
  }
478
708
  }
479
- export function wireRunner({ audioPreviews, db, paths, clock, ids, log, hub, telemetry, flusher, registry, catalogue, ffmpeg, }) {
709
+ export function wireRunner({ onFinished, audioPreviews, db, paths, clock, ids, log, hub, telemetry, flusher, registry, catalogue, ffmpeg, alignSubtitles: aligner = alignSubtitles, }) {
480
710
  // A stage counts what it did and the queue is flushed after each new event. `record`
481
711
  // swallows its own failures, so this can neither fail a stage nor widen what leaves the
482
712
  // machine - the payload allow-list is checked inside it.
@@ -484,7 +714,17 @@ export function wireRunner({ audioPreviews, db, paths, clock, ids, log, hub, tel
484
714
  record(telemetry, type, counters);
485
715
  flusher.soon();
486
716
  };
487
- const execution = { db, paths, ids, clock, log, ffmpeg, alignSubtitles, audioPreviews, count };
717
+ const execution = {
718
+ db,
719
+ paths,
720
+ ids,
721
+ clock,
722
+ log,
723
+ ffmpeg,
724
+ alignSubtitles: aligner,
725
+ audioPreviews,
726
+ count,
727
+ };
488
728
  // A stage slice is handed the wrapped calls, never the registry: every provider call
489
729
  // it makes is already inside the retry policy (kernel/runner/providers.ts).
490
730
  const providers = {
@@ -492,12 +732,23 @@ export function wireRunner({ audioPreviews, db, paths, clock, ids, log, hub, tel
492
732
  attempts: sqliteAttempts(db, ids),
493
733
  clock,
494
734
  log,
735
+ meter: createUsageMeter({ db, ids, clock, catalogue: () => catalogue.read() }),
736
+ limits: createLimitGate({
737
+ db,
738
+ clock,
739
+ log,
740
+ changed: (projectId) => hub.emit(projectId, { type: "project.updated", projectId }),
741
+ }),
495
742
  queue: createProviderQueue((provider) => isLocalCliProvider(provider)
496
743
  ? localCliConcurrency(provider)
497
744
  : (catalogue.read().providers[provider]?.maxConcurrent ?? 1)),
498
745
  };
499
746
  const checkpoints = createCheckpointAuthority({
500
- decide: (work) => checkpointDecisionForWork(execution, work),
747
+ // A review waiting to send its item back holds the item's dependents like a checkpoint.
748
+ decide: (work) => {
749
+ const review = reviewHold(execution, work);
750
+ return review.kind === "held" ? review : checkpointDecisionForWork(execution, work);
751
+ },
501
752
  approve: (projectId, checkpointId, identity) => approveCheckpoint(db, {
502
753
  ...identity,
503
754
  projectId,
@@ -518,20 +769,30 @@ export function wireRunner({ audioPreviews, db, paths, clock, ids, log, hub, tel
518
769
  },
519
770
  standingsOf: (projectId) => executionStandings(execution, projectId),
520
771
  ready: (work) => invocationReady(execution, work),
521
- paused: (projectId) => projectPaused(db, projectId) || queueWaiting(db, projectId),
772
+ // A project in the trash (Settings → Trash) holds like a paused one until it is restored.
773
+ paused: (projectId) => projectPaused(db, projectId) ||
774
+ queueWaiting(db, projectId) ||
775
+ projectTrashed(db, projectId),
522
776
  claim: (work) => {
523
777
  const claimed = claimWork(db, work);
524
778
  projectStandings(execution, work.projectId);
525
779
  return claimed;
526
780
  },
527
781
  maySubmit: (work, pieceId) => maySubmit(db, work, pieceId),
528
- finish: (work, state, reason) => {
782
+ waitToRetry: (work, fault, reason) => transact(db, () => {
783
+ const at = waitToRetry(execution, work, fault, reason, Math.random);
784
+ if (at !== undefined)
785
+ projectStandings(execution, work.projectId);
786
+ return at;
787
+ }),
788
+ finish: (work, state, reason, kind) => {
529
789
  transact(db, () => {
530
- finishWork(db, work, state, reason);
790
+ finishWork(db, work, state, reason, kind ?? null);
531
791
  materializeAdmittedWork(execution, work.projectId);
532
792
  projectStandings(execution, work.projectId);
533
793
  settleReleasedCheckpoints(execution, work.projectId);
534
794
  });
795
+ onFinished?.(work);
535
796
  },
536
797
  },
537
798
  runs: Object.fromEntries(stageKinds.map((kind) => [
@@ -4,6 +4,7 @@ import { openAiImageModels } from "./adapters/image/openai.js";
4
4
  import { replicateModels } from "./adapters/image/replicate.js";
5
5
  import { cartesiaModels } from "./adapters/tts/cartesia.js";
6
6
  import { elevenLabsModels } from "./adapters/tts/elevenlabs.js";
7
+ import { geminiTtsModels } from "./adapters/tts/gemini.js";
7
8
  import { inworldModels } from "./adapters/tts/inworld.js";
8
9
  import { openAiTtsModels } from "./adapters/tts/openai.js";
9
10
  // Composition keeps provider-specific fallback data out of the HTTP and settings layers.
@@ -13,6 +14,7 @@ export function modelSources(registry) {
13
14
  "openai-tts": openAiTtsModels,
14
15
  cartesia: cartesiaModels,
15
16
  inworld: inworldModels,
17
+ "google-tts": geminiTtsModels,
16
18
  fal: falModels,
17
19
  replicate: replicateModels,
18
20
  "openai-image": openAiImageModels,
@@ -0,0 +1,93 @@
1
+ # Slopify 0.4.2 to 1.0
2
+
3
+ Versions 0.4.3 to 1.0.0, released 9 to 13 September 2026 (0.4.2 shipped on 3 September).
4
+
5
+ This is the stretch where Slopify went from a pipeline that ran once, start to finish, to something you can live with: pause it, change your mind halfway, edit a finished project without paying for everything twice, and leave it running on a schedule. It also picked up free local captions, the Gemini CLI, Inworld voices and an updater. 1.0.0 is the first release where a finished project is never a dead end.
6
+
7
+ ## Highlights
8
+
9
+ - Captions, made on your machine for free: English word timing with a 95 MB speech model, no caption service and no key.
10
+ - Edit any finished project. Slopify keeps every version and remakes only what your change affects.
11
+ - Pause and resume a run, and switch its providers while it is paused.
12
+ - A cost review before anything starts, with the exact inputs, prices and a readiness check.
13
+ - Review checkpoints hold Audio, Images or Video until you approve them, while the rest keeps going.
14
+ - Templates for setups you reuse, and daily, weekly or one-off schedules that run them.
15
+ - Gemini CLI joins Claude Code and Codex as a text provider, using the login you already have.
16
+ - Inworld TTS-2 and TTS-2 Flash voices.
17
+ - Update from inside the app with one click.
18
+
19
+ ## What's new
20
+
21
+ ### Getting started
22
+ - An interactive tutorial walks from API keys to a downloaded video in 18 steps (19 from 0.5.1), highlighting the real controls on each screen. Open it from Tutorial in the navigation or Start tutorial on an empty Projects page.
23
+ - The tutorial remembers where you were across reloads and never starts a run for you.
24
+ - Two ways to install on slopify.stream: `npx @gentbajko/slopify@latest` with nothing to install, or `npm install -g @gentbajko/slopify` and then `slopify`.
25
+
26
+ ### Runs
27
+ - Pause and Resume. Pausing stops active work, keeps every finished piece and survives an app restart.
28
+ - Article is the only required stage. Research, Audio, Images, Thumbnail and Video can each be Off.
29
+ - With Images off and Audio on, the run exports a 48 kHz stereo WAV. With Audio off, it renders a silent MP4 at 5 seconds per image.
30
+ - Images from a saved prompt and prewritten thumbnails start straight away, beside research and writing.
31
+ - Uploaded narration is used as it is.
32
+ - Play is four sections, with a preview of the output shape and caption style next to the controls.
33
+ - Play drafts save to this computer as you type, uploads included, and come back after a restart.
34
+ - Review shows the exact inputs, prices and catalogue date before you start. Nothing runs until Start.
35
+ - Review also checks that every provider, model and voice the run needs is ready.
36
+ - Batches: several title or keyword variants from one setup, run one after another.
37
+ - Provider-aware queues cap how many requests go to each provider at once.
38
+
39
+ ### Editing finished projects
40
+ - Change the settings, article, narration, images or captions of any project, finished or not.
41
+ - Every save is a new version in History. Restore any earlier one.
42
+ - Before a rebuild, Slopify lists the outputs your change affects and what they cost. Work that is unaffected is reused, not paid for again.
43
+ - Each project keeps its own copy of the prompts it was made with, so editing a Library prompt never changes an old project.
44
+ - Edit a single image, a caption line, or the article text by hand.
45
+
46
+ ### Review checkpoints
47
+ - Hold Audio, Images or Video/export for approval. Independent work carries on while one part waits.
48
+ - Approve held work from the project's progress view. Approvals survive restarts and later edits.
49
+
50
+ ### Templates and schedules
51
+ - Save a project's setup as a named template and apply it in Play. Templates keep versions, so an old draft is never changed under you.
52
+ - Schedules run a template once, daily or weekly, in your time zone, with up to 49 keyword variants and an optional spend limit.
53
+ - Each schedule has a run history and Pause, Resume, Cancel and Delete. Missed runs follow a policy you choose, and a run never overlaps the one before it.
54
+
55
+ ### Captions and fonts
56
+ - Free English captions for new and existing narrated projects, timed word by word to the actual speech.
57
+ - SRT, VTT and ASS files, and captions burned into the video.
58
+ - Five caption positions, a font size and a live preview in the real output shape.
59
+ - Bundled Barlow, your installed fonts, or upload a TTF or OTF up to 32 MiB.
60
+ - Changing only the caption style reuses the narration and the WAV instead of making them again.
61
+ - When the narration skips a short passage, captions resync on the following words and add a note beside the caption download saying what was missed.
62
+
63
+ ### Narration
64
+ - Inworld TTS-2 and TTS-2 Flash. TTS-2 takes up to 100,000 characters per job.
65
+ - Split narration by characters as well as by paragraph or word count: 3,000 characters by default, always on a sentence end.
66
+ - Hear narration while it is still being made, and watch the article write itself.
67
+
68
+ ### Providers and models
69
+ - Gemini CLI as a text and research provider, signed in with the login you already have.
70
+ - Set the path to each CLI in Settings when Slopify can't find it on your PATH.
71
+ - Model lists load from the provider or the installed CLI, with a refresh button and space to type a model ID.
72
+ - A curated model catalogue with prices, context sizes and thinking settings, refreshed from GitHub.
73
+
74
+ ### Project page
75
+ - A focused workspace: stage list on the left, the output of the chosen stage beside it, and a direct download of the final file.
76
+ - Overall progress next to each stage's own progress.
77
+ - Open folder next to every download.
78
+
79
+ ### Updates and upkeep
80
+ - An update button checks every 15 minutes and installs on one click. It waits for active work, checks the new version starts, and rolls the database back if it doesn't.
81
+ - Settings exports and imports a backup of your templates, prompts, voices and settings. Provider keys are never included.
82
+ - Disk usage per project, cleanup of files no project uses, and a diagnostics download with no secrets in it.
83
+
84
+ ## Fixes
85
+
86
+ - A missing ffmpeg is downloaded into the app's data folder and checked before any provider work starts.
87
+ - Claude Code no longer applies your personal coding setup to article writing.
88
+ - Google's image errors are read correctly; an image quota of zero fails once with a clear message instead of retrying.
89
+ - Slideshow zoom is faster and travels from 100% to 122.5%.
90
+ - The update indicator no longer sticks on Updating after a check finds nothing new.
91
+ - A stalled or interrupted update recovers its status on the next start.
92
+ - Many fixes to editing and rebuilds: regenerated articles open current, uploaded audio can be replaced by generated narration, and interrupted rebuilds recover their unfinished outputs.
93
+ - Many fixes to Play drafts: unfinished uploads, image counts, empty chunk sizes and discarded drafts are all kept or recovered correctly.