@gentbajko/slopify 2.5.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (542) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +24 -41
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +31 -0
  6. package/dist/adapters/alignment/cache.js +4 -1
  7. package/dist/adapters/alignment/ctc.js +25 -23
  8. package/dist/adapters/alignment/index.js +37 -6
  9. package/dist/adapters/alignment/multilingual.js +267 -0
  10. package/dist/adapters/alignment/protocol.js +11 -0
  11. package/dist/adapters/alignment/quality.js +4 -3
  12. package/dist/adapters/alignment/sentences.js +95 -0
  13. package/dist/adapters/alignment/spec.js +24 -0
  14. package/dist/adapters/alignment/spell.js +769 -0
  15. package/dist/adapters/alignment/text.js +56 -9
  16. package/dist/adapters/alignment/window.js +34 -30
  17. package/dist/adapters/alignment/worker.js +23 -21
  18. package/dist/adapters/explain.js +11 -11
  19. package/dist/adapters/host-cli/index.js +44 -5
  20. package/dist/adapters/host-cli/transport.js +7 -5
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +1 -1
  23. package/dist/adapters/image/codex.js +87 -22
  24. package/dist/adapters/image/fal.js +13 -5
  25. package/dist/adapters/image/google.js +11 -8
  26. package/dist/adapters/image/openai.js +14 -7
  27. package/dist/adapters/image/reference.js +34 -0
  28. package/dist/adapters/image/replicate.js +7 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +85 -0
  31. package/dist/adapters/llm/claude-code.js +168 -32
  32. package/dist/adapters/llm/cli-login-error.js +1 -1
  33. package/dist/adapters/llm/codex-limits.js +157 -0
  34. package/dist/adapters/llm/codex.js +99 -12
  35. package/dist/adapters/llm/document-workspace.js +2 -2
  36. package/dist/adapters/llm/gemini-workspace.js +1 -1
  37. package/dist/adapters/llm/gemini.js +16 -3
  38. package/dist/adapters/llm/image-workspace.js +58 -0
  39. package/dist/adapters/llm/openrouter.js +11 -3
  40. package/dist/adapters/llm/run-cli.js +12 -7
  41. package/dist/adapters/tts/cartesia.js +10 -0
  42. package/dist/adapters/tts/elevenlabs.js +31 -9
  43. package/dist/adapters/tts/gemini.js +206 -0
  44. package/dist/adapters/tts/inworld-async.js +9 -5
  45. package/dist/adapters/tts/inworld.js +17 -3
  46. package/dist/adapters/tts/openai.js +3 -0
  47. package/dist/adapters/tts/pcm-mp3.js +62 -0
  48. package/dist/adapters/tts/system.js +147 -0
  49. package/dist/adapters/tts/voice-languages.js +80 -0
  50. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  55. package/dist/assets/fonts/noto/OFL.txt +93 -0
  56. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  57. package/dist/assets/models.yaml +38 -0
  58. package/dist/assets/sample/sample-audiobook.tar +0 -0
  59. package/dist/assets/sample/sample-podcast.tar +0 -0
  60. package/dist/assets/sample/sample-project.tar +0 -0
  61. package/dist/assets/style-preview/narration.mp3 +0 -0
  62. package/dist/assets/style-preview/tall-1.jpg +0 -0
  63. package/dist/assets/style-preview/tall-2.jpg +0 -0
  64. package/dist/assets/style-preview/tall-3.jpg +0 -0
  65. package/dist/assets/style-preview/wide-1.jpg +0 -0
  66. package/dist/assets/style-preview/wide-2.jpg +0 -0
  67. package/dist/assets/style-preview/wide-3.jpg +0 -0
  68. package/dist/catalog/merge.js +132 -0
  69. package/dist/catalog/registry.js +17 -14
  70. package/dist/catalog/schema.js +2 -0
  71. package/dist/catalog/store.js +147 -8
  72. package/dist/catalog/validate.js +2 -2
  73. package/dist/compose.yaml +71 -0
  74. package/dist/edge/autostart/docker-record.js +32 -0
  75. package/dist/edge/autostart/docker.js +118 -0
  76. package/dist/edge/autostart/index.js +78 -0
  77. package/dist/edge/autostart/launcher.js +176 -0
  78. package/dist/edge/autostart/model.js +3 -0
  79. package/dist/edge/autostart/native.js +210 -0
  80. package/dist/edge/autostart/prompt.js +72 -0
  81. package/dist/edge/autostart/service.js +90 -0
  82. package/dist/edge/cli-args.js +71 -0
  83. package/dist/edge/cli.js +64 -63
  84. package/dist/edge/docker-install/activation.js +56 -0
  85. package/dist/edge/docker-install/apply.js +458 -0
  86. package/dist/edge/docker-install/engine.js +362 -0
  87. package/dist/edge/docker-install/run.js +160 -0
  88. package/dist/edge/docker-install/state.js +146 -0
  89. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  90. package/dist/edge/docker.js +14 -22
  91. package/dist/edge/events/hub.js +3 -1
  92. package/dist/edge/host-cli.js +1 -1
  93. package/dist/edge/http/actions.js +38 -11
  94. package/dist/edge/http/app.js +60 -2
  95. package/dist/edge/http/auditions.js +70 -0
  96. package/dist/edge/http/autostart.js +37 -0
  97. package/dist/edge/http/byte-range.js +42 -0
  98. package/dist/edge/http/channel-memory.js +92 -0
  99. package/dist/edge/http/channels.js +128 -0
  100. package/dist/edge/http/diagnostics.js +1 -0
  101. package/dist/edge/http/draft-files.js +2 -4
  102. package/dist/edge/http/entries.js +22 -3
  103. package/dist/edge/http/files.js +11 -7
  104. package/dist/edge/http/folder-location.js +1 -1
  105. package/dist/edge/http/home.js +39 -0
  106. package/dist/edge/http/host-cli.js +29 -5
  107. package/dist/edge/http/narration-peaks.js +76 -0
  108. package/dist/edge/http/onboarding.js +212 -0
  109. package/dist/edge/http/open-folder.js +1 -1
  110. package/dist/edge/http/patch-notes.js +45 -0
  111. package/dist/edge/http/planning.js +4 -2
  112. package/dist/edge/http/project-create.js +87 -0
  113. package/dist/edge/http/project-templates.js +19 -2
  114. package/dist/edge/http/projects.js +37 -100
  115. package/dist/edge/http/prompts.js +40 -3
  116. package/dist/edge/http/pronunciations.js +23 -4
  117. package/dist/edge/http/providers.js +119 -3
  118. package/dist/edge/http/reviews.js +131 -0
  119. package/dist/edge/http/revision-files.js +3 -5
  120. package/dist/edge/http/revisions.js +1 -1
  121. package/dist/edge/http/run-cost.js +25 -0
  122. package/dist/edge/http/schedules.js +115 -6
  123. package/dist/edge/http/settings.js +84 -6
  124. package/dist/edge/http/storage-files.js +50 -0
  125. package/dist/edge/http/storage.js +43 -2
  126. package/dist/edge/http/studio.js +287 -0
  127. package/dist/edge/http/style-preview.js +61 -0
  128. package/dist/edge/http/subtitles.js +1 -1
  129. package/dist/edge/http/trash.js +69 -0
  130. package/dist/edge/http/tutorials.js +59 -0
  131. package/dist/edge/http/update.js +24 -3
  132. package/dist/edge/http/whats-new.js +15 -0
  133. package/dist/edge/http/youtube-edits.js +92 -0
  134. package/dist/edge/native-update.js +62 -0
  135. package/dist/extension/slopify-studio-chrome.zip +0 -0
  136. package/dist/extension/slopify-studio-firefox.zip +0 -0
  137. package/dist/host-cli/gemini-login.js +83 -0
  138. package/dist/host-cli/open-folder.js +10 -10
  139. package/dist/host-cli/runtime.js +9 -1
  140. package/dist/host-cli/status.js +4 -2
  141. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  142. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  143. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  144. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  145. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  146. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  147. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  148. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  149. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  150. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  151. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  152. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  153. package/dist/kernel/db/migrations/0041-cast-hosts.sql +3 -0
  154. package/dist/kernel/paths.js +27 -6
  155. package/dist/kernel/pipeline.js +4 -1
  156. package/dist/kernel/ports/host-cli.js +122 -14
  157. package/dist/kernel/ports/key-probe.js +1 -0
  158. package/dist/kernel/ports/languages.js +157 -0
  159. package/dist/kernel/ports/model.js +10 -5
  160. package/dist/kernel/ports/narration-aliases.js +60 -0
  161. package/dist/kernel/ports/plan-limits.js +36 -0
  162. package/dist/kernel/ports/system-speech.js +203 -0
  163. package/dist/kernel/runner/attempt.js +17 -8
  164. package/dist/kernel/runner/audition.js +36 -0
  165. package/dist/kernel/runner/graph.js +40 -5
  166. package/dist/kernel/runner/index.js +39 -9
  167. package/dist/kernel/runner/meter.js +1 -0
  168. package/dist/kernel/runner/providers.js +142 -7
  169. package/dist/kernel/runner/retry-policy.js +43 -0
  170. package/dist/kernel/runner/standalone.js +134 -0
  171. package/dist/kernel/runner/work-authority.js +28 -2
  172. package/dist/main.js +277 -16
  173. package/dist/model-catalog.js +2 -0
  174. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  175. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  176. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  177. package/dist/patch-notes/3.0.0.md +70 -0
  178. package/dist/patch-notes/3.0.1.md +105 -0
  179. package/dist/patch-notes/index.json +32 -0
  180. package/dist/slices/admission/model.js +27 -0
  181. package/dist/slices/admission/repo.js +24 -4
  182. package/dist/slices/admission/rules.js +80 -2
  183. package/dist/slices/admission/schema.js +58 -1
  184. package/dist/slices/admission/short-mode.js +47 -0
  185. package/dist/slices/admission/start.js +42 -6
  186. package/dist/slices/article/continuation.js +2 -2
  187. package/dist/slices/article/provided-entries.js +2 -2
  188. package/dist/slices/article/run.js +6 -2
  189. package/dist/slices/article/segments.js +3 -3
  190. package/dist/slices/backups/folder.js +14 -6
  191. package/dist/slices/batch/index.js +7 -2
  192. package/dist/slices/cancel/index.js +1 -1
  193. package/dist/slices/channels/cast-images.js +110 -0
  194. package/dist/slices/channels/cast-match.js +32 -0
  195. package/dist/slices/channels/images.js +19 -0
  196. package/dist/slices/channels/model.js +5 -0
  197. package/dist/slices/channels/rebrand.js +100 -0
  198. package/dist/slices/channels/repo.js +221 -0
  199. package/dist/slices/channels/runs.js +129 -0
  200. package/dist/slices/channels/schema.js +152 -0
  201. package/dist/slices/channels/service.js +180 -0
  202. package/dist/slices/channels/studio-csv.js +110 -0
  203. package/dist/slices/channels/videos.js +120 -0
  204. package/dist/slices/checkpoints/recovery.js +10 -0
  205. package/dist/slices/control/index.js +5 -2
  206. package/dist/slices/control/providers.js +4 -1
  207. package/dist/slices/document/fonts.js +1 -1
  208. package/dist/slices/document/render.js +1 -1
  209. package/dist/slices/episodes/related.js +90 -0
  210. package/dist/slices/episodes/repo.js +60 -0
  211. package/dist/slices/episodes/service.js +58 -0
  212. package/dist/slices/episodes/summarize.js +170 -0
  213. package/dist/slices/estimate/index.js +109 -28
  214. package/dist/slices/estimate/requests.js +68 -2
  215. package/dist/slices/eta/history.js +56 -0
  216. package/dist/slices/eta/model.js +63 -0
  217. package/dist/slices/eta/view.js +25 -0
  218. package/dist/slices/fixes/rules.js +73 -0
  219. package/dist/slices/fonts/coverage.js +82 -0
  220. package/dist/slices/fonts/sfnt.js +74 -0
  221. package/dist/slices/images/run.js +8 -5
  222. package/dist/slices/images/scale.js +84 -0
  223. package/dist/slices/library/diff.js +122 -0
  224. package/dist/slices/library/history.js +100 -0
  225. package/dist/slices/library/model.js +3 -0
  226. package/dist/slices/library/photorealistic.js +49 -0
  227. package/dist/slices/library/repo.js +19 -14
  228. package/dist/slices/library/save.js +62 -12
  229. package/dist/slices/library/slots.js +7 -1
  230. package/dist/slices/library/used-by.js +153 -0
  231. package/dist/slices/loudness/level-pieces.js +41 -0
  232. package/dist/slices/loudness/loudnorm.js +164 -0
  233. package/dist/slices/loudness/model.js +153 -0
  234. package/dist/slices/model-upkeep/model.js +10 -0
  235. package/dist/slices/model-upkeep/switch.js +217 -0
  236. package/dist/slices/model-upkeep/usage.js +185 -0
  237. package/dist/slices/narration/aliases-library.js +31 -0
  238. package/dist/slices/narration/aliases-schema.js +53 -0
  239. package/dist/slices/narration/aliases.js +49 -0
  240. package/dist/slices/narration/blocks.js +352 -0
  241. package/dist/slices/narration/chunk.js +19 -7
  242. package/dist/slices/narration/concat.js +2 -2
  243. package/dist/slices/narration/describe.js +45 -0
  244. package/dist/slices/narration/pauses-model.js +63 -0
  245. package/dist/slices/narration/pauses.js +269 -0
  246. package/dist/slices/narration/peaks-model.js +1 -0
  247. package/dist/slices/narration/peaks.js +94 -0
  248. package/dist/slices/narration/preparation.js +21 -3
  249. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  250. package/dist/slices/narration/pronunciation.js +72 -25
  251. package/dist/slices/narration/run.js +6 -6
  252. package/dist/slices/narration/shared-glossary.js +1 -1
  253. package/dist/slices/narration/steering.js +15 -4
  254. package/dist/slices/notifications/notifier.js +47 -3
  255. package/dist/slices/notifications/rules.js +48 -3
  256. package/dist/slices/onboarding/copy.js +143 -0
  257. package/dist/slices/onboarding/first-run.js +72 -0
  258. package/dist/slices/onboarding/full-video.js +81 -0
  259. package/dist/slices/onboarding/install.js +119 -0
  260. package/dist/slices/onboarding/model.js +85 -0
  261. package/dist/slices/onboarding/packs.js +491 -0
  262. package/dist/slices/onboarding/quick-short.js +144 -0
  263. package/dist/slices/onboarding/sample.js +132 -0
  264. package/dist/slices/onboarding/state.js +68 -0
  265. package/dist/slices/onboarding/template.js +73 -0
  266. package/dist/slices/patch-notes/library.js +73 -0
  267. package/dist/slices/patch-notes/seen.js +77 -0
  268. package/dist/slices/play-drafts/convert.js +127 -8
  269. package/dist/slices/play-drafts/readiness.js +4 -1
  270. package/dist/slices/play-drafts/repo.js +2 -0
  271. package/dist/slices/play-drafts/review-inputs.js +20 -3
  272. package/dist/slices/play-drafts/schema.js +89 -0
  273. package/dist/slices/play-drafts/service.js +4 -1
  274. package/dist/slices/play-drafts/start.js +43 -18
  275. package/dist/slices/project-templates/from-project.js +59 -6
  276. package/dist/slices/project-templates/next-chapter.js +94 -0
  277. package/dist/slices/project-templates/one-off.js +26 -0
  278. package/dist/slices/project-templates/repo.js +2 -1
  279. package/dist/slices/project-templates/schema.js +7 -1
  280. package/dist/slices/project-templates/service.js +15 -4
  281. package/dist/slices/project-templates/setup.js +18 -2
  282. package/dist/slices/rebuild/admission-repo.js +1 -1
  283. package/dist/slices/rebuild/model.js +6 -0
  284. package/dist/slices/rebuild/preview-details.js +46 -3
  285. package/dist/slices/rebuild/preview-plan.js +13 -6
  286. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  287. package/dist/slices/rebuild/recipe-audio.js +66 -12
  288. package/dist/slices/rebuild/recipe-build.js +8 -4
  289. package/dist/slices/rebuild/recipe-cast.js +18 -0
  290. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  291. package/dist/slices/rebuild/recipe-describe.js +93 -0
  292. package/dist/slices/rebuild/recipe-edit.js +13 -1
  293. package/dist/slices/rebuild/recipe-exports.js +67 -8
  294. package/dist/slices/rebuild/recipe-input-schema.js +35 -0
  295. package/dist/slices/rebuild/recipe-legacy.js +9 -0
  296. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  297. package/dist/slices/rebuild/recipe-model.js +25 -6
  298. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  299. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  300. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  301. package/dist/slices/rebuild/recipe-reference.js +15 -1
  302. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  303. package/dist/slices/rebuild/recipe-save.js +4 -1
  304. package/dist/slices/rebuild/recipe-shorts.js +23 -3
  305. package/dist/slices/rebuild/recipe-text.js +114 -19
  306. package/dist/slices/rebuild/recipe-validation.js +14 -2
  307. package/dist/slices/rebuild/recipe-visual.js +96 -33
  308. package/dist/slices/rebuild/recipe-voices.js +286 -0
  309. package/dist/slices/rebuild/recipe-work.js +77 -6
  310. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  311. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  312. package/dist/slices/rebuild/recovery-model.js +3 -0
  313. package/dist/slices/rebuild/recovery-selection.js +28 -2
  314. package/dist/slices/rebuild/recovery.js +35 -17
  315. package/dist/slices/rebuild/repo.js +3 -3
  316. package/dist/slices/rebuild/retry.js +18 -0
  317. package/dist/slices/rebuild/review-redo.js +125 -0
  318. package/dist/slices/rebuild/runtime-actions.js +5 -0
  319. package/dist/slices/rebuild/runtime-admission.js +4 -1
  320. package/dist/slices/rebuild/runtime-animate.js +4 -4
  321. package/dist/slices/rebuild/runtime-article.js +5 -5
  322. package/dist/slices/rebuild/runtime-document.js +4 -4
  323. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  324. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  325. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  326. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  327. package/dist/slices/rebuild/runtime-export.js +84 -19
  328. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  329. package/dist/slices/rebuild/runtime-image.js +21 -2
  330. package/dist/slices/rebuild/runtime-local.js +148 -24
  331. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  332. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  333. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  334. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  335. package/dist/slices/rebuild/runtime-piece-label.js +27 -0
  336. package/dist/slices/rebuild/runtime-provider.js +126 -18
  337. package/dist/slices/rebuild/runtime-publication.js +21 -5
  338. package/dist/slices/rebuild/runtime-review.js +267 -0
  339. package/dist/slices/rebuild/runtime-run.js +17 -11
  340. package/dist/slices/rebuild/runtime-shorts.js +44 -17
  341. package/dist/slices/rebuild/runtime-store.js +62 -11
  342. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  343. package/dist/slices/rebuild/runtime-voices.js +103 -0
  344. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  345. package/dist/slices/rebuild/service-readiness.js +3 -3
  346. package/dist/slices/rebuild/soften.js +49 -0
  347. package/dist/slices/rebuild/transition-repo.js +9 -2
  348. package/dist/slices/research/run.js +4 -4
  349. package/dist/slices/research/synthesis.js +2 -2
  350. package/dist/slices/reviews/model.js +105 -0
  351. package/dist/slices/reviews/outcome.js +22 -0
  352. package/dist/slices/reviews/repo.js +118 -0
  353. package/dist/slices/reviews/rules.js +63 -0
  354. package/dist/slices/reviews/verdict.js +71 -0
  355. package/dist/slices/reviews/view.js +30 -0
  356. package/dist/slices/revisions/adopt-content.js +5 -2
  357. package/dist/slices/revisions/adopt.js +3 -1
  358. package/dist/slices/revisions/downloads.js +7 -1
  359. package/dist/slices/revisions/image-plan.js +183 -0
  360. package/dist/slices/revisions/mutation-assets.js +10 -0
  361. package/dist/slices/revisions/mutation-cues.js +71 -0
  362. package/dist/slices/revisions/mutation-request.js +2 -1
  363. package/dist/slices/revisions/mutations.js +97 -3
  364. package/dist/slices/revisions/schema.js +11 -1
  365. package/dist/slices/run-cost/limits.js +128 -0
  366. package/dist/slices/run-cost/meter.js +47 -0
  367. package/dist/slices/run-cost/panel.js +256 -0
  368. package/dist/slices/run-cost/pricing.js +95 -0
  369. package/dist/slices/run-cost/week.js +108 -0
  370. package/dist/slices/schedules/agenda.js +222 -0
  371. package/dist/slices/schedules/repo.js +58 -18
  372. package/dist/slices/schedules/scheduler.js +68 -17
  373. package/dist/slices/schedules/schema.js +179 -1
  374. package/dist/slices/schedules/service.js +43 -4
  375. package/dist/slices/schedules/similar.js +128 -0
  376. package/dist/slices/schedules/topic-list.js +163 -0
  377. package/dist/slices/schedules/topics.js +523 -0
  378. package/dist/slices/settings/first-run.js +106 -0
  379. package/dist/slices/settings/health.js +247 -0
  380. package/dist/slices/settings/key-guides.js +138 -0
  381. package/dist/slices/settings/key-test.js +93 -0
  382. package/dist/slices/settings/keys.js +10 -5
  383. package/dist/slices/settings/model-reach.js +52 -0
  384. package/dist/slices/settings/model.js +55 -1
  385. package/dist/slices/settings/models.js +5 -1
  386. package/dist/slices/settings/playback.js +15 -0
  387. package/dist/slices/settings/readiness.js +14 -2
  388. package/dist/slices/settings/repo.js +17 -3
  389. package/dist/slices/settings/tutorial-schema.js +5 -0
  390. package/dist/slices/settings/voices.js +64 -4
  391. package/dist/slices/settings/whats-new.js +64 -0
  392. package/dist/slices/shorts/captions.js +9 -0
  393. package/dist/slices/shorts/pick.js +3 -3
  394. package/dist/slices/shorts/prompts.js +1 -1
  395. package/dist/slices/shorts/render.js +63 -13
  396. package/dist/slices/storage/asset-name.js +3 -0
  397. package/dist/slices/storage/backup-export.js +48 -9
  398. package/dist/slices/storage/backup-format.js +38 -1
  399. package/dist/slices/storage/backup-import.js +191 -19
  400. package/dist/slices/storage/delete-project.js +7 -4
  401. package/dist/slices/storage/documents.js +87 -0
  402. package/dist/slices/storage/downloads.js +33 -1
  403. package/dist/slices/storage/files-location.js +531 -0
  404. package/dist/slices/storage/layout.js +14 -4
  405. package/dist/slices/storage/model.js +11 -0
  406. package/dist/slices/storage/portable.js +94 -12
  407. package/dist/slices/storage/reconcile.js +14 -1
  408. package/dist/slices/storage/repo.js +4 -1
  409. package/dist/slices/storage/schema.js +11 -0
  410. package/dist/slices/storage/trim.js +146 -0
  411. package/dist/slices/studio/disclosure.js +74 -0
  412. package/dist/slices/studio/model.js +30 -0
  413. package/dist/slices/studio/pack.js +211 -0
  414. package/dist/slices/studio/queue.js +69 -0
  415. package/dist/slices/studio/settings.js +170 -0
  416. package/dist/slices/style-preview/images.js +61 -0
  417. package/dist/slices/style-preview/narration.js +33 -0
  418. package/dist/slices/style-preview/render.js +148 -0
  419. package/dist/slices/style-preview/schema.js +59 -0
  420. package/dist/slices/style-preview/service.js +92 -0
  421. package/dist/slices/style-preview/settings.js +139 -0
  422. package/dist/slices/subtitles/captions.js +42 -9
  423. package/dist/slices/subtitles/model.js +22 -0
  424. package/dist/slices/subtitles/prepare.js +27 -16
  425. package/dist/slices/subtitles/transcript.js +1 -1
  426. package/dist/slices/thumbnail/run.js +7 -7
  427. package/dist/slices/trash/model.js +25 -0
  428. package/dist/slices/trash/service.js +273 -0
  429. package/dist/slices/tutorials/anchors.js +57 -0
  430. package/dist/slices/tutorials/library.js +185 -0
  431. package/dist/slices/uploads/repo.js +20 -0
  432. package/dist/slices/video/ambient-bed-schema.js +15 -0
  433. package/dist/slices/video/ambient-bed.js +95 -0
  434. package/dist/slices/video/ambient-mix.js +72 -0
  435. package/dist/slices/video/audio-inputs.js +2 -2
  436. package/dist/slices/video/cards.js +5 -2
  437. package/dist/slices/video/edit-list.js +23 -2
  438. package/dist/slices/video/edit-settings.js +13 -3
  439. package/dist/slices/video/ffmpeg.js +63 -9
  440. package/dist/slices/video/figure-card.js +492 -0
  441. package/dist/slices/video/figure-spans.js +51 -0
  442. package/dist/slices/video/plan.js +125 -13
  443. package/dist/slices/video/run.js +14 -12
  444. package/dist/slices/video/slideshow.js +39 -4
  445. package/dist/slices/voices/attribution.js +69 -0
  446. package/dist/slices/voices/audio-files.js +87 -0
  447. package/dist/slices/voices/audition.js +25 -0
  448. package/dist/slices/voices/cast.js +74 -0
  449. package/dist/slices/voices/grouping.js +59 -0
  450. package/dist/slices/voices/join.js +35 -0
  451. package/dist/slices/voices/languages.js +25 -0
  452. package/dist/slices/voices/model.js +218 -0
  453. package/dist/slices/voices/palette.js +26 -0
  454. package/dist/slices/voices/panel.js +91 -0
  455. package/dist/slices/voices/portraits.js +55 -0
  456. package/dist/slices/voices/script.js +136 -0
  457. package/dist/slices/voices/timing.js +58 -0
  458. package/dist/slices/youtube/answer.js +1 -1
  459. package/dist/slices/youtube/chapters.js +126 -0
  460. package/dist/slices/youtube/edits-repo.js +84 -0
  461. package/dist/slices/youtube/edits.js +89 -0
  462. package/dist/slices/youtube/placeholders.js +98 -0
  463. package/dist/slices/youtube/transcript.js +11 -4
  464. package/dist/tutorials/AI-CLIs.md +146 -0
  465. package/dist/tutorials/Backups.md +121 -0
  466. package/dist/tutorials/Calendar.md +103 -0
  467. package/dist/tutorials/Cast-Library.md +112 -0
  468. package/dist/tutorials/Channels.md +148 -0
  469. package/dist/tutorials/Costs-and-Run-Cost.md +136 -0
  470. package/dist/tutorials/Docker.md +171 -0
  471. package/dist/tutorials/Document-Themes.md +207 -0
  472. package/dist/tutorials/Editing-a-Project.md +204 -0
  473. package/dist/tutorials/FAQ.md +134 -0
  474. package/dist/tutorials/First-Launch-and-Welcome.md +153 -0
  475. package/dist/tutorials/Home-and-Projects.md +174 -0
  476. package/dist/tutorials/Home.md +119 -0
  477. package/dist/tutorials/Install.md +154 -0
  478. package/dist/tutorials/Intros-and-Outros.md +86 -0
  479. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +215 -0
  480. package/dist/tutorials/Library-Overview.md +75 -0
  481. package/dist/tutorials/Models.md +133 -0
  482. package/dist/tutorials/Multiple-Voices.md +161 -0
  483. package/dist/tutorials/Narration-Aliases-and-Glossary.md +129 -0
  484. package/dist/tutorials/Notifications.md +86 -0
  485. package/dist/tutorials/Other-Languages.md +110 -0
  486. package/dist/tutorials/PDF-Documents.md +100 -0
  487. package/dist/tutorials/Play-Images.md +135 -0
  488. package/dist/tutorials/Play-Narration.md +167 -0
  489. package/dist/tutorials/Play-Outputs.md +109 -0
  490. package/dist/tutorials/Play-Overview.md +216 -0
  491. package/dist/tutorials/Play-Title-and-Article.md +135 -0
  492. package/dist/tutorials/Play-Video-and-Style.md +143 -0
  493. package/dist/tutorials/Project-Page.md +228 -0
  494. package/dist/tutorials/Prompts.md +136 -0
  495. package/dist/tutorials/Providers-and-Keys.md +290 -0
  496. package/dist/tutorials/Publishing-to-YouTube.md +135 -0
  497. package/dist/tutorials/Recovery-and-Retries.md +141 -0
  498. package/dist/tutorials/Reviews-and-Checkpoints.md +143 -0
  499. package/dist/tutorials/Schedules.md +154 -0
  500. package/dist/tutorials/Settings-Reference.md +258 -0
  501. package/dist/tutorials/Shorts.md +116 -0
  502. package/dist/tutorials/Start-at-Login.md +126 -0
  503. package/dist/tutorials/Studio-Extension.md +146 -0
  504. package/dist/tutorials/Templates.md +146 -0
  505. package/dist/tutorials/Trash.md +87 -0
  506. package/dist/tutorials/Troubleshooting.md +149 -0
  507. package/dist/tutorials/Uninstalling-and-Moving.md +113 -0
  508. package/dist/tutorials/Updating-and-Patch-Notes.md +120 -0
  509. package/dist/tutorials/Video-Editing.md +135 -0
  510. package/dist/tutorials/Where-Your-Files-Live.md +149 -0
  511. package/dist/tutorials/YouTube-Description.md +132 -0
  512. package/dist/tutorials/Your-First-Short.md +125 -0
  513. package/dist/tutorials/_Footer.md +1 -0
  514. package/dist/tutorials/_Sidebar.md +69 -0
  515. package/dist/updater/service.js +66 -21
  516. package/dist/web/assets/index-DBLSfqA-.css +1 -0
  517. package/dist/web/assets/index-DFdIhmm0.js +307 -0
  518. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  519. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  520. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  521. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  522. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  523. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  524. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  525. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  526. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  527. package/dist/web/assets/{pdf-BLF2i8c2.js → pdf-B9OY1ioX.js} +1 -1
  528. package/dist/web/index.html +2 -2
  529. package/package.json +5 -5
  530. package/dist/edge/docker-launch.js +0 -81
  531. package/dist/edge/docker-projects/activation.js +0 -35
  532. package/dist/edge/docker-projects/claims.js +0 -85
  533. package/dist/edge/docker-projects/committed.js +0 -40
  534. package/dist/edge/docker-projects/engine.js +0 -461
  535. package/dist/edge/docker-projects/install.js +0 -254
  536. package/dist/edge/docker-projects/prune.js +0 -66
  537. package/dist/edge/docker-projects/recover.js +0 -100
  538. package/dist/edge/docker-projects/state.js +0 -225
  539. package/dist/web/assets/index-CktpxlJd.js +0 -171
  540. package/dist/web/assets/index-DB-vOnGU.css +0 -1
  541. package/scripts/docker-run.sh +0 -3
  542. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -0,0 +1,128 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { zValidator } from "@hono/zod-validator";
3
+ import { Hono } from "hono";
4
+ import { bodyLimit } from "hono/body-limit";
5
+ import { z } from "zod";
6
+ import { deleteCastImage, generateCastImage, uploadCastImage, } from "../../slices/channels/cast-images.js";
7
+ import { castImageMaxBytes } from "../../slices/channels/images.js";
8
+ import { imageBlob } from "../../slices/channels/repo.js";
9
+ import { createCastMember, createChannel, deleteCastMember, deleteChannel, listChannels, moveTemplate, readChannel, setChannelAiDisclosure, updateCastMember, updateChannel, } from "../../slices/channels/service.js";
10
+ import { onInvalid, problem, titleOf } from "./problem.js";
11
+ const idParam = z.object({ id: z.uuid() });
12
+ const memberParam = z.object({ memberId: z.uuid() });
13
+ const imageParam = memberParam.extend({ imageId: z.uuid() });
14
+ const pictureParam = z.object({ sha256: z.string().regex(/^[0-9a-f]{64}$/) });
15
+ const templateParam = z.object({ templateId: z.uuid() });
16
+ // Channels: the channels, their brand kit, series brief and cast, and which channel
17
+ // each template belongs to. The bodies are checked by the slice, so a refusal names the field.
18
+ export function channelRoutes(deps) {
19
+ const service = {
20
+ db: deps.db,
21
+ clock: deps.clock,
22
+ uuid: randomUUID,
23
+ generateImage: deps.generateCastImage,
24
+ log: deps.log,
25
+ };
26
+ return (new Hono()
27
+ .get("/", (c) => c.json({ channels: listChannels(service) }))
28
+ .post("/", async (c) => {
29
+ const result = createChannel(service, await body(c));
30
+ return result.ok ? c.json(result.value, 201) : refused(c, result);
31
+ })
32
+ // The pictures by content hash: they never change, so the browser may keep them.
33
+ .get("/pictures/:sha256", zValidator("param", pictureParam, onInvalid), (c) => {
34
+ const blob = imageBlob(deps.db, c.req.valid("param").sha256);
35
+ if (blob === undefined)
36
+ return refused(c, { ok: false, reason: "not-found" });
37
+ return c.body(new Uint8Array(blob.bytes), 200, {
38
+ "Content-Type": blob.mime,
39
+ "Cache-Control": "private, max-age=31536000, immutable",
40
+ "X-Content-Type-Options": "nosniff",
41
+ });
42
+ })
43
+ .put("/templates/:templateId", zValidator("param", templateParam, onInvalid), async (c) => {
44
+ const result = moveTemplate(service, c.req.valid("param").templateId, await body(c));
45
+ return result.ok ? c.json(result.value) : refused(c, result);
46
+ })
47
+ .put("/cast/:memberId", zValidator("param", memberParam, onInvalid), async (c) => {
48
+ const result = updateCastMember(service, c.req.valid("param").memberId, await body(c));
49
+ return result.ok ? c.json(result.value) : refused(c, result);
50
+ })
51
+ .delete("/cast/:memberId", zValidator("param", memberParam, onInvalid), (c) => {
52
+ const result = deleteCastMember(service, c.req.valid("param").memberId);
53
+ return result.ok ? c.body(null, 204) : refused(c, result);
54
+ })
55
+ // The picture's bytes are the body, as the file input read them.
56
+ .post("/cast/:memberId/images", zValidator("param", memberParam, onInvalid), bodyLimit({
57
+ maxSize: castImageMaxBytes,
58
+ onError: (c) => refused(c, {
59
+ ok: false,
60
+ reason: "not-an-image",
61
+ message: `This picture is larger than ${String(castImageMaxBytes / 1024 / 1024)} MB. Save it smaller (for example as a JPEG) and upload it again.`,
62
+ }),
63
+ }), async (c) => {
64
+ const bytes = new Uint8Array(await c.req.arrayBuffer());
65
+ const result = uploadCastImage(service, c.req.valid("param").memberId, bytes);
66
+ return result.ok ? c.json(result.value, 201) : refused(c, result);
67
+ })
68
+ .post("/cast/:memberId/generate", zValidator("param", memberParam, onInvalid), async (c) => {
69
+ const result = generateCastImage(service, c.req.valid("param").memberId, await body(c));
70
+ return result.ok ? c.json(result.value, 202) : refused(c, result);
71
+ })
72
+ .delete("/cast/:memberId/images/:imageId", zValidator("param", imageParam, onInvalid), (c) => {
73
+ const { memberId, imageId } = c.req.valid("param");
74
+ const result = deleteCastImage(service, memberId, imageId);
75
+ return result.ok ? c.body(null, 204) : refused(c, result);
76
+ })
77
+ .get("/:id", zValidator("param", idParam, onInvalid), (c) => {
78
+ const result = readChannel(service, c.req.valid("param").id);
79
+ return result.ok ? c.json(result.value) : refused(c, result);
80
+ })
81
+ .put("/:id", zValidator("param", idParam, onInvalid), async (c) => {
82
+ const result = updateChannel(service, c.req.valid("param").id, await body(c));
83
+ return result.ok ? c.json(result.value) : refused(c, result);
84
+ })
85
+ .put("/:id/ai-disclosure", zValidator("param", idParam, onInvalid), async (c) => {
86
+ const result = setChannelAiDisclosure(service, c.req.valid("param").id, await body(c));
87
+ return result.ok ? c.json(result.value) : refused(c, result);
88
+ })
89
+ .delete("/:id", zValidator("param", idParam, onInvalid), (c) => {
90
+ const result = deleteChannel(service, c.req.valid("param").id);
91
+ return result.ok ? c.body(null, 204) : refused(c, result);
92
+ })
93
+ .post("/:id/cast", zValidator("param", idParam, onInvalid), async (c) => {
94
+ const result = createCastMember(service, c.req.valid("param").id, await body(c));
95
+ return result.ok ? c.json(result.value, 201) : refused(c, result);
96
+ }));
97
+ }
98
+ async function body(c) {
99
+ try {
100
+ return await c.req.json();
101
+ }
102
+ catch {
103
+ return undefined;
104
+ }
105
+ }
106
+ const details = {
107
+ "not-found": "This channel, cast member or picture no longer exists; it may have been deleted in another tab. Go back to Channels.",
108
+ conflict: "This changed in another tab while you were editing. Reload the page to see the latest, then make your change again.",
109
+ "invalid-input": "Some of these settings are not valid. Check the highlighted fields and try again.",
110
+ "default-channel": "The default channel can't be deleted; projects and templates without a channel belong to it. Rename it in its Brand tab instead.",
111
+ "has-templates": "This channel still has templates. Move them to another channel in its Templates tab, or delete them, then delete the channel.",
112
+ "not-an-image": "This file is not a PNG or JPEG picture. Upload a PNG or JPEG file.",
113
+ "too-many-images": "This cast member already has the most pictures it can keep. Delete one first.",
114
+ "no-image-provider": "Slopify can't make pictures here yet. Set up an image provider in Settings → Providers, or upload a picture instead.",
115
+ };
116
+ function refused(c, result) {
117
+ const status = result.reason === "not-found"
118
+ ? 404
119
+ : result.reason === "conflict" || result.reason === "has-templates"
120
+ ? 409
121
+ : 400;
122
+ return problem(c, {
123
+ status,
124
+ title: titleOf(status),
125
+ detail: result.message ?? details[result.reason],
126
+ extensions: { reason: result.reason },
127
+ });
128
+ }
@@ -11,6 +11,7 @@ export function diagnosticsRoutes(deps) {
11
11
  db: deps.db,
12
12
  probe: deps.probe,
13
13
  hostCliStatus: deps.hostCliStatus,
14
+ host: deps.speechHost,
14
15
  });
15
16
  const projectCount = deps.db.prepare("SELECT count(*) AS count FROM projects").get();
16
17
  const schema = deps.db.prepare("SELECT max(version) AS version FROM schema_migrations").get();
@@ -1,6 +1,4 @@
1
- import { createReadStream } from "node:fs";
2
1
  import { extname } from "node:path";
3
- import { Readable } from "node:stream";
4
2
  import { zValidator } from "@hono/zod-validator";
5
3
  import { Hono } from "hono";
6
4
  import { z } from "zod";
@@ -9,6 +7,7 @@ import { readDraft } from "../../slices/play-drafts/service.js";
9
7
  import { uploadDraftAttachment } from "../../slices/play-drafts/uploads.js";
10
8
  import { stagingPath } from "../../slices/storage/layout.js";
11
9
  import { stagedFileById } from "../../slices/storage/repo.js";
10
+ import { fileResponse } from "./byte-range.js";
12
11
  import { draftProblem } from "./draft-problem.js";
13
12
  import { readMultipart } from "./multipart.js";
14
13
  import { onInvalid, problem, titleOf } from "./problem.js";
@@ -59,9 +58,8 @@ export function draftFileRoutes(service) {
59
58
  title: titleOf(404),
60
59
  detail: "This attached file has not finished uploading, so it cannot be previewed yet. Wait a moment or attach it again.",
61
60
  });
62
- return c.body(Readable.toWeb(createReadStream(stagingPath(deps.paths, staged.path))), 200, {
61
+ return fileResponse(c, stagingPath(deps.paths, staged.path), staged.bytes, {
63
62
  "content-type": contentType,
64
- "content-length": String(staged.bytes),
65
63
  "x-content-type-options": "nosniff",
66
64
  "cache-control": "no-store",
67
65
  });
@@ -1,12 +1,14 @@
1
1
  import { zValidator } from "@hono/zod-validator";
2
2
  import { Hono } from "hono";
3
3
  import { z } from "zod";
4
+ import { versionsOrCurrent } from "../../slices/library/history.js";
4
5
  import { entryCategories, entryModes } from "../../slices/library/model.js";
5
- import { listEntries } from "../../slices/library/repo.js";
6
- import { createEntry, removeEntry, updateEntry } from "../../slices/library/save.js";
6
+ import { entryById, listEntries } from "../../slices/library/repo.js";
7
+ import { createEntry, removeEntry, restoreEntry, updateEntry } from "../../slices/library/save.js";
8
+ import { usedBy } from "../../slices/library/used-by.js";
7
9
  import { onInvalid } from "./problem.js";
8
10
  // One rule set for prompts and entries, so one refusal mapping too.
9
- import { refused } from "./prompts.js";
11
+ import { refused, versionParam } from "./prompts.js";
10
12
  const idParam = z.object({
11
13
  id: z
12
14
  .string()
@@ -33,6 +35,23 @@ export function entryRoutes(deps) {
33
35
  .put("/:id", zValidator("param", idParam, onInvalid), zValidator("json", entryBody, onInvalid), (c) => {
34
36
  const result = updateEntry(library, c.req.valid("param").id, c.req.valid("json"));
35
37
  return result.ok ? c.json(result.value) : refused(c, result, "entry");
38
+ })
39
+ .get("/:id/history", zValidator("param", idParam, onInvalid), (c) => {
40
+ const entry = entryById(deps.db, c.req.valid("param").id);
41
+ return entry === undefined
42
+ ? refused(c, { ok: false, reason: "not-found" }, "entry")
43
+ : c.json({ versions: versionsOrCurrent(deps.db, "entry", entry) });
44
+ })
45
+ .post("/:id/history/:version/restore", zValidator("param", versionParam, onInvalid), (c) => {
46
+ const { id, version } = c.req.valid("param");
47
+ const result = restoreEntry(library, id, version);
48
+ return result.ok ? c.json(result.value) : refused(c, result, "entry");
49
+ })
50
+ .get("/:id/used-by", zValidator("param", idParam, onInvalid), (c) => {
51
+ const entry = entryById(deps.db, c.req.valid("param").id);
52
+ return entry === undefined
53
+ ? refused(c, { ok: false, reason: "not-found" }, "entry")
54
+ : c.json(usedBy(deps.db, { item: "entry", category: entry.category, name: entry.name }));
36
55
  })
37
56
  .delete("/:id", zValidator("param", idParam, onInvalid), (c) => {
38
57
  const result = removeEntry(library, c.req.valid("param").id);
@@ -1,9 +1,8 @@
1
- import { createReadStream } from "node:fs";
2
- import { Readable } from "node:stream";
3
1
  import { zValidator } from "@hono/zod-validator";
4
2
  import { Hono } from "hono";
5
3
  import { z } from "zod";
6
4
  import { findDownload, imagesZip } from "../../slices/storage/downloads.js";
5
+ import { fileResponse } from "./byte-range.js";
7
6
  import { onInvalid, problem, titleOf } from "./problem.js";
8
7
  const projectParam = z.object({
9
8
  projectId: z
@@ -42,11 +41,16 @@ export function fileRoutes(deps) {
42
41
  return missing(c, result.reason);
43
42
  }
44
43
  const { download } = result;
45
- // ceiling: whole-file responses only. Seeking inside the finished video would
46
- // need a Range handler answering 206 with the requested slice.
47
- return c.body(Readable.toWeb(createReadStream(download.path)), 200, {
44
+ // The YouTube description and tags as the page shows them, edits and links in.
45
+ if (download.text !== undefined)
46
+ return c.body(download.text, 200, {
47
+ "content-type": download.contentType,
48
+ "content-length": String(download.text.byteLength),
49
+ "content-disposition": disposition(download.filename),
50
+ });
51
+ // Byte ranges, so the players can jump to any time in the video or narration.
52
+ return fileResponse(c, download.path, download.bytes, {
48
53
  "content-type": download.contentType,
49
- "content-length": String(download.bytes),
50
54
  "content-disposition": disposition(download.filename),
51
55
  });
52
56
  }));
@@ -60,7 +64,7 @@ function missing(c, reason) {
60
64
  const details = {
61
65
  "unknown-project": "This project no longer exists. Go back to Projects to pick another.",
62
66
  "unknown-asset": "This project has no file by that name. Reload the page to see its current files.",
63
- "missing-file": "This file was deleted or moved from the project folder. Use Re-run section on the project page to make it again.",
67
+ "missing-file": "This file was deleted or moved from the project folder. Use More → make it again in its section on the project page to make it again.",
64
68
  "no-images": "This project has no images or thumbnail yet. Wait for the Images step to finish, then try again.",
65
69
  };
66
70
  return problem(c, { status: 404, title: titleOf(404), detail: details[reason] });
@@ -46,7 +46,7 @@ export async function replyForFolder(c, deps, projectId, file) {
46
46
  return problem(c, {
47
47
  status: 404,
48
48
  title: titleOf(404),
49
- detail: "This output's folder was deleted or moved. Use Re-run section on the project page to make the file again.",
49
+ detail: "This output's folder was deleted or moved. Use More → make it again in its section on the project page to make the file again.",
50
50
  });
51
51
  }
52
52
  }
@@ -0,0 +1,39 @@
1
+ import { zValidator } from "@hono/zod-validator";
2
+ import { Hono } from "hono";
3
+ import { z } from "zod";
4
+ import { weekSummary } from "../../slices/run-cost/week.js";
5
+ import { markUploaded } from "../../slices/uploads/repo.js";
6
+ import { onInvalid, problem, titleOf } from "./problem.js";
7
+ const weekQuery = z.object({
8
+ since: z.iso.datetime({ offset: true }),
9
+ channelId: z.string().min(1).max(64).optional(),
10
+ });
11
+ // Home's "This week": videos made, spend and the API equivalent since `since` (the browser's
12
+ // own Monday), for one channel or all of them, and where each CLI plan's windows stand.
13
+ export function homeRoutes(deps) {
14
+ return new Hono().get("/week", zValidator("query", weekQuery, onInvalid), (c) => {
15
+ const { since, channelId } = c.req.valid("query");
16
+ return c.json(weekSummary(deps.db, new Date(since).toISOString(), channelId));
17
+ });
18
+ }
19
+ const idParam = z.object({
20
+ id: z
21
+ .string()
22
+ .min(1)
23
+ .max(64)
24
+ .regex(/^[0-9A-Za-z_-]+$/),
25
+ });
26
+ // Home's "Mark uploaded" and its undo. Slopify never uploads, so this only records what the
27
+ // person says they did.
28
+ export function uploadedRoutes(deps) {
29
+ return new Hono().put("/:id/uploaded", zValidator("param", idParam, onInvalid), zValidator("json", z.object({ uploaded: z.boolean() }).strict(), onInvalid), (c) => {
30
+ const result = markUploaded(deps.db, c.req.valid("param").id, c.req.valid("json").uploaded, deps.clock.now().toISOString());
31
+ if (!result.ok)
32
+ return problem(c, {
33
+ status: 404,
34
+ title: titleOf(404),
35
+ detail: "This project no longer exists. Go back to Projects to pick another.",
36
+ });
37
+ return c.json({ uploadedAt: result.uploadedAt });
38
+ });
39
+ }
@@ -5,7 +5,7 @@ import { stream } from "hono/streaming";
5
5
  import { z } from "zod";
6
6
  import { sniffImage } from "../../adapters/image/bytes.js";
7
7
  import { redact } from "../../kernel/log.js";
8
- import { bridgeLimits, HostFolderRefused, hostCliIds, hostCliProtocol, hostFrameSchema, hostImageSchema, hostLlmIds, hostLlmSchema, hostModelsSchema, hostOpenFolderSchema, hostStatusSchema, } from "../../kernel/ports/host-cli.js";
8
+ import { bridgeLimits, encodeHostImageReport, HostFolderRefused, hostCliIds, hostCliProtocol, hostDoneFrame, hostFrameSchema, hostFramesHeader, hostImageReportHeader, hostImageSchema, hostLlmIds, hostLlmSchema, hostModelsSchema, hostOpenFolderSchema, hostStatusSchema, } from "../../kernel/ports/host-cli.js";
9
9
  import { agentImageTimeoutMs } from "../../kernel/ports/image.js";
10
10
  import { isProviderError, providerError } from "../../kernel/ports/model.js";
11
11
  export function hostGate() {
@@ -49,7 +49,7 @@ export function hostGate() {
49
49
  },
50
50
  };
51
51
  }
52
- const unavailable = "The AI command-line tool on your computer stopped before it finished, so this result is uncertain. Check the section's outputs on the project page, then use Retry stage.";
52
+ const unavailable = "The AI command-line tool on your computer stopped before it finished, so this result is uncertain. Check the section's outputs on the project page, then use Try again.";
53
53
  function fault(error, token) {
54
54
  return {
55
55
  type: "error",
@@ -90,7 +90,7 @@ export function hostCliRoutes(options) {
90
90
  return c.json({
91
91
  type: "error",
92
92
  kind: "unavailable",
93
- message: "The Slopify helper on your computer is busy or restarting. Wait a moment, then use Retry stage.",
93
+ message: "The Slopify helper on your computer is busy or restarting. Wait a moment, then use Try again.",
94
94
  }, 503);
95
95
  const outgoing = c.env?.outgoing;
96
96
  let responseEnded = !outgoing;
@@ -174,6 +174,8 @@ export function hostCliRoutes(options) {
174
174
  const body = hostLlmSchema.safeParse(await json(c.req.raw));
175
175
  if (!body.success)
176
176
  return invalid(c);
177
+ // An app that sends no header reads the original frames (version 1).
178
+ const frames = Number(c.req.header(hostFramesHeader) ?? 1);
177
179
  const job = c.get("job");
178
180
  job.holdStream();
179
181
  const controller = new AbortController();
@@ -191,7 +193,14 @@ export function hostCliRoutes(options) {
191
193
  signal.throwIfAborted();
192
194
  clearTimeout(timer);
193
195
  timer = setTimeout(() => controller.abort(), 120_000);
194
- const parsed = hostFrameSchema.parse(event);
196
+ // The cached-token count, the answering model and the plan windows cross only to
197
+ // an app that asked for frame version 2; an older app rejects fields it does not
198
+ // know. Typed-ahead text stays here: the bridge carries the committed answer only.
199
+ const parsed = hostFrameSchema.parse(event.type === "done"
200
+ ? hostDoneFrame(event, frames)
201
+ : event.type === "partial"
202
+ ? { type: "activity" }
203
+ : event);
195
204
  if (done || parsed.type === "error")
196
205
  throw new Error("Slopify hit an internal error (the AI tool on your computer sent a reply Slopify could not read). Try again; if it keeps happening, use Download diagnostics in Settings and report it.");
197
206
  const frame = `${JSON.stringify(parsed)}\n`;
@@ -229,7 +238,17 @@ export function hostCliRoutes(options) {
229
238
  const signal = AbortSignal.any([job.signal, controller.signal]);
230
239
  const timer = setTimeout(() => controller.abort(), agentImageTimeoutMs);
231
240
  try {
232
- const { reference, thinking, ...rest } = body.data;
241
+ const { reference, thinking, cast: bridgedCast, ...rest } = body.data;
242
+ const cast = bridgedCast?.map((member) => ({
243
+ name: member.name,
244
+ description: member.description,
245
+ images: member.images.map((picture) => ({
246
+ bytes: new Uint8Array(Buffer.from(picture.base64, "base64")),
247
+ mime: picture.mime,
248
+ })),
249
+ }));
250
+ if (cast?.some((member) => member.images.some((picture) => sniffImage(picture.bytes) !== picture.mime)))
251
+ return invalid(c);
233
252
  const image = reference === undefined
234
253
  ? undefined
235
254
  : {
@@ -242,6 +261,7 @@ export function hostCliRoutes(options) {
242
261
  ...rest,
243
262
  ...(thinking === undefined ? {} : { thinking }),
244
263
  ...(image === undefined ? {} : { reference: image }),
264
+ ...(cast === undefined ? {} : { cast }),
245
265
  signal,
246
266
  });
247
267
  if (signal.aborted)
@@ -250,6 +270,10 @@ export function hostCliRoutes(options) {
250
270
  throw new Error("Slopify hit an internal error (the image made on your computer was too large or not a valid image). Try again; if it keeps happening, use Download diagnostics in Settings and report it.");
251
271
  c.header("Content-Type", result.mime);
252
272
  c.header("Content-Length", String(result.bytes.byteLength));
273
+ // Any app may get this: one from before it ignores headers it does not know.
274
+ const report = encodeHostImageReport(result);
275
+ if (report !== undefined)
276
+ c.header(hostImageReportHeader, report);
253
277
  return c.body(new Uint8Array(result.bytes));
254
278
  }
255
279
  catch (error) {
@@ -0,0 +1,76 @@
1
+ import { zValidator } from "@hono/zod-validator";
2
+ import { Hono } from "hono";
3
+ import { z } from "zod";
4
+ import { narrationAudio } from "../../slices/narration/peaks.js";
5
+ import { assetPath } from "../../slices/revisions/mutation-assets.js";
6
+ import { currentRevisionId } from "../../slices/revisions/repo.js";
7
+ import { getRevisionView } from "../../slices/revisions/view.js";
8
+ import { outputPath } from "../../slices/storage/layout.js";
9
+ import { projectTitle } from "../../slices/storage/repo.js";
10
+ import { onInvalid, problem, titleOf } from "./problem.js";
11
+ const projectParam = z.object({
12
+ projectId: z
13
+ .string()
14
+ .min(1)
15
+ .max(64)
16
+ .regex(/^[0-9A-Za-z_-]+$/),
17
+ });
18
+ // ceiling: a few projects' worth of pieces. An asset never changes, so a cached row is never
19
+ // stale; the oldest goes first.
20
+ const cachedPieces = 400;
21
+ // The live view's waveform: the peaks of every finished narration piece of the current
22
+ // version, fetched again each time a `narration.piece` event says one landed.
23
+ export function narrationPeakRoutes(deps) {
24
+ const cache = new Map();
25
+ return new Hono().get("/:projectId/narration/peaks", zValidator("param", projectParam, onInvalid), async (c) => {
26
+ const { projectId } = c.req.valid("param");
27
+ if (projectTitle(deps.db, projectId) === undefined)
28
+ return problem(c, {
29
+ status: 404,
30
+ title: titleOf(404),
31
+ detail: "This project no longer exists. Go back to Projects to pick another.",
32
+ });
33
+ c.header("Cache-Control", "no-store");
34
+ const revisionId = currentRevisionId(deps.db, projectId);
35
+ const view = revisionId === undefined ? undefined : getRevisionView(deps, projectId, revisionId);
36
+ if (revisionId === undefined || view === undefined)
37
+ return c.json({
38
+ revisionId: null,
39
+ complete: false,
40
+ pieces: [],
41
+ });
42
+ const decode = deps.decodePeaks;
43
+ if (decode === undefined)
44
+ return problem(c, {
45
+ status: 503,
46
+ title: titleOf(503),
47
+ detail: "Slopify can't draw the narration waveform because ffmpeg isn't available. Restart Slopify; if it keeps happening, use Download diagnostics in Settings and report it.",
48
+ });
49
+ const audio = narrationAudio(view);
50
+ const pieces = [];
51
+ for (const piece of audio.pieces) {
52
+ let peaks = cache.get(piece.assetId);
53
+ if (peaks === undefined) {
54
+ try {
55
+ peaks = await decode(outputPath(deps.paths, projectId, assetPath(deps, projectId, piece.assetId)), c.req.raw.signal);
56
+ }
57
+ catch (error) {
58
+ deps.log.write("warn", "narration.peaks", {
59
+ projectId,
60
+ stage: "audio",
61
+ detail: error instanceof Error ? error.message : String(error),
62
+ });
63
+ continue;
64
+ }
65
+ cache.set(piece.assetId, peaks);
66
+ while (cache.size > cachedPieces) {
67
+ const oldest = cache.keys().next().value;
68
+ if (oldest !== undefined)
69
+ cache.delete(oldest);
70
+ }
71
+ }
72
+ pieces.push({ key: piece.key, seconds: peaks.seconds, peaks: peaks.peaks });
73
+ }
74
+ return c.json({ revisionId, complete: audio.complete, pieces });
75
+ });
76
+ }