@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,222 @@
1
+ import { z } from "zod";
2
+ import { derive } from "../../kernel/runner/graph.js";
3
+ import { liveProject, projectPaused, stageStandingsByProject } from "../admission/repo.js";
4
+ import { queueEntries } from "../batch/index.js";
5
+ import { sampleIds } from "../onboarding/model.js";
6
+ import { readSampleRecord } from "../onboarding/state.js";
7
+ import { limitWaitsByProject, listingWait } from "../run-cost/panel.js";
8
+ import { uploadedProjects } from "../uploads/repo.js";
9
+ import { nextOccurrence } from "./calendar.js";
10
+ import { scheduleRows } from "./repo.js";
11
+ import { calendarMaxDays, } from "./schema.js";
12
+ import { renderedTitle } from "./topic-list.js";
13
+ // Enough for a run every day of the longest range, with room for a queue.
14
+ const occurrencesMax = 1000;
15
+ const dayMs = 24 * 60 * 60_000;
16
+ // Everything the coming weeks hold between `from` and `to`: every run a live schedule will
17
+ // make, with the topic it will use; every project running in the range or finished in it; and
18
+ // every batch item still waiting its turn.
19
+ export function calendarRange(deps, from, to) {
20
+ if (Number.isNaN(from.valueOf()) ||
21
+ Number.isNaN(to.valueOf()) ||
22
+ to <= from ||
23
+ to.valueOf() - from.valueOf() > calendarMaxDays * dayMs)
24
+ return { ok: false, reason: "invalid-input" };
25
+ const now = deps.clock.now();
26
+ const runs = scheduleRows(deps.db)
27
+ .flatMap((schedule) => upcomingRuns(deps, schedule, now, from, to))
28
+ .sort((a, b) => a.at.localeCompare(b.at) || a.scheduleName.localeCompare(b.scheduleName));
29
+ return {
30
+ ok: true,
31
+ value: {
32
+ from: from.toISOString(),
33
+ to: to.toISOString(),
34
+ runs,
35
+ projects: projectsIn(deps, from, to),
36
+ queued: queued(deps),
37
+ },
38
+ };
39
+ }
40
+ // Run k from now uses the k-th queued topic. A schedule that does not find its own topics
41
+ // completes when its queue empties, so its runs stop there; one with no queue at all runs the
42
+ // template as saved every time.
43
+ function upcomingRuns(deps, schedule, now, from, to) {
44
+ if (schedule.deletedAt !== null)
45
+ return [];
46
+ if (schedule.status !== "active" && schedule.status !== "paused")
47
+ return [];
48
+ const paused = schedule.status === "paused";
49
+ // A paused schedule resumes from the next occurrence after the moment Resume is pressed.
50
+ let at = schedule.nextRunAt === null
51
+ ? null
52
+ : paused
53
+ ? nextOccurrence(schedule.cadence, schedule.timezone, now)
54
+ : new Date(schedule.nextRunAt);
55
+ const template = deps.template(schedule.templateId, schedule.templateVersion);
56
+ const templateName = template?.name ?? null;
57
+ const form = template?.document.form;
58
+ const { items } = schedule;
59
+ const generation = schedule.topicGeneration.mode;
60
+ const held = generation === "hold" ? schedule.topics.held : 0;
61
+ const runs = [];
62
+ for (let k = 0; at !== null && at < to && k < occurrencesMax; k++) {
63
+ if (generation === "off" && items.length > 0 && k >= items.length)
64
+ break;
65
+ const queuedTopic = items[k];
66
+ // Known for a queued topic, and for a run of the template as saved; a held or generated
67
+ // topic has no text yet.
68
+ const known = queuedTopic !== undefined || generation === "off";
69
+ if (at >= from)
70
+ runs.push({
71
+ at: at.toISOString(),
72
+ scheduleId: schedule.id,
73
+ scheduleName: schedule.name,
74
+ scheduleVersion: schedule.version,
75
+ paused,
76
+ templateId: schedule.templateId,
77
+ templateVersion: schedule.templateVersion,
78
+ templateName,
79
+ index: queuedTopic === undefined ? null : k,
80
+ topic: queuedTopic?.title ?? null,
81
+ topicSource: queuedTopic !== undefined
82
+ ? "queued"
83
+ : generation === "off"
84
+ ? "template"
85
+ : k < items.length + held
86
+ ? "held"
87
+ : "generated",
88
+ renderedTitle: form !== undefined && known ? renderedTitle(form, schedule, queuedTopic) : null,
89
+ });
90
+ if (schedule.cadence.kind === "once")
91
+ break;
92
+ at = nextOccurrence(schedule.cadence, schedule.timezone, new Date(at.valueOf() + 1000));
93
+ }
94
+ return runs;
95
+ }
96
+ const projectRow = z.object({
97
+ id: z.string(),
98
+ title: z.string(),
99
+ config: z.string(),
100
+ created_at: z.string(),
101
+ finished_at: z.string().nullable(),
102
+ });
103
+ // Projects the person has something to decide on in the current revision: a review
104
+ // checkpoint holding the run, or an automatic review that failed an item and waits for
105
+ // Overrule or Redo (its latest verdict, with no redo under way).
106
+ function awaitingReview(db) {
107
+ const ids = db
108
+ .prepare(`SELECT c.project_id AS id FROM review_checkpoints c
109
+ JOIN project_heads h ON h.project_id=c.project_id AND h.revision_id=c.revision_id
110
+ WHERE c.state IN ('held','pending-review') AND c.approved_at IS NULL
111
+ UNION
112
+ SELECT v.project_id AS id FROM review_verdicts v
113
+ JOIN project_heads h ON h.project_id=v.project_id AND h.revision_id=v.revision_id
114
+ WHERE v.passed=0 AND v.action IS NULL AND (v.redo_state IS NULL OR v.redo_state='failed')
115
+ AND NOT EXISTS (SELECT 1 FROM review_verdicts n WHERE n.project_id=v.project_id
116
+ AND n.item_key=v.item_key AND (n.created_at>v.created_at OR (n.created_at=v.created_at AND n.rowid>v.rowid)))`)
117
+ .all();
118
+ return new Set(ids.map((row) => z.string().parse(row.id)));
119
+ }
120
+ // Whether a finished project's settings make a video: projects stored before sources were
121
+ // recorded made one.
122
+ const videoSource = z.object({ sources: z.object({ video: z.string() }).partial() }).partial();
123
+ function makesVideo(config) {
124
+ try {
125
+ const parsed = videoSource.safeParse(JSON.parse(config));
126
+ return !parsed.success || parsed.data.sources?.video !== "off";
127
+ }
128
+ catch {
129
+ return true;
130
+ }
131
+ }
132
+ function sampleProjectIds(db) {
133
+ return new Set(sampleIds.flatMap((id) => {
134
+ const record = readSampleRecord(db, id);
135
+ return record?.projectId === undefined ? [] : [record.projectId];
136
+ }));
137
+ }
138
+ // What a project on the calendar asks of the person, if anything. The same readings Home's
139
+ // Needs you uses: a failed or paused run, or one waiting for a review.
140
+ function needsOf(state, reviewing) {
141
+ if (state === "failed")
142
+ return "failed";
143
+ if (state === "paused")
144
+ return "paused";
145
+ if (reviewing && state !== "canceled")
146
+ return "review";
147
+ return undefined;
148
+ }
149
+ // Running (or waiting) projects made before `to`, and finished ones whose last stage ended in
150
+ // the range, with what each needs from the person and whether its video is ready to upload.
151
+ function projectsIn(deps, from, to) {
152
+ const standings = stageStandingsByProject(deps.db);
153
+ const reviewing = awaitingReview(deps.db);
154
+ const uploads = uploadedProjects(deps.db);
155
+ const samples = sampleProjectIds(deps.db);
156
+ const waits = limitWaitsByProject(deps.db);
157
+ const fromSchedule = new Map();
158
+ for (const row of deps.db
159
+ .prepare(`SELECT DISTINCT admitted.value AS project_id, schedule_runs.schedule_id
160
+ FROM schedule_runs, json_each(schedule_runs.project_ids_json) AS admitted`)
161
+ .all()) {
162
+ const parsed = z.object({ project_id: z.string(), schedule_id: z.string() }).parse(row);
163
+ fromSchedule.set(parsed.project_id, parsed.schedule_id);
164
+ }
165
+ return deps.db
166
+ .prepare(`SELECT projects.id,projects.title,projects.config,projects.created_at,max(stages.finished_at) AS finished_at
167
+ FROM projects LEFT JOIN stages ON stages.project_id=projects.id
168
+ WHERE projects.created_at<? AND ${liveProject()} GROUP BY projects.id ORDER BY projects.created_at,projects.id`)
169
+ .all(to.toISOString())
170
+ .flatMap((row) => {
171
+ const project = projectRow.parse(row);
172
+ const state = derive(standings.get(project.id) ?? [], projectPaused(deps.db, project.id));
173
+ const terminal = state === "done" || state === "partial" || state === "failed" || state === "canceled";
174
+ if (terminal) {
175
+ const ended = project.finished_at ?? project.created_at;
176
+ if (ended < from.toISOString() || ended >= to.toISOString())
177
+ return [];
178
+ }
179
+ const needs = needsOf(state, reviewing.has(project.id));
180
+ const ready = (state === "done" || state === "partial") &&
181
+ makesVideo(project.config) &&
182
+ !uploads.has(project.id) &&
183
+ !samples.has(project.id);
184
+ const waiting = waits.get(project.id);
185
+ return [
186
+ {
187
+ id: project.id,
188
+ title: project.title,
189
+ state,
190
+ createdAt: project.created_at,
191
+ finishedAt: terminal ? (project.finished_at ?? project.created_at) : null,
192
+ scheduleId: fromSchedule.get(project.id) ?? null,
193
+ ...(needs === undefined ? {} : { needs }),
194
+ ...(ready ? { readyToUpload: true } : {}),
195
+ ...(waiting === undefined ? {} : { limitWaits: waiting.map(listingWait) }),
196
+ },
197
+ ];
198
+ });
199
+ }
200
+ function queued(deps) {
201
+ return queueEntries(deps.db).flatMap((entry) => {
202
+ if (entry.state === "finished")
203
+ return [];
204
+ const row = deps.db
205
+ .prepare(`SELECT projects.title, batches.created_at FROM projects, batches
206
+ WHERE projects.id=? AND batches.id=? AND ${liveProject()}`)
207
+ .get(entry.projectId, entry.batchId);
208
+ if (row === undefined)
209
+ return [];
210
+ const parsed = z.object({ title: z.string(), created_at: z.string() }).parse(row);
211
+ return [
212
+ {
213
+ projectId: entry.projectId,
214
+ title: parsed.title,
215
+ batchId: entry.batchId,
216
+ position: entry.position,
217
+ state: entry.state,
218
+ queuedAt: parsed.created_at,
219
+ },
220
+ ];
221
+ });
222
+ }
@@ -1,7 +1,8 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { z } from "zod";
2
3
  import { transact } from "../../kernel/db/tx.js";
3
4
  import { derive } from "../../kernel/runner/graph.js";
4
- import { projectPaused, stagesOf } from "../admission/repo.js";
5
+ import { projectExists, projectPaused, stagesOf } from "../admission/repo.js";
5
6
  import { readStartReceipt } from "../play-drafts/start-repo.js";
6
7
  import { scheduleRunSchema, scheduleSummarySchema, } from "./schema.js";
7
8
  const rowSchema = z.object({
@@ -23,30 +24,39 @@ const rowSchema = z.object({
23
24
  created_at: z.string(),
24
25
  updated_at: z.string(),
25
26
  deleted_at: z.string().nullable(),
27
+ brief: z.string().nullable(),
28
+ topic_mode: z.string(),
29
+ topic_min: z.number(),
30
+ topic_llm_json: z.string().nullable(),
31
+ topics_generating_at: z.string().nullable(),
32
+ topics_generated_at: z.string().nullable(),
33
+ topics_failed_at: z.string().nullable(),
34
+ topics_error: z.string().nullable(),
35
+ held_topics: z.number(),
26
36
  });
37
+ const scheduleColumns = `id,name,template_id,template_version,cadence_json,timezone,missed_policy,
38
+ overlap_policy,spend_limit_cents,items_json,topic_keyword,values_json,status,version,next_run_at,
39
+ created_at,updated_at,deleted_at,brief,topic_mode,topic_min,topic_llm_json,topics_generating_at,
40
+ topics_generated_at,topics_failed_at,topics_error,
41
+ (SELECT count(*) FROM schedule_topics AS held
42
+ WHERE held.schedule_id=schedules.id AND held.state='held') AS held_topics`;
27
43
  export function scheduleById(db, id) {
28
44
  const row = db
29
- .prepare(`SELECT id,name,template_id,template_version,cadence_json,timezone,missed_policy,
30
- overlap_policy,spend_limit_cents,items_json,topic_keyword,values_json,status,version,next_run_at,created_at,updated_at,
31
- deleted_at
45
+ .prepare(`SELECT ${scheduleColumns}
32
46
  FROM schedules WHERE id=?`)
33
47
  .get(id);
34
48
  return row === undefined ? undefined : parseSchedule(row);
35
49
  }
36
50
  export function scheduleRows(db) {
37
51
  return db
38
- .prepare(`SELECT id,name,template_id,template_version,cadence_json,timezone,missed_policy,
39
- overlap_policy,spend_limit_cents,items_json,topic_keyword,values_json,status,version,next_run_at,created_at,updated_at,
40
- deleted_at
52
+ .prepare(`SELECT ${scheduleColumns}
41
53
  FROM schedules ORDER BY created_at DESC,id`)
42
54
  .all()
43
55
  .map(parseSchedule);
44
56
  }
45
57
  export function dueSchedules(db, now) {
46
58
  return db
47
- .prepare(`SELECT id,name,template_id,template_version,cadence_json,timezone,missed_policy,
48
- overlap_policy,spend_limit_cents,items_json,topic_keyword,values_json,status,version,next_run_at,created_at,updated_at,
49
- deleted_at
59
+ .prepare(`SELECT ${scheduleColumns}
50
60
  FROM schedules WHERE deleted_at IS NULL AND status='active' AND next_run_at IS NOT NULL
51
61
  AND next_run_at <= ?
52
62
  ORDER BY next_run_at,id`)
@@ -57,17 +67,27 @@ export function insertSchedule(db, schedule, creationHash) {
57
67
  db.prepare(`INSERT INTO schedules
58
68
  (id,name,template_id,template_version,cadence_json,timezone,missed_policy,overlap_policy,
59
69
  spend_limit_cents,items_json,topic_keyword,values_json,status,version,creation_hash,
60
- next_run_at,created_at,updated_at,deleted_at)
61
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(schedule.id, schedule.name, schedule.templateId, schedule.templateVersion, JSON.stringify(schedule.cadence), schedule.timezone, schedule.missedPolicy, schedule.overlapPolicy, schedule.spendLimitCents, JSON.stringify(schedule.items), schedule.topicKeyword, JSON.stringify(schedule.values), schedule.status, schedule.version, creationHash, schedule.nextRunAt, schedule.createdAt, schedule.updatedAt, schedule.deletedAt);
70
+ next_run_at,created_at,updated_at,deleted_at,brief,topic_mode,topic_min,topic_llm_json)
71
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(schedule.id, schedule.name, schedule.templateId, schedule.templateVersion, JSON.stringify(schedule.cadence), schedule.timezone, schedule.missedPolicy, schedule.overlapPolicy, schedule.spendLimitCents, JSON.stringify(schedule.items), schedule.topicKeyword, JSON.stringify(schedule.values), schedule.status, schedule.version, creationHash, schedule.nextRunAt, schedule.createdAt, schedule.updatedAt, schedule.deletedAt, ...generationColumns(schedule));
72
+ }
73
+ function generationColumns(schedule) {
74
+ const generation = schedule.topicGeneration;
75
+ return [
76
+ schedule.brief,
77
+ generation.mode,
78
+ generation.keepAtLeast,
79
+ generation.llm === null ? null : JSON.stringify(generation.llm),
80
+ ];
62
81
  }
63
82
  export function updateScheduleRow(db, schedule, mutationId, mutationHash, baseVersion) {
64
83
  const changed = db
65
84
  .prepare(`UPDATE schedules SET name=?,template_id=?,template_version=?,cadence_json=?,timezone=?,
66
85
  missed_policy=?,overlap_policy=?,spend_limit_cents=?,items_json=?,topic_keyword=?,
67
86
  values_json=?,status=?,version=?,
68
- next_run_at=?,updated_at=?,mutation_id=?,mutation_hash=?
87
+ next_run_at=?,updated_at=?,mutation_id=?,mutation_hash=?,
88
+ brief=?,topic_mode=?,topic_min=?,topic_llm_json=?,topics_failed_at=NULL,topics_error=NULL
69
89
  WHERE id=? AND version=? AND deleted_at IS NULL`)
70
- .run(schedule.name, schedule.templateId, schedule.templateVersion, JSON.stringify(schedule.cadence), schedule.timezone, schedule.missedPolicy, schedule.overlapPolicy, schedule.spendLimitCents, JSON.stringify(schedule.items), schedule.topicKeyword, JSON.stringify(schedule.values), schedule.status, schedule.version, schedule.nextRunAt, schedule.updatedAt, mutationId, mutationHash, schedule.id, baseVersion);
90
+ .run(schedule.name, schedule.templateId, schedule.templateVersion, JSON.stringify(schedule.cadence), schedule.timezone, schedule.missedPolicy, schedule.overlapPolicy, schedule.spendLimitCents, JSON.stringify(schedule.items), schedule.topicKeyword, JSON.stringify(schedule.values), schedule.status, schedule.version, schedule.nextRunAt, schedule.updatedAt, mutationId, mutationHash, ...generationColumns(schedule), schedule.id, baseVersion);
71
91
  return Number(changed.changes) === 1;
72
92
  }
73
93
  export function tombstoneScheduleRow(db, id, version, deletedAt) {
@@ -138,7 +158,13 @@ function consumeTopic(db, scheduleId, used, updatedAt) {
138
158
  if (at === -1)
139
159
  return;
140
160
  const items = schedule.items.filter((_item, index) => index !== at);
141
- const done = items.length === 0 && (schedule.status === "active" || schedule.status === "paused");
161
+ // A schedule that finds its own topics keeps going when its queue empties.
162
+ const done = items.length === 0 &&
163
+ schedule.topicGeneration.mode === "off" &&
164
+ (schedule.status === "active" || schedule.status === "paused");
165
+ // Remembered so topic generation never suggests it again.
166
+ db.prepare(`INSERT INTO schedule_topics (id,schedule_id,title,state,rank,created_at,decided_at)
167
+ VALUES (?,?,?,'used',0,?,?)`).run(randomUUID(), scheduleId, used.title, updatedAt, updatedAt);
142
168
  db.prepare(`UPDATE schedules SET items_json=?,status=?,next_run_at=?,version=version+1,updated_at=?
143
169
  WHERE id=? AND deleted_at IS NULL`).run(JSON.stringify(items), done ? "completed" : schedule.status, done ? null : schedule.nextRunAt, updatedAt, scheduleId);
144
170
  }
@@ -217,10 +243,11 @@ export function settleTerminalScheduleRuns(db, settledAt) {
217
243
  });
218
244
  }
219
245
  function projectIsActive(db, projectId) {
220
- if (db.prepare("SELECT 1 FROM projects WHERE id=?").get(projectId) === undefined)
246
+ // A project in the trash takes no part in a schedule's overlap: it cannot run there.
247
+ if (!projectExists(db, projectId))
221
248
  return false;
222
249
  const state = derive(stagesOf(db, projectId), projectPaused(db, projectId));
223
- return state !== "done" && state !== "failed" && state !== "canceled";
250
+ return state !== "done" && state !== "partial" && state !== "failed" && state !== "canceled";
224
251
  }
225
252
  export function recoverRunningRuns(db, endedAt, error = "Slopify was closed while this run was in progress. Check its video in Projects; the next run will go ahead as planned.") {
226
253
  return transact(db, () => {
@@ -267,6 +294,19 @@ function parseSchedule(row) {
267
294
  items: JSON.parse(value.items_json),
268
295
  topicKeyword: value.topic_keyword,
269
296
  values: JSON.parse(value.values_json),
297
+ brief: value.brief,
298
+ topicGeneration: {
299
+ mode: value.topic_mode,
300
+ keepAtLeast: value.topic_min,
301
+ llm: value.topic_llm_json === null ? null : JSON.parse(value.topic_llm_json),
302
+ },
303
+ topics: {
304
+ held: value.held_topics,
305
+ generatingSince: value.topics_generating_at,
306
+ generatedAt: value.topics_generated_at,
307
+ failedAt: value.topics_failed_at,
308
+ error: value.topics_error,
309
+ },
270
310
  status: value.status,
271
311
  version: value.version,
272
312
  nextRunAt: value.next_run_at,
@@ -308,7 +348,7 @@ function parseRun(row) {
308
348
  const internalRunError = "Slopify hit an internal error while starting this run. The next run will try again; if it keeps happening, use Download diagnostics in Settings and report it.";
309
349
  const runErrors = {
310
350
  "missing-template": "The template this schedule uses was deleted or changed. Edit the schedule and choose a template again.",
311
- "unsupported-media": "This template uses audio, images, a thumbnail or shorts background music you supplied, which scheduled runs cannot use. Pick a template that generates these instead, or remove the music under Outputs → Export → More shorts options on Play and save the template again.",
351
+ "unsupported-media": "This template uses audio, images, a thumbnail, shorts background music or an ambient sound file you supplied, which scheduled runs cannot use. Pick a template that generates these instead, or on Play remove the music under Outputs → More shorts options (or pick Rain, Fireplace or Wind under Video and style → Ambient sound) and save the template again.",
312
352
  "spend-limit": "Not started: the estimated cost was above this schedule's spend limit, or some prices were unknown. Raise the spend limit or choose models with known prices.",
313
353
  readiness: "Not started: a provider was not ready, for example a missing API key, model or voice. Check Settings → Providers; the next run will try again.",
314
354
  "not-found": "The template's settings are no longer valid, for example a prompt or voice it uses was deleted. Fix the template in Library > Templates and save it.",
@@ -1,21 +1,62 @@
1
1
  import { transact } from "../../kernel/db/tx.js";
2
- import { render } from "../admission/substitute.js";
3
2
  import { reviewDraft } from "../play-drafts/review.js";
4
3
  import { createDraft } from "../play-drafts/service.js";
5
4
  import { startPlayDraft } from "../play-drafts/start.js";
6
5
  import { freshTemplateDraft } from "../project-templates/setup.js";
7
6
  import { nextOccurrence } from "./calendar.js";
8
- import { activeRun, dueSchedules, insertRun, recordRunDispatch, recordRunStartIdentity, recoverRunningRuns, scheduleById, updateRun, } from "./repo.js";
7
+ import { activeRun, dueSchedules, insertRun, recordRunDispatch, recordRunStartIdentity, recoverRunningRuns, scheduleById, scheduleRows, updateRun, } from "./repo.js";
8
+ import { renderedTitle, scheduledValues } from "./topic-list.js";
9
+ import { generateTopics, generationDue, releaseTopicLeases } from "./topics.js";
9
10
  const missedGraceMs = 60_000;
10
11
  export function createScheduleRunner(deps) {
11
12
  let ticking = false;
13
+ // ceiling: one entry per schedule generating now.
14
+ const generations = new Set();
15
+ const stopping = new AbortController();
16
+ const generated = async () => {
17
+ while (generations.size > 0)
18
+ await Promise.allSettled([...generations]);
19
+ };
20
+ // Topic generation can take minutes, so a tick starts it and moves on: another schedule's
21
+ // run is never late because of it. The lease inside `generateTopics` is what keeps it to
22
+ // one at a time per schedule.
23
+ const start = (scheduleId, force) => {
24
+ if (stopping.signal.aborted)
25
+ return;
26
+ const running = generateTopics(deps, scheduleId, { signal: stopping.signal, force })
27
+ .then((result) => {
28
+ if (!result.ok && result.reason === "failed")
29
+ deps.log.write("warn", "schedule.topics", { detail: result.message });
30
+ })
31
+ .catch(() => {
32
+ deps.log.write("error", "schedule.topics", {
33
+ detail: "Topic generation could not record its result.",
34
+ });
35
+ })
36
+ .finally(() => {
37
+ generations.delete(running);
38
+ });
39
+ generations.add(running);
40
+ };
41
+ const startGenerations = (now) => {
42
+ for (const schedule of scheduleRows(deps.db))
43
+ if (generationDue(schedule, now))
44
+ start(schedule.id, false);
45
+ };
12
46
  return {
47
+ generated,
48
+ requestTopics: (scheduleId) => start(scheduleId, true),
49
+ stop: async () => {
50
+ stopping.abort();
51
+ await generated();
52
+ },
13
53
  tick: async (now = deps.clock.now()) => {
14
54
  if (ticking)
15
55
  return;
16
56
  ticking = true;
17
57
  try {
18
58
  recoverRunningRuns(deps.db, now.toISOString(), "The previous schedule tick could not record this run's result.");
59
+ startGenerations(now);
19
60
  const due = dueSchedules(deps.db, now.toISOString());
20
61
  const claimed = due.flatMap((schedule) => claim(deps, schedule, now));
21
62
  const settled = await Promise.allSettled(claimed.map((entry) => execute(deps, entry)));
@@ -27,7 +68,10 @@ export function createScheduleRunner(deps) {
27
68
  ticking = false;
28
69
  }
29
70
  },
30
- recover: (now = deps.clock.now()) => recoverRunningRuns(deps.db, now.toISOString()),
71
+ recover: (now = deps.clock.now()) => {
72
+ releaseTopicLeases(deps);
73
+ return recoverRunningRuns(deps.db, now.toISOString());
74
+ },
31
75
  };
32
76
  }
33
77
  function claim(deps, schedule, now) {
@@ -47,7 +91,11 @@ function claim(deps, schedule, now) {
47
91
  current.status !== "active")
48
92
  return [];
49
93
  const overlapping = activeRun(deps.db, schedule.id, now.toISOString());
50
- const skip = occurrence || overlapping;
94
+ // A schedule that finds its own topics never runs the template without one.
95
+ const noTopic = current.topicGeneration.mode !== "off" && current.items.length === 0
96
+ ? noTopicReason(current)
97
+ : null;
98
+ const skip = occurrence || overlapping || noTopic !== null;
51
99
  const run = {
52
100
  id: deps.uuid(),
53
101
  scheduleId: schedule.id,
@@ -61,7 +109,9 @@ function claim(deps, schedule, now) {
61
109
  error: skip
62
110
  ? overlapping
63
111
  ? "Skipped because the previous run of this schedule was still going."
64
- : "Skipped because Slopify was not running at the scheduled time."
112
+ : occurrence
113
+ ? "Skipped because Slopify was not running at the scheduled time."
114
+ : noTopic
65
115
  : null,
66
116
  };
67
117
  if (!insertRun(deps.db, run))
@@ -72,6 +122,14 @@ function claim(deps, schedule, now) {
72
122
  return skip ? [] : [{ run, schedule }];
73
123
  });
74
124
  }
125
+ function noTopicReason(schedule) {
126
+ const { held, error } = schedule.topics;
127
+ if (held > 0)
128
+ return `Skipped because no topic was approved: ${String(held)} ${held === 1 ? "topic is" : "topics are"} waiting for you. Approve them under Schedules → ${schedule.name} → Topics waiting.`;
129
+ if (error !== null)
130
+ return `Skipped because the topic queue was empty. ${error}`;
131
+ return "Skipped because the topic queue was empty and new topics were still being generated. The next run uses the first one.";
132
+ }
75
133
  async function execute(deps, claimed) {
76
134
  let run = claimed.run;
77
135
  try {
@@ -136,21 +194,14 @@ async function execute(deps, claimed) {
136
194
  }
137
195
  // One project per run. The first queued topic fills the chosen keyword, the schedule's fixed
138
196
  // values fill the rest over the template's own, and the template's title is filled from the
139
- // same keywords, so "D&D Lore: {{Topic}}" names the project after the topic. A topic saved
197
+ // same keywords, so "History: {{Topic}}" names the project after the topic. A topic saved
140
198
  // before topics had a keyword brings its own title and values instead.
141
199
  function runForm(form, schedule, topic) {
142
- const values = {
143
- ...form.values,
144
- ...schedule.values,
145
- ...(topic?.values ?? {}),
146
- ...(topic !== undefined && schedule.topicKeyword !== null
147
- ? { [schedule.topicKeyword]: topic.title }
148
- : {}),
200
+ return {
201
+ ...form,
202
+ title: renderedTitle(form, schedule, topic),
203
+ values: scheduledValues(form, schedule, topic),
149
204
  };
150
- const title = topic !== undefined && schedule.topicKeyword === null
151
- ? topic.title
152
- : render(form.title, values);
153
- return { ...form, title, values };
154
205
  }
155
206
  class ScheduleDispatchError extends Error {
156
207
  reason;