@gentbajko/slopify 2.4.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (469) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +21 -40
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +11 -1
  6. package/dist/adapters/alignment/cache.js +4 -1
  7. package/dist/adapters/alignment/ctc.js +25 -23
  8. package/dist/adapters/alignment/index.js +37 -6
  9. package/dist/adapters/alignment/multilingual.js +267 -0
  10. package/dist/adapters/alignment/protocol.js +11 -0
  11. package/dist/adapters/alignment/quality.js +4 -3
  12. package/dist/adapters/alignment/sentences.js +95 -0
  13. package/dist/adapters/alignment/spec.js +24 -0
  14. package/dist/adapters/alignment/spell.js +769 -0
  15. package/dist/adapters/alignment/text.js +56 -9
  16. package/dist/adapters/alignment/window.js +34 -30
  17. package/dist/adapters/alignment/worker.js +23 -21
  18. package/dist/adapters/explain.js +11 -11
  19. package/dist/adapters/host-cli/index.js +55 -5
  20. package/dist/adapters/host-cli/transport.js +13 -6
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +60 -18
  23. package/dist/adapters/image/codex.js +166 -25
  24. package/dist/adapters/image/fal.js +31 -3
  25. package/dist/adapters/image/google.js +17 -2
  26. package/dist/adapters/image/openai.js +45 -16
  27. package/dist/adapters/image/reference.js +41 -0
  28. package/dist/adapters/image/replicate.js +14 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +36 -0
  31. package/dist/adapters/llm/claude-code.js +168 -32
  32. package/dist/adapters/llm/cli-login-error.js +1 -1
  33. package/dist/adapters/llm/codex-limits.js +157 -0
  34. package/dist/adapters/llm/codex-models.js +2 -1
  35. package/dist/adapters/llm/codex-server-models.js +2 -1
  36. package/dist/adapters/llm/codex.js +99 -12
  37. package/dist/adapters/llm/document-workspace.js +2 -2
  38. package/dist/adapters/llm/gemini-workspace.js +1 -1
  39. package/dist/adapters/llm/gemini.js +16 -3
  40. package/dist/adapters/llm/image-workspace.js +58 -0
  41. package/dist/adapters/llm/openrouter.js +11 -3
  42. package/dist/adapters/llm/run-cli.js +26 -9
  43. package/dist/adapters/tts/cartesia.js +10 -0
  44. package/dist/adapters/tts/elevenlabs.js +31 -9
  45. package/dist/adapters/tts/inworld-async.js +9 -5
  46. package/dist/adapters/tts/inworld.js +17 -3
  47. package/dist/adapters/tts/openai.js +3 -0
  48. package/dist/adapters/tts/voice-languages.js +80 -0
  49. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  50. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/OFL.txt +93 -0
  55. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  56. package/dist/assets/models.yaml +7 -0
  57. package/dist/assets/sample/sample-audiobook.tar +0 -0
  58. package/dist/assets/sample/sample-podcast.tar +0 -0
  59. package/dist/assets/sample/sample-project.tar +0 -0
  60. package/dist/catalog/merge.js +132 -0
  61. package/dist/catalog/registry.js +33 -12
  62. package/dist/catalog/runtime-models.js +1 -1
  63. package/dist/catalog/schema.js +12 -1
  64. package/dist/catalog/store.js +147 -8
  65. package/dist/catalog/validate.js +17 -2
  66. package/dist/compose.yaml +71 -0
  67. package/dist/edge/autostart/docker-record.js +32 -0
  68. package/dist/edge/autostart/docker.js +50 -0
  69. package/dist/edge/autostart/index.js +78 -0
  70. package/dist/edge/autostart/launcher.js +176 -0
  71. package/dist/edge/autostart/model.js +3 -0
  72. package/dist/edge/autostart/native.js +210 -0
  73. package/dist/edge/autostart/prompt.js +66 -0
  74. package/dist/edge/autostart/service.js +81 -0
  75. package/dist/edge/cli.js +57 -51
  76. package/dist/edge/docker-install/activation.js +56 -0
  77. package/dist/edge/docker-install/apply.js +458 -0
  78. package/dist/edge/docker-install/engine.js +362 -0
  79. package/dist/edge/docker-install/run.js +154 -0
  80. package/dist/edge/docker-install/state.js +146 -0
  81. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  82. package/dist/edge/docker.js +14 -22
  83. package/dist/edge/events/hub.js +11 -0
  84. package/dist/edge/host-cli.js +1 -1
  85. package/dist/edge/http/actions.js +32 -10
  86. package/dist/edge/http/app.js +60 -2
  87. package/dist/edge/http/auditions.js +70 -0
  88. package/dist/edge/http/autostart.js +37 -0
  89. package/dist/edge/http/backups.js +40 -0
  90. package/dist/edge/http/channel-memory.js +92 -0
  91. package/dist/edge/http/channels.js +128 -0
  92. package/dist/edge/http/entries.js +22 -3
  93. package/dist/edge/http/files.js +1 -1
  94. package/dist/edge/http/folder-location.js +1 -1
  95. package/dist/edge/http/home.js +39 -0
  96. package/dist/edge/http/host-cli.js +45 -6
  97. package/dist/edge/http/narration-peaks.js +76 -0
  98. package/dist/edge/http/onboarding.js +152 -0
  99. package/dist/edge/http/open-folder.js +1 -1
  100. package/dist/edge/http/patch-notes.js +45 -0
  101. package/dist/edge/http/planning.js +4 -2
  102. package/dist/edge/http/problem.js +1 -0
  103. package/dist/edge/http/project-create.js +87 -0
  104. package/dist/edge/http/projects.js +31 -99
  105. package/dist/edge/http/prompts.js +40 -3
  106. package/dist/edge/http/pronunciations.js +23 -4
  107. package/dist/edge/http/providers.js +86 -0
  108. package/dist/edge/http/reviews.js +121 -0
  109. package/dist/edge/http/revision-files.js +1 -1
  110. package/dist/edge/http/revisions.js +1 -1
  111. package/dist/edge/http/run-cost.js +25 -0
  112. package/dist/edge/http/schedules.js +111 -6
  113. package/dist/edge/http/settings.js +128 -6
  114. package/dist/edge/http/storage-files.js +50 -0
  115. package/dist/edge/http/storage.js +29 -2
  116. package/dist/edge/http/studio.js +207 -0
  117. package/dist/edge/http/style-preview.js +100 -0
  118. package/dist/edge/http/subtitles.js +1 -1
  119. package/dist/edge/http/trash.js +69 -0
  120. package/dist/edge/http/update.js +24 -3
  121. package/dist/edge/http/whats-new.js +15 -0
  122. package/dist/edge/http/youtube-edits.js +83 -0
  123. package/dist/edge/native-update.js +62 -0
  124. package/dist/host-cli/open-folder.js +10 -10
  125. package/dist/host-cli/runtime.js +12 -3
  126. package/dist/kernel/db/migrations/0022-drop-duplicate-held-work.sql +56 -0
  127. package/dist/kernel/db/migrations/0024-reference-attachments.sql +18 -0
  128. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  129. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  130. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  131. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  132. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  133. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  134. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  135. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  136. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  137. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  138. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  139. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  140. package/dist/kernel/paths.js +27 -6
  141. package/dist/kernel/pipeline.js +4 -1
  142. package/dist/kernel/ports/host-cli.js +139 -16
  143. package/dist/kernel/ports/image.js +3 -1
  144. package/dist/kernel/ports/key-probe.js +1 -0
  145. package/dist/kernel/ports/languages.js +157 -0
  146. package/dist/kernel/ports/llm.js +3 -1
  147. package/dist/kernel/ports/model.js +5 -2
  148. package/dist/kernel/ports/narration-aliases.js +60 -0
  149. package/dist/kernel/ports/plan-limits.js +36 -0
  150. package/dist/kernel/runner/attempt.js +18 -9
  151. package/dist/kernel/runner/audition.js +36 -0
  152. package/dist/kernel/runner/graph.js +24 -5
  153. package/dist/kernel/runner/index.js +35 -5
  154. package/dist/kernel/runner/meter.js +1 -0
  155. package/dist/kernel/runner/providers.js +164 -9
  156. package/dist/kernel/runner/retry-policy.js +43 -0
  157. package/dist/kernel/runner/standalone.js +134 -0
  158. package/dist/kernel/runner/work-authority.js +28 -2
  159. package/dist/main.js +321 -20
  160. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  161. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  162. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  163. package/dist/patch-notes/3.0.0.md +70 -0
  164. package/dist/patch-notes/index.json +26 -0
  165. package/dist/slices/admission/model.js +35 -0
  166. package/dist/slices/admission/repo.js +24 -4
  167. package/dist/slices/admission/rules.js +126 -1
  168. package/dist/slices/admission/schema.js +68 -1
  169. package/dist/slices/admission/short-mode.js +47 -0
  170. package/dist/slices/admission/start.js +90 -16
  171. package/dist/slices/article/continuation.js +2 -2
  172. package/dist/slices/article/provided-entries.js +2 -2
  173. package/dist/slices/article/run.js +6 -2
  174. package/dist/slices/article/segments.js +3 -3
  175. package/dist/slices/backups/files.js +135 -0
  176. package/dist/slices/backups/folder.js +43 -0
  177. package/dist/slices/backups/model.js +65 -0
  178. package/dist/slices/backups/repo.js +28 -0
  179. package/dist/slices/backups/schedule.js +60 -0
  180. package/dist/slices/backups/service.js +311 -0
  181. package/dist/slices/batch/index.js +9 -2
  182. package/dist/slices/cancel/index.js +1 -1
  183. package/dist/slices/channels/cast-images.js +110 -0
  184. package/dist/slices/channels/cast-match.js +32 -0
  185. package/dist/slices/channels/images.js +19 -0
  186. package/dist/slices/channels/model.js +5 -0
  187. package/dist/slices/channels/rebrand.js +100 -0
  188. package/dist/slices/channels/repo.js +218 -0
  189. package/dist/slices/channels/runs.js +129 -0
  190. package/dist/slices/channels/schema.js +129 -0
  191. package/dist/slices/channels/service.js +180 -0
  192. package/dist/slices/channels/studio-csv.js +110 -0
  193. package/dist/slices/channels/videos.js +120 -0
  194. package/dist/slices/checkpoints/recovery.js +10 -0
  195. package/dist/slices/control/index.js +2 -1
  196. package/dist/slices/document/fonts.js +1 -1
  197. package/dist/slices/document/render.js +1 -1
  198. package/dist/slices/episodes/related.js +90 -0
  199. package/dist/slices/episodes/repo.js +60 -0
  200. package/dist/slices/episodes/service.js +58 -0
  201. package/dist/slices/episodes/summarize.js +170 -0
  202. package/dist/slices/estimate/index.js +113 -24
  203. package/dist/slices/estimate/requests.js +60 -2
  204. package/dist/slices/fixes/rules.js +73 -0
  205. package/dist/slices/fonts/coverage.js +82 -0
  206. package/dist/slices/fonts/sfnt.js +74 -0
  207. package/dist/slices/images/run.js +8 -5
  208. package/dist/slices/images/scale.js +84 -0
  209. package/dist/slices/library/diff.js +122 -0
  210. package/dist/slices/library/history.js +100 -0
  211. package/dist/slices/library/model.js +3 -0
  212. package/dist/slices/library/photorealistic.js +49 -0
  213. package/dist/slices/library/repo.js +19 -14
  214. package/dist/slices/library/save.js +62 -12
  215. package/dist/slices/library/slots.js +15 -3
  216. package/dist/slices/library/snapshot.js +8 -2
  217. package/dist/slices/library/used-by.js +153 -0
  218. package/dist/slices/loudness/level-pieces.js +41 -0
  219. package/dist/slices/loudness/loudnorm.js +164 -0
  220. package/dist/slices/loudness/model.js +153 -0
  221. package/dist/slices/model-upkeep/model.js +10 -0
  222. package/dist/slices/model-upkeep/switch.js +217 -0
  223. package/dist/slices/model-upkeep/usage.js +183 -0
  224. package/dist/slices/narration/aliases-library.js +31 -0
  225. package/dist/slices/narration/aliases-schema.js +53 -0
  226. package/dist/slices/narration/aliases.js +49 -0
  227. package/dist/slices/narration/blocks.js +352 -0
  228. package/dist/slices/narration/chunk.js +19 -7
  229. package/dist/slices/narration/concat.js +2 -2
  230. package/dist/slices/narration/describe.js +45 -0
  231. package/dist/slices/narration/pauses-model.js +63 -0
  232. package/dist/slices/narration/pauses.js +269 -0
  233. package/dist/slices/narration/peaks-model.js +1 -0
  234. package/dist/slices/narration/peaks.js +94 -0
  235. package/dist/slices/narration/preparation.js +21 -3
  236. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  237. package/dist/slices/narration/pronunciation.js +72 -25
  238. package/dist/slices/narration/run.js +6 -6
  239. package/dist/slices/narration/shared-glossary.js +1 -1
  240. package/dist/slices/narration/steering.js +15 -4
  241. package/dist/slices/notifications/notifier.js +94 -0
  242. package/dist/slices/notifications/rules.js +102 -0
  243. package/dist/slices/notifications/send.js +40 -0
  244. package/dist/slices/notifications/settings.js +34 -0
  245. package/dist/slices/onboarding/copy.js +143 -0
  246. package/dist/slices/onboarding/first-run.js +46 -0
  247. package/dist/slices/onboarding/install.js +119 -0
  248. package/dist/slices/onboarding/model.js +64 -0
  249. package/dist/slices/onboarding/packs.js +491 -0
  250. package/dist/slices/onboarding/quick-short.js +129 -0
  251. package/dist/slices/onboarding/sample.js +132 -0
  252. package/dist/slices/onboarding/state.js +68 -0
  253. package/dist/slices/onboarding/template.js +73 -0
  254. package/dist/slices/patch-notes/library.js +73 -0
  255. package/dist/slices/patch-notes/seen.js +77 -0
  256. package/dist/slices/play-drafts/convert.js +160 -9
  257. package/dist/slices/play-drafts/readiness.js +3 -0
  258. package/dist/slices/play-drafts/repo.js +9 -1
  259. package/dist/slices/play-drafts/review-inputs.js +22 -3
  260. package/dist/slices/play-drafts/schema.js +109 -2
  261. package/dist/slices/play-drafts/service.js +10 -1
  262. package/dist/slices/play-drafts/start.js +43 -18
  263. package/dist/slices/play-drafts/uploads.js +2 -1
  264. package/dist/slices/project-templates/from-project.js +70 -4
  265. package/dist/slices/project-templates/one-off.js +26 -0
  266. package/dist/slices/project-templates/repo.js +2 -1
  267. package/dist/slices/project-templates/service.js +15 -4
  268. package/dist/slices/project-templates/setup.js +29 -2
  269. package/dist/slices/rebuild/admission-repo.js +1 -1
  270. package/dist/slices/rebuild/model.js +6 -0
  271. package/dist/slices/rebuild/preview-details.js +67 -2
  272. package/dist/slices/rebuild/preview-plan.js +13 -6
  273. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  274. package/dist/slices/rebuild/recipe-audio.js +66 -12
  275. package/dist/slices/rebuild/recipe-build.js +15 -5
  276. package/dist/slices/rebuild/recipe-cast.js +18 -0
  277. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  278. package/dist/slices/rebuild/recipe-describe.js +93 -0
  279. package/dist/slices/rebuild/recipe-edit.js +13 -1
  280. package/dist/slices/rebuild/recipe-exports.js +62 -8
  281. package/dist/slices/rebuild/recipe-input-schema.js +43 -1
  282. package/dist/slices/rebuild/recipe-legacy.js +12 -0
  283. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  284. package/dist/slices/rebuild/recipe-model.js +25 -6
  285. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  286. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  287. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  288. package/dist/slices/rebuild/recipe-reference.js +58 -0
  289. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  290. package/dist/slices/rebuild/recipe-save.js +4 -1
  291. package/dist/slices/rebuild/recipe-shorts.js +32 -5
  292. package/dist/slices/rebuild/recipe-text.js +114 -19
  293. package/dist/slices/rebuild/recipe-validation.js +29 -3
  294. package/dist/slices/rebuild/recipe-visual.js +104 -29
  295. package/dist/slices/rebuild/recipe-voices.js +286 -0
  296. package/dist/slices/rebuild/recipe-work.js +77 -6
  297. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  298. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  299. package/dist/slices/rebuild/recovery-model.js +3 -0
  300. package/dist/slices/rebuild/recovery-selection.js +28 -2
  301. package/dist/slices/rebuild/recovery.js +35 -17
  302. package/dist/slices/rebuild/repo.js +3 -3
  303. package/dist/slices/rebuild/retry.js +18 -0
  304. package/dist/slices/rebuild/review-redo.js +110 -0
  305. package/dist/slices/rebuild/runtime-actions.js +14 -2
  306. package/dist/slices/rebuild/runtime-admission.js +4 -1
  307. package/dist/slices/rebuild/runtime-animate.js +4 -4
  308. package/dist/slices/rebuild/runtime-article.js +5 -5
  309. package/dist/slices/rebuild/runtime-document.js +4 -4
  310. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  311. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  312. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  313. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  314. package/dist/slices/rebuild/runtime-export.js +84 -19
  315. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  316. package/dist/slices/rebuild/runtime-image.js +69 -0
  317. package/dist/slices/rebuild/runtime-local.js +154 -27
  318. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  319. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  320. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  321. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  322. package/dist/slices/rebuild/runtime-piece-label.js +29 -0
  323. package/dist/slices/rebuild/runtime-provider.js +134 -19
  324. package/dist/slices/rebuild/runtime-publication.js +21 -5
  325. package/dist/slices/rebuild/runtime-review.js +237 -0
  326. package/dist/slices/rebuild/runtime-run.js +17 -11
  327. package/dist/slices/rebuild/runtime-shorts.js +48 -23
  328. package/dist/slices/rebuild/runtime-store.js +39 -10
  329. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  330. package/dist/slices/rebuild/runtime-voices.js +103 -0
  331. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  332. package/dist/slices/rebuild/service-readiness.js +15 -1
  333. package/dist/slices/rebuild/soften.js +49 -0
  334. package/dist/slices/rebuild/transition-repo.js +10 -3
  335. package/dist/slices/research/run.js +4 -4
  336. package/dist/slices/research/synthesis.js +2 -2
  337. package/dist/slices/reviews/model.js +105 -0
  338. package/dist/slices/reviews/outcome.js +22 -0
  339. package/dist/slices/reviews/repo.js +106 -0
  340. package/dist/slices/reviews/rules.js +63 -0
  341. package/dist/slices/reviews/verdict.js +71 -0
  342. package/dist/slices/reviews/view.js +30 -0
  343. package/dist/slices/revisions/adopt-content.js +12 -2
  344. package/dist/slices/revisions/adopt.js +9 -2
  345. package/dist/slices/revisions/downloads.js +7 -1
  346. package/dist/slices/revisions/image-plan.js +183 -0
  347. package/dist/slices/revisions/mutation-assets.js +43 -9
  348. package/dist/slices/revisions/mutation-cues.js +71 -0
  349. package/dist/slices/revisions/mutation-prepare.js +8 -10
  350. package/dist/slices/revisions/mutation-request.js +2 -1
  351. package/dist/slices/revisions/mutations.js +97 -3
  352. package/dist/slices/revisions/schema.js +16 -2
  353. package/dist/slices/run-cost/limits.js +128 -0
  354. package/dist/slices/run-cost/meter.js +47 -0
  355. package/dist/slices/run-cost/panel.js +234 -0
  356. package/dist/slices/run-cost/pricing.js +92 -0
  357. package/dist/slices/run-cost/week.js +108 -0
  358. package/dist/slices/schedules/agenda.js +158 -0
  359. package/dist/slices/schedules/repo.js +58 -18
  360. package/dist/slices/schedules/scheduler.js +67 -16
  361. package/dist/slices/schedules/schema.js +138 -1
  362. package/dist/slices/schedules/service.js +48 -4
  363. package/dist/slices/schedules/similar.js +90 -0
  364. package/dist/slices/schedules/topic-list.js +163 -0
  365. package/dist/slices/schedules/topics.js +422 -0
  366. package/dist/slices/settings/first-run.js +102 -0
  367. package/dist/slices/settings/health.js +190 -0
  368. package/dist/slices/settings/key-guides.js +128 -0
  369. package/dist/slices/settings/key-test.js +85 -0
  370. package/dist/slices/settings/model.js +11 -1
  371. package/dist/slices/settings/playback.js +15 -0
  372. package/dist/slices/settings/repo.js +17 -3
  373. package/dist/slices/settings/voices.js +64 -4
  374. package/dist/slices/settings/whats-new.js +64 -0
  375. package/dist/slices/shorts/captions.js +9 -0
  376. package/dist/slices/shorts/pick.js +3 -3
  377. package/dist/slices/shorts/prompts.js +1 -1
  378. package/dist/slices/shorts/render.js +54 -5
  379. package/dist/slices/storage/asset-name.js +3 -0
  380. package/dist/slices/storage/backup-export.js +48 -9
  381. package/dist/slices/storage/backup-format.js +38 -1
  382. package/dist/slices/storage/backup-import.js +191 -19
  383. package/dist/slices/storage/delete-project.js +7 -4
  384. package/dist/slices/storage/documents.js +79 -0
  385. package/dist/slices/storage/files-location.js +531 -0
  386. package/dist/slices/storage/layout.js +20 -0
  387. package/dist/slices/storage/model.js +14 -0
  388. package/dist/slices/storage/portable.js +95 -12
  389. package/dist/slices/storage/reconcile.js +19 -2
  390. package/dist/slices/storage/repo.js +4 -1
  391. package/dist/slices/storage/schema.js +11 -0
  392. package/dist/slices/storage/trim.js +146 -0
  393. package/dist/slices/studio/disclosure.js +74 -0
  394. package/dist/slices/studio/model.js +28 -0
  395. package/dist/slices/studio/pack.js +209 -0
  396. package/dist/slices/studio/settings.js +133 -0
  397. package/dist/slices/style-preview/images.js +61 -0
  398. package/dist/slices/style-preview/render.js +161 -0
  399. package/dist/slices/style-preview/schema.js +45 -0
  400. package/dist/slices/style-preview/service.js +93 -0
  401. package/dist/slices/style-preview/settings.js +89 -0
  402. package/dist/slices/subtitles/captions.js +42 -9
  403. package/dist/slices/subtitles/model.js +22 -0
  404. package/dist/slices/subtitles/prepare.js +27 -16
  405. package/dist/slices/subtitles/transcript.js +1 -1
  406. package/dist/slices/thumbnail/run.js +7 -7
  407. package/dist/slices/trash/model.js +25 -0
  408. package/dist/slices/trash/service.js +273 -0
  409. package/dist/slices/uploads/repo.js +20 -0
  410. package/dist/slices/video/ambient-bed-schema.js +15 -0
  411. package/dist/slices/video/ambient-bed.js +95 -0
  412. package/dist/slices/video/ambient-mix.js +72 -0
  413. package/dist/slices/video/audio-inputs.js +2 -2
  414. package/dist/slices/video/cards.js +5 -2
  415. package/dist/slices/video/edit-list.js +23 -2
  416. package/dist/slices/video/edit-settings.js +13 -3
  417. package/dist/slices/video/ffmpeg.js +63 -9
  418. package/dist/slices/video/figure-card.js +492 -0
  419. package/dist/slices/video/figure-spans.js +51 -0
  420. package/dist/slices/video/plan.js +125 -13
  421. package/dist/slices/video/run.js +14 -12
  422. package/dist/slices/video/slideshow.js +39 -4
  423. package/dist/slices/voices/attribution.js +69 -0
  424. package/dist/slices/voices/audio-files.js +82 -0
  425. package/dist/slices/voices/audition.js +25 -0
  426. package/dist/slices/voices/cast.js +56 -0
  427. package/dist/slices/voices/grouping.js +49 -0
  428. package/dist/slices/voices/join.js +35 -0
  429. package/dist/slices/voices/languages.js +25 -0
  430. package/dist/slices/voices/model.js +179 -0
  431. package/dist/slices/voices/palette.js +26 -0
  432. package/dist/slices/voices/panel.js +91 -0
  433. package/dist/slices/voices/portraits.js +55 -0
  434. package/dist/slices/voices/script.js +136 -0
  435. package/dist/slices/voices/timing.js +58 -0
  436. package/dist/slices/youtube/answer.js +1 -1
  437. package/dist/slices/youtube/chapters.js +126 -0
  438. package/dist/slices/youtube/edits-repo.js +69 -0
  439. package/dist/slices/youtube/edits.js +89 -0
  440. package/dist/slices/youtube/placeholders.js +98 -0
  441. package/dist/slices/youtube/transcript.js +11 -4
  442. package/dist/updater/service.js +66 -21
  443. package/dist/web/assets/index-CUA2sUxU.css +1 -0
  444. package/dist/web/assets/index-CX590kE-.js +303 -0
  445. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  446. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  447. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  448. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  449. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  450. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  451. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  452. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  453. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  454. package/dist/web/assets/{pdf-CAJNT254.js → pdf-BG8n_uc5.js} +1 -1
  455. package/dist/web/index.html +2 -2
  456. package/package.json +4 -4
  457. package/dist/edge/docker-launch.js +0 -81
  458. package/dist/edge/docker-projects/activation.js +0 -35
  459. package/dist/edge/docker-projects/claims.js +0 -85
  460. package/dist/edge/docker-projects/committed.js +0 -40
  461. package/dist/edge/docker-projects/engine.js +0 -461
  462. package/dist/edge/docker-projects/install.js +0 -254
  463. package/dist/edge/docker-projects/prune.js +0 -66
  464. package/dist/edge/docker-projects/recover.js +0 -100
  465. package/dist/edge/docker-projects/state.js +0 -225
  466. package/dist/web/assets/index-BGgYMm2u.css +0 -1
  467. package/dist/web/assets/index-BVVgtntL.js +0 -171
  468. package/scripts/docker-run.sh +0 -3
  469. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -0,0 +1,87 @@
1
+ import { modelFields } from "../../catalog/validate.js";
2
+ import { stagesOf } from "../../slices/admission/repo.js";
3
+ import { admit } from "../../slices/admission/rules.js";
4
+ import { startRun } from "../../slices/admission/start.js";
5
+ import { castVoicedRun } from "../../slices/channels/runs.js";
6
+ import { resolveFont } from "../../slices/fonts/index.js";
7
+ import { pickTemplates, renderPicked } from "../../slices/library/slots.js";
8
+ import { stagedFiles } from "../../slices/storage/repo.js";
9
+ import { record } from "../../slices/telemetry/record.js";
10
+ // Starts a project from a whole draft, the way POST /api/projects always has: the prompt
11
+ // bodies are read now, the draft is admitted, the project and its first work are committed in
12
+ // one transaction, and only then is the runner woken. "Make a 60-second short" starts its
13
+ // project through the same door.
14
+ export async function createProject(deps, sent) {
15
+ // Cast speakers take the cast's voices as they are now, as a run started from Play does.
16
+ const draft = castVoicedRun(deps.db, sent);
17
+ if (draft.checkpoints?.length)
18
+ return {
19
+ ok: false,
20
+ status: 409,
21
+ detail: "This project has checkpoints turned on, so it must be started from Play with Review and start.",
22
+ fields: [],
23
+ };
24
+ const subtitles = draft.subtitles;
25
+ if (subtitles !== undefined && subtitles.mode !== "off") {
26
+ try {
27
+ await resolveFont(deps.paths, subtitles.fontId);
28
+ }
29
+ catch {
30
+ return {
31
+ ok: false,
32
+ status: 400,
33
+ detail: "The subtitle font you picked is no longer available. Choose another font before starting.",
34
+ fields: [
35
+ {
36
+ field: "subtitles.fontId",
37
+ message: "This font is no longer available. Choose another or upload it again in Settings.",
38
+ },
39
+ ],
40
+ };
41
+ }
42
+ }
43
+ // The bodies are read here, at the click, so an edit made since the prompt was selected is
44
+ // the one that runs.
45
+ const picked = pickTemplates(deps.db, draft);
46
+ const admitted = admit({
47
+ draft: picked.draft,
48
+ staged: stagedFiles(deps.db),
49
+ requiredSlots: picked.requiredSlots,
50
+ });
51
+ const modelErrors = modelFields(picked.draft, deps.catalogue);
52
+ if (!admitted.ok || picked.missing.length > 0 || modelErrors.length > 0)
53
+ return {
54
+ ok: false,
55
+ status: 400,
56
+ detail: "This video cannot start yet. Fix the highlighted fields, then try again.",
57
+ fields: [...picked.missing, ...modelErrors, ...(admitted.ok ? [] : admitted.fields)],
58
+ };
59
+ const storage = {
60
+ catalogue: deps.catalogue,
61
+ db: deps.db,
62
+ paths: deps.paths,
63
+ ids: deps.ids,
64
+ clock: deps.clock,
65
+ log: deps.log,
66
+ emit: (event) => {
67
+ deps.hub.emitGlobal(event);
68
+ },
69
+ };
70
+ // Rendered from the values admit() has trimmed, so the stored text carries no padding the
71
+ // user did not intend.
72
+ const { project } = startRun(storage, admitted.draft, renderPicked(picked, admitted.draft.values), false, Object.fromEntries(picked.bodies.map(({ key, body }) => [key, body])));
73
+ const telemetry = {
74
+ db: deps.db,
75
+ ids: deps.ids,
76
+ clock: deps.clock,
77
+ log: deps.log,
78
+ appVersion: deps.version,
79
+ };
80
+ // One event per project created. record() swallows its own failures, so a broken telemetry
81
+ // write cannot cost the user the run.
82
+ record(telemetry, "project.created", {});
83
+ deps.flushSoon();
84
+ // The run starts only once the project is committed.
85
+ deps.runner.tick(project.id);
86
+ return { ok: true, project, stages: stagesOf(deps.db, project.id) };
87
+ }
@@ -1,21 +1,19 @@
1
1
  import { zValidator } from "@hono/zod-validator";
2
2
  import { Hono } from "hono";
3
3
  import { z } from "zod";
4
- import { modelFields } from "../../catalog/validate.js";
5
4
  import { derive, progressOf } from "../../kernel/runner/graph.js";
6
- import { listProjects, projectById, runDraftSchema, stageStandingsByProject, stagesOf, } from "../../slices/admission/repo.js";
7
- import { admit } from "../../slices/admission/rules.js";
8
- import { startRun } from "../../slices/admission/start.js";
5
+ import { listProjects, projectById, projectTrashed, runDraftSchema, stageStandingsByProject, stagesOf, } from "../../slices/admission/repo.js";
6
+ import { defaultChannelId } from "../../slices/channels/model.js";
7
+ import { projectChannels } from "../../slices/channels/repo.js";
9
8
  import { withProjectControl } from "../../slices/control/lock.js";
10
- import { resolveFont } from "../../slices/fonts/index.js";
11
- import { pickTemplates, renderPicked } from "../../slices/library/slots.js";
12
9
  import { resumable } from "../../slices/rebuild/recovery-repo.js";
13
10
  import { adoptBaseline } from "../../slices/revisions/adopt.js";
14
11
  import { currentRevisionId } from "../../slices/revisions/repo.js";
15
- import { deleteProject } from "../../slices/storage/delete-project.js";
16
- import { outputsOf, stagedFiles } from "../../slices/storage/repo.js";
17
- import { record } from "../../slices/telemetry/record.js";
12
+ import { outputsOf } from "../../slices/storage/repo.js";
13
+ import { trashProject } from "../../slices/trash/service.js";
14
+ import { uploadedProjects } from "../../slices/uploads/repo.js";
18
15
  import { onInvalid, problem, titleOf } from "./problem.js";
16
+ import { createProject } from "./project-create.js";
19
17
  const idParam = z.object({
20
18
  id: z
21
19
  .string()
@@ -26,28 +24,11 @@ const idParam = z.object({
26
24
  // The return type is inferred so Hono keeps the route types the SPA's client is
27
25
  // generated from; see stagingRoutes.
28
26
  export function projectRoutes(deps) {
29
- const storage = {
30
- catalogue: deps.catalogue,
27
+ const storageForTrash = {
31
28
  db: deps.db,
32
29
  paths: deps.paths,
33
- ids: deps.ids,
34
30
  clock: deps.clock,
35
31
  log: deps.log,
36
- emit: (event) => {
37
- deps.hub.emitGlobal(event);
38
- },
39
- };
40
- const telemetry = {
41
- db: deps.db,
42
- ids: deps.ids,
43
- clock: deps.clock,
44
- log: deps.log,
45
- appVersion: deps.version,
46
- };
47
- const storageForDelete = {
48
- db: deps.db,
49
- paths: deps.paths,
50
- log: deps.log,
51
32
  hasInflight: deps.runner.hasInflight,
52
33
  };
53
34
  const summarise = (project) => ({
@@ -56,76 +37,33 @@ export function projectRoutes(deps) {
56
37
  });
57
38
  return (new Hono()
58
39
  .post("/", zValidator("json", runDraftSchema, onInvalid), async (c) => {
59
- if (c.req.valid("json").checkpoints?.length)
60
- return problem(c, {
61
- status: 409,
62
- title: titleOf(409),
63
- detail: "This project has checkpoints turned on, so it must be started from Play with Review and start.",
64
- });
65
- const requestedSubtitles = c.req.valid("json").subtitles;
66
- if (requestedSubtitles !== undefined && requestedSubtitles.mode !== "off") {
67
- try {
68
- await resolveFont(deps.paths, requestedSubtitles.fontId);
69
- }
70
- catch {
71
- return problem(c, {
72
- status: 400,
73
- title: titleOf(400),
74
- detail: "The subtitle font you picked is no longer available. Choose another font before starting.",
75
- extensions: {
76
- fields: [
77
- {
78
- field: "subtitles.fontId",
79
- message: "This font is no longer available. Choose another or upload it again in Settings.",
80
- },
81
- ],
82
- },
83
- });
84
- }
85
- }
86
- // The bodies are read here, at the click, so an edit made since the
87
- // prompt was selected is the one that runs.
88
- const picked = pickTemplates(deps.db, c.req.valid("json"));
89
- const admitted = admit({
90
- draft: picked.draft,
91
- staged: stagedFiles(deps.db),
92
- requiredSlots: picked.requiredSlots,
93
- });
94
- const modelErrors = modelFields(picked.draft, deps.catalogue);
95
- if (!admitted.ok || picked.missing.length > 0 || modelErrors.length > 0) {
40
+ const created = await createProject(deps, c.req.valid("json"));
41
+ if (!created.ok)
96
42
  return problem(c, {
97
- status: 400,
98
- title: titleOf(400),
99
- detail: "This video cannot start yet. Fix the highlighted fields, then try again.",
100
- extensions: {
101
- fields: [...picked.missing, ...modelErrors, ...(admitted.ok ? [] : admitted.fields)],
102
- },
43
+ status: created.status,
44
+ title: titleOf(created.status),
45
+ detail: created.detail,
46
+ ...(created.fields.length === 0 ? {} : { extensions: { fields: created.fields } }),
103
47
  });
104
- }
105
- // Rendered from the values admit() has trimmed, so the stored text carries no
106
- // padding the user did not intend.
107
- const { project } = startRun(storage, admitted.draft, renderPicked(picked, admitted.draft.values), false, Object.fromEntries(picked.bodies.map(({ key, body }) => [key, body])));
108
- // One event per project created. record() swallows its own
109
- // failures, so a broken telemetry write cannot cost the user the run.
110
- record(telemetry, "project.created", {});
111
- deps.flushSoon();
112
- // The run starts only once the project is committed.
113
- deps.runner.tick(project.id);
114
48
  // Read back after the tick, not from the rows startRun built: the runner has
115
49
  // already claimed every eligible stage, and a body that paired status "running"
116
50
  // with "video: pending" would contradict itself.
117
- return c.json({ project: summarise(project), stages: stagesOf(deps.db, project.id) }, 201);
51
+ return c.json({ project: summarise(created.project), stages: stagesOf(deps.db, created.project.id) }, 201);
118
52
  })
119
53
  // One statement for every project's stage standings, not one per row: the list needs
120
54
  // a status word and a meter, and both come out of the same five columns.
121
55
  .get("/", (c) => {
122
56
  const standings = stageStandingsByProject(deps.db);
57
+ const channels = projectChannels(deps.db);
58
+ const uploads = uploadedProjects(deps.db);
123
59
  const projects = listProjects(deps.db).map((project) => {
124
60
  const stages = standings.get(project.id) ?? [];
125
61
  return {
126
62
  ...project,
127
63
  status: derive(stages, project.paused),
128
64
  progress: progressOf(stages),
65
+ channelId: channels.get(project.id) ?? defaultChannelId,
66
+ uploadedAt: uploads.get(project.id) ?? null,
129
67
  };
130
68
  });
131
69
  return c.json({ projects });
@@ -136,7 +74,9 @@ export function projectRoutes(deps) {
136
74
  return problem(c, {
137
75
  status: 404,
138
76
  title: titleOf(404),
139
- detail: "This project no longer exists. Go back to Projects to pick another.",
77
+ detail: projectTrashed(deps.db, c.req.valid("param").id)
78
+ ? "This project is in the trash. Restore it in Settings → Trash to open it again."
79
+ : "This project no longer exists. Go back to Projects to pick another.",
140
80
  });
141
81
  }
142
82
  if (deps.catalogue !== undefined)
@@ -149,30 +89,22 @@ export function projectRoutes(deps) {
149
89
  outputs: outputsOf(deps.db, project.id),
150
90
  });
151
91
  })
152
- // Irreversible, and only from the app: the Projects screen puts a confirmation
153
- // dialog in front of it.
92
+ // Moves the project to the trash (Settings → Trash) for 30 days; removing it for good is
93
+ // Delete now there, or the daily purge (`slices/trash`).
154
94
  .delete("/:id", zValidator("param", idParam, onInvalid), (c) => {
155
95
  const id = c.req.valid("param").id;
156
96
  return withProjectControl(deps.db, id, () => {
157
- const result = deleteProject(storageForDelete, id);
97
+ const result = trashProject(storageForTrash, id);
158
98
  if (result.ok)
159
99
  return c.body(null, 204);
100
+ const status = result.reason === "running" ? 409 : 404;
160
101
  return problem(c, {
161
- status: deleteStatus[result.reason],
162
- title: titleOf(deleteStatus[result.reason]),
163
- detail: result.detail ?? deleteDetails[result.reason],
102
+ status,
103
+ title: titleOf(status),
104
+ detail: result.reason === "running"
105
+ ? "This project is still running. Use Cancel run on the project page first, then delete it."
106
+ : "This project no longer exists. Go back to Projects to pick another.",
164
107
  });
165
108
  });
166
109
  }));
167
110
  }
168
- const deleteStatus = {
169
- "no-project": 404,
170
- running: 409,
171
- files: 500,
172
- };
173
- const deleteDetails = {
174
- "no-project": "This project no longer exists. Go back to Projects to pick another.",
175
- // The run has to be stopped before its files can go.
176
- running: "This project is still running. Use Cancel run on the project page first, then delete it.",
177
- files: "Some of this project's files could not be removed. Close any program using files in the project folder, then try deleting again.",
178
- };
@@ -1,9 +1,12 @@
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 { promptKinds } from "../../slices/library/model.js";
5
- import { listPrompts } from "../../slices/library/repo.js";
6
- import { createPrompt, removePrompt, updatePrompt } from "../../slices/library/save.js";
6
+ import { setPromptPhotorealistic, withPhotorealistic, } from "../../slices/library/photorealistic.js";
7
+ import { listPrompts, promptById } from "../../slices/library/repo.js";
8
+ import { createPrompt, removePrompt, restorePrompt, updatePrompt, } from "../../slices/library/save.js";
9
+ import { usedBy } from "../../slices/library/used-by.js";
7
10
  import { onInvalid, problem, titleOf } from "./problem.js";
8
11
  const idParam = z.object({
9
12
  id: z
@@ -12,6 +15,8 @@ const idParam = z.object({
12
15
  .max(64)
13
16
  .regex(/^[0-9A-Za-z_-]+$/),
14
17
  });
18
+ // History's Restore names the version by its number.
19
+ export const versionParam = idParam.extend({ version: z.coerce.number().int().positive() });
15
20
  // Shape only. The rules - trimming, emptiness, length, the slot lint - are the slice's,
16
21
  // so one set of messages reaches the editor.
17
22
  const promptBody = z.object({
@@ -26,7 +31,7 @@ export function promptRoutes(deps) {
26
31
  return (new Hono()
27
32
  // Every kind in one list: 04 Prompts filters by tab, and Duplicate needs the body it
28
33
  // is copying.
29
- .get("/", (c) => c.json({ prompts: listPrompts(deps.db) }))
34
+ .get("/", (c) => c.json({ prompts: withPhotorealistic(deps.db, listPrompts(deps.db)) }))
30
35
  .post("/", zValidator("json", promptBody, onInvalid), (c) => {
31
36
  const result = createPrompt(library, c.req.valid("json"));
32
37
  return result.ok ? c.json(result.value, 201) : refused(c, result, "prompt");
@@ -34,6 +39,38 @@ export function promptRoutes(deps) {
34
39
  .put("/:id", zValidator("param", idParam, onInvalid), zValidator("json", promptBody, onInvalid), (c) => {
35
40
  const result = updatePrompt(library, c.req.valid("param").id, c.req.valid("json"));
36
41
  return result.ok ? c.json(result.value) : refused(c, result, "prompt");
42
+ })
43
+ // An Image prompt's "Draws photorealistic pictures" tick (`slices/library/photorealistic.ts`).
44
+ .put("/:id/photorealistic", zValidator("param", idParam, onInvalid), zValidator("json", z.object({ photorealistic: z.boolean() }), onInvalid), (c) => {
45
+ const result = setPromptPhotorealistic(deps.db, c.req.valid("param").id, c.req.valid("json").photorealistic);
46
+ if (result.ok)
47
+ return c.body(null, 204);
48
+ return result.reason === "not-found"
49
+ ? refused(c, { ok: false, reason: "not-found" }, "prompt")
50
+ : problem(c, {
51
+ status: 400,
52
+ title: titleOf(400),
53
+ detail: "Only an Image prompt can be marked photorealistic. Open the prompt from the Image tab in Library → Prompts.",
54
+ });
55
+ })
56
+ // Every saved version, newest first (`slices/library/history.ts`).
57
+ .get("/:id/history", zValidator("param", idParam, onInvalid), (c) => {
58
+ const prompt = promptById(deps.db, c.req.valid("param").id);
59
+ return prompt === undefined
60
+ ? refused(c, { ok: false, reason: "not-found" }, "prompt")
61
+ : c.json({ versions: versionsOrCurrent(deps.db, "prompt", prompt) });
62
+ })
63
+ .post("/:id/history/:version/restore", zValidator("param", versionParam, onInvalid), (c) => {
64
+ const { id, version } = c.req.valid("param");
65
+ const result = restorePrompt(library, id, version);
66
+ return result.ok ? c.json(result.value) : refused(c, result, "prompt");
67
+ })
68
+ // The templates, schedules and projects that name it.
69
+ .get("/:id/used-by", zValidator("param", idParam, onInvalid), (c) => {
70
+ const prompt = promptById(deps.db, c.req.valid("param").id);
71
+ return prompt === undefined
72
+ ? refused(c, { ok: false, reason: "not-found" }, "prompt")
73
+ : c.json(usedBy(deps.db, { item: "prompt", kind: prompt.kind, name: prompt.name }));
37
74
  })
38
75
  // A project holds its own rendered text, so nothing cascades and a
39
76
  // template used by past projects is deleted like any other.
@@ -1,17 +1,36 @@
1
1
  import { zValidator } from "@hono/zod-validator";
2
2
  import { Hono } from "hono";
3
3
  import { z } from "zod";
4
+ import { aliasCountMax } from "../../kernel/ports/narration-aliases.js";
5
+ import { listNarrationAliases, saveNarrationAliases, } from "../../slices/narration/aliases-library.js";
4
6
  import { collectSharedGlossary } from "../../slices/narration/shared-glossary.js";
5
- import { onInvalid } from "./problem.js";
7
+ import { onInvalid, problem, titleOf } from "./problem.js";
6
8
  // The pronunciations of every project's glossary, merged, for Edit project to copy into a
7
9
  // project that shares them. `except` leaves out the project being edited, whose own glossary
8
- // already comes first.
10
+ // already comes first. Library → Aliases lives here too: the other way to change how a word
11
+ // is said.
9
12
  export function pronunciationRoutes(deps) {
10
- return new Hono().get("/shared", zValidator("query", z.object({
13
+ const library = { db: deps.db, ids: deps.ids, clock: deps.clock };
14
+ return (new Hono()
15
+ .get("/shared", zValidator("query", z.object({
11
16
  except: z
12
17
  .string()
13
18
  .max(64)
14
19
  .regex(/^[0-9A-Za-z_-]*$/)
15
20
  .optional(),
16
- }), onInvalid), (c) => c.json(collectSharedGlossary(deps, c.req.valid("query").except)));
21
+ }), onInvalid), (c) => c.json(collectSharedGlossary(deps, c.req.valid("query").except)))
22
+ .get("/aliases", (c) => c.json({ aliases: listNarrationAliases(deps.db) }))
23
+ // The editor saves the whole ordered list at once; each row is checked in the slice, so
24
+ // the editor gets "Alias 3: …" sentences rather than schema paths.
25
+ .put("/aliases", zValidator("json", z.object({ aliases: z.array(z.unknown()).max(aliasCountMax * 2) }), onInvalid), (c) => {
26
+ const result = saveNarrationAliases(library, c.req.valid("json").aliases);
27
+ if (result.ok)
28
+ return c.json({ aliases: result.aliases });
29
+ return problem(c, {
30
+ status: 400,
31
+ title: titleOf(400),
32
+ detail: `These aliases can't be saved yet: ${result.fields.map((field) => field.message).join(" ")}`,
33
+ extensions: { fields: result.fields },
34
+ });
35
+ }));
17
36
  }
@@ -1,8 +1,16 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { zValidator } from "@hono/zod-validator";
2
3
  import { Hono } from "hono";
3
4
  import { z } from "zod";
5
+ import { keyProbes } from "../../adapters/key-probes.js";
4
6
  import { videoModelsOf } from "../../catalog/schema.js";
7
+ import { switchAllRetired, switchRetiredModel } from "../../slices/model-upkeep/switch.js";
8
+ import { retiredModelUsage, usageKinds, usageSlots } from "../../slices/model-upkeep/usage.js";
5
9
  import { cliPathMaxLength, saveCliPath } from "../../slices/settings/cli-paths.js";
10
+ import { dismissFirstRun, firstRunStatus } from "../../slices/settings/first-run.js";
11
+ import { checkProviderHealth } from "../../slices/settings/health.js";
12
+ import { keyGuides } from "../../slices/settings/key-guides.js";
13
+ import { testProviderKey } from "../../slices/settings/key-test.js";
6
14
  import { keyStatus, removeProviderKey, saveProviderKey } from "../../slices/settings/keys.js";
7
15
  import { isLocalCliProvider, providerById, providerIds } from "../../slices/settings/model.js";
8
16
  import { createModelCatalog } from "../../slices/settings/models.js";
@@ -14,6 +22,17 @@ const providerParam = z.object({ id: z.enum(providerIds) });
14
22
  // ever issues something longer.
15
23
  const pathBody = z.object({ path: z.string().max(cliPathMaxLength) });
16
24
  const keyBody = z.object({ key: z.string().min(1).max(4096) });
25
+ const modelId = z.string().min(1).max(200);
26
+ const switchBody = z
27
+ .object({
28
+ kind: z.enum(usageKinds),
29
+ id: z.string().min(1).max(64),
30
+ slot: z.enum(usageSlots),
31
+ from: z.object({ provider: z.string().min(1).max(64), model: modelId }).strict(),
32
+ to: modelId,
33
+ })
34
+ .strict();
35
+ const noFetch = "Slopify cannot reach the internet from here yet because it is still starting. Wait a moment, reload the page and try again.";
17
36
  // The return type is inferred so Hono keeps the route types the SPA's client is
18
37
  // generated from; see stagingRoutes.
19
38
  export function providerRoutes(deps) {
@@ -29,7 +48,65 @@ export function providerRoutes(deps) {
29
48
  now: () => deps.clock.now().getTime(),
30
49
  report: (provider) => deps.log.write("warn", "model-catalog", { detail: `Could not load models for ${provider}` }),
31
50
  });
51
+ const upkeep = () => ({ ...deps, uuid: deps.drafts?.uuid ?? randomUUID });
32
52
  return (new Hono()
53
+ // The step lists Settings → Providers shows beside each key field.
54
+ .get("/key-guides", (c) => c.json({ guides: keyGuides }))
55
+ // What the first launch found, and the providers Play picks by default.
56
+ .get("/first-run", async (c) => {
57
+ c.header("Cache-Control", "no-store");
58
+ return c.json(await firstRunStatus({ db: deps.db, ...(deps.modelsFor === undefined ? {} : { modelsFor: deps.modelsFor }) }, await providerStatuses(readiness)));
59
+ })
60
+ .post("/first-run/dismiss", (c) => {
61
+ dismissFirstRun(deps.db);
62
+ return c.body(null, 204);
63
+ })
64
+ // "Check all": every CLI signed in, every key valid, every chosen model still offered.
65
+ .post("/health", async (c) => {
66
+ if (deps.fetch === undefined)
67
+ return problem(c, { status: 503, title: titleOf(503), detail: noFetch });
68
+ const catalogue = deps.catalogue;
69
+ return c.json(await checkProviderHealth({
70
+ ...readiness,
71
+ clock: deps.clock,
72
+ fetch: deps.fetch,
73
+ probes: keyProbes,
74
+ ...(catalogue === undefined ? {} : { catalogue: () => catalogue.read() }),
75
+ ...(deps.modelsFor === undefined ? {} : { modelsFor: deps.modelsFor }),
76
+ ...(deps.cliLogin === undefined ? {} : { login: deps.cliLogin }),
77
+ }));
78
+ })
79
+ // Checks for new, repriced and retired models now instead of waiting for the daily check.
80
+ .post("/catalogue/check", async (c) => {
81
+ if (!deps.catalogue?.sync)
82
+ return problem(c, {
83
+ status: 503,
84
+ title: titleOf(503),
85
+ detail: "The model list (models.yaml) is not loaded yet. Wait a moment and reload the page; if it stays like this, restart Slopify.",
86
+ });
87
+ await deps.catalogue.sync();
88
+ return c.json(deps.catalogue.status());
89
+ })
90
+ // Every template, schedule, draft and unfinished project still using a retired model.
91
+ .get("/catalogue/retired", (c) => {
92
+ c.header("Cache-Control", "no-store");
93
+ return c.json({
94
+ usages: deps.catalogue === undefined ? [] : retiredModelUsage(deps.db, deps.catalogue.read()),
95
+ });
96
+ })
97
+ .post("/catalogue/retired/switch", zValidator("json", switchBody, onInvalid), async (c) => {
98
+ if (deps.catalogue === undefined)
99
+ return problem(c, { status: 503, title: titleOf(503), detail: noFetch });
100
+ const result = await switchRetiredModel(upkeep(), deps.catalogue.read(), c.req.valid("json"));
101
+ if (!result.ok)
102
+ return problem(c, { status: 409, title: titleOf(409), detail: result.message });
103
+ return c.json(result);
104
+ })
105
+ .post("/catalogue/retired/switch-all", async (c) => {
106
+ if (deps.catalogue === undefined)
107
+ return problem(c, { status: 503, title: titleOf(503), detail: noFetch });
108
+ return c.json(await switchAllRetired(upkeep(), deps.catalogue.read()));
109
+ })
33
110
  .get("/catalogue", (c) => c.json(deps.catalogue?.status() ?? {
34
111
  warning: "The model list (models.yaml) is not loaded yet. Wait a moment and reload the page; if it stays like this, restart Slopify.",
35
112
  }))
@@ -101,6 +178,15 @@ export function providerRoutes(deps) {
101
178
  catalog.invalidate(id);
102
179
  // The response says a key is stored and shows the mask, never the value.
103
180
  return c.json(keyStatus(keys, id));
181
+ })
182
+ // The Test button: the cheapest read the provider offers, answered in plain words.
183
+ .post("/:id/key/test", zValidator("param", providerParam, onInvalid), async (c) => {
184
+ const { id } = c.req.valid("param");
185
+ if (providerById(id).auth === "cli")
186
+ return refusal(c, id, "cli-provider");
187
+ if (deps.fetch === undefined)
188
+ return problem(c, { status: 503, title: titleOf(503), detail: noFetch });
189
+ return c.json(await testProviderKey({ db: deps.db, clock: deps.clock, fetch: deps.fetch, probes: keyProbes }, id));
104
190
  })
105
191
  .delete("/:id/key", zValidator("param", providerParam, onInvalid), (c) => {
106
192
  const { id } = c.req.valid("param");
@@ -0,0 +1,121 @@
1
+ import { zValidator } from "@hono/zod-validator";
2
+ import { Hono } from "hono";
3
+ import { z } from "zod";
4
+ import { revisionControlSchema } from "../../slices/control/revision-control-schema.js";
5
+ import { recoverProject } from "../../slices/rebuild/recovery.js";
6
+ import { actOnVerdict, listVerdicts, verdictById } from "../../slices/reviews/repo.js";
7
+ import { latestReviews } from "../../slices/reviews/view.js";
8
+ import { currentRevisionId } from "../../slices/revisions/repo.js";
9
+ import { getRevisionView } from "../../slices/revisions/view.js";
10
+ import { onInvalid, problem, titleOf } from "./problem.js";
11
+ const id = z
12
+ .string()
13
+ .min(1)
14
+ .max(64)
15
+ .regex(/^[0-9A-Za-z_-]+$/);
16
+ const projectParam = z.object({ id });
17
+ const verdictParam = projectParam.extend({ verdictId: id });
18
+ // The project page's review column: each reviewed item's latest verdict with its reasons,
19
+ // Overrule to accept a failed item as it is, and Redo to have it made again through the
20
+ // same path as More → make it again in its section.
21
+ export function reviewRoutes(deps) {
22
+ const gone = (c) => problem(c, {
23
+ status: 404,
24
+ title: titleOf(404),
25
+ detail: "This review no longer exists; the project may have been deleted or restored from a backup. Reload the project page.",
26
+ });
27
+ const notify = (projectId) => {
28
+ try {
29
+ deps.hub.emit(projectId, { type: "project.updated", projectId });
30
+ }
31
+ catch (error) {
32
+ deps.log.write("error", "review.notify", {
33
+ projectId,
34
+ detail: error instanceof Error ? error.message : String(error),
35
+ });
36
+ }
37
+ };
38
+ return new Hono()
39
+ .get("/:id/reviews", zValidator("param", projectParam, onInvalid), (c) => {
40
+ const projectId = c.req.valid("param").id;
41
+ const head = currentRevisionId(deps.db, projectId);
42
+ const view = head === undefined ? undefined : getRevisionView(deps, projectId, head);
43
+ const outputs = (view?.outputs ?? [])
44
+ .filter((row) => row.selected)
45
+ .map((row) => ({
46
+ workKey: row.workKey,
47
+ fingerprint: row.fingerprint,
48
+ outputId: row.output.id,
49
+ }));
50
+ return c.json({ reviews: latestReviews(listVerdicts(deps.db, projectId), outputs) });
51
+ })
52
+ .post("/:id/reviews/:verdictId/overrule", zValidator("param", verdictParam, onInvalid), (c) => {
53
+ const { id: projectId, verdictId } = c.req.valid("param");
54
+ const result = actOnVerdict(deps.db, projectId, verdictId, "overruled", deps.clock.now().toISOString());
55
+ if (!result.ok)
56
+ return result.reason === "not-found"
57
+ ? gone(c)
58
+ : problem(c, {
59
+ status: 409,
60
+ title: titleOf(409),
61
+ detail: "This verdict can't be overruled: it passed, or Slopify is already making the item again. Reload the project page to see where it is.",
62
+ extensions: { reason: "conflict" },
63
+ });
64
+ notify(projectId);
65
+ return c.json({ review: result.value });
66
+ })
67
+ .post("/:id/reviews/:verdictId/redo", zValidator("param", verdictParam, onInvalid), zValidator("json", revisionControlSchema, onInvalid), async (c) => {
68
+ const { id: projectId, verdictId } = c.req.valid("param");
69
+ const verdict = verdictById(deps.db, projectId, verdictId);
70
+ if (verdict === undefined)
71
+ return gone(c);
72
+ if (deps.rebuild === undefined)
73
+ return problem(c, {
74
+ status: 503,
75
+ title: titleOf(503),
76
+ detail: "Redo is not ready yet because Slopify is still starting. Wait a moment, reload the page and try again.",
77
+ });
78
+ if (verdict.redoState === "pending" || verdict.redoState === "started")
79
+ return problem(c, {
80
+ status: 409,
81
+ title: titleOf(409),
82
+ detail: "Slopify is already making this item again. Wait for it to land, then reload the project page.",
83
+ extensions: { reason: "conflict" },
84
+ });
85
+ const result = await recoverProject(deps.rebuild, projectId, {
86
+ ...c.req.valid("json"),
87
+ action: { kind: "redo", item: verdict.itemKey },
88
+ });
89
+ if (!result.ok)
90
+ return refused(c, result, projectId);
91
+ actOnVerdict(deps.db, projectId, verdictId, "redone", deps.clock.now().toISOString());
92
+ notify(projectId);
93
+ return c.json(result, 202);
94
+ });
95
+ function refused(c, result, projectId) {
96
+ const status = result.reason === "no-project"
97
+ ? 404
98
+ : result.reason === "invalid-selection" || result.reason === "invalid-edit"
99
+ ? 400
100
+ : 409;
101
+ const lead = result.reason === "running"
102
+ ? "This item, or work that depends on it, is still running. Wait for it to finish, or Pause the project, then use Redo again."
103
+ : result.reason === "readiness"
104
+ ? "Slopify can't make this item again yet. Fix what is listed in Settings → Providers or Edit project, then use Redo again."
105
+ : result.reason === "conflict" || result.reason === "control-changed"
106
+ ? "This project changed since the page loaded. Reload the project page, then use Redo again."
107
+ : result.reason === "invalid-selection"
108
+ ? "This item can't be made again."
109
+ : "Slopify couldn't make this item again.";
110
+ return problem(c, {
111
+ status,
112
+ title: titleOf(status),
113
+ detail: [lead, ...(result.fields ?? []).map((row) => row.message)].join(" "),
114
+ extensions: {
115
+ reason: result.reason,
116
+ fields: result.fields ?? [],
117
+ currentRevisionId: currentRevisionId(deps.db, projectId) ?? null,
118
+ },
119
+ });
120
+ }
121
+ }
@@ -65,7 +65,7 @@ function unavailable(c, reason) {
65
65
  status: 404,
66
66
  title: titleOf(404),
67
67
  detail: reason === "missing-file"
68
- ? "This file was deleted or moved from the project folder. In the Edit tab, use Rebuild affected outputs to make it again."
68
+ ? "This file was deleted or moved from the project folder. In the Edit tab, use Choose what to remake (in the project's More menu) to make it again."
69
69
  : "This file is no longer part of the project. Reload the page to see its current files.",
70
70
  extensions: { reason },
71
71
  });
@@ -37,7 +37,7 @@ const details = {
37
37
  conflict: "This project changed while you were editing. Reload the page to see the latest, then make your change again.",
38
38
  "idempotency-conflict": "This change was already sent with different details. Reload the page, then try again.",
39
39
  "invalid-edit": "Some of your changes are not valid. Fix the highlighted fields and save again.",
40
- "stale-preview": "What needs rebuilding changed since you opened this review. Cancel it and open Rebuild affected outputs again to see the latest.",
40
+ "stale-preview": "What needs rebuilding changed since you opened this review. Cancel it and open Choose what to remake (in the project's More menu) again to see the latest.",
41
41
  "invalid-selection": "Some of the outputs you picked can no longer be rebuilt. Reload the page and choose again.",
42
42
  "review-required": 'This rebuild touches content you supplied yourself. Tick "Keep the provided content" for each item in the review, then choose Start rebuild.',
43
43
  "cost-ack-required": "Some costs for this rebuild are unknown. Tick the box confirming you understand, then choose Start rebuild.",