@gentbajko/slopify 2.4.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (469) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +21 -40
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +11 -1
  6. package/dist/adapters/alignment/cache.js +4 -1
  7. package/dist/adapters/alignment/ctc.js +25 -23
  8. package/dist/adapters/alignment/index.js +37 -6
  9. package/dist/adapters/alignment/multilingual.js +267 -0
  10. package/dist/adapters/alignment/protocol.js +11 -0
  11. package/dist/adapters/alignment/quality.js +4 -3
  12. package/dist/adapters/alignment/sentences.js +95 -0
  13. package/dist/adapters/alignment/spec.js +24 -0
  14. package/dist/adapters/alignment/spell.js +769 -0
  15. package/dist/adapters/alignment/text.js +56 -9
  16. package/dist/adapters/alignment/window.js +34 -30
  17. package/dist/adapters/alignment/worker.js +23 -21
  18. package/dist/adapters/explain.js +11 -11
  19. package/dist/adapters/host-cli/index.js +55 -5
  20. package/dist/adapters/host-cli/transport.js +13 -6
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +60 -18
  23. package/dist/adapters/image/codex.js +166 -25
  24. package/dist/adapters/image/fal.js +31 -3
  25. package/dist/adapters/image/google.js +17 -2
  26. package/dist/adapters/image/openai.js +45 -16
  27. package/dist/adapters/image/reference.js +41 -0
  28. package/dist/adapters/image/replicate.js +14 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +36 -0
  31. package/dist/adapters/llm/claude-code.js +168 -32
  32. package/dist/adapters/llm/cli-login-error.js +1 -1
  33. package/dist/adapters/llm/codex-limits.js +157 -0
  34. package/dist/adapters/llm/codex-models.js +2 -1
  35. package/dist/adapters/llm/codex-server-models.js +2 -1
  36. package/dist/adapters/llm/codex.js +99 -12
  37. package/dist/adapters/llm/document-workspace.js +2 -2
  38. package/dist/adapters/llm/gemini-workspace.js +1 -1
  39. package/dist/adapters/llm/gemini.js +16 -3
  40. package/dist/adapters/llm/image-workspace.js +58 -0
  41. package/dist/adapters/llm/openrouter.js +11 -3
  42. package/dist/adapters/llm/run-cli.js +26 -9
  43. package/dist/adapters/tts/cartesia.js +10 -0
  44. package/dist/adapters/tts/elevenlabs.js +31 -9
  45. package/dist/adapters/tts/inworld-async.js +9 -5
  46. package/dist/adapters/tts/inworld.js +17 -3
  47. package/dist/adapters/tts/openai.js +3 -0
  48. package/dist/adapters/tts/voice-languages.js +80 -0
  49. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  50. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/OFL.txt +93 -0
  55. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  56. package/dist/assets/models.yaml +7 -0
  57. package/dist/assets/sample/sample-audiobook.tar +0 -0
  58. package/dist/assets/sample/sample-podcast.tar +0 -0
  59. package/dist/assets/sample/sample-project.tar +0 -0
  60. package/dist/catalog/merge.js +132 -0
  61. package/dist/catalog/registry.js +33 -12
  62. package/dist/catalog/runtime-models.js +1 -1
  63. package/dist/catalog/schema.js +12 -1
  64. package/dist/catalog/store.js +147 -8
  65. package/dist/catalog/validate.js +17 -2
  66. package/dist/compose.yaml +71 -0
  67. package/dist/edge/autostart/docker-record.js +32 -0
  68. package/dist/edge/autostart/docker.js +50 -0
  69. package/dist/edge/autostart/index.js +78 -0
  70. package/dist/edge/autostart/launcher.js +176 -0
  71. package/dist/edge/autostart/model.js +3 -0
  72. package/dist/edge/autostart/native.js +210 -0
  73. package/dist/edge/autostart/prompt.js +66 -0
  74. package/dist/edge/autostart/service.js +81 -0
  75. package/dist/edge/cli.js +57 -51
  76. package/dist/edge/docker-install/activation.js +56 -0
  77. package/dist/edge/docker-install/apply.js +458 -0
  78. package/dist/edge/docker-install/engine.js +362 -0
  79. package/dist/edge/docker-install/run.js +154 -0
  80. package/dist/edge/docker-install/state.js +146 -0
  81. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  82. package/dist/edge/docker.js +14 -22
  83. package/dist/edge/events/hub.js +11 -0
  84. package/dist/edge/host-cli.js +1 -1
  85. package/dist/edge/http/actions.js +32 -10
  86. package/dist/edge/http/app.js +60 -2
  87. package/dist/edge/http/auditions.js +70 -0
  88. package/dist/edge/http/autostart.js +37 -0
  89. package/dist/edge/http/backups.js +40 -0
  90. package/dist/edge/http/channel-memory.js +92 -0
  91. package/dist/edge/http/channels.js +128 -0
  92. package/dist/edge/http/entries.js +22 -3
  93. package/dist/edge/http/files.js +1 -1
  94. package/dist/edge/http/folder-location.js +1 -1
  95. package/dist/edge/http/home.js +39 -0
  96. package/dist/edge/http/host-cli.js +45 -6
  97. package/dist/edge/http/narration-peaks.js +76 -0
  98. package/dist/edge/http/onboarding.js +152 -0
  99. package/dist/edge/http/open-folder.js +1 -1
  100. package/dist/edge/http/patch-notes.js +45 -0
  101. package/dist/edge/http/planning.js +4 -2
  102. package/dist/edge/http/problem.js +1 -0
  103. package/dist/edge/http/project-create.js +87 -0
  104. package/dist/edge/http/projects.js +31 -99
  105. package/dist/edge/http/prompts.js +40 -3
  106. package/dist/edge/http/pronunciations.js +23 -4
  107. package/dist/edge/http/providers.js +86 -0
  108. package/dist/edge/http/reviews.js +121 -0
  109. package/dist/edge/http/revision-files.js +1 -1
  110. package/dist/edge/http/revisions.js +1 -1
  111. package/dist/edge/http/run-cost.js +25 -0
  112. package/dist/edge/http/schedules.js +111 -6
  113. package/dist/edge/http/settings.js +128 -6
  114. package/dist/edge/http/storage-files.js +50 -0
  115. package/dist/edge/http/storage.js +29 -2
  116. package/dist/edge/http/studio.js +207 -0
  117. package/dist/edge/http/style-preview.js +100 -0
  118. package/dist/edge/http/subtitles.js +1 -1
  119. package/dist/edge/http/trash.js +69 -0
  120. package/dist/edge/http/update.js +24 -3
  121. package/dist/edge/http/whats-new.js +15 -0
  122. package/dist/edge/http/youtube-edits.js +83 -0
  123. package/dist/edge/native-update.js +62 -0
  124. package/dist/host-cli/open-folder.js +10 -10
  125. package/dist/host-cli/runtime.js +12 -3
  126. package/dist/kernel/db/migrations/0022-drop-duplicate-held-work.sql +56 -0
  127. package/dist/kernel/db/migrations/0024-reference-attachments.sql +18 -0
  128. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  129. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  130. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  131. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  132. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  133. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  134. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  135. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  136. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  137. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  138. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  139. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  140. package/dist/kernel/paths.js +27 -6
  141. package/dist/kernel/pipeline.js +4 -1
  142. package/dist/kernel/ports/host-cli.js +139 -16
  143. package/dist/kernel/ports/image.js +3 -1
  144. package/dist/kernel/ports/key-probe.js +1 -0
  145. package/dist/kernel/ports/languages.js +157 -0
  146. package/dist/kernel/ports/llm.js +3 -1
  147. package/dist/kernel/ports/model.js +5 -2
  148. package/dist/kernel/ports/narration-aliases.js +60 -0
  149. package/dist/kernel/ports/plan-limits.js +36 -0
  150. package/dist/kernel/runner/attempt.js +18 -9
  151. package/dist/kernel/runner/audition.js +36 -0
  152. package/dist/kernel/runner/graph.js +24 -5
  153. package/dist/kernel/runner/index.js +35 -5
  154. package/dist/kernel/runner/meter.js +1 -0
  155. package/dist/kernel/runner/providers.js +164 -9
  156. package/dist/kernel/runner/retry-policy.js +43 -0
  157. package/dist/kernel/runner/standalone.js +134 -0
  158. package/dist/kernel/runner/work-authority.js +28 -2
  159. package/dist/main.js +321 -20
  160. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  161. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  162. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  163. package/dist/patch-notes/3.0.0.md +70 -0
  164. package/dist/patch-notes/index.json +26 -0
  165. package/dist/slices/admission/model.js +35 -0
  166. package/dist/slices/admission/repo.js +24 -4
  167. package/dist/slices/admission/rules.js +126 -1
  168. package/dist/slices/admission/schema.js +68 -1
  169. package/dist/slices/admission/short-mode.js +47 -0
  170. package/dist/slices/admission/start.js +90 -16
  171. package/dist/slices/article/continuation.js +2 -2
  172. package/dist/slices/article/provided-entries.js +2 -2
  173. package/dist/slices/article/run.js +6 -2
  174. package/dist/slices/article/segments.js +3 -3
  175. package/dist/slices/backups/files.js +135 -0
  176. package/dist/slices/backups/folder.js +43 -0
  177. package/dist/slices/backups/model.js +65 -0
  178. package/dist/slices/backups/repo.js +28 -0
  179. package/dist/slices/backups/schedule.js +60 -0
  180. package/dist/slices/backups/service.js +311 -0
  181. package/dist/slices/batch/index.js +9 -2
  182. package/dist/slices/cancel/index.js +1 -1
  183. package/dist/slices/channels/cast-images.js +110 -0
  184. package/dist/slices/channels/cast-match.js +32 -0
  185. package/dist/slices/channels/images.js +19 -0
  186. package/dist/slices/channels/model.js +5 -0
  187. package/dist/slices/channels/rebrand.js +100 -0
  188. package/dist/slices/channels/repo.js +218 -0
  189. package/dist/slices/channels/runs.js +129 -0
  190. package/dist/slices/channels/schema.js +129 -0
  191. package/dist/slices/channels/service.js +180 -0
  192. package/dist/slices/channels/studio-csv.js +110 -0
  193. package/dist/slices/channels/videos.js +120 -0
  194. package/dist/slices/checkpoints/recovery.js +10 -0
  195. package/dist/slices/control/index.js +2 -1
  196. package/dist/slices/document/fonts.js +1 -1
  197. package/dist/slices/document/render.js +1 -1
  198. package/dist/slices/episodes/related.js +90 -0
  199. package/dist/slices/episodes/repo.js +60 -0
  200. package/dist/slices/episodes/service.js +58 -0
  201. package/dist/slices/episodes/summarize.js +170 -0
  202. package/dist/slices/estimate/index.js +113 -24
  203. package/dist/slices/estimate/requests.js +60 -2
  204. package/dist/slices/fixes/rules.js +73 -0
  205. package/dist/slices/fonts/coverage.js +82 -0
  206. package/dist/slices/fonts/sfnt.js +74 -0
  207. package/dist/slices/images/run.js +8 -5
  208. package/dist/slices/images/scale.js +84 -0
  209. package/dist/slices/library/diff.js +122 -0
  210. package/dist/slices/library/history.js +100 -0
  211. package/dist/slices/library/model.js +3 -0
  212. package/dist/slices/library/photorealistic.js +49 -0
  213. package/dist/slices/library/repo.js +19 -14
  214. package/dist/slices/library/save.js +62 -12
  215. package/dist/slices/library/slots.js +15 -3
  216. package/dist/slices/library/snapshot.js +8 -2
  217. package/dist/slices/library/used-by.js +153 -0
  218. package/dist/slices/loudness/level-pieces.js +41 -0
  219. package/dist/slices/loudness/loudnorm.js +164 -0
  220. package/dist/slices/loudness/model.js +153 -0
  221. package/dist/slices/model-upkeep/model.js +10 -0
  222. package/dist/slices/model-upkeep/switch.js +217 -0
  223. package/dist/slices/model-upkeep/usage.js +183 -0
  224. package/dist/slices/narration/aliases-library.js +31 -0
  225. package/dist/slices/narration/aliases-schema.js +53 -0
  226. package/dist/slices/narration/aliases.js +49 -0
  227. package/dist/slices/narration/blocks.js +352 -0
  228. package/dist/slices/narration/chunk.js +19 -7
  229. package/dist/slices/narration/concat.js +2 -2
  230. package/dist/slices/narration/describe.js +45 -0
  231. package/dist/slices/narration/pauses-model.js +63 -0
  232. package/dist/slices/narration/pauses.js +269 -0
  233. package/dist/slices/narration/peaks-model.js +1 -0
  234. package/dist/slices/narration/peaks.js +94 -0
  235. package/dist/slices/narration/preparation.js +21 -3
  236. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  237. package/dist/slices/narration/pronunciation.js +72 -25
  238. package/dist/slices/narration/run.js +6 -6
  239. package/dist/slices/narration/shared-glossary.js +1 -1
  240. package/dist/slices/narration/steering.js +15 -4
  241. package/dist/slices/notifications/notifier.js +94 -0
  242. package/dist/slices/notifications/rules.js +102 -0
  243. package/dist/slices/notifications/send.js +40 -0
  244. package/dist/slices/notifications/settings.js +34 -0
  245. package/dist/slices/onboarding/copy.js +143 -0
  246. package/dist/slices/onboarding/first-run.js +46 -0
  247. package/dist/slices/onboarding/install.js +119 -0
  248. package/dist/slices/onboarding/model.js +64 -0
  249. package/dist/slices/onboarding/packs.js +491 -0
  250. package/dist/slices/onboarding/quick-short.js +129 -0
  251. package/dist/slices/onboarding/sample.js +132 -0
  252. package/dist/slices/onboarding/state.js +68 -0
  253. package/dist/slices/onboarding/template.js +73 -0
  254. package/dist/slices/patch-notes/library.js +73 -0
  255. package/dist/slices/patch-notes/seen.js +77 -0
  256. package/dist/slices/play-drafts/convert.js +160 -9
  257. package/dist/slices/play-drafts/readiness.js +3 -0
  258. package/dist/slices/play-drafts/repo.js +9 -1
  259. package/dist/slices/play-drafts/review-inputs.js +22 -3
  260. package/dist/slices/play-drafts/schema.js +109 -2
  261. package/dist/slices/play-drafts/service.js +10 -1
  262. package/dist/slices/play-drafts/start.js +43 -18
  263. package/dist/slices/play-drafts/uploads.js +2 -1
  264. package/dist/slices/project-templates/from-project.js +70 -4
  265. package/dist/slices/project-templates/one-off.js +26 -0
  266. package/dist/slices/project-templates/repo.js +2 -1
  267. package/dist/slices/project-templates/service.js +15 -4
  268. package/dist/slices/project-templates/setup.js +29 -2
  269. package/dist/slices/rebuild/admission-repo.js +1 -1
  270. package/dist/slices/rebuild/model.js +6 -0
  271. package/dist/slices/rebuild/preview-details.js +67 -2
  272. package/dist/slices/rebuild/preview-plan.js +13 -6
  273. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  274. package/dist/slices/rebuild/recipe-audio.js +66 -12
  275. package/dist/slices/rebuild/recipe-build.js +15 -5
  276. package/dist/slices/rebuild/recipe-cast.js +18 -0
  277. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  278. package/dist/slices/rebuild/recipe-describe.js +93 -0
  279. package/dist/slices/rebuild/recipe-edit.js +13 -1
  280. package/dist/slices/rebuild/recipe-exports.js +62 -8
  281. package/dist/slices/rebuild/recipe-input-schema.js +43 -1
  282. package/dist/slices/rebuild/recipe-legacy.js +12 -0
  283. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  284. package/dist/slices/rebuild/recipe-model.js +25 -6
  285. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  286. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  287. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  288. package/dist/slices/rebuild/recipe-reference.js +58 -0
  289. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  290. package/dist/slices/rebuild/recipe-save.js +4 -1
  291. package/dist/slices/rebuild/recipe-shorts.js +32 -5
  292. package/dist/slices/rebuild/recipe-text.js +114 -19
  293. package/dist/slices/rebuild/recipe-validation.js +29 -3
  294. package/dist/slices/rebuild/recipe-visual.js +104 -29
  295. package/dist/slices/rebuild/recipe-voices.js +286 -0
  296. package/dist/slices/rebuild/recipe-work.js +77 -6
  297. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  298. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  299. package/dist/slices/rebuild/recovery-model.js +3 -0
  300. package/dist/slices/rebuild/recovery-selection.js +28 -2
  301. package/dist/slices/rebuild/recovery.js +35 -17
  302. package/dist/slices/rebuild/repo.js +3 -3
  303. package/dist/slices/rebuild/retry.js +18 -0
  304. package/dist/slices/rebuild/review-redo.js +110 -0
  305. package/dist/slices/rebuild/runtime-actions.js +14 -2
  306. package/dist/slices/rebuild/runtime-admission.js +4 -1
  307. package/dist/slices/rebuild/runtime-animate.js +4 -4
  308. package/dist/slices/rebuild/runtime-article.js +5 -5
  309. package/dist/slices/rebuild/runtime-document.js +4 -4
  310. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  311. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  312. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  313. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  314. package/dist/slices/rebuild/runtime-export.js +84 -19
  315. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  316. package/dist/slices/rebuild/runtime-image.js +69 -0
  317. package/dist/slices/rebuild/runtime-local.js +154 -27
  318. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  319. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  320. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  321. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  322. package/dist/slices/rebuild/runtime-piece-label.js +29 -0
  323. package/dist/slices/rebuild/runtime-provider.js +134 -19
  324. package/dist/slices/rebuild/runtime-publication.js +21 -5
  325. package/dist/slices/rebuild/runtime-review.js +237 -0
  326. package/dist/slices/rebuild/runtime-run.js +17 -11
  327. package/dist/slices/rebuild/runtime-shorts.js +48 -23
  328. package/dist/slices/rebuild/runtime-store.js +39 -10
  329. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  330. package/dist/slices/rebuild/runtime-voices.js +103 -0
  331. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  332. package/dist/slices/rebuild/service-readiness.js +15 -1
  333. package/dist/slices/rebuild/soften.js +49 -0
  334. package/dist/slices/rebuild/transition-repo.js +10 -3
  335. package/dist/slices/research/run.js +4 -4
  336. package/dist/slices/research/synthesis.js +2 -2
  337. package/dist/slices/reviews/model.js +105 -0
  338. package/dist/slices/reviews/outcome.js +22 -0
  339. package/dist/slices/reviews/repo.js +106 -0
  340. package/dist/slices/reviews/rules.js +63 -0
  341. package/dist/slices/reviews/verdict.js +71 -0
  342. package/dist/slices/reviews/view.js +30 -0
  343. package/dist/slices/revisions/adopt-content.js +12 -2
  344. package/dist/slices/revisions/adopt.js +9 -2
  345. package/dist/slices/revisions/downloads.js +7 -1
  346. package/dist/slices/revisions/image-plan.js +183 -0
  347. package/dist/slices/revisions/mutation-assets.js +43 -9
  348. package/dist/slices/revisions/mutation-cues.js +71 -0
  349. package/dist/slices/revisions/mutation-prepare.js +8 -10
  350. package/dist/slices/revisions/mutation-request.js +2 -1
  351. package/dist/slices/revisions/mutations.js +97 -3
  352. package/dist/slices/revisions/schema.js +16 -2
  353. package/dist/slices/run-cost/limits.js +128 -0
  354. package/dist/slices/run-cost/meter.js +47 -0
  355. package/dist/slices/run-cost/panel.js +234 -0
  356. package/dist/slices/run-cost/pricing.js +92 -0
  357. package/dist/slices/run-cost/week.js +108 -0
  358. package/dist/slices/schedules/agenda.js +158 -0
  359. package/dist/slices/schedules/repo.js +58 -18
  360. package/dist/slices/schedules/scheduler.js +67 -16
  361. package/dist/slices/schedules/schema.js +138 -1
  362. package/dist/slices/schedules/service.js +48 -4
  363. package/dist/slices/schedules/similar.js +90 -0
  364. package/dist/slices/schedules/topic-list.js +163 -0
  365. package/dist/slices/schedules/topics.js +422 -0
  366. package/dist/slices/settings/first-run.js +102 -0
  367. package/dist/slices/settings/health.js +190 -0
  368. package/dist/slices/settings/key-guides.js +128 -0
  369. package/dist/slices/settings/key-test.js +85 -0
  370. package/dist/slices/settings/model.js +11 -1
  371. package/dist/slices/settings/playback.js +15 -0
  372. package/dist/slices/settings/repo.js +17 -3
  373. package/dist/slices/settings/voices.js +64 -4
  374. package/dist/slices/settings/whats-new.js +64 -0
  375. package/dist/slices/shorts/captions.js +9 -0
  376. package/dist/slices/shorts/pick.js +3 -3
  377. package/dist/slices/shorts/prompts.js +1 -1
  378. package/dist/slices/shorts/render.js +54 -5
  379. package/dist/slices/storage/asset-name.js +3 -0
  380. package/dist/slices/storage/backup-export.js +48 -9
  381. package/dist/slices/storage/backup-format.js +38 -1
  382. package/dist/slices/storage/backup-import.js +191 -19
  383. package/dist/slices/storage/delete-project.js +7 -4
  384. package/dist/slices/storage/documents.js +79 -0
  385. package/dist/slices/storage/files-location.js +531 -0
  386. package/dist/slices/storage/layout.js +20 -0
  387. package/dist/slices/storage/model.js +14 -0
  388. package/dist/slices/storage/portable.js +95 -12
  389. package/dist/slices/storage/reconcile.js +19 -2
  390. package/dist/slices/storage/repo.js +4 -1
  391. package/dist/slices/storage/schema.js +11 -0
  392. package/dist/slices/storage/trim.js +146 -0
  393. package/dist/slices/studio/disclosure.js +74 -0
  394. package/dist/slices/studio/model.js +28 -0
  395. package/dist/slices/studio/pack.js +209 -0
  396. package/dist/slices/studio/settings.js +133 -0
  397. package/dist/slices/style-preview/images.js +61 -0
  398. package/dist/slices/style-preview/render.js +161 -0
  399. package/dist/slices/style-preview/schema.js +45 -0
  400. package/dist/slices/style-preview/service.js +93 -0
  401. package/dist/slices/style-preview/settings.js +89 -0
  402. package/dist/slices/subtitles/captions.js +42 -9
  403. package/dist/slices/subtitles/model.js +22 -0
  404. package/dist/slices/subtitles/prepare.js +27 -16
  405. package/dist/slices/subtitles/transcript.js +1 -1
  406. package/dist/slices/thumbnail/run.js +7 -7
  407. package/dist/slices/trash/model.js +25 -0
  408. package/dist/slices/trash/service.js +273 -0
  409. package/dist/slices/uploads/repo.js +20 -0
  410. package/dist/slices/video/ambient-bed-schema.js +15 -0
  411. package/dist/slices/video/ambient-bed.js +95 -0
  412. package/dist/slices/video/ambient-mix.js +72 -0
  413. package/dist/slices/video/audio-inputs.js +2 -2
  414. package/dist/slices/video/cards.js +5 -2
  415. package/dist/slices/video/edit-list.js +23 -2
  416. package/dist/slices/video/edit-settings.js +13 -3
  417. package/dist/slices/video/ffmpeg.js +63 -9
  418. package/dist/slices/video/figure-card.js +492 -0
  419. package/dist/slices/video/figure-spans.js +51 -0
  420. package/dist/slices/video/plan.js +125 -13
  421. package/dist/slices/video/run.js +14 -12
  422. package/dist/slices/video/slideshow.js +39 -4
  423. package/dist/slices/voices/attribution.js +69 -0
  424. package/dist/slices/voices/audio-files.js +82 -0
  425. package/dist/slices/voices/audition.js +25 -0
  426. package/dist/slices/voices/cast.js +56 -0
  427. package/dist/slices/voices/grouping.js +49 -0
  428. package/dist/slices/voices/join.js +35 -0
  429. package/dist/slices/voices/languages.js +25 -0
  430. package/dist/slices/voices/model.js +179 -0
  431. package/dist/slices/voices/palette.js +26 -0
  432. package/dist/slices/voices/panel.js +91 -0
  433. package/dist/slices/voices/portraits.js +55 -0
  434. package/dist/slices/voices/script.js +136 -0
  435. package/dist/slices/voices/timing.js +58 -0
  436. package/dist/slices/youtube/answer.js +1 -1
  437. package/dist/slices/youtube/chapters.js +126 -0
  438. package/dist/slices/youtube/edits-repo.js +69 -0
  439. package/dist/slices/youtube/edits.js +89 -0
  440. package/dist/slices/youtube/placeholders.js +98 -0
  441. package/dist/slices/youtube/transcript.js +11 -4
  442. package/dist/updater/service.js +66 -21
  443. package/dist/web/assets/index-CUA2sUxU.css +1 -0
  444. package/dist/web/assets/index-CX590kE-.js +303 -0
  445. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  446. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  447. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  448. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  449. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  450. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  451. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  452. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  453. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  454. package/dist/web/assets/{pdf-CAJNT254.js → pdf-BG8n_uc5.js} +1 -1
  455. package/dist/web/index.html +2 -2
  456. package/package.json +4 -4
  457. package/dist/edge/docker-launch.js +0 -81
  458. package/dist/edge/docker-projects/activation.js +0 -35
  459. package/dist/edge/docker-projects/claims.js +0 -85
  460. package/dist/edge/docker-projects/committed.js +0 -40
  461. package/dist/edge/docker-projects/engine.js +0 -461
  462. package/dist/edge/docker-projects/install.js +0 -254
  463. package/dist/edge/docker-projects/prune.js +0 -66
  464. package/dist/edge/docker-projects/recover.js +0 -100
  465. package/dist/edge/docker-projects/state.js +0 -225
  466. package/dist/web/assets/index-BGgYMm2u.css +0 -1
  467. package/dist/web/assets/index-BVVgtntL.js +0 -171
  468. package/scripts/docker-run.sh +0 -3
  469. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -1,21 +1,68 @@
1
+ import { aliasMatches } from "../../kernel/ports/narration-aliases.js";
1
2
  import { cardinal, numberForms } from "./numbers.js";
2
3
  // Original display words remain intact; only acoustic matching uses normalized English.
3
- export function speechWords(text, observed = "") {
4
+ // Narration aliases ("Dr." said as "Doctor") make the aliased form the default for the
5
+ // written words they cover, with the written form kept as an alternative for audio that
6
+ // was not aliased (an uploaded chunk, say).
7
+ // `formsOf` is the language's normaliser (`multilingual.ts` passes its own); the default is
8
+ // English, and `empty` is the sentence for a text with nothing to say.
9
+ export function speechWords(text, observed = "", aliases = [], formsOf = wordForms, empty = "Local subtitles currently require an English transcript with spoken words.") {
10
+ const matches = aliases.length === 0 ? [] : aliasMatches(text, aliases);
4
11
  const words = [];
5
- for (const textWord of text.trim().split(/\s+/)) {
6
- const forms = wordForms(textWord);
7
- const spoken = forms.find((form) => form !== "" && ` ${observed} `.includes(` ${form} `)) ?? forms[0] ?? "";
12
+ for (const token of text.matchAll(/\S+/g)) {
13
+ const textWord = token[0];
14
+ const start = token.index;
15
+ const end = start + textWord.length;
16
+ const plain = formsOf(textWord);
17
+ const touching = matches.filter((match) => match.start < end && start < match.end);
18
+ let forms = [...plain];
19
+ if (touching.length > 0) {
20
+ let said = "";
21
+ let at = start;
22
+ for (const match of touching) {
23
+ if (match.start >= at)
24
+ said += textWord.slice(at - start, match.start - start);
25
+ // The spoken form belongs to the word the written form starts in.
26
+ if (match.start >= start)
27
+ said += ` ${match.spoken} `;
28
+ at = Math.max(at, Math.min(match.end, end));
29
+ }
30
+ said += textWord.slice(at - start);
31
+ const aliased = said
32
+ .trim()
33
+ .split(/\s+/)
34
+ .map((piece) => (piece === "" ? "" : (formsOf(piece)[0] ?? "")))
35
+ .filter((form) => form !== "")
36
+ .join(" ");
37
+ forms = [aliased, ...plain.filter((form) => form !== aliased)];
38
+ }
39
+ const spoken = pick(forms, observed);
40
+ const previous = words.at(-1);
8
41
  if (spoken === "") {
9
- const previous = words.at(-1);
10
- if (previous !== undefined)
42
+ if (previous !== undefined) {
11
43
  previous.text += ` ${textWord}`;
44
+ // A word an alias swallowed ("al." of "et al.") still counts when the written
45
+ // form was read out.
46
+ const next = plain[0];
47
+ if (touching.length > 0 && next !== undefined)
48
+ previous.forms = previous.forms.map((form, index) => index === 0 && previous.aliased ? form : `${form} ${next}`);
49
+ }
12
50
  continue;
13
51
  }
14
- words.push({ text: textWord, spoken });
52
+ words.push({ text: textWord, spoken, forms, aliased: touching.length > 0 });
15
53
  }
16
54
  if (words.length === 0)
17
- throw new Error("Local subtitles currently require an English transcript with spoken words.");
18
- return words;
55
+ throw new Error(empty);
56
+ return words.map(({ text: shown, spoken, forms }) => forms.length > 1 ? { text: shown, spoken, forms } : { text: shown, spoken });
57
+ }
58
+ // The form of a word the recording seems to say, else its default.
59
+ export function respoken(word, observed, formsOf = wordForms) {
60
+ if (word.forms === undefined)
61
+ return speechWords(word.text, observed, [], formsOf)[0] ?? word;
62
+ return { ...word, spoken: pick(word.forms, observed) };
63
+ }
64
+ function pick(forms, observed) {
65
+ return (forms.find((form) => form !== "" && ` ${observed} `.includes(` ${form} `)) ?? forms[0] ?? "");
19
66
  }
20
67
  function wordForms(raw) {
21
68
  const plain = raw.normalize("NFKD").replace(/\p{M}/gu, "").replace(/[‘’]/g, "'");
@@ -1,11 +1,13 @@
1
- import { alignWindow, frameSeconds, mismatch } from "./ctc.js";
1
+ import { alignWindow, frameSeconds } from "./ctc.js";
2
2
  import { agreesWithSpeech } from "./quality.js";
3
- import { letters } from "./vocabulary.js";
3
+ import { englishSpec } from "./spec.js";
4
4
  // Recovery omits bounded transcript spans only after confirming surrounding speech.
5
- export function alignSpeechWindow(logits, frames, candidate, complete, cutoff, skipBudget) {
5
+ export function alignSpeechWindow(logits, frames, candidate, complete, cutoff, skipBudget, spec = englishSpec) {
6
+ const { gates, mismatch } = spec;
7
+ const agrees = (expected, observed, maximumError) => agreesWithSpeech(expected, observed, maximumError, spec.comparable);
6
8
  try {
7
9
  return {
8
- words: accepted(logits, frames, candidate, complete, cutoff),
10
+ words: accepted(logits, frames, candidate, complete, cutoff, spec),
9
11
  skipped: 0,
10
12
  omissionStart: 0,
11
13
  };
@@ -16,19 +18,20 @@ export function alignSpeechWindow(logits, frames, candidate, complete, cutoff, s
16
18
  }
17
19
  if (skipBudget <= 0)
18
20
  throw new Error(mismatch);
19
- const heard = greedy(logits, frames).split(/\s+/);
21
+ const heard = greedy(logits, frames, spec).split(/\s+/);
20
22
  for (let skipped = 1; skipped <= Math.min(40, skipBudget, candidate.length - 4); skipped += 1) {
21
23
  const remaining = candidate.slice(skipped);
22
24
  const anchor = remaining
23
25
  .slice(0, 4)
24
26
  .map((word) => word.spoken)
25
27
  .join(" ");
26
- if (anchor.replace(/[^A-Z]/g, "").length < 20 ||
27
- !agreesWithSpeech(anchor, heard.slice(0, anchor.split(/\s+/).length).join(" "), 0.2))
28
+ if (spec.lettersOnly(anchor).length < gates.anchorLetters ||
29
+ !agrees(anchor, heard.slice(0, anchor.split(/\s+/).length).join(" "), 0.2))
28
30
  continue;
29
31
  try {
30
- const words = accepted(logits, frames, remaining, complete, cutoff);
31
- if (words.length < 4 || words.slice(0, 4).some((word) => (word.confidence ?? 0) < 0.75))
32
+ const words = accepted(logits, frames, remaining, complete, cutoff, spec);
33
+ if (words.length < 4 ||
34
+ words.slice(0, 4).some((word) => (word.confidence ?? 0) < gates.anchorConfidence))
32
35
  continue;
33
36
  return { words, skipped, omissionStart: 0 };
34
37
  }
@@ -37,32 +40,32 @@ export function alignSpeechWindow(logits, frames, candidate, complete, cutoff, s
37
40
  throw error;
38
41
  }
39
42
  }
40
- const observed = greedy(logits, frames).replace(/[^A-Z']/g, "");
43
+ const observed = spec.comparable(greedy(logits, frames, spec));
41
44
  for (let start = candidate.length - 4; start >= 4; start -= 1) {
42
- const prefix = candidate
45
+ const prefix = spec.comparable(candidate
43
46
  .slice(0, start)
44
47
  .map((word) => word.spoken)
45
- .join("")
46
- .replace(/[^A-Z']/g, "");
48
+ .join(""));
47
49
  if (prefix.length > observed.length)
48
50
  continue;
49
- if (!agreesWithSpeech(prefix, observed.slice(0, prefix.length), 0.2))
51
+ if (!agrees(prefix, observed.slice(0, prefix.length), 0.2))
50
52
  continue;
51
53
  for (let skipped = 1; skipped <= Math.min(40, skipBudget, candidate.length - start - 4); skipped += 1) {
52
54
  const remaining = [...candidate.slice(0, start), ...candidate.slice(start + skipped)];
53
- const anchor = remaining
55
+ const anchor = spec.comparable(remaining
54
56
  .slice(start, start + 4)
55
57
  .map((word) => word.spoken)
56
- .join("")
57
- .replace(/[^A-Z']/g, "");
58
- if (anchor.length < 20 ||
59
- !agreesWithSpeech(anchor, observed.slice(prefix.length, prefix.length + anchor.length), 0.2) ||
60
- !agreesWithSpeech(prefix + anchor, observed.slice(0, prefix.length + anchor.length), 0.15))
58
+ .join(""));
59
+ if (anchor.length < gates.anchorLetters ||
60
+ !agrees(anchor, observed.slice(prefix.length, prefix.length + anchor.length), 0.2) ||
61
+ !agrees(prefix + anchor, observed.slice(0, prefix.length + anchor.length), 0.15))
61
62
  continue;
62
63
  try {
63
- const words = accepted(logits, frames, remaining, complete, cutoff, 0.2);
64
+ const words = accepted(logits, frames, remaining, complete, cutoff, spec, 0.2);
64
65
  if (words.length < start + 4 ||
65
- words.slice(start - 4, start + 4).some((word) => (word.confidence ?? 0) < 0.75))
66
+ words
67
+ .slice(start - 4, start + 4)
68
+ .some((word) => (word.confidence ?? 0) < gates.anchorConfidence))
66
69
  continue;
67
70
  return { words, skipped, omissionStart: start };
68
71
  }
@@ -74,27 +77,28 @@ export function alignSpeechWindow(logits, frames, candidate, complete, cutoff, s
74
77
  }
75
78
  throw new Error(mismatch);
76
79
  }
77
- function accepted(logits, frames, candidate, complete, cutoff, maximumError = 0.42) {
78
- const words = alignWindow(logits, frames, candidate, complete).words.filter((word) => word.end <= cutoff);
80
+ function accepted(logits, frames, candidate, complete, cutoff, spec, maximumError = spec.gates.maximumError) {
81
+ const words = alignWindow(logits, frames, candidate, complete, spec).words.filter((word) => word.end <= cutoff);
79
82
  const last = words.at(-1);
80
83
  if (last === undefined ||
81
84
  !agreesWithSpeech(candidate
82
85
  .slice(0, words.length)
83
86
  .map((word) => word.spoken)
84
- .join(" "), greedy(logits, Math.min(frames, Math.ceil(last.end / frameSeconds))), maximumError))
85
- throw new Error(mismatch);
87
+ .join(" "), greedy(logits, Math.min(frames, Math.ceil(last.end / frameSeconds)), spec), maximumError, spec.comparable))
88
+ throw new Error(spec.mismatch);
86
89
  return words;
87
90
  }
88
- export function greedy(logits, frames) {
91
+ export function greedy(logits, frames, spec = englishSpec) {
92
+ const labels = spec.labels;
89
93
  let previous = -1;
90
94
  let text = "";
91
95
  for (let frame = 0; frame < frames; frame += 1) {
92
96
  let best = 0;
93
- for (let label = 1; label < 32; label += 1)
94
- if ((logits[frame * 32 + label] ?? -Infinity) > (logits[frame * 32 + best] ?? -Infinity))
97
+ for (let label = 1; label < labels; label += 1)
98
+ if ((logits[frame * labels + label] ?? -Infinity) > (logits[frame * labels + best] ?? -Infinity))
95
99
  best = label;
96
100
  if (best !== previous && best !== 0)
97
- text += letters[best] ?? "";
101
+ text += spec.letters[best] ?? "";
98
102
  previous = best;
99
103
  }
100
104
  return text.trim().replace(/\s+/g, " ");
@@ -1,8 +1,7 @@
1
1
  import { open, readFile, stat } from "node:fs/promises";
2
2
  import { SubtitleMismatch } from "../../kernel/ports/subtitles.js";
3
- import { mismatch } from "./ctc.js";
3
+ import { alignmentSpecFor } from "./multilingual.js";
4
4
  import { workerInput } from "./protocol.js";
5
- import { speechWords } from "./text.js";
6
5
  import { subtitleThreads } from "./threads.js";
7
6
  import { alignSpeechWindow, greedy } from "./window.js";
8
7
  const sampleRate = 16000;
@@ -26,13 +25,15 @@ function send(message) {
26
25
  process.send?.(message);
27
26
  }
28
27
  async function run(input) {
28
+ const spec = alignmentSpecFor(input.language);
29
+ const { mismatch } = spec;
29
30
  const { ort, session } = await openSession(input.modelPath);
30
31
  const file = await open(input.pcmPath, "r");
31
32
  try {
32
33
  const totalSamples = (await stat(input.pcmPath)).size / 4;
33
34
  if (!Number.isInteger(totalSamples) || totalSamples < sampleRate / 10)
34
35
  throw new Error("The narration is too short to align subtitles.");
35
- const source = speechWords(input.text);
36
+ const source = spec.words(input.text, "", input.aliases ?? []);
36
37
  const output = [];
37
38
  let cursor = 0;
38
39
  let omitted = 0;
@@ -51,28 +52,29 @@ async function run(input) {
51
52
  const result = await session.run({
52
53
  input_values: new ort.Tensor("float32", normalized, [1, count]),
53
54
  });
54
- const logits = result.logits;
55
- if (logits === undefined ||
56
- !(logits.data instanceof Float32Array) ||
57
- logits.dims.length !== 3 ||
58
- logits.dims[2] !== 32)
55
+ const raw = result.logits;
56
+ if (raw === undefined ||
57
+ !(raw.data instanceof Float32Array) ||
58
+ raw.dims.length !== 3 ||
59
+ raw.dims[2] !== spec.modelLabels)
59
60
  throw new Error("The local subtitle model returned an unsupported result.");
60
- const frames = logits.dims[1] ?? 0;
61
- const observed = greedy(logits.data, frames);
62
- if (observed.replace(/[^A-Z]/g, "").length === 0) {
61
+ const frames = raw.dims[1] ?? 0;
62
+ const logits = { data: spec.compact?.(raw.data, frames) ?? raw.data };
63
+ const observed = greedy(logits.data, frames, spec);
64
+ if (spec.lettersOnly(observed).length === 0) {
63
65
  sampleAt += count;
64
66
  for (const tensor of Object.values(result))
65
67
  tensor.dispose();
66
68
  continue;
67
69
  }
68
- const candidate = candidates(source, cursor, observed);
70
+ const candidate = candidates(source, cursor, observed, spec);
69
71
  const finalWindow = sampleAt + count >= totalSamples;
70
72
  const complete = finalWindow && cursor + candidate.length === source.length;
71
73
  const cutoff = finalWindow ? count / sampleRate : count / sampleRate - overlapSeconds;
72
74
  const stuck = () => new SubtitleMismatch(mismatch, sampleAt / sampleRate, snippet(candidate.map((word) => word.text)), snippet(observed.split(/\s+/)));
73
75
  let recovered;
74
76
  try {
75
- recovered = alignSpeechWindow(logits.data, frames, candidate, complete, cutoff, cursor === 0 ? 0 : omissionBudget - omitted);
77
+ recovered = alignSpeechWindow(logits.data, frames, candidate, complete, cutoff, cursor === 0 ? 0 : omissionBudget - omitted, spec);
76
78
  }
77
79
  catch (error) {
78
80
  if (error instanceof Error && error.message === mismatch)
@@ -121,10 +123,12 @@ async function run(input) {
121
123
  input_values: new ort.Tensor("float32", audio, [1, count]),
122
124
  });
123
125
  const logits = result.logits;
124
- if (logits !== undefined &&
125
- logits.data instanceof Float32Array &&
126
- greedy(logits.data, logits.dims[1] ?? 0).replace(/[^A-Z]/g, "").length > 8)
127
- throw new SubtitleMismatch(mismatch, sampleAt / sampleRate, "", snippet(greedy(logits.data, logits.dims[1] ?? 0).split(/\s+/)));
126
+ if (logits !== undefined && logits.data instanceof Float32Array) {
127
+ const frames = logits.dims[1] ?? 0;
128
+ const heard = greedy(spec.compact?.(logits.data, frames) ?? logits.data, frames, spec);
129
+ if (spec.lettersOnly(heard).length > 8)
130
+ throw new SubtitleMismatch(mismatch, sampleAt / sampleRate, "", snippet(heard.split(/\s+/)));
131
+ }
128
132
  for (const tensor of Object.values(result))
129
133
  tensor.dispose();
130
134
  }
@@ -173,16 +177,14 @@ async function openSession(modelPath) {
173
177
  });
174
178
  return { ort: native, session };
175
179
  }
176
- function candidates(source, cursor, observed) {
180
+ function candidates(source, cursor, observed, spec) {
177
181
  const selected = [];
178
182
  let length = 0;
179
183
  for (let index = cursor; index < source.length; index += 1) {
180
184
  const word = source[index];
181
185
  if (word === undefined)
182
186
  break;
183
- const normalized = speechWords(word.text, observed)[0];
184
- if (normalized === undefined)
185
- continue;
187
+ const normalized = spec.respoken(word, observed);
186
188
  if (length + normalized.spoken.length + 1 > 900)
187
189
  break;
188
190
  selected.push(normalized);
@@ -6,14 +6,14 @@
6
6
  // A provider's error body can be a whole HTML page; the first few hundred characters say why.
7
7
  const detailMax = 300;
8
8
  export function missingKey(provider) {
9
- return `No ${provider} API key is saved. Add one in Settings → Providers, then use Retry stage.`;
9
+ return `No ${provider} API key is saved. Add one in Settings → Providers, then use Try again.`;
10
10
  }
11
11
  export const refreshList = "refresh the list";
12
- export const checkChoice = "Check the provider and model chosen in the Providers section of Edit project, then use Retry stage.";
12
+ export const checkChoice = "Check the provider and model chosen in the Providers section of Edit project, then use Try again.";
13
13
  export function httpFailure(failure) {
14
14
  const { provider, status } = failure;
15
15
  const said = quoted(failure.label ?? `error ${String(status)}`, failure.detail);
16
- const next = failure.next ?? "use Retry stage";
16
+ const next = failure.next ?? "use Try again";
17
17
  if (status === 401) {
18
18
  return `${provider} did not accept the API key${said}. The key may be wrong, expired or revoked: paste a current key in Settings → Providers, then ${next}.`;
19
19
  }
@@ -32,23 +32,23 @@ export function httpFailure(failure) {
32
32
  if (status >= 500) {
33
33
  return `${provider} had a problem on its side${said}. This is usually temporary: wait a few minutes, then ${next}.`;
34
34
  }
35
- return `${provider} answered with an unexpected error${said}. ${failure.next === undefined ? "Use Retry stage" : "Try again"}; if it keeps failing, use Download diagnostics in Settings and report it.`;
35
+ return `${provider} answered with an unexpected error${said}. ${failure.next === undefined ? "Use Try again" : "Try again"}; if it keeps failing, use Download diagnostics in Settings and report it.`;
36
36
  }
37
37
  // A failure the provider reported inside an otherwise successful answer, with no status.
38
38
  export function providerSaid(provider, what, detail, next) {
39
39
  return `${provider} ${what}${quoted("", detail)}. ${next}`;
40
40
  }
41
41
  export function unreadable(provider) {
42
- return `${provider} sent back an answer Slopify could not read. This is usually temporary: use Retry stage; if it keeps happening, use Download diagnostics in Settings and report it.`;
42
+ return `${provider} sent back an answer Slopify could not read. This is usually temporary: use Try again; if it keeps happening, use Download diagnostics in Settings and report it.`;
43
43
  }
44
44
  export function droppedStream(provider) {
45
- return `The connection to ${provider} dropped before the answer was complete. Check your internet connection, then use Retry stage.`;
45
+ return `The connection to ${provider} dropped before the answer was complete. Check your internet connection, then use Try again.`;
46
46
  }
47
47
  export function noAudio(provider) {
48
- return `${provider} finished without sending any audio. Use Retry stage; if it keeps happening, check the voice in Settings → Voices.`;
48
+ return `${provider} finished without sending any audio. Use Try again; if it keeps happening, check the voice in Settings → Voices.`;
49
49
  }
50
50
  export function noImage(provider) {
51
- return `${provider} finished without sending an image. Use Retry stage; if it keeps happening, reword the image prompt in the Images section of Edit project or choose another image model in its Providers section.`;
51
+ return `${provider} finished without sending an image. Use Try again; if it keeps happening, reword the image prompt in the Images section of Edit project or choose another image model in its Providers section.`;
52
52
  }
53
53
  export function refusedImage(provider, detail) {
54
54
  return `${provider} refused to make this image under its content rules${quoted("", detail)}. Reword the image prompt in the Images section of Edit project, or choose another image provider in its Providers section.`;
@@ -56,10 +56,10 @@ export function refusedImage(provider, detail) {
56
56
  // A narration request the provider turned down is most often a voice that no longer exists
57
57
  // or a part longer than the model takes.
58
58
  export function voiceFix(voiceId) {
59
- return `Check that voice "${voiceId}" still exists in Settings → Voices and that each narration part fits the model's length limit (Chunking, in the Providers section of Edit project), then use Retry stage.`;
59
+ return `Check that voice "${voiceId}" still exists in Settings → Voices and that each narration part fits the model's length limit (Chunking, in the Providers section of Edit project), then use Try again.`;
60
60
  }
61
61
  export function internalError(what) {
62
- return `Slopify hit an internal error (${what}). Use Retry stage; if it happens again, use Download diagnostics in Settings and report it.`;
62
+ return `Slopify hit an internal error (${what}). Use Try again; if it happens again, use Download diagnostics in Settings and report it.`;
63
63
  }
64
64
  const cliNames = {
65
65
  claude: "Claude Code CLI",
@@ -81,7 +81,7 @@ export function cliReported(binary, detail, next) {
81
81
  return `The ${cliName(binary)} reported an error${quoted("", detail)}. ${next}`;
82
82
  }
83
83
  export function cliCheck(binary) {
84
- return `Use Retry stage; if it keeps failing, run ${commandOf(binary)} in a terminal to check it works and is signed in, or choose another model in the Providers section of Edit project.`;
84
+ return `Use Try again; if it keeps failing, run ${commandOf(binary)} in a terminal to check it works and is signed in, or choose another model in the Providers section of Edit project.`;
85
85
  }
86
86
  // ` (error 401: "Unauthorized")`, ` (error 401)`, ` ("Unauthorized")`, or nothing.
87
87
  export function quoted(label, detail) {
@@ -1,9 +1,12 @@
1
1
  import { redact } from "../../kernel/log.js";
2
- import { bridgeLimits, hostFaultSchema, hostFrameSchema, hostHealthSchema, hostImageSchema, hostLlmSchema, hostModelsSchema, hostOpenedSchema, hostOpenFolderSchema, hostStatusSchema, } from "../../kernel/ports/host-cli.js";
2
+ import { bridgeLimits, decodeHostImageReport, hostFaultSchema, hostFrameSchema, hostHealthSchema, hostImageReportHeader, hostImageSchema, hostLlmSchema, hostModelsSchema, hostOpenedSchema, hostOpenFolderSchema, hostStatusSchema, } from "../../kernel/ports/host-cli.js";
3
+ import { agentImageTimeoutMs } from "../../kernel/ports/image.js";
3
4
  import { isProviderError, providerError } from "../../kernel/ports/model.js";
4
5
  import { sniffImage } from "../image/bytes.js";
6
+ import { claudePlanLimit } from "../llm/claude-code.js";
7
+ import { codexPlanLimit } from "../llm/codex-limits.js";
5
8
  import { hostRequest, hostUnavailable, readHostBytes, } from "./transport.js";
6
- const tooBigForHost = "Slopify could not send this request to the host helper because it is larger than, or shaped differently from, what the helper accepts. Make the inputs shorter in Edit project, then use Retry stage; if it happens again, use Download diagnostics in Settings and report it.";
9
+ const tooBigForHost = "Slopify could not send this request to the host helper because it is larger than, or shaped differently from, what the helper accepts. Make the inputs shorter in Edit project, then use Try again; if it happens again, use Download diagnostics in Settings and report it.";
7
10
  export function createHostCliClient(options) {
8
11
  async function request(input) {
9
12
  if (!options.directory)
@@ -124,6 +127,8 @@ export function createHostCliClient(options) {
124
127
  kind: "llm",
125
128
  body: Buffer.from(JSON.stringify(parsed.data)),
126
129
  signal: req.signal,
130
+ }).catch((error) => {
131
+ throw isProviderError(error) ? withPlanLimit(error, id) : error;
127
132
  });
128
133
  try {
129
134
  if (response.headers["content-type"]?.split(";")[0] !== "application/x-ndjson")
@@ -132,7 +137,7 @@ export function createHostCliClient(options) {
132
137
  }
133
138
  catch (error) {
134
139
  if (isProviderError(error))
135
- throw error;
140
+ throw withPlanLimit(error, id);
136
141
  throw hostUnavailable(true);
137
142
  }
138
143
  finally {
@@ -142,12 +147,34 @@ export function createHostCliClient(options) {
142
147
  }),
143
148
  image: {
144
149
  id: "codex-image",
150
+ timeoutMs: agentImageTimeoutMs,
145
151
  models: () => models("codex-image"),
146
152
  generate: async (req) => {
147
153
  const parsed = hostImageSchema.safeParse({
148
154
  model: req.model,
149
155
  prompt: req.prompt,
150
156
  aspect: req.aspect,
157
+ ...(req.thinking === undefined ? {} : { thinking: req.thinking }),
158
+ ...(req.reference === undefined
159
+ ? {}
160
+ : {
161
+ reference: {
162
+ mime: req.reference.mime,
163
+ base64: Buffer.from(req.reference.bytes).toString("base64"),
164
+ },
165
+ }),
166
+ ...(req.cast === undefined
167
+ ? {}
168
+ : {
169
+ cast: req.cast.map((member) => ({
170
+ name: member.name,
171
+ description: member.description,
172
+ images: member.images.map((image) => ({
173
+ mime: image.mime,
174
+ base64: Buffer.from(image.bytes).toString("base64"),
175
+ })),
176
+ })),
177
+ }),
151
178
  });
152
179
  if (!parsed.success)
153
180
  throw providerError({
@@ -161,6 +188,8 @@ export function createHostCliClient(options) {
161
188
  kind: "image",
162
189
  body: Buffer.from(JSON.stringify(parsed.data)),
163
190
  signal: req.signal,
191
+ }).catch((error) => {
192
+ throw isProviderError(error) ? withPlanLimit(error, "codex-image") : error;
164
193
  });
165
194
  try {
166
195
  const mime = response.headers["content-type"];
@@ -169,11 +198,15 @@ export function createHostCliClient(options) {
169
198
  const bytes = await readHostBytes(response, bridgeLimits.image);
170
199
  if (sniffImage(bytes) !== mime)
171
200
  throw hostUnavailable(true);
172
- return { bytes, mime };
201
+ return {
202
+ bytes,
203
+ mime,
204
+ ...decodeHostImageReport(response.headers[hostImageReportHeader]),
205
+ };
173
206
  }
174
207
  catch (error) {
175
208
  if (isProviderError(error))
176
- throw error;
209
+ throw withPlanLimit(error, "codex-image");
177
210
  throw hostUnavailable(true);
178
211
  }
179
212
  finally {
@@ -183,6 +216,23 @@ export function createHostCliClient(options) {
183
216
  },
184
217
  };
185
218
  }
219
+ // A used-up plan crosses the bridge as a rate-limit fault carrying the CLI's own words, which
220
+ // the host-side adapter already quoted; they are read again here so a Docker run waits for the
221
+ // reset like a local one instead of failing.
222
+ export function withPlanLimit(error, id) {
223
+ if (error.fault.kind !== "rate_limit" || error.fault.planLimit !== undefined)
224
+ return error;
225
+ const planLimit = id === "claude-code"
226
+ ? claudePlanLimit(error.message, 429, undefined)
227
+ : id === "gemini"
228
+ ? /daily quota/i.test(error.message)
229
+ ? { account: "gemini", resetsAt: null }
230
+ : undefined
231
+ : codexPlanLimit(error.message, new Date());
232
+ return planLimit === undefined
233
+ ? error
234
+ : providerError({ kind: "rate_limit", message: error.message, planLimit });
235
+ }
186
236
  async function* readEvents(response) {
187
237
  const decoder = new TextDecoder("utf-8", { fatal: true });
188
238
  let pending = "";
@@ -2,14 +2,15 @@ import { constants } from "node:fs";
2
2
  import { open } from "node:fs/promises";
3
3
  import { request } from "node:http";
4
4
  import { join } from "node:path";
5
- import { bridgeLimits } from "../../kernel/ports/host-cli.js";
5
+ import { bridgeLimits, hostFramesHeader, hostFramesVersion } from "../../kernel/ports/host-cli.js";
6
+ import { agentImageTimeoutMs } from "../../kernel/ports/image.js";
6
7
  import { providerError } from "../../kernel/ports/model.js";
7
8
  export function hostUnavailable(submitted = false) {
8
9
  return providerError({
9
10
  kind: "unavailable",
10
11
  message: submitted
11
- ? "The connection to the host helper dropped before the CLI's answer arrived, so Slopify cannot tell whether it finished (it may already have used your quota). Check the host helper is still running, then use Retry stage."
12
- : "Slopify cannot reach its host helper, which runs the Claude Code, Codex and Gemini CLIs outside Docker. On the host computer, run npx @gentbajko/slopify@latest --docker to set it up or restart it, then use Retry stage.",
12
+ ? "The connection to the host helper dropped before the CLI's answer arrived, so Slopify cannot tell whether it finished (it may already have used your quota). Check the host helper is still running, then use Try again."
13
+ : "Slopify cannot reach its host helper, which runs the Claude Code, Codex and Gemini CLIs outside Docker. On the host computer, run npx @gentbajko/slopify@latest --docker to set it up or restart it, then use Try again.",
13
14
  });
14
15
  }
15
16
  async function tokenAt(directory) {
@@ -33,7 +34,7 @@ export async function hostRequest(options) {
33
34
  if (options.body && options.body.byteLength > bridgeLimits.request)
34
35
  throw providerError({
35
36
  kind: "unsupported",
36
- message: "This request is larger than the host helper accepts (16 MB), so nothing was sent. Make the inputs shorter in Edit project, or choose an API provider such as OpenRouter in its Providers section, then use Retry stage.",
37
+ message: "This request is larger than the host helper accepts (16 MB), so nothing was sent. Make the inputs shorter in Edit project, or choose an API provider such as OpenRouter in its Providers section, then use Try again.",
37
38
  });
38
39
  let token;
39
40
  try {
@@ -54,6 +55,8 @@ export async function hostRequest(options) {
54
55
  headers: {
55
56
  authorization: `Bearer ${token}`,
56
57
  "content-type": "application/json",
58
+ // The stream frames this app reads; an older helper ignores the header.
59
+ ...(options.kind === "llm" ? { [hostFramesHeader]: String(hostFramesVersion) } : {}),
57
60
  ...(options.body ? { "content-length": options.body.byteLength } : {}),
58
61
  },
59
62
  }, (incoming) => {
@@ -81,7 +84,11 @@ export async function hostRequest(options) {
81
84
  reject(error);
82
85
  };
83
86
  const connectTimer = setTimeout(fail, 5000);
84
- const deadline = setTimeout(fail, options.kind === "image" ? 300_000 : options.kind === "metadata" ? 35_000 : 120_000);
87
+ const deadline = setTimeout(fail, options.kind === "image"
88
+ ? agentImageTimeoutMs
89
+ : options.kind === "metadata"
90
+ ? 35_000
91
+ : 120_000);
85
92
  const cleanup = () => {
86
93
  clearTimeout(connectTimer);
87
94
  clearTimeout(deadline);
@@ -121,7 +128,7 @@ export async function readHostBytes(response, maximum) {
121
128
  if (length > maximum)
122
129
  throw providerError({
123
130
  kind: "unavailable",
124
- message: "The host helper sent back more data than Slopify accepts, so the answer was dropped. Use Retry stage; if it happens again, use Download diagnostics in Settings and report it.",
131
+ message: "The host helper sent back more data than Slopify accepts, so the answer was dropped. Use Try again; if it happens again, use Download diagnostics in Settings and report it.",
125
132
  });
126
133
  chunks.push(value);
127
134
  }
@@ -40,7 +40,7 @@ export async function downloadImage(download) {
40
40
  // quoted back - a fal or Replicate delivery link carries its own signature.
41
41
  throw providerError({
42
42
  kind: "other",
43
- message: `${download.provider} made the image, but Slopify could not download it (error ${String(response.status)}). Check your internet connection, then use Retry stage.`,
43
+ message: `${download.provider} made the image, but Slopify could not download it (error ${String(response.status)}). Check your internet connection, then use Try again.`,
44
44
  });
45
45
  }
46
46
  const bytes = new Uint8Array(await response.arrayBuffer());
@@ -48,7 +48,7 @@ export async function downloadImage(download) {
48
48
  if (mime === undefined) {
49
49
  throw providerError({
50
50
  kind: "other",
51
- message: `${download.provider} sent back something that is not a PNG or JPEG image (${describeBytes(bytes)}). Use Retry stage; if it keeps happening, choose another image model in the Providers section of Edit project.`,
51
+ message: `${download.provider} sent back something that is not a PNG or JPEG image (${describeBytes(bytes)}). Use Try again; if it keeps happening, choose another image model in the Providers section of Edit project.`,
52
52
  });
53
53
  }
54
54
  return { bytes, mime };