@gentbajko/slopify 2.4.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (469) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +21 -40
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +11 -1
  6. package/dist/adapters/alignment/cache.js +4 -1
  7. package/dist/adapters/alignment/ctc.js +25 -23
  8. package/dist/adapters/alignment/index.js +37 -6
  9. package/dist/adapters/alignment/multilingual.js +267 -0
  10. package/dist/adapters/alignment/protocol.js +11 -0
  11. package/dist/adapters/alignment/quality.js +4 -3
  12. package/dist/adapters/alignment/sentences.js +95 -0
  13. package/dist/adapters/alignment/spec.js +24 -0
  14. package/dist/adapters/alignment/spell.js +769 -0
  15. package/dist/adapters/alignment/text.js +56 -9
  16. package/dist/adapters/alignment/window.js +34 -30
  17. package/dist/adapters/alignment/worker.js +23 -21
  18. package/dist/adapters/explain.js +11 -11
  19. package/dist/adapters/host-cli/index.js +55 -5
  20. package/dist/adapters/host-cli/transport.js +13 -6
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +60 -18
  23. package/dist/adapters/image/codex.js +166 -25
  24. package/dist/adapters/image/fal.js +31 -3
  25. package/dist/adapters/image/google.js +17 -2
  26. package/dist/adapters/image/openai.js +45 -16
  27. package/dist/adapters/image/reference.js +41 -0
  28. package/dist/adapters/image/replicate.js +14 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +36 -0
  31. package/dist/adapters/llm/claude-code.js +168 -32
  32. package/dist/adapters/llm/cli-login-error.js +1 -1
  33. package/dist/adapters/llm/codex-limits.js +157 -0
  34. package/dist/adapters/llm/codex-models.js +2 -1
  35. package/dist/adapters/llm/codex-server-models.js +2 -1
  36. package/dist/adapters/llm/codex.js +99 -12
  37. package/dist/adapters/llm/document-workspace.js +2 -2
  38. package/dist/adapters/llm/gemini-workspace.js +1 -1
  39. package/dist/adapters/llm/gemini.js +16 -3
  40. package/dist/adapters/llm/image-workspace.js +58 -0
  41. package/dist/adapters/llm/openrouter.js +11 -3
  42. package/dist/adapters/llm/run-cli.js +26 -9
  43. package/dist/adapters/tts/cartesia.js +10 -0
  44. package/dist/adapters/tts/elevenlabs.js +31 -9
  45. package/dist/adapters/tts/inworld-async.js +9 -5
  46. package/dist/adapters/tts/inworld.js +17 -3
  47. package/dist/adapters/tts/openai.js +3 -0
  48. package/dist/adapters/tts/voice-languages.js +80 -0
  49. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  50. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/OFL.txt +93 -0
  55. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  56. package/dist/assets/models.yaml +7 -0
  57. package/dist/assets/sample/sample-audiobook.tar +0 -0
  58. package/dist/assets/sample/sample-podcast.tar +0 -0
  59. package/dist/assets/sample/sample-project.tar +0 -0
  60. package/dist/catalog/merge.js +132 -0
  61. package/dist/catalog/registry.js +33 -12
  62. package/dist/catalog/runtime-models.js +1 -1
  63. package/dist/catalog/schema.js +12 -1
  64. package/dist/catalog/store.js +147 -8
  65. package/dist/catalog/validate.js +17 -2
  66. package/dist/compose.yaml +71 -0
  67. package/dist/edge/autostart/docker-record.js +32 -0
  68. package/dist/edge/autostart/docker.js +50 -0
  69. package/dist/edge/autostart/index.js +78 -0
  70. package/dist/edge/autostart/launcher.js +176 -0
  71. package/dist/edge/autostart/model.js +3 -0
  72. package/dist/edge/autostart/native.js +210 -0
  73. package/dist/edge/autostart/prompt.js +66 -0
  74. package/dist/edge/autostart/service.js +81 -0
  75. package/dist/edge/cli.js +57 -51
  76. package/dist/edge/docker-install/activation.js +56 -0
  77. package/dist/edge/docker-install/apply.js +458 -0
  78. package/dist/edge/docker-install/engine.js +362 -0
  79. package/dist/edge/docker-install/run.js +154 -0
  80. package/dist/edge/docker-install/state.js +146 -0
  81. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  82. package/dist/edge/docker.js +14 -22
  83. package/dist/edge/events/hub.js +11 -0
  84. package/dist/edge/host-cli.js +1 -1
  85. package/dist/edge/http/actions.js +32 -10
  86. package/dist/edge/http/app.js +60 -2
  87. package/dist/edge/http/auditions.js +70 -0
  88. package/dist/edge/http/autostart.js +37 -0
  89. package/dist/edge/http/backups.js +40 -0
  90. package/dist/edge/http/channel-memory.js +92 -0
  91. package/dist/edge/http/channels.js +128 -0
  92. package/dist/edge/http/entries.js +22 -3
  93. package/dist/edge/http/files.js +1 -1
  94. package/dist/edge/http/folder-location.js +1 -1
  95. package/dist/edge/http/home.js +39 -0
  96. package/dist/edge/http/host-cli.js +45 -6
  97. package/dist/edge/http/narration-peaks.js +76 -0
  98. package/dist/edge/http/onboarding.js +152 -0
  99. package/dist/edge/http/open-folder.js +1 -1
  100. package/dist/edge/http/patch-notes.js +45 -0
  101. package/dist/edge/http/planning.js +4 -2
  102. package/dist/edge/http/problem.js +1 -0
  103. package/dist/edge/http/project-create.js +87 -0
  104. package/dist/edge/http/projects.js +31 -99
  105. package/dist/edge/http/prompts.js +40 -3
  106. package/dist/edge/http/pronunciations.js +23 -4
  107. package/dist/edge/http/providers.js +86 -0
  108. package/dist/edge/http/reviews.js +121 -0
  109. package/dist/edge/http/revision-files.js +1 -1
  110. package/dist/edge/http/revisions.js +1 -1
  111. package/dist/edge/http/run-cost.js +25 -0
  112. package/dist/edge/http/schedules.js +111 -6
  113. package/dist/edge/http/settings.js +128 -6
  114. package/dist/edge/http/storage-files.js +50 -0
  115. package/dist/edge/http/storage.js +29 -2
  116. package/dist/edge/http/studio.js +207 -0
  117. package/dist/edge/http/style-preview.js +100 -0
  118. package/dist/edge/http/subtitles.js +1 -1
  119. package/dist/edge/http/trash.js +69 -0
  120. package/dist/edge/http/update.js +24 -3
  121. package/dist/edge/http/whats-new.js +15 -0
  122. package/dist/edge/http/youtube-edits.js +83 -0
  123. package/dist/edge/native-update.js +62 -0
  124. package/dist/host-cli/open-folder.js +10 -10
  125. package/dist/host-cli/runtime.js +12 -3
  126. package/dist/kernel/db/migrations/0022-drop-duplicate-held-work.sql +56 -0
  127. package/dist/kernel/db/migrations/0024-reference-attachments.sql +18 -0
  128. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  129. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  130. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  131. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  132. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  133. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  134. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  135. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  136. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  137. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  138. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  139. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  140. package/dist/kernel/paths.js +27 -6
  141. package/dist/kernel/pipeline.js +4 -1
  142. package/dist/kernel/ports/host-cli.js +139 -16
  143. package/dist/kernel/ports/image.js +3 -1
  144. package/dist/kernel/ports/key-probe.js +1 -0
  145. package/dist/kernel/ports/languages.js +157 -0
  146. package/dist/kernel/ports/llm.js +3 -1
  147. package/dist/kernel/ports/model.js +5 -2
  148. package/dist/kernel/ports/narration-aliases.js +60 -0
  149. package/dist/kernel/ports/plan-limits.js +36 -0
  150. package/dist/kernel/runner/attempt.js +18 -9
  151. package/dist/kernel/runner/audition.js +36 -0
  152. package/dist/kernel/runner/graph.js +24 -5
  153. package/dist/kernel/runner/index.js +35 -5
  154. package/dist/kernel/runner/meter.js +1 -0
  155. package/dist/kernel/runner/providers.js +164 -9
  156. package/dist/kernel/runner/retry-policy.js +43 -0
  157. package/dist/kernel/runner/standalone.js +134 -0
  158. package/dist/kernel/runner/work-authority.js +28 -2
  159. package/dist/main.js +321 -20
  160. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  161. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  162. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  163. package/dist/patch-notes/3.0.0.md +70 -0
  164. package/dist/patch-notes/index.json +26 -0
  165. package/dist/slices/admission/model.js +35 -0
  166. package/dist/slices/admission/repo.js +24 -4
  167. package/dist/slices/admission/rules.js +126 -1
  168. package/dist/slices/admission/schema.js +68 -1
  169. package/dist/slices/admission/short-mode.js +47 -0
  170. package/dist/slices/admission/start.js +90 -16
  171. package/dist/slices/article/continuation.js +2 -2
  172. package/dist/slices/article/provided-entries.js +2 -2
  173. package/dist/slices/article/run.js +6 -2
  174. package/dist/slices/article/segments.js +3 -3
  175. package/dist/slices/backups/files.js +135 -0
  176. package/dist/slices/backups/folder.js +43 -0
  177. package/dist/slices/backups/model.js +65 -0
  178. package/dist/slices/backups/repo.js +28 -0
  179. package/dist/slices/backups/schedule.js +60 -0
  180. package/dist/slices/backups/service.js +311 -0
  181. package/dist/slices/batch/index.js +9 -2
  182. package/dist/slices/cancel/index.js +1 -1
  183. package/dist/slices/channels/cast-images.js +110 -0
  184. package/dist/slices/channels/cast-match.js +32 -0
  185. package/dist/slices/channels/images.js +19 -0
  186. package/dist/slices/channels/model.js +5 -0
  187. package/dist/slices/channels/rebrand.js +100 -0
  188. package/dist/slices/channels/repo.js +218 -0
  189. package/dist/slices/channels/runs.js +129 -0
  190. package/dist/slices/channels/schema.js +129 -0
  191. package/dist/slices/channels/service.js +180 -0
  192. package/dist/slices/channels/studio-csv.js +110 -0
  193. package/dist/slices/channels/videos.js +120 -0
  194. package/dist/slices/checkpoints/recovery.js +10 -0
  195. package/dist/slices/control/index.js +2 -1
  196. package/dist/slices/document/fonts.js +1 -1
  197. package/dist/slices/document/render.js +1 -1
  198. package/dist/slices/episodes/related.js +90 -0
  199. package/dist/slices/episodes/repo.js +60 -0
  200. package/dist/slices/episodes/service.js +58 -0
  201. package/dist/slices/episodes/summarize.js +170 -0
  202. package/dist/slices/estimate/index.js +113 -24
  203. package/dist/slices/estimate/requests.js +60 -2
  204. package/dist/slices/fixes/rules.js +73 -0
  205. package/dist/slices/fonts/coverage.js +82 -0
  206. package/dist/slices/fonts/sfnt.js +74 -0
  207. package/dist/slices/images/run.js +8 -5
  208. package/dist/slices/images/scale.js +84 -0
  209. package/dist/slices/library/diff.js +122 -0
  210. package/dist/slices/library/history.js +100 -0
  211. package/dist/slices/library/model.js +3 -0
  212. package/dist/slices/library/photorealistic.js +49 -0
  213. package/dist/slices/library/repo.js +19 -14
  214. package/dist/slices/library/save.js +62 -12
  215. package/dist/slices/library/slots.js +15 -3
  216. package/dist/slices/library/snapshot.js +8 -2
  217. package/dist/slices/library/used-by.js +153 -0
  218. package/dist/slices/loudness/level-pieces.js +41 -0
  219. package/dist/slices/loudness/loudnorm.js +164 -0
  220. package/dist/slices/loudness/model.js +153 -0
  221. package/dist/slices/model-upkeep/model.js +10 -0
  222. package/dist/slices/model-upkeep/switch.js +217 -0
  223. package/dist/slices/model-upkeep/usage.js +183 -0
  224. package/dist/slices/narration/aliases-library.js +31 -0
  225. package/dist/slices/narration/aliases-schema.js +53 -0
  226. package/dist/slices/narration/aliases.js +49 -0
  227. package/dist/slices/narration/blocks.js +352 -0
  228. package/dist/slices/narration/chunk.js +19 -7
  229. package/dist/slices/narration/concat.js +2 -2
  230. package/dist/slices/narration/describe.js +45 -0
  231. package/dist/slices/narration/pauses-model.js +63 -0
  232. package/dist/slices/narration/pauses.js +269 -0
  233. package/dist/slices/narration/peaks-model.js +1 -0
  234. package/dist/slices/narration/peaks.js +94 -0
  235. package/dist/slices/narration/preparation.js +21 -3
  236. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  237. package/dist/slices/narration/pronunciation.js +72 -25
  238. package/dist/slices/narration/run.js +6 -6
  239. package/dist/slices/narration/shared-glossary.js +1 -1
  240. package/dist/slices/narration/steering.js +15 -4
  241. package/dist/slices/notifications/notifier.js +94 -0
  242. package/dist/slices/notifications/rules.js +102 -0
  243. package/dist/slices/notifications/send.js +40 -0
  244. package/dist/slices/notifications/settings.js +34 -0
  245. package/dist/slices/onboarding/copy.js +143 -0
  246. package/dist/slices/onboarding/first-run.js +46 -0
  247. package/dist/slices/onboarding/install.js +119 -0
  248. package/dist/slices/onboarding/model.js +64 -0
  249. package/dist/slices/onboarding/packs.js +491 -0
  250. package/dist/slices/onboarding/quick-short.js +129 -0
  251. package/dist/slices/onboarding/sample.js +132 -0
  252. package/dist/slices/onboarding/state.js +68 -0
  253. package/dist/slices/onboarding/template.js +73 -0
  254. package/dist/slices/patch-notes/library.js +73 -0
  255. package/dist/slices/patch-notes/seen.js +77 -0
  256. package/dist/slices/play-drafts/convert.js +160 -9
  257. package/dist/slices/play-drafts/readiness.js +3 -0
  258. package/dist/slices/play-drafts/repo.js +9 -1
  259. package/dist/slices/play-drafts/review-inputs.js +22 -3
  260. package/dist/slices/play-drafts/schema.js +109 -2
  261. package/dist/slices/play-drafts/service.js +10 -1
  262. package/dist/slices/play-drafts/start.js +43 -18
  263. package/dist/slices/play-drafts/uploads.js +2 -1
  264. package/dist/slices/project-templates/from-project.js +70 -4
  265. package/dist/slices/project-templates/one-off.js +26 -0
  266. package/dist/slices/project-templates/repo.js +2 -1
  267. package/dist/slices/project-templates/service.js +15 -4
  268. package/dist/slices/project-templates/setup.js +29 -2
  269. package/dist/slices/rebuild/admission-repo.js +1 -1
  270. package/dist/slices/rebuild/model.js +6 -0
  271. package/dist/slices/rebuild/preview-details.js +67 -2
  272. package/dist/slices/rebuild/preview-plan.js +13 -6
  273. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  274. package/dist/slices/rebuild/recipe-audio.js +66 -12
  275. package/dist/slices/rebuild/recipe-build.js +15 -5
  276. package/dist/slices/rebuild/recipe-cast.js +18 -0
  277. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  278. package/dist/slices/rebuild/recipe-describe.js +93 -0
  279. package/dist/slices/rebuild/recipe-edit.js +13 -1
  280. package/dist/slices/rebuild/recipe-exports.js +62 -8
  281. package/dist/slices/rebuild/recipe-input-schema.js +43 -1
  282. package/dist/slices/rebuild/recipe-legacy.js +12 -0
  283. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  284. package/dist/slices/rebuild/recipe-model.js +25 -6
  285. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  286. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  287. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  288. package/dist/slices/rebuild/recipe-reference.js +58 -0
  289. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  290. package/dist/slices/rebuild/recipe-save.js +4 -1
  291. package/dist/slices/rebuild/recipe-shorts.js +32 -5
  292. package/dist/slices/rebuild/recipe-text.js +114 -19
  293. package/dist/slices/rebuild/recipe-validation.js +29 -3
  294. package/dist/slices/rebuild/recipe-visual.js +104 -29
  295. package/dist/slices/rebuild/recipe-voices.js +286 -0
  296. package/dist/slices/rebuild/recipe-work.js +77 -6
  297. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  298. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  299. package/dist/slices/rebuild/recovery-model.js +3 -0
  300. package/dist/slices/rebuild/recovery-selection.js +28 -2
  301. package/dist/slices/rebuild/recovery.js +35 -17
  302. package/dist/slices/rebuild/repo.js +3 -3
  303. package/dist/slices/rebuild/retry.js +18 -0
  304. package/dist/slices/rebuild/review-redo.js +110 -0
  305. package/dist/slices/rebuild/runtime-actions.js +14 -2
  306. package/dist/slices/rebuild/runtime-admission.js +4 -1
  307. package/dist/slices/rebuild/runtime-animate.js +4 -4
  308. package/dist/slices/rebuild/runtime-article.js +5 -5
  309. package/dist/slices/rebuild/runtime-document.js +4 -4
  310. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  311. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  312. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  313. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  314. package/dist/slices/rebuild/runtime-export.js +84 -19
  315. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  316. package/dist/slices/rebuild/runtime-image.js +69 -0
  317. package/dist/slices/rebuild/runtime-local.js +154 -27
  318. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  319. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  320. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  321. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  322. package/dist/slices/rebuild/runtime-piece-label.js +29 -0
  323. package/dist/slices/rebuild/runtime-provider.js +134 -19
  324. package/dist/slices/rebuild/runtime-publication.js +21 -5
  325. package/dist/slices/rebuild/runtime-review.js +237 -0
  326. package/dist/slices/rebuild/runtime-run.js +17 -11
  327. package/dist/slices/rebuild/runtime-shorts.js +48 -23
  328. package/dist/slices/rebuild/runtime-store.js +39 -10
  329. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  330. package/dist/slices/rebuild/runtime-voices.js +103 -0
  331. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  332. package/dist/slices/rebuild/service-readiness.js +15 -1
  333. package/dist/slices/rebuild/soften.js +49 -0
  334. package/dist/slices/rebuild/transition-repo.js +10 -3
  335. package/dist/slices/research/run.js +4 -4
  336. package/dist/slices/research/synthesis.js +2 -2
  337. package/dist/slices/reviews/model.js +105 -0
  338. package/dist/slices/reviews/outcome.js +22 -0
  339. package/dist/slices/reviews/repo.js +106 -0
  340. package/dist/slices/reviews/rules.js +63 -0
  341. package/dist/slices/reviews/verdict.js +71 -0
  342. package/dist/slices/reviews/view.js +30 -0
  343. package/dist/slices/revisions/adopt-content.js +12 -2
  344. package/dist/slices/revisions/adopt.js +9 -2
  345. package/dist/slices/revisions/downloads.js +7 -1
  346. package/dist/slices/revisions/image-plan.js +183 -0
  347. package/dist/slices/revisions/mutation-assets.js +43 -9
  348. package/dist/slices/revisions/mutation-cues.js +71 -0
  349. package/dist/slices/revisions/mutation-prepare.js +8 -10
  350. package/dist/slices/revisions/mutation-request.js +2 -1
  351. package/dist/slices/revisions/mutations.js +97 -3
  352. package/dist/slices/revisions/schema.js +16 -2
  353. package/dist/slices/run-cost/limits.js +128 -0
  354. package/dist/slices/run-cost/meter.js +47 -0
  355. package/dist/slices/run-cost/panel.js +234 -0
  356. package/dist/slices/run-cost/pricing.js +92 -0
  357. package/dist/slices/run-cost/week.js +108 -0
  358. package/dist/slices/schedules/agenda.js +158 -0
  359. package/dist/slices/schedules/repo.js +58 -18
  360. package/dist/slices/schedules/scheduler.js +67 -16
  361. package/dist/slices/schedules/schema.js +138 -1
  362. package/dist/slices/schedules/service.js +48 -4
  363. package/dist/slices/schedules/similar.js +90 -0
  364. package/dist/slices/schedules/topic-list.js +163 -0
  365. package/dist/slices/schedules/topics.js +422 -0
  366. package/dist/slices/settings/first-run.js +102 -0
  367. package/dist/slices/settings/health.js +190 -0
  368. package/dist/slices/settings/key-guides.js +128 -0
  369. package/dist/slices/settings/key-test.js +85 -0
  370. package/dist/slices/settings/model.js +11 -1
  371. package/dist/slices/settings/playback.js +15 -0
  372. package/dist/slices/settings/repo.js +17 -3
  373. package/dist/slices/settings/voices.js +64 -4
  374. package/dist/slices/settings/whats-new.js +64 -0
  375. package/dist/slices/shorts/captions.js +9 -0
  376. package/dist/slices/shorts/pick.js +3 -3
  377. package/dist/slices/shorts/prompts.js +1 -1
  378. package/dist/slices/shorts/render.js +54 -5
  379. package/dist/slices/storage/asset-name.js +3 -0
  380. package/dist/slices/storage/backup-export.js +48 -9
  381. package/dist/slices/storage/backup-format.js +38 -1
  382. package/dist/slices/storage/backup-import.js +191 -19
  383. package/dist/slices/storage/delete-project.js +7 -4
  384. package/dist/slices/storage/documents.js +79 -0
  385. package/dist/slices/storage/files-location.js +531 -0
  386. package/dist/slices/storage/layout.js +20 -0
  387. package/dist/slices/storage/model.js +14 -0
  388. package/dist/slices/storage/portable.js +95 -12
  389. package/dist/slices/storage/reconcile.js +19 -2
  390. package/dist/slices/storage/repo.js +4 -1
  391. package/dist/slices/storage/schema.js +11 -0
  392. package/dist/slices/storage/trim.js +146 -0
  393. package/dist/slices/studio/disclosure.js +74 -0
  394. package/dist/slices/studio/model.js +28 -0
  395. package/dist/slices/studio/pack.js +209 -0
  396. package/dist/slices/studio/settings.js +133 -0
  397. package/dist/slices/style-preview/images.js +61 -0
  398. package/dist/slices/style-preview/render.js +161 -0
  399. package/dist/slices/style-preview/schema.js +45 -0
  400. package/dist/slices/style-preview/service.js +93 -0
  401. package/dist/slices/style-preview/settings.js +89 -0
  402. package/dist/slices/subtitles/captions.js +42 -9
  403. package/dist/slices/subtitles/model.js +22 -0
  404. package/dist/slices/subtitles/prepare.js +27 -16
  405. package/dist/slices/subtitles/transcript.js +1 -1
  406. package/dist/slices/thumbnail/run.js +7 -7
  407. package/dist/slices/trash/model.js +25 -0
  408. package/dist/slices/trash/service.js +273 -0
  409. package/dist/slices/uploads/repo.js +20 -0
  410. package/dist/slices/video/ambient-bed-schema.js +15 -0
  411. package/dist/slices/video/ambient-bed.js +95 -0
  412. package/dist/slices/video/ambient-mix.js +72 -0
  413. package/dist/slices/video/audio-inputs.js +2 -2
  414. package/dist/slices/video/cards.js +5 -2
  415. package/dist/slices/video/edit-list.js +23 -2
  416. package/dist/slices/video/edit-settings.js +13 -3
  417. package/dist/slices/video/ffmpeg.js +63 -9
  418. package/dist/slices/video/figure-card.js +492 -0
  419. package/dist/slices/video/figure-spans.js +51 -0
  420. package/dist/slices/video/plan.js +125 -13
  421. package/dist/slices/video/run.js +14 -12
  422. package/dist/slices/video/slideshow.js +39 -4
  423. package/dist/slices/voices/attribution.js +69 -0
  424. package/dist/slices/voices/audio-files.js +82 -0
  425. package/dist/slices/voices/audition.js +25 -0
  426. package/dist/slices/voices/cast.js +56 -0
  427. package/dist/slices/voices/grouping.js +49 -0
  428. package/dist/slices/voices/join.js +35 -0
  429. package/dist/slices/voices/languages.js +25 -0
  430. package/dist/slices/voices/model.js +179 -0
  431. package/dist/slices/voices/palette.js +26 -0
  432. package/dist/slices/voices/panel.js +91 -0
  433. package/dist/slices/voices/portraits.js +55 -0
  434. package/dist/slices/voices/script.js +136 -0
  435. package/dist/slices/voices/timing.js +58 -0
  436. package/dist/slices/youtube/answer.js +1 -1
  437. package/dist/slices/youtube/chapters.js +126 -0
  438. package/dist/slices/youtube/edits-repo.js +69 -0
  439. package/dist/slices/youtube/edits.js +89 -0
  440. package/dist/slices/youtube/placeholders.js +98 -0
  441. package/dist/slices/youtube/transcript.js +11 -4
  442. package/dist/updater/service.js +66 -21
  443. package/dist/web/assets/index-CUA2sUxU.css +1 -0
  444. package/dist/web/assets/index-CX590kE-.js +303 -0
  445. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  446. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  447. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  448. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  449. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  450. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  451. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  452. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  453. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  454. package/dist/web/assets/{pdf-CAJNT254.js → pdf-BG8n_uc5.js} +1 -1
  455. package/dist/web/index.html +2 -2
  456. package/package.json +4 -4
  457. package/dist/edge/docker-launch.js +0 -81
  458. package/dist/edge/docker-projects/activation.js +0 -35
  459. package/dist/edge/docker-projects/claims.js +0 -85
  460. package/dist/edge/docker-projects/committed.js +0 -40
  461. package/dist/edge/docker-projects/engine.js +0 -461
  462. package/dist/edge/docker-projects/install.js +0 -254
  463. package/dist/edge/docker-projects/prune.js +0 -66
  464. package/dist/edge/docker-projects/recover.js +0 -100
  465. package/dist/edge/docker-projects/state.js +0 -225
  466. package/dist/web/assets/index-BGgYMm2u.css +0 -1
  467. package/dist/web/assets/index-BVVgtntL.js +0 -171
  468. package/scripts/docker-run.sh +0 -3
  469. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -1,10 +1,10 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { lstat, readdir } from "node:fs/promises";
3
3
  import { isAbsolute, join, relative, sep } from "node:path";
4
- import { readState, receiptSchema } from "../edge/docker-projects/state.js";
4
+ import { installSchema, readState } from "../edge/docker-install/state.js";
5
5
  import { HostFolderRefused } from "../kernel/ports/host-cli.js";
6
6
  import { hasCode } from "./paths.js";
7
- const again = "run the Docker launcher again (npx @gentbajko/slopify@latest --docker)";
7
+ const again = "run the Docker install again (npx @gentbajko/slopify@latest --docker)";
8
8
  /** Starts the desktop's opener and returns once it is running; the file manager is not awaited. */
9
9
  export function launchXdgOpen(directory) {
10
10
  return new Promise((resolve, reject) => {
@@ -24,7 +24,7 @@ export function launchXdgOpen(directory) {
24
24
  });
25
25
  });
26
26
  }
27
- /** Only folders inside a launcher receipt's project folder, reached through real folders you own, are opened. */
27
+ /** Only folders inside an installation's recorded project folder, reached through real folders you own, are opened. */
28
28
  export function createHostFolderOpener(deps) {
29
29
  async function receipts() {
30
30
  const found = [];
@@ -42,9 +42,9 @@ export function createHostFolderOpener(deps) {
42
42
  if (!entry.isDirectory())
43
43
  continue;
44
44
  try {
45
- const receipt = await readState(join(deps.root, entry.name, "receipt.json"), receiptSchema, deps.uid);
46
- if (receipt)
47
- found.push(receipt);
45
+ const install = await readState(join(deps.root, entry.name, "install.json"), installSchema, deps.uid);
46
+ if (install)
47
+ found.push(install);
48
48
  }
49
49
  catch {
50
50
  // An unreadable or damaged receipt grants nothing; the others still count.
@@ -59,13 +59,13 @@ export function createHostFolderOpener(deps) {
59
59
  root.uid !== BigInt(deps.uid) ||
60
60
  String(root.dev) !== identity.dev ||
61
61
  String(root.ino) !== identity.ino)
62
- throw new HostFolderRefused(`The Slopify host helper did not open this folder because the project folder ${projects} was moved or replaced since the Docker launcher set it up. Put the original folder back, or ${again}.`);
62
+ throw new HostFolderRefused(`The Slopify host helper did not open this folder because the project folder ${projects} was moved or replaced since the Docker install set it up. Put the original folder back, or ${again}.`);
63
63
  let current = projects;
64
64
  for (const part of r === "" ? [] : r.split(sep)) {
65
65
  current = join(current, part);
66
66
  const s = await lstat(current).catch(() => undefined);
67
67
  if (!s?.isDirectory() || s.isSymbolicLink() || s.uid !== deps.uid)
68
- throw new HostFolderRefused(`The Slopify host helper did not open ${current} because it is missing, a link or a file, or isn't owned by you. Use Re-run section on the project page to make the file again, or copy the path into your file manager.`);
68
+ throw new HostFolderRefused(`The Slopify host helper did not open ${current} because it is missing, a link or a file, or isn't owned by you. Use More → make it again in its section on the project page to make the file again, or copy the path into your file manager.`);
69
69
  }
70
70
  }
71
71
  return async (path) => {
@@ -77,7 +77,7 @@ export function createHostFolderOpener(deps) {
77
77
  if (r === ".." || r.startsWith(`..${sep}`) || isAbsolute(r))
78
78
  continue;
79
79
  try {
80
- await verify(receipt.projects, receipt.directoryIdentity, r);
80
+ await verify(receipt.projects, receipt.projectsIdentity, r);
81
81
  }
82
82
  catch (error) {
83
83
  if (!(error instanceof HostFolderRefused))
@@ -90,6 +90,6 @@ export function createHostFolderOpener(deps) {
90
90
  return;
91
91
  }
92
92
  throw (refusal ??
93
- new HostFolderRefused(`The Slopify host helper did not open ${path} because it isn't inside a project folder set up by the Docker launcher. Copy the path into your file manager, or if you moved your project folder, ${again}.`));
93
+ new HostFolderRefused(`The Slopify host helper did not open ${path} because it isn't inside a project folder set up by the Docker install. Copy the path into your file manager, or if you moved your project folder, ${again}.`));
94
94
  };
95
95
  }
@@ -1,10 +1,11 @@
1
1
  import { mkdtemp, rm } from "node:fs/promises";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
- import { codexImage } from "../adapters/image/codex.js";
4
+ import { codexImage, codexImageTimeoutMs } from "../adapters/image/codex.js";
5
5
  import { claudeCodeLlm } from "../adapters/llm/claude-code.js";
6
6
  import { nodeClaudeCodeModels } from "../adapters/llm/claude-code-models.js";
7
7
  import { codexLlm } from "../adapters/llm/codex.js";
8
+ import { nodeCodexLimits } from "../adapters/llm/codex-limits.js";
8
9
  import { nodeCodexModels } from "../adapters/llm/codex-models.js";
9
10
  import { geminiLlm } from "../adapters/llm/gemini.js";
10
11
  import { nodeGeminiModels } from "../adapters/llm/gemini-models.js";
@@ -12,6 +13,7 @@ import { providerError } from "../kernel/ports/model.js";
12
13
  import { createHostFolderOpener } from "./open-folder.js";
13
14
  import { createHostStatus } from "./status.js";
14
15
  export function createHostRuntime(deps) {
16
+ const readCodexLimits = deps.readCodexLimits ?? nodeCodexLimits;
15
17
  const status = createHostStatus(deps);
16
18
  async function command(id) {
17
19
  const ready = await status(id);
@@ -56,6 +58,7 @@ export function createHostRuntime(deps) {
56
58
  binary,
57
59
  env: deps.env,
58
60
  readModels: () => models(id),
61
+ ...(id === "codex" ? { readLimits: () => readCodexLimits(binary, deps.env) } : {}),
59
62
  }).complete({
60
63
  ...request,
61
64
  ...(id === "codex" && request.thinking !== undefined
@@ -75,10 +78,16 @@ export function createHostRuntime(deps) {
75
78
  }),
76
79
  image: {
77
80
  id: "codex-image",
78
- models: () => codexImage({ run: deps.run }).models(),
81
+ timeoutMs: codexImageTimeoutMs,
82
+ models: () => codexImage({ run: deps.run, readModels: () => models("codex") }).models(),
79
83
  generate: async (request) => {
80
84
  const binary = await command("codex-image");
81
- return codexImage({ run: deps.run, binary, env: deps.env }).generate(request);
85
+ return codexImage({
86
+ run: deps.run,
87
+ binary,
88
+ env: deps.env,
89
+ readLimits: () => readCodexLimits(binary, deps.env),
90
+ }).generate(request);
82
91
  },
83
92
  },
84
93
  };
@@ -0,0 +1,56 @@
1
+ -- Before 2.4.0 every save made while a step waited for Resume minted a new held row for it,
2
+ -- even when the project already had a row for exactly that step (same key and fingerprint).
3
+ -- Save now reserves the existing one instead (existingWork in
4
+ -- slices/rebuild/transition-repo.ts); this removes the copies left behind by older saves.
5
+ --
6
+ -- A row counts for a step the way Save counts it: a finished row (work and piece done), or a
7
+ -- held row nothing ran for (pending, held, unsent, no planning context, no attempt). Per
8
+ -- project, key and fingerprint, the row Save would pick is kept: a finished one first,
9
+ -- otherwise the newest held one. Only held rows that are not that pick are removed, and only
10
+ -- when they carry one piece and no review checkpoint. A revision that reserved a removed
11
+ -- copy is pointed at the kept held row, which is the same unstarted step. A copy some
12
+ -- revision reserved while a finished row exists is left alone: pointing that revision at
13
+ -- the finished result would change what it shows. Finished, running, failed and admitted
14
+ -- work, and assets, are never touched.
15
+ CREATE TEMP TABLE m0022_eligible AS
16
+ SELECT w.id AS work_id, p.id AS piece_id, w.project_id, p.work_key, p.fingerprint,
17
+ w.rowid AS rid, w.state = 'done' AS done
18
+ FROM revision_work w JOIN revision_work_pieces p ON p.work_id = w.id
19
+ WHERE (w.state = 'done' AND p.state = 'done')
20
+ OR (w.state = 'pending' AND w.dispatch_state = 'held' AND p.state = 'held'
21
+ AND p.submitted_at IS NULL AND w.recipe_context IS NULL
22
+ AND NOT EXISTS (SELECT 1 FROM attempts a WHERE a.work_id = w.id));
23
+
24
+ CREATE TEMP TABLE m0022_kept AS
25
+ SELECT project_id, work_key, fingerprint, work_id, piece_id, done FROM (
26
+ SELECT e.*, row_number() OVER (
27
+ PARTITION BY project_id, work_key, fingerprint ORDER BY done DESC, rid DESC
28
+ ) AS pick
29
+ FROM m0022_eligible e
30
+ ) WHERE pick = 1;
31
+
32
+ CREATE TEMP TABLE m0022_copies AS
33
+ SELECT e.work_id, k.work_id AS kept_work_id, k.piece_id AS kept_piece_id
34
+ FROM m0022_eligible e
35
+ JOIN m0022_kept k ON k.project_id = e.project_id AND k.work_key = e.work_key
36
+ AND k.fingerprint = e.fingerprint
37
+ WHERE e.done = 0 AND e.work_id <> k.work_id
38
+ AND (SELECT count(*) FROM revision_work_pieces q WHERE q.work_id = e.work_id) = 1
39
+ AND NOT EXISTS (SELECT 1 FROM review_checkpoints c WHERE c.work_id = e.work_id)
40
+ AND (k.done = 0
41
+ OR NOT EXISTS (SELECT 1 FROM revision_work_reservations r WHERE r.work_id = e.work_id));
42
+
43
+ UPDATE revision_work_reservations
44
+ SET piece_id = CASE WHEN piece_id IS NULL THEN NULL ELSE (
45
+ SELECT c.kept_piece_id FROM m0022_copies c WHERE c.work_id = revision_work_reservations.work_id
46
+ ) END,
47
+ work_id = (
48
+ SELECT c.kept_work_id FROM m0022_copies c WHERE c.work_id = revision_work_reservations.work_id
49
+ )
50
+ WHERE work_id IN (SELECT work_id FROM m0022_copies);
51
+
52
+ DELETE FROM revision_work WHERE id IN (SELECT work_id FROM m0022_copies);
53
+
54
+ DROP TABLE m0022_eligible;
55
+ DROP TABLE m0022_kept;
56
+ DROP TABLE m0022_copies;
@@ -0,0 +1,18 @@
1
+ -- A Play draft can carry an uploaded establishing image (the Images stage's reference). SQLite
2
+ -- cannot widen a CHECK constraint in place, so the attachments table is rebuilt with the new
3
+ -- kind. Nothing references it, so enforcement stays on.
4
+ CREATE TABLE play_draft_attachments_reference_upgrade (
5
+ id TEXT PRIMARY KEY,
6
+ draft_id TEXT NOT NULL REFERENCES play_drafts(id) ON DELETE CASCADE,
7
+ staged_file_id TEXT REFERENCES staged_files(id) ON DELETE SET NULL,
8
+ kind TEXT NOT NULL CHECK(kind IN ('audio','images','thumbnail','reference')),
9
+ original_filename TEXT NOT NULL,
10
+ status TEXT NOT NULL CHECK(status IN ('pending','ready','reattach')),
11
+ error TEXT
12
+ );
13
+ INSERT INTO play_draft_attachments_reference_upgrade (id,draft_id,staged_file_id,kind,original_filename,status,error)
14
+ SELECT id,draft_id,staged_file_id,kind,original_filename,status,error FROM play_draft_attachments ORDER BY rowid;
15
+ DROP TABLE play_draft_attachments;
16
+ ALTER TABLE play_draft_attachments_reference_upgrade RENAME TO play_draft_attachments;
17
+ CREATE INDEX play_draft_attachment_file ON play_draft_attachments(staged_file_id);
18
+ CREATE INDEX play_draft_attachment_owner ON play_draft_attachments(draft_id);
@@ -0,0 +1,40 @@
1
+ -- Automatic reviews. Library prompts of the new "review" kind are what a reviewer model is
2
+ -- asked per stage; SQLite cannot change a CHECK in place, so the table is copied into one whose
3
+ -- CHECK lists the new kind, like 0019 did for "shorts".
4
+ CREATE TABLE prompts_review_upgrade (
5
+ id TEXT PRIMARY KEY,
6
+ kind TEXT NOT NULL CHECK (kind IN ('article','image','thumbnail','narration','description','shorts','review')),
7
+ name TEXT NOT NULL,
8
+ body TEXT NOT NULL,
9
+ slots TEXT NOT NULL,
10
+ updated_at TEXT NOT NULL
11
+ );
12
+ INSERT INTO prompts_review_upgrade (id,kind,name,body,slots,updated_at)
13
+ SELECT id,kind,name,body,slots,updated_at FROM prompts;
14
+ DROP TABLE prompts;
15
+ ALTER TABLE prompts_review_upgrade RENAME TO prompts;
16
+ CREATE UNIQUE INDEX prompts_name ON prompts(kind,lower(name));
17
+ -- Every verdict a reviewer gave, with its reasons, what Slopify did about it (passed, flagged,
18
+ -- or sent back to be made again) and what the person did (overruled it, or had it redone).
19
+ -- One row per review step fingerprint: the same review of the same output is not asked twice.
20
+ CREATE TABLE review_verdicts (
21
+ id TEXT PRIMARY KEY,
22
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
23
+ revision_id TEXT NOT NULL,
24
+ item_key TEXT NOT NULL,
25
+ stage TEXT NOT NULL CHECK(stage IN ('article','images','narration','thumbnail','shorts')),
26
+ item_fingerprint TEXT NOT NULL,
27
+ review_fingerprint TEXT NOT NULL,
28
+ passed INTEGER NOT NULL CHECK(passed IN (0,1)),
29
+ reasons TEXT NOT NULL,
30
+ outcome TEXT NOT NULL CHECK(outcome IN ('passed','flagged','redo')),
31
+ attempt INTEGER NOT NULL CHECK(attempt >= 1),
32
+ action TEXT CHECK(action IN ('overruled','redone')),
33
+ action_at TEXT,
34
+ redo_state TEXT CHECK(redo_state IN ('pending','started','failed')),
35
+ redo_error TEXT,
36
+ created_at TEXT NOT NULL,
37
+ UNIQUE(project_id,review_fingerprint)
38
+ );
39
+ CREATE INDEX review_verdicts_item ON review_verdicts(project_id,item_key);
40
+ CREATE INDEX review_verdicts_redo ON review_verdicts(redo_state);
@@ -0,0 +1,23 @@
1
+ -- A schedule can find its own topics: a series brief, a generation mode and how many topics to
2
+ -- keep queued. `topic_llm_json` is NULL for the template's own LLM. The generating/failed
3
+ -- columns are the generation's own bookkeeping and never bump the schedule's version.
4
+ ALTER TABLE schedules ADD COLUMN brief TEXT;
5
+ ALTER TABLE schedules ADD COLUMN topic_mode TEXT NOT NULL DEFAULT 'off' CHECK(topic_mode IN ('off','queue','hold'));
6
+ ALTER TABLE schedules ADD COLUMN topic_min INTEGER NOT NULL DEFAULT 10 CHECK(topic_min BETWEEN 1 AND 100);
7
+ ALTER TABLE schedules ADD COLUMN topic_llm_json TEXT CHECK(topic_llm_json IS NULL OR json_valid(topic_llm_json));
8
+ ALTER TABLE schedules ADD COLUMN topics_generating_at TEXT;
9
+ ALTER TABLE schedules ADD COLUMN topics_generated_at TEXT;
10
+ ALTER TABLE schedules ADD COLUMN topics_failed_at TEXT;
11
+ ALTER TABLE schedules ADD COLUMN topics_error TEXT;
12
+ -- Topics outside the queue: generated ones held for approval, ones the user rejected, and ones
13
+ -- a run used. Rejected and used rows stay so a later generation never suggests them again.
14
+ CREATE TABLE schedule_topics (
15
+ id TEXT PRIMARY KEY,
16
+ schedule_id TEXT NOT NULL REFERENCES schedules(id) ON DELETE CASCADE,
17
+ title TEXT NOT NULL CHECK(length(trim(title)) BETWEEN 1 AND 200),
18
+ state TEXT NOT NULL CHECK(state IN ('held','rejected','used')),
19
+ rank INTEGER NOT NULL,
20
+ created_at TEXT NOT NULL,
21
+ decided_at TEXT
22
+ );
23
+ CREATE INDEX schedule_topics_schedule ON schedule_topics(schedule_id, state, rank);
@@ -0,0 +1,58 @@
1
+ -- What every successful provider call used and cost, for the project's Run cost tab. The price
2
+ -- is worked out when the call lands and stored with the rates it used, so a later catalogue
3
+ -- change never rewrites what a finished run cost. A CLI call costs nothing on the user's plan
4
+ -- (`on_plan`); `api_cost` is what the same tokens would have cost through the API.
5
+ CREATE TABLE provider_usage (
6
+ id TEXT PRIMARY KEY,
7
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
8
+ stage TEXT NOT NULL,
9
+ kind TEXT NOT NULL CHECK(kind IN ('llm','tts','image','video')),
10
+ provider TEXT NOT NULL,
11
+ model TEXT NOT NULL,
12
+ tokens_in INTEGER,
13
+ tokens_out INTEGER,
14
+ tokens_cached INTEGER,
15
+ characters INTEGER,
16
+ images INTEGER,
17
+ seconds REAL,
18
+ size TEXT,
19
+ quality TEXT,
20
+ wall_ms INTEGER NOT NULL,
21
+ on_plan INTEGER NOT NULL DEFAULT 0 CHECK(on_plan IN (0,1)),
22
+ cost REAL,
23
+ api_model TEXT,
24
+ api_cost REAL,
25
+ price_json TEXT CHECK(price_json IS NULL OR json_valid(price_json)),
26
+ created_at TEXT NOT NULL
27
+ );
28
+ CREATE INDEX provider_usage_project ON provider_usage(project_id, created_at);
29
+
30
+ -- The plan windows a CLI reported around a project's calls, in the order they arrived, so the
31
+ -- tab can say how much of the 5-hour and weekly allowance the run took.
32
+ CREATE TABLE plan_limit_readings (
33
+ id TEXT PRIMARY KEY,
34
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
35
+ account TEXT NOT NULL,
36
+ reading_json TEXT NOT NULL CHECK(json_valid(reading_json)),
37
+ created_at TEXT NOT NULL
38
+ );
39
+ CREATE INDEX plan_limit_readings_project ON plan_limit_readings(project_id, created_at);
40
+
41
+ -- A CLI whose plan allowance is used up, until when. One row per account: every project's
42
+ -- calls to it wait on the same reset.
43
+ CREATE TABLE plan_limit_waits (
44
+ account TEXT PRIMARY KEY,
45
+ resets_at TEXT,
46
+ retry_at TEXT NOT NULL,
47
+ detected_at TEXT NOT NULL
48
+ );
49
+
50
+ -- The stages waiting on an account. They outlive a restart: the boot resumes these projects
51
+ -- instead of leaving the stage interrupted.
52
+ CREATE TABLE plan_limit_waiters (
53
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
54
+ stage TEXT NOT NULL,
55
+ account TEXT NOT NULL,
56
+ since TEXT NOT NULL,
57
+ PRIMARY KEY(project_id, stage, account)
58
+ );
@@ -0,0 +1,18 @@
1
+ -- A step that failed on something time can fix (a rate limit, a timeout, a dropped connection
2
+ -- or CLI) waits and runs again by itself. The wait is kept on the row, so a restart keeps it,
3
+ -- and the count is what caps it. `failure_kind` is the provider error's kind, which picks the
4
+ -- fix-it button a failed step shows.
5
+ ALTER TABLE revision_work ADD COLUMN auto_retries INTEGER NOT NULL DEFAULT 0;
6
+ ALTER TABLE revision_work ADD COLUMN retry_at TEXT;
7
+ ALTER TABLE revision_work ADD COLUMN failure_kind TEXT;
8
+ CREATE INDEX revision_work_retry ON revision_work(retry_at) WHERE retry_at IS NOT NULL;
9
+ ALTER TABLE stages ADD COLUMN retry_at TEXT;
10
+ ALTER TABLE stages ADD COLUMN failure_kind TEXT;
11
+ -- Soften and retry: the image step whose prompt a content filter refused rewords it with the
12
+ -- project's AI model before drawing again. One request per step, cleared once it succeeds.
13
+ CREATE TABLE prompt_softening (
14
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
15
+ work_key TEXT NOT NULL,
16
+ requested_at TEXT NOT NULL,
17
+ PRIMARY KEY(project_id, work_key)
18
+ );
@@ -0,0 +1,33 @@
1
+ -- Every save of a Library prompt or intro/outro keeps a version, so History can show and
2
+ -- restore any of them. One table serves both libraries: `item_kind` says which, `kind` holds the
3
+ -- prompt's kind or the entry's category, and `mode` the entry's mode (NULL for a prompt). No
4
+ -- foreign key: the row it belongs to lives in one of two tables, and the save path deletes the
5
+ -- versions with the prompt or entry.
6
+ CREATE TABLE library_versions (
7
+ item_kind TEXT NOT NULL CHECK(item_kind IN ('prompt','entry')),
8
+ item_id TEXT NOT NULL,
9
+ version INTEGER NOT NULL CHECK(version >= 1),
10
+ kind TEXT NOT NULL,
11
+ mode TEXT CHECK(mode IS NULL OR mode IN ('text','llm')),
12
+ name TEXT NOT NULL,
13
+ body TEXT NOT NULL,
14
+ author TEXT NOT NULL,
15
+ restored_from INTEGER,
16
+ created_at TEXT NOT NULL,
17
+ PRIMARY KEY(item_kind,item_id,version)
18
+ );
19
+ -- What is saved today becomes version 1 of each.
20
+ INSERT INTO library_versions (item_kind,item_id,version,kind,mode,name,body,author,restored_from,created_at)
21
+ SELECT 'prompt',id,1,kind,NULL,name,body,'you',NULL,updated_at FROM prompts;
22
+ INSERT INTO library_versions (item_kind,item_id,version,kind,mode,name,body,author,restored_from,created_at)
23
+ SELECT 'entry',id,1,category,mode,name,body,'you',NULL,updated_at FROM entries;
24
+ -- The project page's hand edits to the YouTube description, chapters, hashtags and tags. Each
25
+ -- field keeps the user's text and the generated text it was edited from, so a regenerated
26
+ -- description can tell whether the user changed that field. `links_json` holds this project's
27
+ -- own channel links (such as "Previous video"), which win over the Settings list.
28
+ CREATE TABLE youtube_description_edits (
29
+ project_id TEXT PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
30
+ fields_json TEXT NOT NULL CHECK(json_valid(fields_json)),
31
+ links_json TEXT NOT NULL CHECK(json_valid(links_json)),
32
+ updated_at TEXT NOT NULL
33
+ );
@@ -0,0 +1,65 @@
1
+ -- Channels: the brand kit, the cast library and the series brief that templates, schedules
2
+ -- and projects belong to. Every install gets one default channel, "My channel", with a fixed
3
+ -- id so a backup made on one install names the same default on another, and everything that
4
+ -- exists moves into it. Nothing about a project's config changes, so no fingerprint does.
5
+ --
6
+ -- A template's channel is a plain column and a project's is a row of project_channels, neither
7
+ -- with a foreign key to channels: a backup can bring in a row naming a channel this install
8
+ -- never had, and such a row (like none at all) reads as the default channel. The projects
9
+ -- table itself is left as it is. A schedule's channel is its template's.
10
+ CREATE TABLE channels (
11
+ id TEXT PRIMARY KEY,
12
+ name TEXT NOT NULL CHECK(length(trim(name)) BETWEEN 1 AND 200),
13
+ is_default INTEGER NOT NULL DEFAULT 0 CHECK(is_default IN (0,1)),
14
+ brand_json TEXT NOT NULL DEFAULT '{}' CHECK(json_valid(brand_json)),
15
+ series_brief TEXT NOT NULL DEFAULT '',
16
+ version INTEGER NOT NULL DEFAULT 1 CHECK(version >= 1),
17
+ created_at TEXT NOT NULL,
18
+ updated_at TEXT NOT NULL
19
+ );
20
+ CREATE UNIQUE INDEX channels_one_default ON channels(is_default) WHERE is_default = 1;
21
+ INSERT INTO channels (id, name, is_default, created_at, updated_at) VALUES (
22
+ '00000000-0000-4000-8000-000000000001', 'My channel', 1,
23
+ strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
24
+ );
25
+ -- The characters, creatures, places and objects a channel keeps drawing the same way.
26
+ CREATE TABLE cast_members (
27
+ id TEXT PRIMARY KEY,
28
+ channel_id TEXT NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
29
+ kind TEXT NOT NULL CHECK(kind IN ('character','creature','place','object')),
30
+ name TEXT NOT NULL CHECK(length(trim(name)) BETWEEN 1 AND 200),
31
+ aliases_json TEXT NOT NULL DEFAULT '[]' CHECK(json_valid(aliases_json)),
32
+ description TEXT NOT NULL DEFAULT '',
33
+ version INTEGER NOT NULL DEFAULT 1 CHECK(version >= 1),
34
+ created_at TEXT NOT NULL,
35
+ updated_at TEXT NOT NULL
36
+ );
37
+ CREATE INDEX cast_members_channel ON cast_members(channel_id);
38
+ -- Picture bytes by content hash: cast reference images and the brand kit's end screen image.
39
+ -- A project names the hashes it was started with, so a row is never removed while it may be
40
+ -- named; the bytes are in the database so every database backup carries them.
41
+ CREATE TABLE image_blobs (
42
+ sha256 TEXT PRIMARY KEY,
43
+ mime TEXT NOT NULL CHECK(mime IN ('image/png','image/jpeg')),
44
+ bytes BLOB NOT NULL,
45
+ created_at TEXT NOT NULL
46
+ );
47
+ CREATE TABLE cast_images (
48
+ id TEXT PRIMARY KEY,
49
+ member_id TEXT NOT NULL REFERENCES cast_members(id) ON DELETE CASCADE,
50
+ source TEXT NOT NULL CHECK(source IN ('upload','generate')),
51
+ prompt TEXT,
52
+ state TEXT NOT NULL CHECK(state IN ('ready','generating','failed')),
53
+ error TEXT,
54
+ sha256 TEXT REFERENCES image_blobs(sha256),
55
+ created_at TEXT NOT NULL
56
+ );
57
+ CREATE INDEX cast_images_member ON cast_images(member_id);
58
+ ALTER TABLE project_templates ADD COLUMN channel_id TEXT;
59
+ CREATE TABLE project_channels (
60
+ project_id TEXT PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
61
+ channel_id TEXT NOT NULL
62
+ );
63
+ UPDATE project_templates SET channel_id = '00000000-0000-4000-8000-000000000001';
64
+ INSERT INTO project_channels (project_id, channel_id)
65
+ SELECT id, '00000000-0000-4000-8000-000000000001' FROM projects;
@@ -0,0 +1,23 @@
1
+ -- Multiple voices.
2
+ --
3
+ -- Library prompts of the new "script" kind: what a multi-voice run (audiobook, podcast, radio
4
+ -- drama, interview) asks the text model for in place of an article - speaker turns. SQLite
5
+ -- cannot change a CHECK in place, so the table is copied into one whose CHECK lists the new
6
+ -- kind, like 0025 did for "review".
7
+ CREATE TABLE prompts_script_upgrade (
8
+ id TEXT PRIMARY KEY,
9
+ kind TEXT NOT NULL CHECK (kind IN ('article','image','thumbnail','narration','description','shorts','review','script')),
10
+ name TEXT NOT NULL,
11
+ body TEXT NOT NULL,
12
+ slots TEXT NOT NULL,
13
+ updated_at TEXT NOT NULL
14
+ );
15
+ INSERT INTO prompts_script_upgrade (id,kind,name,body,slots,updated_at)
16
+ SELECT id,kind,name,body,slots,updated_at FROM prompts;
17
+ DROP TABLE prompts;
18
+ ALTER TABLE prompts_script_upgrade RENAME TO prompts;
19
+ CREATE UNIQUE INDEX prompts_name ON prompts(kind,lower(name));
20
+ -- A cast member's voice: provider, model, voice, pace and pronunciations, so a character or a
21
+ -- channel's host sounds the same in every episode. NULL is no voice, which every member saved
22
+ -- before this has.
23
+ ALTER TABLE cast_members ADD COLUMN voice_json TEXT CHECK (voice_json IS NULL OR json_valid(voice_json));
@@ -0,0 +1,13 @@
1
+ -- Library → Aliases: words the narrator says differently from how they are written ("Dr." as
2
+ -- "Doctor"). A project keeps its own copy of the list it started with, so nothing references
3
+ -- these rows and editing them never changes a project behind its back. `position` is the
4
+ -- order the editor shows them in.
5
+ CREATE TABLE narration_aliases (
6
+ id TEXT PRIMARY KEY,
7
+ position INTEGER NOT NULL,
8
+ written TEXT NOT NULL CHECK (length(trim(written)) > 0),
9
+ spoken TEXT NOT NULL CHECK (length(trim(spoken)) > 0),
10
+ whole_word INTEGER NOT NULL CHECK (whole_word IN (0, 1)),
11
+ case_sensitive INTEGER NOT NULL CHECK (case_sensitive IN (0, 1)),
12
+ updated_at TEXT NOT NULL
13
+ );
@@ -0,0 +1,8 @@
1
+ -- "Mark uploaded" on Home: the person says a finished video is on YouTube, so it leaves Ready
2
+ -- to upload. A row of its own rather than a projects column, like project_channels: nothing
3
+ -- about the project's config changes, so no fingerprint does, and deleting the project takes
4
+ -- the row with it.
5
+ CREATE TABLE project_uploads (
6
+ project_id TEXT PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
7
+ uploaded_at TEXT NOT NULL
8
+ );
@@ -0,0 +1,7 @@
1
+ -- Other languages.
2
+ --
3
+ -- The languages a saved voice speaks, as a JSON array of primary language codes (["es"]):
4
+ -- looked up from the provider when the voice is added, or typed in Settings → Voices. Play
5
+ -- lists a project's voices by it. NULL is unknown, which every voice saved before this is,
6
+ -- and an unknown voice is offered for every language.
7
+ ALTER TABLE voices ADD COLUMN languages_json TEXT CHECK (languages_json IS NULL OR json_valid(languages_json));
@@ -0,0 +1,64 @@
1
+ -- Channel essentials.
2
+ --
3
+ -- Trash: deleting a project, a Library prompt or intro/outro, or a template now only stamps
4
+ -- `deleted_at`; Settings → Trash lists the stamped rows for 30 days, and a daily purge removes
5
+ -- them for good. Every list and lookup skips stamped rows. The name indexes only count rows
6
+ -- that are not in the trash, so a name can be reused while the old one waits there. A later
7
+ -- migration that rebuilds one of these tables must carry `deleted_at` over.
8
+ --
9
+ -- A project's stamp lives in its own table rather than a column: the projects table is read
10
+ -- and written positionally in many places, and a project has no name index to relax.
11
+ CREATE TABLE project_trash (
12
+ project_id TEXT PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
13
+ deleted_at TEXT NOT NULL
14
+ );
15
+ ALTER TABLE prompts ADD COLUMN deleted_at TEXT;
16
+ ALTER TABLE entries ADD COLUMN deleted_at TEXT;
17
+ ALTER TABLE project_templates ADD COLUMN deleted_at TEXT;
18
+ DROP INDEX prompts_name;
19
+ CREATE UNIQUE INDEX prompts_name ON prompts(kind,lower(name)) WHERE deleted_at IS NULL;
20
+ DROP INDEX entries_name;
21
+ CREATE UNIQUE INDEX entries_name ON entries(category,lower(name)) WHERE deleted_at IS NULL;
22
+ -- A schedule already kept a `deleted_at` (0010: its history outlives it). `purged_at` marks one
23
+ -- that has left the trash; every schedule deleted before the trash existed counts as left.
24
+ ALTER TABLE schedules ADD COLUMN purged_at TEXT;
25
+ UPDATE schedules SET purged_at = deleted_at WHERE deleted_at IS NOT NULL;
26
+
27
+ -- Episode memory: a short summary each finished project leaves on its channel, read back into
28
+ -- the article and script prompts of later related episodes while the channel's setting is on.
29
+ -- No foreign key to projects: the memory outlives the project it came from.
30
+ CREATE TABLE episode_memories (
31
+ id TEXT PRIMARY KEY,
32
+ channel_id TEXT NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
33
+ project_id TEXT NOT NULL,
34
+ title TEXT NOT NULL,
35
+ summary TEXT NOT NULL CHECK(length(trim(summary)) > 0),
36
+ cast_json TEXT NOT NULL DEFAULT '[]' CHECK(json_valid(cast_json)),
37
+ source TEXT NOT NULL CHECK(source IN ('generated','edited')),
38
+ created_at TEXT NOT NULL,
39
+ updated_at TEXT NOT NULL
40
+ );
41
+ CREATE UNIQUE INDEX episode_memories_project ON episode_memories(project_id);
42
+ CREATE INDEX episode_memories_channel ON episode_memories(channel_id, created_at);
43
+ -- Off for every channel that exists; a channel made from now on starts with it on. A fresh
44
+ -- install (no project yet) counts its default channel as new.
45
+ ALTER TABLE channels ADD COLUMN episode_memory INTEGER NOT NULL DEFAULT 0 CHECK(episode_memory IN (0,1));
46
+ UPDATE channels SET episode_memory = 1 WHERE NOT EXISTS (SELECT 1 FROM projects);
47
+ -- YouTube's "Altered or synthetic content" answer Studio prep gives: 'auto' follows the rule in
48
+ -- slices/studio/disclosure.ts, 'yes' and 'no' override it for the channel.
49
+ ALTER TABLE channels ADD COLUMN ai_disclosure TEXT NOT NULL DEFAULT 'auto' CHECK(ai_disclosure IN ('auto','yes','no'));
50
+
51
+ -- The channel's videos made before (or outside) Slopify: titles pasted or imported from a
52
+ -- YouTube Studio CSV, so topic suggestions and duplicate checks skip them too.
53
+ CREATE TABLE channel_videos (
54
+ id TEXT PRIMARY KEY,
55
+ channel_id TEXT NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
56
+ title TEXT NOT NULL CHECK(length(trim(title)) BETWEEN 1 AND 500),
57
+ created_at TEXT NOT NULL
58
+ );
59
+ CREATE UNIQUE INDEX channel_videos_title ON channel_videos(channel_id, lower(title));
60
+
61
+ -- Episode memory (appended): what a generated summary was made from, a SHA-256 of the prompt
62
+ -- version, title and article text, so finishing the same article again asks no LLM. A summary
63
+ -- the user edited is never replaced by a later finish.
64
+ ALTER TABLE episode_memories ADD COLUMN recipe TEXT;
@@ -0,0 +1,35 @@
1
+ -- What every successful provider call made outside a project used and cost: a schedule's
2
+ -- topic generation, a channel's episode summary and its cast pictures. provider_usage cannot
3
+ -- hold them, since each of its rows belongs to a project. Priced the same way when the call
4
+ -- lands. There is no link to the schedule or channel: deleting one keeps what it cost this
5
+ -- week. `channel_id` is the channel the call was made for, resolved when it landed, for Home's
6
+ -- channel filter. A CLI's plan windows reported around the call ride along (`account`,
7
+ -- `reading_json`), so Home's plan standings read them beside a project's.
8
+ CREATE TABLE standalone_usage (
9
+ id TEXT PRIMARY KEY,
10
+ owner_kind TEXT NOT NULL CHECK(owner_kind IN ('schedule','channel')),
11
+ owner_id TEXT NOT NULL,
12
+ channel_id TEXT NOT NULL,
13
+ purpose TEXT NOT NULL,
14
+ kind TEXT NOT NULL CHECK(kind IN ('llm','tts','image','video')),
15
+ provider TEXT NOT NULL,
16
+ model TEXT NOT NULL,
17
+ tokens_in INTEGER,
18
+ tokens_out INTEGER,
19
+ tokens_cached INTEGER,
20
+ characters INTEGER,
21
+ images INTEGER,
22
+ seconds REAL,
23
+ size TEXT,
24
+ quality TEXT,
25
+ wall_ms INTEGER NOT NULL,
26
+ on_plan INTEGER NOT NULL DEFAULT 0 CHECK(on_plan IN (0,1)),
27
+ cost REAL,
28
+ api_model TEXT,
29
+ api_cost REAL,
30
+ price_json TEXT CHECK(price_json IS NULL OR json_valid(price_json)),
31
+ account TEXT,
32
+ reading_json TEXT CHECK(reading_json IS NULL OR json_valid(reading_json)),
33
+ created_at TEXT NOT NULL
34
+ );
35
+ CREATE INDEX standalone_usage_created ON standalone_usage(created_at);
@@ -1,18 +1,38 @@
1
1
  import { chmodSync, mkdirSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
- export function layout(dataDir) {
3
+ export function layout(dataDir, files) {
4
4
  const root = resolve(dataDir);
5
+ const projects = join(root, "projects");
5
6
  return {
6
7
  dataDir: root,
7
8
  db: join(root, "slopify.db"),
8
- projects: join(root, "projects"),
9
+ projects: files?.projects ?? projects,
10
+ backups: files?.backups ?? join(projects, "Backups"),
11
+ exports: files === undefined ? null : files.exports,
9
12
  staging: join(root, "staging"),
10
13
  logs: join(root, "logs"),
11
14
  lock: join(root, ".lock"),
12
15
  };
13
16
  }
17
+ // Points a running install's Paths at moved files. The one object every slice was handed is
18
+ // changed in place, so the next file each one reads or writes is in the new place.
19
+ export function repoint(paths, files) {
20
+ Object.assign(paths, {
21
+ projects: files.projects,
22
+ backups: files.backups,
23
+ exports: files.exports,
24
+ });
25
+ }
14
26
  export function ensureDirs(paths, options) {
15
- for (const dir of [paths.dataDir, paths.projects, paths.staging, paths.logs]) {
27
+ ensureDataDirs(paths, options);
28
+ ensureOwnerOnly([paths.projects], options);
29
+ }
30
+ // The data dir's own folders, before it is known where the user-visible files go.
31
+ export function ensureDataDirs(paths, options) {
32
+ ensureOwnerOnly([paths.dataDir, paths.staging, paths.logs], options);
33
+ }
34
+ function ensureOwnerOnly(dirs, options) {
35
+ for (const dir of dirs) {
16
36
  mkdirSync(dir, { recursive: true, mode: options.mode });
17
37
  // mkdir's mode is masked by the umask and ignored for a directory that already
18
38
  // exists; provider keys sit in this tree, so the mode has to be forced.
@@ -20,7 +40,8 @@ export function ensureDirs(paths, options) {
20
40
  }
21
41
  }
22
42
  // The subtitle-timing model's cache. The app readies it at start and every captioned render
23
- // reads it, so both name it through here.
24
- export function subtitleModelDir(dataDir) {
25
- return join(dataDir, "models", "english-subtitles");
43
+ // reads it, so both name it through here. Other languages use the multilingual model, kept
44
+ // in a folder of its own and downloaded on first use, never at start.
45
+ export function subtitleModelDir(dataDir, language = "en") {
46
+ return join(dataDir, "models", language === "en" ? "english-subtitles" : "multilingual-subtitles");
26
47
  }