@gentbajko/slopify 2.5.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 (446) 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 +3 -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 +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.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/inworld-async.js +9 -5
  44. package/dist/adapters/tts/inworld.js +17 -3
  45. package/dist/adapters/tts/openai.js +3 -0
  46. package/dist/adapters/tts/voice-languages.js +80 -0
  47. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  48. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  49. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  50. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/OFL.txt +93 -0
  53. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  54. package/dist/assets/sample/sample-audiobook.tar +0 -0
  55. package/dist/assets/sample/sample-podcast.tar +0 -0
  56. package/dist/assets/sample/sample-project.tar +0 -0
  57. package/dist/catalog/merge.js +132 -0
  58. package/dist/catalog/registry.js +13 -13
  59. package/dist/catalog/schema.js +2 -0
  60. package/dist/catalog/store.js +147 -8
  61. package/dist/compose.yaml +71 -0
  62. package/dist/edge/autostart/docker-record.js +32 -0
  63. package/dist/edge/autostart/docker.js +50 -0
  64. package/dist/edge/autostart/index.js +78 -0
  65. package/dist/edge/autostart/launcher.js +176 -0
  66. package/dist/edge/autostart/model.js +3 -0
  67. package/dist/edge/autostart/native.js +210 -0
  68. package/dist/edge/autostart/prompt.js +66 -0
  69. package/dist/edge/autostart/service.js +81 -0
  70. package/dist/edge/cli.js +57 -51
  71. package/dist/edge/docker-install/activation.js +56 -0
  72. package/dist/edge/docker-install/apply.js +458 -0
  73. package/dist/edge/docker-install/engine.js +362 -0
  74. package/dist/edge/docker-install/run.js +154 -0
  75. package/dist/edge/docker-install/state.js +146 -0
  76. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  77. package/dist/edge/docker.js +14 -22
  78. package/dist/edge/host-cli.js +1 -1
  79. package/dist/edge/http/actions.js +32 -10
  80. package/dist/edge/http/app.js +58 -2
  81. package/dist/edge/http/auditions.js +70 -0
  82. package/dist/edge/http/autostart.js +37 -0
  83. package/dist/edge/http/channel-memory.js +92 -0
  84. package/dist/edge/http/channels.js +128 -0
  85. package/dist/edge/http/entries.js +22 -3
  86. package/dist/edge/http/files.js +1 -1
  87. package/dist/edge/http/folder-location.js +1 -1
  88. package/dist/edge/http/home.js +39 -0
  89. package/dist/edge/http/host-cli.js +29 -5
  90. package/dist/edge/http/narration-peaks.js +76 -0
  91. package/dist/edge/http/onboarding.js +152 -0
  92. package/dist/edge/http/open-folder.js +1 -1
  93. package/dist/edge/http/patch-notes.js +45 -0
  94. package/dist/edge/http/planning.js +4 -2
  95. package/dist/edge/http/project-create.js +87 -0
  96. package/dist/edge/http/projects.js +31 -99
  97. package/dist/edge/http/prompts.js +40 -3
  98. package/dist/edge/http/pronunciations.js +23 -4
  99. package/dist/edge/http/providers.js +86 -0
  100. package/dist/edge/http/reviews.js +121 -0
  101. package/dist/edge/http/revision-files.js +1 -1
  102. package/dist/edge/http/revisions.js +1 -1
  103. package/dist/edge/http/run-cost.js +25 -0
  104. package/dist/edge/http/schedules.js +111 -6
  105. package/dist/edge/http/settings.js +84 -6
  106. package/dist/edge/http/storage-files.js +50 -0
  107. package/dist/edge/http/storage.js +29 -2
  108. package/dist/edge/http/studio.js +207 -0
  109. package/dist/edge/http/style-preview.js +100 -0
  110. package/dist/edge/http/subtitles.js +1 -1
  111. package/dist/edge/http/trash.js +69 -0
  112. package/dist/edge/http/update.js +24 -3
  113. package/dist/edge/http/whats-new.js +15 -0
  114. package/dist/edge/http/youtube-edits.js +83 -0
  115. package/dist/edge/native-update.js +62 -0
  116. package/dist/host-cli/open-folder.js +10 -10
  117. package/dist/host-cli/runtime.js +9 -1
  118. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  119. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  120. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  121. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  122. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  123. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  124. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  125. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  126. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  127. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  128. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  129. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  130. package/dist/kernel/paths.js +27 -6
  131. package/dist/kernel/pipeline.js +4 -1
  132. package/dist/kernel/ports/host-cli.js +122 -14
  133. package/dist/kernel/ports/key-probe.js +1 -0
  134. package/dist/kernel/ports/languages.js +157 -0
  135. package/dist/kernel/ports/model.js +5 -2
  136. package/dist/kernel/ports/narration-aliases.js +60 -0
  137. package/dist/kernel/ports/plan-limits.js +36 -0
  138. package/dist/kernel/runner/attempt.js +17 -8
  139. package/dist/kernel/runner/audition.js +36 -0
  140. package/dist/kernel/runner/graph.js +24 -5
  141. package/dist/kernel/runner/index.js +35 -5
  142. package/dist/kernel/runner/meter.js +1 -0
  143. package/dist/kernel/runner/providers.js +142 -7
  144. package/dist/kernel/runner/retry-policy.js +43 -0
  145. package/dist/kernel/runner/standalone.js +134 -0
  146. package/dist/kernel/runner/work-authority.js +28 -2
  147. package/dist/main.js +272 -16
  148. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  149. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  150. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  151. package/dist/patch-notes/3.0.0.md +70 -0
  152. package/dist/patch-notes/index.json +26 -0
  153. package/dist/slices/admission/model.js +27 -0
  154. package/dist/slices/admission/repo.js +24 -4
  155. package/dist/slices/admission/rules.js +79 -1
  156. package/dist/slices/admission/schema.js +58 -1
  157. package/dist/slices/admission/short-mode.js +47 -0
  158. package/dist/slices/admission/start.js +41 -5
  159. package/dist/slices/article/continuation.js +2 -2
  160. package/dist/slices/article/provided-entries.js +2 -2
  161. package/dist/slices/article/run.js +6 -2
  162. package/dist/slices/article/segments.js +3 -3
  163. package/dist/slices/backups/folder.js +14 -6
  164. package/dist/slices/batch/index.js +7 -2
  165. package/dist/slices/cancel/index.js +1 -1
  166. package/dist/slices/channels/cast-images.js +110 -0
  167. package/dist/slices/channels/cast-match.js +32 -0
  168. package/dist/slices/channels/images.js +19 -0
  169. package/dist/slices/channels/model.js +5 -0
  170. package/dist/slices/channels/rebrand.js +100 -0
  171. package/dist/slices/channels/repo.js +218 -0
  172. package/dist/slices/channels/runs.js +129 -0
  173. package/dist/slices/channels/schema.js +129 -0
  174. package/dist/slices/channels/service.js +180 -0
  175. package/dist/slices/channels/studio-csv.js +110 -0
  176. package/dist/slices/channels/videos.js +120 -0
  177. package/dist/slices/checkpoints/recovery.js +10 -0
  178. package/dist/slices/control/index.js +2 -1
  179. package/dist/slices/document/fonts.js +1 -1
  180. package/dist/slices/document/render.js +1 -1
  181. package/dist/slices/episodes/related.js +90 -0
  182. package/dist/slices/episodes/repo.js +60 -0
  183. package/dist/slices/episodes/service.js +58 -0
  184. package/dist/slices/episodes/summarize.js +170 -0
  185. package/dist/slices/estimate/index.js +109 -28
  186. package/dist/slices/estimate/requests.js +60 -2
  187. package/dist/slices/fixes/rules.js +73 -0
  188. package/dist/slices/fonts/coverage.js +82 -0
  189. package/dist/slices/fonts/sfnt.js +74 -0
  190. package/dist/slices/images/run.js +8 -5
  191. package/dist/slices/images/scale.js +84 -0
  192. package/dist/slices/library/diff.js +122 -0
  193. package/dist/slices/library/history.js +100 -0
  194. package/dist/slices/library/model.js +3 -0
  195. package/dist/slices/library/photorealistic.js +49 -0
  196. package/dist/slices/library/repo.js +19 -14
  197. package/dist/slices/library/save.js +62 -12
  198. package/dist/slices/library/slots.js +7 -1
  199. package/dist/slices/library/used-by.js +153 -0
  200. package/dist/slices/loudness/level-pieces.js +41 -0
  201. package/dist/slices/loudness/loudnorm.js +164 -0
  202. package/dist/slices/loudness/model.js +153 -0
  203. package/dist/slices/model-upkeep/model.js +10 -0
  204. package/dist/slices/model-upkeep/switch.js +217 -0
  205. package/dist/slices/model-upkeep/usage.js +183 -0
  206. package/dist/slices/narration/aliases-library.js +31 -0
  207. package/dist/slices/narration/aliases-schema.js +53 -0
  208. package/dist/slices/narration/aliases.js +49 -0
  209. package/dist/slices/narration/blocks.js +352 -0
  210. package/dist/slices/narration/chunk.js +19 -7
  211. package/dist/slices/narration/concat.js +2 -2
  212. package/dist/slices/narration/describe.js +45 -0
  213. package/dist/slices/narration/pauses-model.js +63 -0
  214. package/dist/slices/narration/pauses.js +269 -0
  215. package/dist/slices/narration/peaks-model.js +1 -0
  216. package/dist/slices/narration/peaks.js +94 -0
  217. package/dist/slices/narration/preparation.js +21 -3
  218. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  219. package/dist/slices/narration/pronunciation.js +72 -25
  220. package/dist/slices/narration/run.js +6 -6
  221. package/dist/slices/narration/shared-glossary.js +1 -1
  222. package/dist/slices/narration/steering.js +15 -4
  223. package/dist/slices/notifications/notifier.js +21 -3
  224. package/dist/slices/notifications/rules.js +22 -1
  225. package/dist/slices/onboarding/copy.js +143 -0
  226. package/dist/slices/onboarding/first-run.js +46 -0
  227. package/dist/slices/onboarding/install.js +119 -0
  228. package/dist/slices/onboarding/model.js +64 -0
  229. package/dist/slices/onboarding/packs.js +491 -0
  230. package/dist/slices/onboarding/quick-short.js +129 -0
  231. package/dist/slices/onboarding/sample.js +132 -0
  232. package/dist/slices/onboarding/state.js +68 -0
  233. package/dist/slices/onboarding/template.js +73 -0
  234. package/dist/slices/patch-notes/library.js +73 -0
  235. package/dist/slices/patch-notes/seen.js +77 -0
  236. package/dist/slices/play-drafts/convert.js +125 -6
  237. package/dist/slices/play-drafts/readiness.js +3 -0
  238. package/dist/slices/play-drafts/repo.js +2 -0
  239. package/dist/slices/play-drafts/review-inputs.js +20 -3
  240. package/dist/slices/play-drafts/schema.js +89 -0
  241. package/dist/slices/play-drafts/service.js +4 -1
  242. package/dist/slices/play-drafts/start.js +43 -18
  243. package/dist/slices/project-templates/from-project.js +50 -4
  244. package/dist/slices/project-templates/one-off.js +26 -0
  245. package/dist/slices/project-templates/repo.js +2 -1
  246. package/dist/slices/project-templates/service.js +15 -4
  247. package/dist/slices/project-templates/setup.js +18 -2
  248. package/dist/slices/rebuild/admission-repo.js +1 -1
  249. package/dist/slices/rebuild/model.js +6 -0
  250. package/dist/slices/rebuild/preview-details.js +46 -3
  251. package/dist/slices/rebuild/preview-plan.js +13 -6
  252. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  253. package/dist/slices/rebuild/recipe-audio.js +66 -12
  254. package/dist/slices/rebuild/recipe-build.js +8 -4
  255. package/dist/slices/rebuild/recipe-cast.js +18 -0
  256. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  257. package/dist/slices/rebuild/recipe-describe.js +93 -0
  258. package/dist/slices/rebuild/recipe-edit.js +13 -1
  259. package/dist/slices/rebuild/recipe-exports.js +62 -8
  260. package/dist/slices/rebuild/recipe-input-schema.js +35 -0
  261. package/dist/slices/rebuild/recipe-legacy.js +9 -0
  262. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  263. package/dist/slices/rebuild/recipe-model.js +25 -6
  264. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  265. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  266. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  267. package/dist/slices/rebuild/recipe-reference.js +15 -1
  268. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  269. package/dist/slices/rebuild/recipe-save.js +4 -1
  270. package/dist/slices/rebuild/recipe-shorts.js +23 -3
  271. package/dist/slices/rebuild/recipe-text.js +114 -19
  272. package/dist/slices/rebuild/recipe-validation.js +14 -2
  273. package/dist/slices/rebuild/recipe-visual.js +96 -33
  274. package/dist/slices/rebuild/recipe-voices.js +286 -0
  275. package/dist/slices/rebuild/recipe-work.js +77 -6
  276. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  277. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  278. package/dist/slices/rebuild/recovery-model.js +3 -0
  279. package/dist/slices/rebuild/recovery-selection.js +28 -2
  280. package/dist/slices/rebuild/recovery.js +35 -17
  281. package/dist/slices/rebuild/repo.js +3 -3
  282. package/dist/slices/rebuild/retry.js +18 -0
  283. package/dist/slices/rebuild/review-redo.js +110 -0
  284. package/dist/slices/rebuild/runtime-actions.js +5 -0
  285. package/dist/slices/rebuild/runtime-admission.js +4 -1
  286. package/dist/slices/rebuild/runtime-animate.js +4 -4
  287. package/dist/slices/rebuild/runtime-article.js +5 -5
  288. package/dist/slices/rebuild/runtime-document.js +4 -4
  289. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  290. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  291. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  292. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  293. package/dist/slices/rebuild/runtime-export.js +84 -19
  294. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  295. package/dist/slices/rebuild/runtime-image.js +21 -2
  296. package/dist/slices/rebuild/runtime-local.js +148 -24
  297. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  298. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  299. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  300. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  301. package/dist/slices/rebuild/runtime-piece-label.js +27 -0
  302. package/dist/slices/rebuild/runtime-provider.js +126 -18
  303. package/dist/slices/rebuild/runtime-publication.js +21 -5
  304. package/dist/slices/rebuild/runtime-review.js +237 -0
  305. package/dist/slices/rebuild/runtime-run.js +17 -11
  306. package/dist/slices/rebuild/runtime-shorts.js +44 -17
  307. package/dist/slices/rebuild/runtime-store.js +39 -10
  308. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  309. package/dist/slices/rebuild/runtime-voices.js +103 -0
  310. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  311. package/dist/slices/rebuild/soften.js +49 -0
  312. package/dist/slices/rebuild/transition-repo.js +9 -2
  313. package/dist/slices/research/run.js +4 -4
  314. package/dist/slices/research/synthesis.js +2 -2
  315. package/dist/slices/reviews/model.js +105 -0
  316. package/dist/slices/reviews/outcome.js +22 -0
  317. package/dist/slices/reviews/repo.js +106 -0
  318. package/dist/slices/reviews/rules.js +63 -0
  319. package/dist/slices/reviews/verdict.js +71 -0
  320. package/dist/slices/reviews/view.js +30 -0
  321. package/dist/slices/revisions/adopt-content.js +5 -2
  322. package/dist/slices/revisions/adopt.js +3 -1
  323. package/dist/slices/revisions/downloads.js +7 -1
  324. package/dist/slices/revisions/image-plan.js +183 -0
  325. package/dist/slices/revisions/mutation-assets.js +10 -0
  326. package/dist/slices/revisions/mutation-cues.js +71 -0
  327. package/dist/slices/revisions/mutation-request.js +2 -1
  328. package/dist/slices/revisions/mutations.js +97 -3
  329. package/dist/slices/revisions/schema.js +11 -1
  330. package/dist/slices/run-cost/limits.js +128 -0
  331. package/dist/slices/run-cost/meter.js +47 -0
  332. package/dist/slices/run-cost/panel.js +234 -0
  333. package/dist/slices/run-cost/pricing.js +92 -0
  334. package/dist/slices/run-cost/week.js +108 -0
  335. package/dist/slices/schedules/agenda.js +158 -0
  336. package/dist/slices/schedules/repo.js +58 -18
  337. package/dist/slices/schedules/scheduler.js +67 -16
  338. package/dist/slices/schedules/schema.js +138 -1
  339. package/dist/slices/schedules/service.js +43 -4
  340. package/dist/slices/schedules/similar.js +90 -0
  341. package/dist/slices/schedules/topic-list.js +163 -0
  342. package/dist/slices/schedules/topics.js +422 -0
  343. package/dist/slices/settings/first-run.js +102 -0
  344. package/dist/slices/settings/health.js +190 -0
  345. package/dist/slices/settings/key-guides.js +128 -0
  346. package/dist/slices/settings/key-test.js +85 -0
  347. package/dist/slices/settings/model.js +6 -1
  348. package/dist/slices/settings/playback.js +15 -0
  349. package/dist/slices/settings/repo.js +17 -3
  350. package/dist/slices/settings/voices.js +64 -4
  351. package/dist/slices/settings/whats-new.js +64 -0
  352. package/dist/slices/shorts/captions.js +9 -0
  353. package/dist/slices/shorts/pick.js +3 -3
  354. package/dist/slices/shorts/prompts.js +1 -1
  355. package/dist/slices/shorts/render.js +54 -5
  356. package/dist/slices/storage/asset-name.js +3 -0
  357. package/dist/slices/storage/backup-export.js +48 -9
  358. package/dist/slices/storage/backup-format.js +38 -1
  359. package/dist/slices/storage/backup-import.js +191 -19
  360. package/dist/slices/storage/delete-project.js +7 -4
  361. package/dist/slices/storage/documents.js +79 -0
  362. package/dist/slices/storage/files-location.js +531 -0
  363. package/dist/slices/storage/layout.js +14 -4
  364. package/dist/slices/storage/model.js +11 -0
  365. package/dist/slices/storage/portable.js +92 -12
  366. package/dist/slices/storage/reconcile.js +14 -1
  367. package/dist/slices/storage/repo.js +4 -1
  368. package/dist/slices/storage/schema.js +11 -0
  369. package/dist/slices/storage/trim.js +146 -0
  370. package/dist/slices/studio/disclosure.js +74 -0
  371. package/dist/slices/studio/model.js +28 -0
  372. package/dist/slices/studio/pack.js +209 -0
  373. package/dist/slices/studio/settings.js +133 -0
  374. package/dist/slices/style-preview/images.js +61 -0
  375. package/dist/slices/style-preview/render.js +161 -0
  376. package/dist/slices/style-preview/schema.js +45 -0
  377. package/dist/slices/style-preview/service.js +93 -0
  378. package/dist/slices/style-preview/settings.js +89 -0
  379. package/dist/slices/subtitles/captions.js +42 -9
  380. package/dist/slices/subtitles/model.js +22 -0
  381. package/dist/slices/subtitles/prepare.js +27 -16
  382. package/dist/slices/subtitles/transcript.js +1 -1
  383. package/dist/slices/thumbnail/run.js +7 -7
  384. package/dist/slices/trash/model.js +25 -0
  385. package/dist/slices/trash/service.js +273 -0
  386. package/dist/slices/uploads/repo.js +20 -0
  387. package/dist/slices/video/ambient-bed-schema.js +15 -0
  388. package/dist/slices/video/ambient-bed.js +95 -0
  389. package/dist/slices/video/ambient-mix.js +72 -0
  390. package/dist/slices/video/audio-inputs.js +2 -2
  391. package/dist/slices/video/cards.js +5 -2
  392. package/dist/slices/video/edit-list.js +23 -2
  393. package/dist/slices/video/edit-settings.js +13 -3
  394. package/dist/slices/video/ffmpeg.js +63 -9
  395. package/dist/slices/video/figure-card.js +492 -0
  396. package/dist/slices/video/figure-spans.js +51 -0
  397. package/dist/slices/video/plan.js +125 -13
  398. package/dist/slices/video/run.js +14 -12
  399. package/dist/slices/video/slideshow.js +39 -4
  400. package/dist/slices/voices/attribution.js +69 -0
  401. package/dist/slices/voices/audio-files.js +82 -0
  402. package/dist/slices/voices/audition.js +25 -0
  403. package/dist/slices/voices/cast.js +56 -0
  404. package/dist/slices/voices/grouping.js +49 -0
  405. package/dist/slices/voices/join.js +35 -0
  406. package/dist/slices/voices/languages.js +25 -0
  407. package/dist/slices/voices/model.js +179 -0
  408. package/dist/slices/voices/palette.js +26 -0
  409. package/dist/slices/voices/panel.js +91 -0
  410. package/dist/slices/voices/portraits.js +55 -0
  411. package/dist/slices/voices/script.js +136 -0
  412. package/dist/slices/voices/timing.js +58 -0
  413. package/dist/slices/youtube/answer.js +1 -1
  414. package/dist/slices/youtube/chapters.js +126 -0
  415. package/dist/slices/youtube/edits-repo.js +69 -0
  416. package/dist/slices/youtube/edits.js +89 -0
  417. package/dist/slices/youtube/placeholders.js +98 -0
  418. package/dist/slices/youtube/transcript.js +11 -4
  419. package/dist/updater/service.js +66 -21
  420. package/dist/web/assets/index-CUA2sUxU.css +1 -0
  421. package/dist/web/assets/index-CX590kE-.js +303 -0
  422. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  423. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  424. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  425. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  426. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  427. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  428. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  429. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  430. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  431. package/dist/web/assets/{pdf-BLF2i8c2.js → pdf-BG8n_uc5.js} +1 -1
  432. package/dist/web/index.html +2 -2
  433. package/package.json +4 -4
  434. package/dist/edge/docker-launch.js +0 -81
  435. package/dist/edge/docker-projects/activation.js +0 -35
  436. package/dist/edge/docker-projects/claims.js +0 -85
  437. package/dist/edge/docker-projects/committed.js +0 -40
  438. package/dist/edge/docker-projects/engine.js +0 -461
  439. package/dist/edge/docker-projects/install.js +0 -254
  440. package/dist/edge/docker-projects/prune.js +0 -66
  441. package/dist/edge/docker-projects/recover.js +0 -100
  442. package/dist/edge/docker-projects/state.js +0 -225
  443. package/dist/web/assets/index-CktpxlJd.js +0 -171
  444. package/dist/web/assets/index-DB-vOnGU.css +0 -1
  445. package/scripts/docker-run.sh +0 -3
  446. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -0,0 +1,100 @@
1
+ import { createReadStream } from "node:fs";
2
+ import { Readable } from "node:stream";
3
+ import { zValidator } from "@hono/zod-validator";
4
+ import { Hono } from "hono";
5
+ import { z } from "zod";
6
+ import { StylePreviewError } from "../../slices/style-preview/render.js";
7
+ import { stylePreviewRequestSchema } from "../../slices/style-preview/schema.js";
8
+ import { onInvalid, problem, titleOf } from "./problem.js";
9
+ // POST renders (or finds) the preview for a set of caption and Look settings and answers where
10
+ // to play it; GET streams the mp4, with byte ranges so the browser's player can seek.
11
+ const fileParam = z.object({ file: z.string().regex(/^[a-f0-9]{64}\.mp4$/) });
12
+ export function stylePreviewRoutes(deps) {
13
+ return new Hono()
14
+ .post("/", zValidator("json", stylePreviewRequestSchema, onInvalid), async (c) => {
15
+ const previews = deps.stylePreviews;
16
+ if (previews === undefined)
17
+ return unavailable(c);
18
+ try {
19
+ const result = await previews.render(c.req.valid("json"));
20
+ c.header("Cache-Control", "no-store");
21
+ return c.json({
22
+ hash: result.hash,
23
+ url: `/api/style-preview/${result.hash}.mp4?v=${String(result.version)}`,
24
+ cached: result.cached,
25
+ seconds: result.seconds,
26
+ });
27
+ }
28
+ catch (error) {
29
+ if (!(error instanceof StylePreviewError))
30
+ throw error;
31
+ const status = error.reason === "font-missing" ? 409 : error.reason === "ffmpeg-missing" ? 503 : 500;
32
+ return problem(c, {
33
+ status,
34
+ title: titleOf(status),
35
+ detail: error.message,
36
+ extensions: { reason: error.reason },
37
+ });
38
+ }
39
+ })
40
+ .get("/:file", zValidator("param", fileParam, onInvalid), (c) => {
41
+ const saved = deps.stylePreviews?.file(c.req.valid("param").file.slice(0, 64));
42
+ if (saved === undefined)
43
+ return problem(c, {
44
+ status: 404,
45
+ title: titleOf(404),
46
+ detail: "This style preview is no longer saved; Slopify keeps only the most recent ones. Press Render again to make it.",
47
+ });
48
+ const headers = {
49
+ "content-type": "video/mp4",
50
+ "accept-ranges": "bytes",
51
+ "cache-control": "no-cache",
52
+ "x-content-type-options": "nosniff",
53
+ };
54
+ const range = byteRange(c.req.header("range"), saved.bytes);
55
+ if (range === "unsatisfiable")
56
+ return new Response(null, {
57
+ status: 416,
58
+ headers: { ...headers, "content-range": `bytes */${String(saved.bytes)}` },
59
+ });
60
+ const { start, end } = range ?? { start: 0, end: saved.bytes - 1 };
61
+ const body = Readable.toWeb(createReadStream(saved.path, { start, end }));
62
+ return new Response(body, {
63
+ status: range === undefined ? 200 : 206,
64
+ headers: {
65
+ ...headers,
66
+ "content-length": String(end - start + 1),
67
+ ...(range === undefined
68
+ ? {}
69
+ : { "content-range": `bytes ${String(start)}-${String(end)}/${String(saved.bytes)}` }),
70
+ },
71
+ });
72
+ });
73
+ }
74
+ // One `bytes=` range, as browsers ask for media; anything else is answered with the whole file.
75
+ export function byteRange(header, size) {
76
+ const matched = /^bytes=(\d*)-(\d*)$/.exec(header?.trim() ?? "");
77
+ if (matched === null)
78
+ return undefined;
79
+ const [, from = "", to = ""] = matched;
80
+ if (from === "" && to === "")
81
+ return undefined;
82
+ if (from === "") {
83
+ const suffix = Number(to);
84
+ if (suffix === 0)
85
+ return "unsatisfiable";
86
+ return { start: Math.max(0, size - suffix), end: size - 1 };
87
+ }
88
+ const start = Number(from);
89
+ const end = to === "" ? size - 1 : Math.min(Number(to), size - 1);
90
+ if (start >= size || end < start)
91
+ return "unsatisfiable";
92
+ return { start, end };
93
+ }
94
+ function unavailable(c) {
95
+ return problem(c, {
96
+ status: 503,
97
+ title: titleOf(503),
98
+ detail: "The style preview isn't available because this Slopify started without its video renderer. Restart Slopify, then press Render again.",
99
+ });
100
+ }
@@ -16,7 +16,7 @@ function revisionRequired(c) {
16
16
  return problem(c, {
17
17
  status: 409,
18
18
  title: titleOf(409),
19
- detail: "This change is now made from the Edit tab. Choose Edit project, save your change, then use Rebuild affected outputs.",
19
+ detail: "This change is now made from the Edit tab. Choose Edit project, save your change, then use Choose what to remake (in the project's More menu).",
20
20
  extensions: { reason: "revision-required" },
21
21
  });
22
22
  }
@@ -0,0 +1,69 @@
1
+ import { zValidator } from "@hono/zod-validator";
2
+ import { Hono } from "hono";
3
+ import { z } from "zod";
4
+ import { withProjectControl } from "../../slices/control/lock.js";
5
+ import { trashKinds } from "../../slices/trash/model.js";
6
+ import { deleteNow, listTrash, restoreItem } from "../../slices/trash/service.js";
7
+ import { onInvalid, problem, titleOf } from "./problem.js";
8
+ const itemParam = z.object({
9
+ kind: z.enum(trashKinds),
10
+ id: z
11
+ .string()
12
+ .min(1)
13
+ .max(64)
14
+ .regex(/^[0-9A-Za-z_-]+$/),
15
+ });
16
+ const status = {
17
+ "not-found": 404,
18
+ running: 409,
19
+ files: 500,
20
+ "template-in-trash": 409,
21
+ "template-gone": 409,
22
+ };
23
+ const details = {
24
+ "not-found": "This item is no longer in the trash: it was restored or removed for good. Reload Settings → Trash to see what is left.",
25
+ running: "This project is still running. Use Cancel run on the project page first, then try again.",
26
+ files: "Some of this project's files could not be removed. Close any program using files in the project folder, then press Delete now again.",
27
+ "template-in-trash": "This schedule's template is in the trash too. Restore the template first (it is listed here), then restore the schedule.",
28
+ "template-gone": "This schedule's template was removed for good, so the schedule has nothing to run. It cannot be restored; Delete now removes it from the trash.",
29
+ };
30
+ function refused(c, reason, detail) {
31
+ return problem(c, {
32
+ status: status[reason],
33
+ title: titleOf(status[reason]),
34
+ detail: detail === undefined ? details[reason] : `${details[reason]} (${detail})`,
35
+ extensions: { reason },
36
+ });
37
+ }
38
+ // Settings → Trash: the list, Restore and Delete now. The daily purge runs from main.ts.
39
+ export function trashRoutes(deps) {
40
+ const trash = {
41
+ db: deps.db,
42
+ paths: deps.paths,
43
+ clock: deps.clock,
44
+ log: deps.log,
45
+ hasInflight: deps.runner.hasInflight,
46
+ };
47
+ return new Hono()
48
+ .get("/", (c) => c.json({ items: listTrash(trash) }))
49
+ .post("/:kind/:id/restore", zValidator("param", itemParam, onInvalid), (c) => {
50
+ const { kind, id } = c.req.valid("param");
51
+ const result = restoreItem(trash, kind, id);
52
+ if (!result.ok)
53
+ return refused(c, result.reason);
54
+ // A restored project picks up where it was: unfinished work goes on, a queued batch
55
+ // item is back in its place.
56
+ if (kind === "project")
57
+ deps.runner.tick(id);
58
+ return c.json({ restored: result.value });
59
+ })
60
+ .delete("/:kind/:id", zValidator("param", itemParam, onInvalid), async (c) => {
61
+ const { kind, id } = c.req.valid("param");
62
+ const result = kind === "project"
63
+ ? await withProjectControl(deps.db, id, () => deleteNow(trash, kind, id))
64
+ : deleteNow(trash, kind, id);
65
+ if (!result.ok)
66
+ return refused(c, result.reason, result.detail);
67
+ return c.body(null, 204);
68
+ });
69
+ }
@@ -10,7 +10,7 @@ export function updateRoutes(deps) {
10
10
  status: "idle",
11
11
  blockedReason: "This copy of Slopify cannot update itself from the app. Update it the way you installed it, for example by running the launcher again.",
12
12
  };
13
- return new Hono()
13
+ return (new Hono()
14
14
  .get("/ready", (c) => {
15
15
  c.header("Cache-Control", "no-store");
16
16
  if (!deps.updater?.ready(c.req.header("X-Slopify-Update-Token") ?? ""))
@@ -31,6 +31,23 @@ export function updateRoutes(deps) {
31
31
  return c.json(deps.updater === undefined
32
32
  ? unavailable
33
33
  : await deps.updater.check(c.req.query("refresh") === "1"));
34
+ })
35
+ // Drops an update that is waiting for running work to finish.
36
+ .delete("/", async (c) => {
37
+ const origin = c.req.header("Origin");
38
+ if (origin !== undefined && origin !== new URL(c.req.url).origin)
39
+ return problem(c, {
40
+ status: 403,
41
+ title: titleOf(403),
42
+ detail: "For your safety, updates can only be changed from the Slopify page itself. Open Slopify and try again there.",
43
+ });
44
+ if (deps.updater?.cancelWaiting() !== true)
45
+ return problem(c, {
46
+ status: 409,
47
+ title: titleOf(409),
48
+ detail: "No update is waiting to install. Reload the page to see the current update status.",
49
+ });
50
+ return c.json(await deps.updater.check());
34
51
  })
35
52
  .post("/", async (c) => {
36
53
  const origin = c.req.header("Origin");
@@ -41,7 +58,11 @@ export function updateRoutes(deps) {
41
58
  detail: "For your safety, updates can only be started from the Slopify page itself. Open Slopify and try again there.",
42
59
  });
43
60
  if (deps.updater === undefined)
44
- return problem(c, { status: 409, title: titleOf(409), detail: unavailable.blockedReason });
61
+ return problem(c, {
62
+ status: 409,
63
+ title: titleOf(409),
64
+ detail: unavailable.blockedReason,
65
+ });
45
66
  const result = await deps.updater.start();
46
67
  if (result.ok)
47
68
  return c.json(result.info, 202);
@@ -53,5 +74,5 @@ export function updateRoutes(deps) {
53
74
  result.info.error ??
54
75
  "You already have the latest version of Slopify.",
55
76
  });
56
- });
77
+ }));
57
78
  }
@@ -0,0 +1,15 @@
1
+ import { Hono } from "hono";
2
+ import { markPatchNotesSeen } from "../../slices/patch-notes/seen.js";
3
+ import { dismissWhatsNew, readWhatsNew } from "../../slices/settings/whats-new.js";
4
+ // Preserve Hono's inferred route types for the web client.
5
+ export function whatsNewRoutes(deps) {
6
+ return (new Hono()
7
+ .get("/", (c) => c.json(readWhatsNew(deps.db, deps.version)))
8
+ // The tour stands in for the patch notes on a major update (its last step links to
9
+ // them), so closing it also closes this version's notes: never two popups in a row.
10
+ .post("/seen", (c) => {
11
+ const view = dismissWhatsNew(deps.db, deps.version);
12
+ markPatchNotesSeen(deps.db, deps.version);
13
+ return c.json(view);
14
+ }));
15
+ }
@@ -0,0 +1,83 @@
1
+ import { zValidator } from "@hono/zod-validator";
2
+ import { Hono } from "hono";
3
+ import { z } from "zod";
4
+ import { transact } from "../../kernel/db/tx.js";
5
+ import { projectExists } from "../../slices/admission/repo.js";
6
+ import { descriptionFields, withEdit, withoutEdit } from "../../slices/youtube/edits.js";
7
+ import { readDescriptionEdits, writeDescriptionEdits } from "../../slices/youtube/edits-repo.js";
8
+ import { descriptionMaxCharacters } from "../../slices/youtube/model.js";
9
+ import { channelLinksProblem } from "../../slices/youtube/placeholders.js";
10
+ import { onInvalid, problem, titleOf } from "./problem.js";
11
+ import { channelLinksBody } from "./settings.js";
12
+ const idParam = z.object({ id: z.string().min(1).max(64) });
13
+ const fieldParam = idParam.extend({ field: z.enum(descriptionFields) });
14
+ // A field is at most a description long; the bound keeps a pasted novel off the table, and
15
+ // YouTube's own limit is shown beside the field rather than refused here.
16
+ const fieldBody = z.object({
17
+ text: z.string().max(descriptionMaxCharacters * 4),
18
+ base: z.string().max(descriptionMaxCharacters * 4),
19
+ });
20
+ const gone = "This project no longer exists. Go back to Projects to pick another.";
21
+ // The project page's hand edits to the YouTube description (`slices/youtube/edits.ts`). The
22
+ // server keeps them; the page applies the merge rules to the text it shows.
23
+ export function youtubeEditRoutes(deps) {
24
+ const exists = (id) => projectExists(deps.db, id);
25
+ const missing = (c) => problem(c, { status: 404, title: titleOf(404), detail: gone });
26
+ const now = () => deps.clock.now().toISOString();
27
+ return (new Hono()
28
+ .get("/:id/youtube-edits", zValidator("param", idParam, onInvalid), (c) => {
29
+ const { id } = c.req.valid("param");
30
+ return exists(id) ? c.json(readDescriptionEdits(deps.db, id)) : missing(c);
31
+ })
32
+ // Saves the user's text for one field with the generated text it was edited from; text
33
+ // equal to that generated text clears the edit. "Keep mine" is this call with the new
34
+ // generated text as the base.
35
+ .put("/:id/youtube-edits/fields/:field", zValidator("param", fieldParam, onInvalid), zValidator("json", fieldBody, onInvalid), (c) => {
36
+ const { id, field } = c.req.valid("param");
37
+ if (!exists(id))
38
+ return missing(c);
39
+ const { text, base } = c.req.valid("json");
40
+ const saved = transact(deps.db, () => {
41
+ const current = readDescriptionEdits(deps.db, id);
42
+ const next = { ...current, fields: withEdit(current.fields, field, text, base) };
43
+ writeDescriptionEdits(deps.db, id, next, now());
44
+ return next;
45
+ });
46
+ return c.json(saved);
47
+ })
48
+ // "Use it": the field follows the generated text again.
49
+ .delete("/:id/youtube-edits/fields/:field", zValidator("param", fieldParam, onInvalid), (c) => {
50
+ const { id, field } = c.req.valid("param");
51
+ if (!exists(id))
52
+ return missing(c);
53
+ const saved = transact(deps.db, () => {
54
+ const current = readDescriptionEdits(deps.db, id);
55
+ const next = { ...current, fields: withoutEdit(current.fields, field) };
56
+ writeDescriptionEdits(deps.db, id, next, now());
57
+ return next;
58
+ });
59
+ return c.json(saved);
60
+ })
61
+ // The project's own links, such as its "Previous video"; they win over Settings' list.
62
+ .put("/:id/youtube-edits/links", zValidator("param", idParam, onInvalid), zValidator("json", channelLinksBody, onInvalid), (c) => {
63
+ const { id } = c.req.valid("param");
64
+ if (!exists(id))
65
+ return missing(c);
66
+ const links = c.req
67
+ .valid("json")
68
+ .links.map((link) => ({ name: link.name.trim(), url: link.url.trim() }));
69
+ const refused = channelLinksProblem(links);
70
+ if (refused !== undefined)
71
+ return problem(c, {
72
+ status: 400,
73
+ title: titleOf(400),
74
+ detail: `This project's links weren't saved: ${refused} Then press Save links under YouTube on the Video section.`,
75
+ });
76
+ const saved = transact(deps.db, () => {
77
+ const next = { ...readDescriptionEdits(deps.db, id), links };
78
+ writeDescriptionEdits(deps.db, id, next, now());
79
+ return next;
80
+ });
81
+ return c.json(saved);
82
+ }));
83
+ }
@@ -0,0 +1,62 @@
1
+ import { setTimeout as delay } from "node:timers/promises";
2
+ /**
3
+ * `slopify update` for a native install: asks the running app to update itself, exactly as the
4
+ * Update button in Settings does (wait for running work, install, back up the database, start the
5
+ * new version, roll back if it doesn't come up), and follows it until it has finished.
6
+ */
7
+ export async function runNativeUpdate(deps) {
8
+ const poll = deps.pollMs ?? 2000;
9
+ const read = async (refresh = false) => {
10
+ const response = await deps.fetch(`${deps.origin}/api/update${refresh ? "?refresh=1" : ""}`);
11
+ if (!response.ok)
12
+ throw new Error(`Slopify answered ${response.status}.`);
13
+ return (await response.json());
14
+ };
15
+ let info;
16
+ try {
17
+ info = await read(true);
18
+ }
19
+ catch {
20
+ throw new Error(`Slopify isn't running at ${deps.origin}, so there is nothing to update. Start it (npx @gentbajko/slopify) and run slopify update again; if it runs on another port, add --port <number>. To just start the newest version, run npx @gentbajko/slopify@latest.`);
21
+ }
22
+ if (info.status === "error" && info.error !== undefined && info.latestVersion === null)
23
+ throw new Error(info.error);
24
+ if (!info.available) {
25
+ deps.report(`Slopify ${info.currentVersion} is already the newest version.`);
26
+ return;
27
+ }
28
+ const target = info.latestVersion;
29
+ let told = false;
30
+ while (info.busy) {
31
+ if (!told)
32
+ deps.report("Slopify is generating right now. Waiting for running work to finish before updating (press Ctrl+C to cancel; nothing has been changed yet)...");
33
+ told = true;
34
+ await delay(poll);
35
+ info = await read();
36
+ }
37
+ if (!info.canUpdate)
38
+ throw new Error(info.blockedReason ??
39
+ info.error ??
40
+ "Slopify can't update right now. Open Settings in Slopify to see why.");
41
+ const started = await deps.fetch(`${deps.origin}/api/update`, { method: "POST" });
42
+ if (!started.ok) {
43
+ const body = (await started.json().catch(() => ({})));
44
+ throw new Error(body.detail ?? `Slopify refused to start the update (${started.status}).`);
45
+ }
46
+ deps.report(`Updating Slopify ${info.currentVersion} to ${target}...`);
47
+ const deadline = Date.now() + (deps.restartMs ?? 10 * 60_000);
48
+ while (Date.now() < deadline) {
49
+ await delay(poll);
50
+ const now = await read().catch(() => undefined);
51
+ if (now === undefined || now.status === "installing" || now.status === "restarting")
52
+ continue;
53
+ if (now.currentVersion === target && now.status !== "error") {
54
+ deps.report(`Slopify ${target} is running at ${deps.origin}`);
55
+ return;
56
+ }
57
+ if (now.status === "error" || now.error !== undefined)
58
+ throw new Error(now.error ??
59
+ "The update did not finish, so Slopify kept or put back the previous version. The reason is in logs/updates.log inside your Slopify data folder.");
60
+ }
61
+ throw new Error(`Slopify didn't answer at ${deps.origin} within 10 minutes after the update started. Check it in the browser; the updater puts the previous version back on its own if the new one doesn't start, and the reason is in logs/updates.log inside your Slopify data folder.`);
62
+ }
@@ -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
  }
@@ -5,6 +5,7 @@ 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
@@ -79,7 +82,12 @@ export function createHostRuntime(deps) {
79
82
  models: () => codexImage({ run: deps.run, readModels: () => models("codex") }).models(),
80
83
  generate: async (request) => {
81
84
  const binary = await command("codex-image");
82
- 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);
83
91
  },
84
92
  },
85
93
  };
@@ -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
+ );