@gentbajko/slopify 2.5.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (542) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +24 -41
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +31 -0
  6. package/dist/adapters/alignment/cache.js +4 -1
  7. package/dist/adapters/alignment/ctc.js +25 -23
  8. package/dist/adapters/alignment/index.js +37 -6
  9. package/dist/adapters/alignment/multilingual.js +267 -0
  10. package/dist/adapters/alignment/protocol.js +11 -0
  11. package/dist/adapters/alignment/quality.js +4 -3
  12. package/dist/adapters/alignment/sentences.js +95 -0
  13. package/dist/adapters/alignment/spec.js +24 -0
  14. package/dist/adapters/alignment/spell.js +769 -0
  15. package/dist/adapters/alignment/text.js +56 -9
  16. package/dist/adapters/alignment/window.js +34 -30
  17. package/dist/adapters/alignment/worker.js +23 -21
  18. package/dist/adapters/explain.js +11 -11
  19. package/dist/adapters/host-cli/index.js +44 -5
  20. package/dist/adapters/host-cli/transport.js +7 -5
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +1 -1
  23. package/dist/adapters/image/codex.js +87 -22
  24. package/dist/adapters/image/fal.js +13 -5
  25. package/dist/adapters/image/google.js +11 -8
  26. package/dist/adapters/image/openai.js +14 -7
  27. package/dist/adapters/image/reference.js +34 -0
  28. package/dist/adapters/image/replicate.js +7 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +85 -0
  31. package/dist/adapters/llm/claude-code.js +168 -32
  32. package/dist/adapters/llm/cli-login-error.js +1 -1
  33. package/dist/adapters/llm/codex-limits.js +157 -0
  34. package/dist/adapters/llm/codex.js +99 -12
  35. package/dist/adapters/llm/document-workspace.js +2 -2
  36. package/dist/adapters/llm/gemini-workspace.js +1 -1
  37. package/dist/adapters/llm/gemini.js +16 -3
  38. package/dist/adapters/llm/image-workspace.js +58 -0
  39. package/dist/adapters/llm/openrouter.js +11 -3
  40. package/dist/adapters/llm/run-cli.js +12 -7
  41. package/dist/adapters/tts/cartesia.js +10 -0
  42. package/dist/adapters/tts/elevenlabs.js +31 -9
  43. package/dist/adapters/tts/gemini.js +206 -0
  44. package/dist/adapters/tts/inworld-async.js +9 -5
  45. package/dist/adapters/tts/inworld.js +17 -3
  46. package/dist/adapters/tts/openai.js +3 -0
  47. package/dist/adapters/tts/pcm-mp3.js +62 -0
  48. package/dist/adapters/tts/system.js +147 -0
  49. package/dist/adapters/tts/voice-languages.js +80 -0
  50. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  55. package/dist/assets/fonts/noto/OFL.txt +93 -0
  56. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  57. package/dist/assets/models.yaml +38 -0
  58. package/dist/assets/sample/sample-audiobook.tar +0 -0
  59. package/dist/assets/sample/sample-podcast.tar +0 -0
  60. package/dist/assets/sample/sample-project.tar +0 -0
  61. package/dist/assets/style-preview/narration.mp3 +0 -0
  62. package/dist/assets/style-preview/tall-1.jpg +0 -0
  63. package/dist/assets/style-preview/tall-2.jpg +0 -0
  64. package/dist/assets/style-preview/tall-3.jpg +0 -0
  65. package/dist/assets/style-preview/wide-1.jpg +0 -0
  66. package/dist/assets/style-preview/wide-2.jpg +0 -0
  67. package/dist/assets/style-preview/wide-3.jpg +0 -0
  68. package/dist/catalog/merge.js +132 -0
  69. package/dist/catalog/registry.js +17 -14
  70. package/dist/catalog/schema.js +2 -0
  71. package/dist/catalog/store.js +147 -8
  72. package/dist/catalog/validate.js +2 -2
  73. package/dist/compose.yaml +71 -0
  74. package/dist/edge/autostart/docker-record.js +32 -0
  75. package/dist/edge/autostart/docker.js +118 -0
  76. package/dist/edge/autostart/index.js +78 -0
  77. package/dist/edge/autostart/launcher.js +176 -0
  78. package/dist/edge/autostart/model.js +3 -0
  79. package/dist/edge/autostart/native.js +210 -0
  80. package/dist/edge/autostart/prompt.js +72 -0
  81. package/dist/edge/autostart/service.js +90 -0
  82. package/dist/edge/cli-args.js +71 -0
  83. package/dist/edge/cli.js +64 -63
  84. package/dist/edge/docker-install/activation.js +56 -0
  85. package/dist/edge/docker-install/apply.js +458 -0
  86. package/dist/edge/docker-install/engine.js +362 -0
  87. package/dist/edge/docker-install/run.js +160 -0
  88. package/dist/edge/docker-install/state.js +146 -0
  89. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  90. package/dist/edge/docker.js +14 -22
  91. package/dist/edge/events/hub.js +3 -1
  92. package/dist/edge/host-cli.js +1 -1
  93. package/dist/edge/http/actions.js +38 -11
  94. package/dist/edge/http/app.js +60 -2
  95. package/dist/edge/http/auditions.js +70 -0
  96. package/dist/edge/http/autostart.js +37 -0
  97. package/dist/edge/http/byte-range.js +42 -0
  98. package/dist/edge/http/channel-memory.js +92 -0
  99. package/dist/edge/http/channels.js +128 -0
  100. package/dist/edge/http/diagnostics.js +1 -0
  101. package/dist/edge/http/draft-files.js +2 -4
  102. package/dist/edge/http/entries.js +22 -3
  103. package/dist/edge/http/files.js +11 -7
  104. package/dist/edge/http/folder-location.js +1 -1
  105. package/dist/edge/http/home.js +39 -0
  106. package/dist/edge/http/host-cli.js +29 -5
  107. package/dist/edge/http/narration-peaks.js +76 -0
  108. package/dist/edge/http/onboarding.js +212 -0
  109. package/dist/edge/http/open-folder.js +1 -1
  110. package/dist/edge/http/patch-notes.js +45 -0
  111. package/dist/edge/http/planning.js +4 -2
  112. package/dist/edge/http/project-create.js +87 -0
  113. package/dist/edge/http/project-templates.js +19 -2
  114. package/dist/edge/http/projects.js +37 -100
  115. package/dist/edge/http/prompts.js +40 -3
  116. package/dist/edge/http/pronunciations.js +23 -4
  117. package/dist/edge/http/providers.js +119 -3
  118. package/dist/edge/http/reviews.js +131 -0
  119. package/dist/edge/http/revision-files.js +3 -5
  120. package/dist/edge/http/revisions.js +1 -1
  121. package/dist/edge/http/run-cost.js +25 -0
  122. package/dist/edge/http/schedules.js +115 -6
  123. package/dist/edge/http/settings.js +84 -6
  124. package/dist/edge/http/storage-files.js +50 -0
  125. package/dist/edge/http/storage.js +43 -2
  126. package/dist/edge/http/studio.js +287 -0
  127. package/dist/edge/http/style-preview.js +61 -0
  128. package/dist/edge/http/subtitles.js +1 -1
  129. package/dist/edge/http/trash.js +69 -0
  130. package/dist/edge/http/tutorials.js +59 -0
  131. package/dist/edge/http/update.js +24 -3
  132. package/dist/edge/http/whats-new.js +15 -0
  133. package/dist/edge/http/youtube-edits.js +92 -0
  134. package/dist/edge/native-update.js +62 -0
  135. package/dist/extension/slopify-studio-chrome.zip +0 -0
  136. package/dist/extension/slopify-studio-firefox.zip +0 -0
  137. package/dist/host-cli/gemini-login.js +83 -0
  138. package/dist/host-cli/open-folder.js +10 -10
  139. package/dist/host-cli/runtime.js +9 -1
  140. package/dist/host-cli/status.js +4 -2
  141. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  142. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  143. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  144. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  145. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  146. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  147. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  148. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  149. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  150. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  151. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  152. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  153. package/dist/kernel/db/migrations/0041-cast-hosts.sql +3 -0
  154. package/dist/kernel/paths.js +27 -6
  155. package/dist/kernel/pipeline.js +4 -1
  156. package/dist/kernel/ports/host-cli.js +122 -14
  157. package/dist/kernel/ports/key-probe.js +1 -0
  158. package/dist/kernel/ports/languages.js +157 -0
  159. package/dist/kernel/ports/model.js +10 -5
  160. package/dist/kernel/ports/narration-aliases.js +60 -0
  161. package/dist/kernel/ports/plan-limits.js +36 -0
  162. package/dist/kernel/ports/system-speech.js +203 -0
  163. package/dist/kernel/runner/attempt.js +17 -8
  164. package/dist/kernel/runner/audition.js +36 -0
  165. package/dist/kernel/runner/graph.js +40 -5
  166. package/dist/kernel/runner/index.js +39 -9
  167. package/dist/kernel/runner/meter.js +1 -0
  168. package/dist/kernel/runner/providers.js +142 -7
  169. package/dist/kernel/runner/retry-policy.js +43 -0
  170. package/dist/kernel/runner/standalone.js +134 -0
  171. package/dist/kernel/runner/work-authority.js +28 -2
  172. package/dist/main.js +277 -16
  173. package/dist/model-catalog.js +2 -0
  174. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  175. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  176. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  177. package/dist/patch-notes/3.0.0.md +70 -0
  178. package/dist/patch-notes/3.0.1.md +105 -0
  179. package/dist/patch-notes/index.json +32 -0
  180. package/dist/slices/admission/model.js +27 -0
  181. package/dist/slices/admission/repo.js +24 -4
  182. package/dist/slices/admission/rules.js +80 -2
  183. package/dist/slices/admission/schema.js +58 -1
  184. package/dist/slices/admission/short-mode.js +47 -0
  185. package/dist/slices/admission/start.js +42 -6
  186. package/dist/slices/article/continuation.js +2 -2
  187. package/dist/slices/article/provided-entries.js +2 -2
  188. package/dist/slices/article/run.js +6 -2
  189. package/dist/slices/article/segments.js +3 -3
  190. package/dist/slices/backups/folder.js +14 -6
  191. package/dist/slices/batch/index.js +7 -2
  192. package/dist/slices/cancel/index.js +1 -1
  193. package/dist/slices/channels/cast-images.js +110 -0
  194. package/dist/slices/channels/cast-match.js +32 -0
  195. package/dist/slices/channels/images.js +19 -0
  196. package/dist/slices/channels/model.js +5 -0
  197. package/dist/slices/channels/rebrand.js +100 -0
  198. package/dist/slices/channels/repo.js +221 -0
  199. package/dist/slices/channels/runs.js +129 -0
  200. package/dist/slices/channels/schema.js +152 -0
  201. package/dist/slices/channels/service.js +180 -0
  202. package/dist/slices/channels/studio-csv.js +110 -0
  203. package/dist/slices/channels/videos.js +120 -0
  204. package/dist/slices/checkpoints/recovery.js +10 -0
  205. package/dist/slices/control/index.js +5 -2
  206. package/dist/slices/control/providers.js +4 -1
  207. package/dist/slices/document/fonts.js +1 -1
  208. package/dist/slices/document/render.js +1 -1
  209. package/dist/slices/episodes/related.js +90 -0
  210. package/dist/slices/episodes/repo.js +60 -0
  211. package/dist/slices/episodes/service.js +58 -0
  212. package/dist/slices/episodes/summarize.js +170 -0
  213. package/dist/slices/estimate/index.js +109 -28
  214. package/dist/slices/estimate/requests.js +68 -2
  215. package/dist/slices/eta/history.js +56 -0
  216. package/dist/slices/eta/model.js +63 -0
  217. package/dist/slices/eta/view.js +25 -0
  218. package/dist/slices/fixes/rules.js +73 -0
  219. package/dist/slices/fonts/coverage.js +82 -0
  220. package/dist/slices/fonts/sfnt.js +74 -0
  221. package/dist/slices/images/run.js +8 -5
  222. package/dist/slices/images/scale.js +84 -0
  223. package/dist/slices/library/diff.js +122 -0
  224. package/dist/slices/library/history.js +100 -0
  225. package/dist/slices/library/model.js +3 -0
  226. package/dist/slices/library/photorealistic.js +49 -0
  227. package/dist/slices/library/repo.js +19 -14
  228. package/dist/slices/library/save.js +62 -12
  229. package/dist/slices/library/slots.js +7 -1
  230. package/dist/slices/library/used-by.js +153 -0
  231. package/dist/slices/loudness/level-pieces.js +41 -0
  232. package/dist/slices/loudness/loudnorm.js +164 -0
  233. package/dist/slices/loudness/model.js +153 -0
  234. package/dist/slices/model-upkeep/model.js +10 -0
  235. package/dist/slices/model-upkeep/switch.js +217 -0
  236. package/dist/slices/model-upkeep/usage.js +185 -0
  237. package/dist/slices/narration/aliases-library.js +31 -0
  238. package/dist/slices/narration/aliases-schema.js +53 -0
  239. package/dist/slices/narration/aliases.js +49 -0
  240. package/dist/slices/narration/blocks.js +352 -0
  241. package/dist/slices/narration/chunk.js +19 -7
  242. package/dist/slices/narration/concat.js +2 -2
  243. package/dist/slices/narration/describe.js +45 -0
  244. package/dist/slices/narration/pauses-model.js +63 -0
  245. package/dist/slices/narration/pauses.js +269 -0
  246. package/dist/slices/narration/peaks-model.js +1 -0
  247. package/dist/slices/narration/peaks.js +94 -0
  248. package/dist/slices/narration/preparation.js +21 -3
  249. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  250. package/dist/slices/narration/pronunciation.js +72 -25
  251. package/dist/slices/narration/run.js +6 -6
  252. package/dist/slices/narration/shared-glossary.js +1 -1
  253. package/dist/slices/narration/steering.js +15 -4
  254. package/dist/slices/notifications/notifier.js +47 -3
  255. package/dist/slices/notifications/rules.js +48 -3
  256. package/dist/slices/onboarding/copy.js +143 -0
  257. package/dist/slices/onboarding/first-run.js +72 -0
  258. package/dist/slices/onboarding/full-video.js +81 -0
  259. package/dist/slices/onboarding/install.js +119 -0
  260. package/dist/slices/onboarding/model.js +85 -0
  261. package/dist/slices/onboarding/packs.js +491 -0
  262. package/dist/slices/onboarding/quick-short.js +144 -0
  263. package/dist/slices/onboarding/sample.js +132 -0
  264. package/dist/slices/onboarding/state.js +68 -0
  265. package/dist/slices/onboarding/template.js +73 -0
  266. package/dist/slices/patch-notes/library.js +73 -0
  267. package/dist/slices/patch-notes/seen.js +77 -0
  268. package/dist/slices/play-drafts/convert.js +127 -8
  269. package/dist/slices/play-drafts/readiness.js +4 -1
  270. package/dist/slices/play-drafts/repo.js +2 -0
  271. package/dist/slices/play-drafts/review-inputs.js +20 -3
  272. package/dist/slices/play-drafts/schema.js +89 -0
  273. package/dist/slices/play-drafts/service.js +4 -1
  274. package/dist/slices/play-drafts/start.js +43 -18
  275. package/dist/slices/project-templates/from-project.js +59 -6
  276. package/dist/slices/project-templates/next-chapter.js +94 -0
  277. package/dist/slices/project-templates/one-off.js +26 -0
  278. package/dist/slices/project-templates/repo.js +2 -1
  279. package/dist/slices/project-templates/schema.js +7 -1
  280. package/dist/slices/project-templates/service.js +15 -4
  281. package/dist/slices/project-templates/setup.js +18 -2
  282. package/dist/slices/rebuild/admission-repo.js +1 -1
  283. package/dist/slices/rebuild/model.js +6 -0
  284. package/dist/slices/rebuild/preview-details.js +46 -3
  285. package/dist/slices/rebuild/preview-plan.js +13 -6
  286. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  287. package/dist/slices/rebuild/recipe-audio.js +66 -12
  288. package/dist/slices/rebuild/recipe-build.js +8 -4
  289. package/dist/slices/rebuild/recipe-cast.js +18 -0
  290. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  291. package/dist/slices/rebuild/recipe-describe.js +93 -0
  292. package/dist/slices/rebuild/recipe-edit.js +13 -1
  293. package/dist/slices/rebuild/recipe-exports.js +67 -8
  294. package/dist/slices/rebuild/recipe-input-schema.js +35 -0
  295. package/dist/slices/rebuild/recipe-legacy.js +9 -0
  296. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  297. package/dist/slices/rebuild/recipe-model.js +25 -6
  298. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  299. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  300. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  301. package/dist/slices/rebuild/recipe-reference.js +15 -1
  302. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  303. package/dist/slices/rebuild/recipe-save.js +4 -1
  304. package/dist/slices/rebuild/recipe-shorts.js +23 -3
  305. package/dist/slices/rebuild/recipe-text.js +114 -19
  306. package/dist/slices/rebuild/recipe-validation.js +14 -2
  307. package/dist/slices/rebuild/recipe-visual.js +96 -33
  308. package/dist/slices/rebuild/recipe-voices.js +286 -0
  309. package/dist/slices/rebuild/recipe-work.js +77 -6
  310. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  311. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  312. package/dist/slices/rebuild/recovery-model.js +3 -0
  313. package/dist/slices/rebuild/recovery-selection.js +28 -2
  314. package/dist/slices/rebuild/recovery.js +35 -17
  315. package/dist/slices/rebuild/repo.js +3 -3
  316. package/dist/slices/rebuild/retry.js +18 -0
  317. package/dist/slices/rebuild/review-redo.js +125 -0
  318. package/dist/slices/rebuild/runtime-actions.js +5 -0
  319. package/dist/slices/rebuild/runtime-admission.js +4 -1
  320. package/dist/slices/rebuild/runtime-animate.js +4 -4
  321. package/dist/slices/rebuild/runtime-article.js +5 -5
  322. package/dist/slices/rebuild/runtime-document.js +4 -4
  323. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  324. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  325. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  326. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  327. package/dist/slices/rebuild/runtime-export.js +84 -19
  328. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  329. package/dist/slices/rebuild/runtime-image.js +21 -2
  330. package/dist/slices/rebuild/runtime-local.js +148 -24
  331. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  332. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  333. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  334. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  335. package/dist/slices/rebuild/runtime-piece-label.js +27 -0
  336. package/dist/slices/rebuild/runtime-provider.js +126 -18
  337. package/dist/slices/rebuild/runtime-publication.js +21 -5
  338. package/dist/slices/rebuild/runtime-review.js +267 -0
  339. package/dist/slices/rebuild/runtime-run.js +17 -11
  340. package/dist/slices/rebuild/runtime-shorts.js +44 -17
  341. package/dist/slices/rebuild/runtime-store.js +62 -11
  342. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  343. package/dist/slices/rebuild/runtime-voices.js +103 -0
  344. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  345. package/dist/slices/rebuild/service-readiness.js +3 -3
  346. package/dist/slices/rebuild/soften.js +49 -0
  347. package/dist/slices/rebuild/transition-repo.js +9 -2
  348. package/dist/slices/research/run.js +4 -4
  349. package/dist/slices/research/synthesis.js +2 -2
  350. package/dist/slices/reviews/model.js +105 -0
  351. package/dist/slices/reviews/outcome.js +22 -0
  352. package/dist/slices/reviews/repo.js +118 -0
  353. package/dist/slices/reviews/rules.js +63 -0
  354. package/dist/slices/reviews/verdict.js +71 -0
  355. package/dist/slices/reviews/view.js +30 -0
  356. package/dist/slices/revisions/adopt-content.js +5 -2
  357. package/dist/slices/revisions/adopt.js +3 -1
  358. package/dist/slices/revisions/downloads.js +7 -1
  359. package/dist/slices/revisions/image-plan.js +183 -0
  360. package/dist/slices/revisions/mutation-assets.js +10 -0
  361. package/dist/slices/revisions/mutation-cues.js +71 -0
  362. package/dist/slices/revisions/mutation-request.js +2 -1
  363. package/dist/slices/revisions/mutations.js +97 -3
  364. package/dist/slices/revisions/schema.js +11 -1
  365. package/dist/slices/run-cost/limits.js +128 -0
  366. package/dist/slices/run-cost/meter.js +47 -0
  367. package/dist/slices/run-cost/panel.js +256 -0
  368. package/dist/slices/run-cost/pricing.js +95 -0
  369. package/dist/slices/run-cost/week.js +108 -0
  370. package/dist/slices/schedules/agenda.js +222 -0
  371. package/dist/slices/schedules/repo.js +58 -18
  372. package/dist/slices/schedules/scheduler.js +68 -17
  373. package/dist/slices/schedules/schema.js +179 -1
  374. package/dist/slices/schedules/service.js +43 -4
  375. package/dist/slices/schedules/similar.js +128 -0
  376. package/dist/slices/schedules/topic-list.js +163 -0
  377. package/dist/slices/schedules/topics.js +523 -0
  378. package/dist/slices/settings/first-run.js +106 -0
  379. package/dist/slices/settings/health.js +247 -0
  380. package/dist/slices/settings/key-guides.js +138 -0
  381. package/dist/slices/settings/key-test.js +93 -0
  382. package/dist/slices/settings/keys.js +10 -5
  383. package/dist/slices/settings/model-reach.js +52 -0
  384. package/dist/slices/settings/model.js +55 -1
  385. package/dist/slices/settings/models.js +5 -1
  386. package/dist/slices/settings/playback.js +15 -0
  387. package/dist/slices/settings/readiness.js +14 -2
  388. package/dist/slices/settings/repo.js +17 -3
  389. package/dist/slices/settings/tutorial-schema.js +5 -0
  390. package/dist/slices/settings/voices.js +64 -4
  391. package/dist/slices/settings/whats-new.js +64 -0
  392. package/dist/slices/shorts/captions.js +9 -0
  393. package/dist/slices/shorts/pick.js +3 -3
  394. package/dist/slices/shorts/prompts.js +1 -1
  395. package/dist/slices/shorts/render.js +63 -13
  396. package/dist/slices/storage/asset-name.js +3 -0
  397. package/dist/slices/storage/backup-export.js +48 -9
  398. package/dist/slices/storage/backup-format.js +38 -1
  399. package/dist/slices/storage/backup-import.js +191 -19
  400. package/dist/slices/storage/delete-project.js +7 -4
  401. package/dist/slices/storage/documents.js +87 -0
  402. package/dist/slices/storage/downloads.js +33 -1
  403. package/dist/slices/storage/files-location.js +531 -0
  404. package/dist/slices/storage/layout.js +14 -4
  405. package/dist/slices/storage/model.js +11 -0
  406. package/dist/slices/storage/portable.js +94 -12
  407. package/dist/slices/storage/reconcile.js +14 -1
  408. package/dist/slices/storage/repo.js +4 -1
  409. package/dist/slices/storage/schema.js +11 -0
  410. package/dist/slices/storage/trim.js +146 -0
  411. package/dist/slices/studio/disclosure.js +74 -0
  412. package/dist/slices/studio/model.js +30 -0
  413. package/dist/slices/studio/pack.js +211 -0
  414. package/dist/slices/studio/queue.js +69 -0
  415. package/dist/slices/studio/settings.js +170 -0
  416. package/dist/slices/style-preview/images.js +61 -0
  417. package/dist/slices/style-preview/narration.js +33 -0
  418. package/dist/slices/style-preview/render.js +148 -0
  419. package/dist/slices/style-preview/schema.js +59 -0
  420. package/dist/slices/style-preview/service.js +92 -0
  421. package/dist/slices/style-preview/settings.js +139 -0
  422. package/dist/slices/subtitles/captions.js +42 -9
  423. package/dist/slices/subtitles/model.js +22 -0
  424. package/dist/slices/subtitles/prepare.js +27 -16
  425. package/dist/slices/subtitles/transcript.js +1 -1
  426. package/dist/slices/thumbnail/run.js +7 -7
  427. package/dist/slices/trash/model.js +25 -0
  428. package/dist/slices/trash/service.js +273 -0
  429. package/dist/slices/tutorials/anchors.js +57 -0
  430. package/dist/slices/tutorials/library.js +185 -0
  431. package/dist/slices/uploads/repo.js +20 -0
  432. package/dist/slices/video/ambient-bed-schema.js +15 -0
  433. package/dist/slices/video/ambient-bed.js +95 -0
  434. package/dist/slices/video/ambient-mix.js +72 -0
  435. package/dist/slices/video/audio-inputs.js +2 -2
  436. package/dist/slices/video/cards.js +5 -2
  437. package/dist/slices/video/edit-list.js +23 -2
  438. package/dist/slices/video/edit-settings.js +13 -3
  439. package/dist/slices/video/ffmpeg.js +63 -9
  440. package/dist/slices/video/figure-card.js +492 -0
  441. package/dist/slices/video/figure-spans.js +51 -0
  442. package/dist/slices/video/plan.js +125 -13
  443. package/dist/slices/video/run.js +14 -12
  444. package/dist/slices/video/slideshow.js +39 -4
  445. package/dist/slices/voices/attribution.js +69 -0
  446. package/dist/slices/voices/audio-files.js +87 -0
  447. package/dist/slices/voices/audition.js +25 -0
  448. package/dist/slices/voices/cast.js +74 -0
  449. package/dist/slices/voices/grouping.js +59 -0
  450. package/dist/slices/voices/join.js +35 -0
  451. package/dist/slices/voices/languages.js +25 -0
  452. package/dist/slices/voices/model.js +218 -0
  453. package/dist/slices/voices/palette.js +26 -0
  454. package/dist/slices/voices/panel.js +91 -0
  455. package/dist/slices/voices/portraits.js +55 -0
  456. package/dist/slices/voices/script.js +136 -0
  457. package/dist/slices/voices/timing.js +58 -0
  458. package/dist/slices/youtube/answer.js +1 -1
  459. package/dist/slices/youtube/chapters.js +126 -0
  460. package/dist/slices/youtube/edits-repo.js +84 -0
  461. package/dist/slices/youtube/edits.js +89 -0
  462. package/dist/slices/youtube/placeholders.js +98 -0
  463. package/dist/slices/youtube/transcript.js +11 -4
  464. package/dist/tutorials/AI-CLIs.md +146 -0
  465. package/dist/tutorials/Backups.md +121 -0
  466. package/dist/tutorials/Calendar.md +103 -0
  467. package/dist/tutorials/Cast-Library.md +112 -0
  468. package/dist/tutorials/Channels.md +148 -0
  469. package/dist/tutorials/Costs-and-Run-Cost.md +136 -0
  470. package/dist/tutorials/Docker.md +171 -0
  471. package/dist/tutorials/Document-Themes.md +207 -0
  472. package/dist/tutorials/Editing-a-Project.md +204 -0
  473. package/dist/tutorials/FAQ.md +134 -0
  474. package/dist/tutorials/First-Launch-and-Welcome.md +153 -0
  475. package/dist/tutorials/Home-and-Projects.md +174 -0
  476. package/dist/tutorials/Home.md +119 -0
  477. package/dist/tutorials/Install.md +154 -0
  478. package/dist/tutorials/Intros-and-Outros.md +86 -0
  479. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +215 -0
  480. package/dist/tutorials/Library-Overview.md +75 -0
  481. package/dist/tutorials/Models.md +133 -0
  482. package/dist/tutorials/Multiple-Voices.md +161 -0
  483. package/dist/tutorials/Narration-Aliases-and-Glossary.md +129 -0
  484. package/dist/tutorials/Notifications.md +86 -0
  485. package/dist/tutorials/Other-Languages.md +110 -0
  486. package/dist/tutorials/PDF-Documents.md +100 -0
  487. package/dist/tutorials/Play-Images.md +135 -0
  488. package/dist/tutorials/Play-Narration.md +167 -0
  489. package/dist/tutorials/Play-Outputs.md +109 -0
  490. package/dist/tutorials/Play-Overview.md +216 -0
  491. package/dist/tutorials/Play-Title-and-Article.md +135 -0
  492. package/dist/tutorials/Play-Video-and-Style.md +143 -0
  493. package/dist/tutorials/Project-Page.md +228 -0
  494. package/dist/tutorials/Prompts.md +136 -0
  495. package/dist/tutorials/Providers-and-Keys.md +290 -0
  496. package/dist/tutorials/Publishing-to-YouTube.md +135 -0
  497. package/dist/tutorials/Recovery-and-Retries.md +141 -0
  498. package/dist/tutorials/Reviews-and-Checkpoints.md +143 -0
  499. package/dist/tutorials/Schedules.md +154 -0
  500. package/dist/tutorials/Settings-Reference.md +258 -0
  501. package/dist/tutorials/Shorts.md +116 -0
  502. package/dist/tutorials/Start-at-Login.md +126 -0
  503. package/dist/tutorials/Studio-Extension.md +146 -0
  504. package/dist/tutorials/Templates.md +146 -0
  505. package/dist/tutorials/Trash.md +87 -0
  506. package/dist/tutorials/Troubleshooting.md +149 -0
  507. package/dist/tutorials/Uninstalling-and-Moving.md +113 -0
  508. package/dist/tutorials/Updating-and-Patch-Notes.md +120 -0
  509. package/dist/tutorials/Video-Editing.md +135 -0
  510. package/dist/tutorials/Where-Your-Files-Live.md +149 -0
  511. package/dist/tutorials/YouTube-Description.md +132 -0
  512. package/dist/tutorials/Your-First-Short.md +125 -0
  513. package/dist/tutorials/_Footer.md +1 -0
  514. package/dist/tutorials/_Sidebar.md +69 -0
  515. package/dist/updater/service.js +66 -21
  516. package/dist/web/assets/index-DBLSfqA-.css +1 -0
  517. package/dist/web/assets/index-DFdIhmm0.js +307 -0
  518. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  519. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  520. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  521. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  522. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  523. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  524. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  525. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  526. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  527. package/dist/web/assets/{pdf-BLF2i8c2.js → pdf-B9OY1ioX.js} +1 -1
  528. package/dist/web/index.html +2 -2
  529. package/package.json +5 -5
  530. package/dist/edge/docker-launch.js +0 -81
  531. package/dist/edge/docker-projects/activation.js +0 -35
  532. package/dist/edge/docker-projects/claims.js +0 -85
  533. package/dist/edge/docker-projects/committed.js +0 -40
  534. package/dist/edge/docker-projects/engine.js +0 -461
  535. package/dist/edge/docker-projects/install.js +0 -254
  536. package/dist/edge/docker-projects/prune.js +0 -66
  537. package/dist/edge/docker-projects/recover.js +0 -100
  538. package/dist/edge/docker-projects/state.js +0 -225
  539. package/dist/web/assets/index-CktpxlJd.js +0 -171
  540. package/dist/web/assets/index-DB-vOnGU.css +0 -1
  541. package/scripts/docker-run.sh +0 -3
  542. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -6,9 +6,11 @@ import { redact } from "../../kernel/log.js";
6
6
  import { providerError } from "../../kernel/ports/model.js";
7
7
  import { cliCheck, cliReported } from "../explain.js";
8
8
  import { cliLoginError } from "./cli-login-error.js";
9
+ import { codexPlanLimit } from "./codex-limits.js";
9
10
  import { nodeCodexModels } from "./codex-models.js";
10
11
  import { documentServerName, documentTool, documentWorkspace, } from "./document-workspace.js";
11
- import { cliEvent, cliInput, cliShaped, deliveredInput, endedWithout, promptOf, stopCliRun, stuckCli, } from "./run-cli.js";
12
+ import { imageWorkspace } from "./image-workspace.js";
13
+ import { cliEvent, cliInput, cliShaped, deliveredInput, endedKind, endedWithout, promptOf, stopCliRun, stuckCli, } from "./run-cli.js";
12
14
  import { lines } from "./sse-lines.js";
13
15
  // The local-agent adapter for the Codex CLI. Same shape as Claude Code's and a different
14
16
  // vocabulary: Codex writes a JSONL thread of `thread.started`, `item.*` and `turn.*`
@@ -45,7 +47,12 @@ const disabledFeatures = [
45
47
  // plus a zero project-doc budget excludes AGENTS.md and project `.codex` context. Read-only is
46
48
  // defense in depth if a future release exposes a new filesystem tool. The TOML strings keep their
47
49
  // quotes because each `-c` value is parsed as TOML rather than as a shell expression.
48
- export function codexArgs(req, directory, documents) {
50
+ //
51
+ // Pictures (a review's) are attached to the prompt with `--image` (`codex exec --help` 0.155.1:
52
+ // "-i, --image <FILE>... Optional image(s) to attach to the initial prompt"), which needs no
53
+ // tool: `view_image` stays disabled. Each is given as its own flag so a path is never read as
54
+ // the prompt.
55
+ export function codexArgs(req, directory, documents, images) {
49
56
  return [
50
57
  "exec",
51
58
  "--json",
@@ -100,19 +107,29 @@ export function codexArgs(req, directory, documents) {
100
107
  }).flatMap(([key, value]) => ["-c", `mcp_servers.${documentServerName}.${key}=${value}`]),
101
108
  ]
102
109
  : []),
110
+ ...(images?.files.flatMap((one) => ["--image", one.path]) ?? []),
103
111
  // A literal '-' requests stdin; report bodies never become OS arguments.
104
112
  "--",
105
113
  "-",
106
114
  ];
107
115
  }
116
+ const itemProgress = z.object({
117
+ item: z.object({ id: z.string().optional(), type: z.string(), text: z.string().optional() }),
118
+ });
108
119
  const itemCompleted = z.object({
109
120
  item: z.object({ type: z.string(), text: z.string().optional() }),
110
121
  });
111
122
  // The field names are the shipped binary's own: `TurnCompletedEvent` carries `usage`, and
112
123
  // its counts are `input_tokens`, `cached_input_tokens`, `cache_write_input_tokens`,
113
- // `output_tokens`, `reasoning_output_tokens`. Only the two the port has a home for are read.
124
+ // `output_tokens`, `reasoning_output_tokens`. `input_tokens` already counts the cached ones.
114
125
  const turnCompleted = z.object({
115
- usage: z.object({ input_tokens: z.number(), output_tokens: z.number() }).nullish(),
126
+ usage: z
127
+ .object({
128
+ input_tokens: z.number(),
129
+ output_tokens: z.number(),
130
+ cached_input_tokens: z.number().optional(),
131
+ })
132
+ .nullish(),
116
133
  });
117
134
  const turnFailed = z.object({ error: z.object({ message: z.string() }) });
118
135
  // The top-level `error` event carries its text in `message`, not in `error.message`.
@@ -123,13 +140,27 @@ export function codexLlm(deps) {
123
140
  req.signal.throwIfAborted();
124
141
  const workspace = codexWorkspace(deps.env ?? process.env);
125
142
  let documents;
143
+ const typed = new Map();
144
+ let images;
126
145
  let run;
127
146
  let ended;
147
+ // Asked beside the run rather than before it: the read takes about a second and the
148
+ // turn spends nothing until its first answer, so the reading is still the one before.
149
+ const before = deps.readLimits?.().catch(() => null);
128
150
  try {
129
151
  documents = documentWorkspace(req.documents);
130
- run = deps.run(binary, codexArgs(req, workspace.directory, documents), req.signal, {
152
+ images = imageWorkspace(req.images);
153
+ run = deps.run(binary, codexArgs(req, workspace.directory, documents, images), req.signal, {
131
154
  ...workspace.options,
132
- stdin: cliInput([documents?.instructions, promptOf(req.messages)].filter(Boolean).join("\n\n")),
155
+ stdin: cliInput([
156
+ documents?.instructions,
157
+ images === undefined
158
+ ? undefined
159
+ : `The attached images, in order:\n${images.files.map((one, index) => `${String(index + 1)}. ${one.name}`).join("\n")}`,
160
+ promptOf(req.messages),
161
+ ]
162
+ .filter(Boolean)
163
+ .join("\n\n")),
133
164
  });
134
165
  for await (const line of lines(run.stdout, req.signal)) {
135
166
  if (line.trim() === "") {
@@ -138,6 +169,21 @@ export function codexLlm(deps) {
138
169
  const event = cliEvent(binary, line);
139
170
  yield { type: "activity" };
140
171
  req.signal.throwIfAborted();
172
+ // An agent message may be reported while it is written; its text so far is shown on
173
+ // the live panel, and only the completed item becomes the answer.
174
+ if (event.type === "item.started" || event.type === "item.updated") {
175
+ const item = itemProgress.safeParse(event.value);
176
+ if (item.success && item.data.item.type === "agent_message") {
177
+ const id = item.data.item.id ?? "";
178
+ const seen = typed.get(id) ?? "";
179
+ const text = item.data.item.text ?? "";
180
+ if (text.length > seen.length && text.startsWith(seen)) {
181
+ typed.set(id, text);
182
+ yield { type: "partial", text: text.slice(seen.length) };
183
+ }
184
+ }
185
+ continue;
186
+ }
141
187
  if (event.type === "item.completed") {
142
188
  const { item } = cliShaped(binary, itemCompleted, event.value);
143
189
  // A turn also completes `reasoning`, `web_search`, `command_execution` and
@@ -156,22 +202,36 @@ export function codexLlm(deps) {
156
202
  // ceiling: Codex reports no stop reason, so the continuation loop cannot tell a
157
203
  // finished answer from one cut at the output limit. A `--output-schema` run
158
204
  // would, at the cost of constraining every stage's answer.
159
- yield { type: "done", usage: usageOf(usage), finishReason: null };
205
+ const limits = await readingOf(before, deps.readLimits);
206
+ yield {
207
+ type: "done",
208
+ usage: usageOf(usage),
209
+ finishReason: null,
210
+ ...(limits === undefined ? {} : { limits }),
211
+ };
160
212
  return;
161
213
  }
162
214
  if (event.type === "turn.failed") {
163
- const login = cliLoginError("codex", cliShaped(binary, turnFailed, event.value).error.message);
215
+ const failed = cliShaped(binary, turnFailed, event.value).error.message;
216
+ const login = cliLoginError("codex", failed);
164
217
  if (login)
165
218
  throw login;
219
+ const planLimit = codexPlanLimit(failed, (deps.now ?? (() => new Date()))());
220
+ if (planLimit !== undefined)
221
+ throw codexLimitError(failed, planLimit);
166
222
  throw providerError({
167
223
  kind: "other",
168
224
  message: cliReported(binary, redact(cliShaped(binary, turnFailed, event.value).error.message), cliCheck(binary)),
169
225
  });
170
226
  }
171
227
  if (event.type === "error") {
172
- const login = cliLoginError("codex", cliShaped(binary, errorEvent, event.value).message);
228
+ const failed = cliShaped(binary, errorEvent, event.value).message;
229
+ const login = cliLoginError("codex", failed);
173
230
  if (login)
174
231
  throw login;
232
+ const planLimit = codexPlanLimit(failed, (deps.now ?? (() => new Date()))());
233
+ if (planLimit !== undefined)
234
+ throw codexLimitError(failed, planLimit);
175
235
  throw providerError({
176
236
  kind: "other",
177
237
  message: cliReported(binary, redact(cliShaped(binary, errorEvent, event.value).message), cliCheck(binary)),
@@ -190,6 +250,7 @@ export function codexLlm(deps) {
190
250
  }
191
251
  finally {
192
252
  documents?.remove();
253
+ images?.remove();
193
254
  workspace.remove();
194
255
  }
195
256
  }
@@ -201,15 +262,18 @@ export function codexLlm(deps) {
201
262
  const login = cliLoginError("codex", run.stderr());
202
263
  if (login)
203
264
  throw login;
265
+ const planLimit = codexPlanLimit(run.stderr(), (deps.now ?? (() => new Date()))());
266
+ if (planLimit !== undefined)
267
+ throw codexLimitError(run.stderr().trim(), planLimit);
204
268
  throw providerError({
205
- kind: "other",
269
+ kind: endedKind(ended),
206
270
  message: ended === undefined ? stuckCli(binary) : endedWithout(binary, ended, run.stderr()),
207
271
  });
208
272
  }
209
273
  return {
210
274
  id: "codex",
211
275
  // Prose arrives as whole messages; other JSONL events carry activity separately.
212
- capabilities: { streams: true, reportsUsage: true, webSearch: true },
276
+ capabilities: { streams: true, reportsUsage: true, webSearch: true, images: true },
213
277
  models: deps.readModels ?? (() => nodeCodexModels(process.env, binary)),
214
278
  complete,
215
279
  };
@@ -238,5 +302,28 @@ function codexWorkspace(sourceEnv) {
238
302
  function usageOf(usage) {
239
303
  return usage === null || usage === undefined
240
304
  ? null
241
- : { inputTokens: usage.input_tokens, outputTokens: usage.output_tokens };
305
+ : {
306
+ inputTokens: usage.input_tokens,
307
+ outputTokens: usage.output_tokens,
308
+ ...(usage.cached_input_tokens ? { cachedInputTokens: usage.cached_input_tokens } : {}),
309
+ };
310
+ }
311
+ // The windows before (already asked) and after the call; undefined when neither arrived.
312
+ export async function readingOf(before, read) {
313
+ if (read === undefined)
314
+ return undefined;
315
+ const [start, end] = await Promise.all([before, read().catch(() => null)]);
316
+ if (!start?.length && !end?.length)
317
+ return undefined;
318
+ return {
319
+ ...(start?.length ? { before: start } : {}),
320
+ ...(end?.length ? { after: end } : {}),
321
+ };
322
+ }
323
+ export function codexLimitError(message, planLimit) {
324
+ return providerError({
325
+ kind: "rate_limit",
326
+ message: `Your Codex plan's usage limit is used up (the Codex CLI said: ${redact(message)}). Slopify waits for it to reset and then carries on by itself.`,
327
+ planLimit,
328
+ });
242
329
  }
@@ -16,7 +16,7 @@ export function documentWorkspace(input) {
16
16
  if (!parsed.success)
17
17
  throw providerError({
18
18
  kind: "unsupported",
19
- message: "The research notes are too large, or not in a form the AI model can be given, so nothing was sent. Make the inputs shorter in Edit project, then use Retry stage; if it keeps happening, use Download diagnostics in Settings and report it.",
19
+ message: "The research notes are too large, or not in a form the AI model can be given, so nothing was sent. Make the inputs shorter in Edit project, then use Try again; if it keeps happening, use Download diagnostics in Settings and report it.",
20
20
  });
21
21
  const documents = parsed.data;
22
22
  const directory = mkdtempSync(join(tmpdir(), "slopify-documents-"));
@@ -82,6 +82,6 @@ export function documentWorkspace(input) {
82
82
  function unread() {
83
83
  return providerError({
84
84
  kind: "unavailable",
85
- message: "The AI model did not read all of the research notes, so its answer was not used (the attempt may still have used your quota). Use Retry stage; if it keeps happening, choose another model in the Providers section of Edit project.",
85
+ message: "The AI model did not read all of the research notes, so its answer was not used (the attempt may still have used your quota). Use Try again; if it keeps happening, choose another model in the Providers section of Edit project.",
86
86
  });
87
87
  }
@@ -33,7 +33,7 @@ export function geminiWorkspace(webSearch, request, documents, env = process.env
33
33
  catch {
34
34
  throw providerError({
35
35
  kind: "unavailable",
36
- message: "Slopify could not read the Gemini CLI's settings file (settings.json in the .gemini folder of your home directory), which says how it signs in. Fix or remove that file, run gemini in a terminal to sign in again, then use Retry stage.",
36
+ message: "Slopify could not read the Gemini CLI's settings file (settings.json in the .gemini folder of your home directory), which says how it signs in. Fix or remove that file, run gemini in a terminal to sign in again, then use Try again.",
37
37
  });
38
38
  }
39
39
  const oauth = join(originalState, "oauth_creds.json");
@@ -5,6 +5,7 @@ import { cliCheck, cliName, cliReported } from "../explain.js";
5
5
  import { documentWorkspace } from "./document-workspace.js";
6
6
  import { nodeGeminiModels } from "./gemini-models.js";
7
7
  import { geminiWorkspace } from "./gemini-workspace.js";
8
+ import { noImages } from "./image-workspace.js";
8
9
  import { cliEvent, cliInput, cliShaped, deliveredInput, endedWithout, promptOf, stopCliRun, stuckCli, } from "./run-cli.js";
9
10
  import { lines } from "./sse-lines.js";
10
11
  export const geminiBinary = "gemini";
@@ -42,6 +43,9 @@ export function geminiLlm(deps) {
42
43
  const binary = deps.binary ?? geminiBinary;
43
44
  async function* complete(req) {
44
45
  req.signal.throwIfAborted();
46
+ // Its CLI has no image flag, and `@file` inclusion would need a file tool this adapter keeps off.
47
+ if (req.images?.length)
48
+ throw noImages("the Gemini CLI");
45
49
  const documents = documentWorkspace(req.documents);
46
50
  let workspace;
47
51
  let run;
@@ -104,7 +108,7 @@ export function geminiLlm(deps) {
104
108
  }
105
109
  req.signal.throwIfAborted();
106
110
  if (run === undefined)
107
- throw failure(binary, "", `The ${cliName(binary)} could not be started. Check it is installed and set up in Settings → Providers, then use Retry stage.`);
111
+ throw failure(binary, "", `The ${cliName(binary)} could not be started. Check it is installed and set up in Settings → Providers, then use Try again.`);
108
112
  if (authRequired(run.stderr()))
109
113
  throw authFailure();
110
114
  throw failure(binary, run.stderr(), ended === undefined ? stuckCli(binary) : endedWithout(binary, ended, run.stderr()));
@@ -122,7 +126,16 @@ function failure(binary, message, sentence) {
122
126
  if (/(?:#?3501\b|do not have a valid licen[cs]e of this product)/i.test(message)) {
123
127
  return providerError({
124
128
  kind: "unsupported",
125
- message: "Google refused the Gemini CLI because the signed-in account has no licence for it (#3501). Update the Gemini CLI, run gemini in a terminal to sign in again, then use Retry stage; for a work or school account, ask your administrator for a Gemini licence.",
129
+ message: "Google refused the Gemini CLI because the signed-in account has no licence for it (#3501). Update the Gemini CLI, run gemini in a terminal to sign in again, then use Try again; for a work or school account, ask your administrator for a Gemini licence.",
130
+ });
131
+ }
132
+ // The daily allowance of a signed-in Gemini plan. The CLI names no reset time, so the wait
133
+ // checks again later; a per-minute quota is an ordinary rate limit and is retried as one.
134
+ if (/(?:daily|per.day).{0,40}quota|quota.{0,40}(?:daily|per.day)/i.test(message)) {
135
+ return providerError({
136
+ kind: "rate_limit",
137
+ message: `Your Gemini plan's daily quota is used up (the Gemini CLI said: ${redact(message)}). Slopify waits and tries again later by itself.`,
138
+ planLimit: { account: "gemini", resetsAt: null },
126
139
  });
127
140
  }
128
141
  return authRequired(message)
@@ -138,7 +151,7 @@ function authRequired(message) {
138
151
  function authFailure() {
139
152
  return providerError({
140
153
  kind: "missing_key",
141
- message: 'The Gemini CLI is not signed in, or its sign-in has expired. Open a terminal on the computer running the CLI, run "gemini" and sign in, then use Retry stage.',
154
+ message: 'The Gemini CLI is not signed in, or its sign-in has expired. Open a terminal on the computer running the CLI, run "gemini" and sign in, then use Try again.',
142
155
  });
143
156
  }
144
157
  async function* authChecked(source) {
@@ -0,0 +1,58 @@
1
+ import { copyFileSync, mkdtempSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { basename, extname, join } from "node:path";
4
+ import { providerError } from "../../kernel/ports/model.js";
5
+ const extensions = new Set([".png", ".jpg", ".jpeg", ".webp"]);
6
+ // ceiling: a review shows one image, or a short's handful plus the establishing image.
7
+ export const imagesMax = 12;
8
+ // The pictures a CLI model is asked to look at, copied into a private folder of their own so
9
+ // the CLI's file tool can be confined to it: nothing else on the disk is in reach.
10
+ export function imageWorkspace(input) {
11
+ if (!input?.length)
12
+ return undefined;
13
+ if (input.length > imagesMax ||
14
+ input.some((image) => !extensions.has(extname(image.path).toLowerCase())))
15
+ throw providerError({
16
+ kind: "unsupported",
17
+ message: "Slopify could not hand these pictures to the reviewer (too many, or not PNG, JPEG or WebP files), so nothing was sent. Use Try again; if it happens again, use Download diagnostics in Settings and report it.",
18
+ });
19
+ const directory = mkdtempSync(join(tmpdir(), "slopify-images-"));
20
+ const remove = () => rmSync(directory, { recursive: true, force: true, maxRetries: 3 });
21
+ try {
22
+ const files = input.map((image, index) => {
23
+ const file = `image-${String(index + 1)}${extname(image.path).toLowerCase()}`;
24
+ copyFileSync(image.path, join(directory, file));
25
+ return { file, path: join(directory, file), name: image.name };
26
+ });
27
+ return {
28
+ directory,
29
+ files,
30
+ remove,
31
+ instructions: `Open and look at every one of these image files with the Read tool before you answer; they are in the current folder and are the only files you may open:\n${files.map((one) => `- ${one.file}: ${one.name}`).join("\n")}`,
32
+ // Which files a Read call named, by file name: the CLI may report a relative or an
33
+ // absolute path.
34
+ unread(read) {
35
+ return files.filter((one) => !read.has(one.file)).map((one) => one.file);
36
+ },
37
+ };
38
+ }
39
+ catch (error) {
40
+ remove();
41
+ throw error;
42
+ }
43
+ }
44
+ export function readFileName(path) {
45
+ return typeof path === "string" && path !== "" ? basename(path) : undefined;
46
+ }
47
+ export function unseenImages() {
48
+ return providerError({
49
+ kind: "unavailable",
50
+ message: "The reviewer answered without opening every picture it was given, so its verdict was not used (the attempt may still have used your quota). Use Try again; if it keeps happening, choose Codex as the reviewer in Edit project → Reviews.",
51
+ });
52
+ }
53
+ export function noImages(provider) {
54
+ return providerError({
55
+ kind: "unsupported",
56
+ message: `Slopify can't show pictures to ${provider}, so a review that needs them was not sent. In Edit project → Reviews, choose Claude Code or Codex as the reviewer, or set the picture reviews to Off, then use Try again.`,
57
+ });
58
+ }
@@ -4,6 +4,7 @@ import { documentMessages } from "../../kernel/ports/llm-documents.js";
4
4
  import { providerError } from "../../kernel/ports/model.js";
5
5
  import { droppedStream, httpFailure, missingKey, providerSaid, refreshList, unreadable, } from "../explain.js";
6
6
  import { retryAfter } from "../retry-after.js";
7
+ import { noImages } from "./image-workspace.js";
7
8
  import { sseData } from "./sse-lines.js";
8
9
  // The HTTP gateway adapter: the platform's own `fetch` plus the line reader beside this
9
10
  // file, no SDK. Neither is worth a dependency.
@@ -39,6 +40,10 @@ const streamChunk = z.object({
39
40
  });
40
41
  export function openRouterLlm(deps) {
41
42
  async function* complete(req) {
43
+ // ceiling: text only. The catalogue does not yet say which models take images, and an older
44
+ // Slopify parses the fetched catalogue strictly, so a new key there would break it.
45
+ if (req.images?.length)
46
+ throw noImages("OpenRouter models");
42
47
  const response = await deps.fetch(`${openRouterBase}/chat/completions`, {
43
48
  method: "POST",
44
49
  signal: req.signal,
@@ -63,7 +68,7 @@ export function openRouterLlm(deps) {
63
68
  throw await failure(response);
64
69
  }
65
70
  if (response.body === null) {
66
- throw providerError({ kind: "other", message: droppedStream("OpenRouter") });
71
+ throw providerError({ kind: "dropped", message: droppedStream("OpenRouter") });
67
72
  }
68
73
  let usage = null;
69
74
  let finishReason = null;
@@ -108,7 +113,7 @@ export function openRouterLlm(deps) {
108
113
  // Neither `[DONE]` nor the usage frame arrived, so the connection dropped part-way.
109
114
  // Saying so is what stops a truncated answer being stored as a whole one.
110
115
  throw providerError({
111
- kind: "other",
116
+ kind: "dropped",
112
117
  message: droppedStream("OpenRouter"),
113
118
  });
114
119
  }
@@ -162,6 +167,9 @@ function kindOf(status) {
162
167
  // ceiling: everything else is `other` and is retried, so a 400 or a 402 fails the same
163
168
  // way four times over. A terminal "this will never work" kind has to reach the port's
164
169
  // error contract first, which is not this adapter's to widen.
170
+ // The provider's own server failed; the same request may well succeed later.
171
+ if (status >= 500)
172
+ return "dropped";
165
173
  return "other";
166
174
  }
167
175
  async function failure(response, next) {
@@ -185,7 +193,7 @@ async function failure(response, next) {
185
193
  function streamFailure(status, detail) {
186
194
  return status >= 400
187
195
  ? httpFailure({ provider: "OpenRouter", status, detail })
188
- : providerSaid("OpenRouter", "stopped part-way through the answer", detail, "Use Retry stage; if it keeps happening, choose another model in the Providers section of Edit project.");
196
+ : providerSaid("OpenRouter", "stopped part-way through the answer", detail, "Use Try again; if it keeps happening, choose another model in the Providers section of Edit project.");
189
197
  }
190
198
  function statusOf(code) {
191
199
  return typeof code === "number" ? code : Number(code ?? 0);
@@ -130,7 +130,7 @@ export async function deliveredInput(run) {
130
130
  catch {
131
131
  throw providerError({
132
132
  kind: "unavailable",
133
- message: "Slopify could not hand the whole prompt to the CLI, so it cannot tell what the CLI worked on (it may already have used your quota). Use Retry stage to run it again.",
133
+ message: "Slopify could not hand the whole prompt to the CLI, so it cannot tell what the CLI worked on (it may already have used your quota). Use Try again to run it again.",
134
134
  });
135
135
  }
136
136
  }
@@ -139,7 +139,7 @@ export function cliInput(text) {
139
139
  if (Buffer.byteLength(text) >= 8 * 1024 * 1024)
140
140
  throw providerError({
141
141
  kind: "unsupported",
142
- message: "This prompt is larger than the CLI accepts (8 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.",
142
+ message: "This prompt is larger than the CLI accepts (8 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.",
143
143
  });
144
144
  return text;
145
145
  }
@@ -168,13 +168,18 @@ export function promptOf(messages) {
168
168
  export function endedWithout(binary, ended, stderr) {
169
169
  const said = redact(stderr.trim());
170
170
  if (ended.error !== null)
171
- return `The ${cliName(binary)} could not be started${quoted("", redact(ended.error.message))}. Check it is installed and set up in Settings → Providers, then use Retry stage.`;
171
+ return `The ${cliName(binary)} could not be started${quoted("", redact(ended.error.message))}. Check it is installed and set up in Settings → Providers, then use Try again.`;
172
172
  const how = ended.code === null ? "killed by the system" : `exit code ${String(ended.code)}`;
173
- return `The ${cliName(binary)} stopped without answering${quoted(how, said)}. Run ${commandOf(binary)} in a terminal to check it works and is signed in, then use Retry stage.`;
173
+ return `The ${cliName(binary)} stopped without answering${quoted(how, said)}. Run ${commandOf(binary)} in a terminal to check it works and is signed in, then use Try again.`;
174
+ }
175
+ // A CLI the system killed mid-answer (out of memory, a sleep, a closed session) dropped the
176
+ // call; one that exited with an error of its own said something about the request.
177
+ export function endedKind(ended) {
178
+ return ended !== undefined && ended.error === null && ended.code === null ? "dropped" : "other";
174
179
  }
175
180
  // The run outlived both the polite and the forced stop.
176
181
  export function stuckCli(binary) {
177
- return `The ${cliName(binary)} stopped responding and could not be shut down. Close any leftover ${commandOf(binary)} processes or restart your computer, then use Retry stage.`;
182
+ return `The ${cliName(binary)} stopped responding and could not be shut down. Close any leftover ${commandOf(binary)} processes or restart your computer, then use Try again.`;
178
183
  }
179
184
  // Unreachable while stdio names `pipe` for fd 1; it is here so the type carries no null
180
185
  // and no non-null assertion is needed to read the stream.
@@ -195,7 +200,7 @@ export function cliEvent(binary, line) {
195
200
  // know. The text is not echoed back: half a JSON object is noise to the user.
196
201
  throw providerError({
197
202
  kind: "other",
198
- message: `The ${cliName(binary)} sent output Slopify could not read. Update it to the latest version, then use Retry stage.`,
203
+ message: `The ${cliName(binary)} sent output Slopify could not read. Update it to the latest version, then use Try again.`,
199
204
  });
200
205
  }
201
206
  return { type: parsed.data.type, value };
@@ -205,7 +210,7 @@ export function cliShaped(binary, schema, value) {
205
210
  if (!parsed.success) {
206
211
  throw providerError({
207
212
  kind: "other",
208
- message: `The ${cliName(binary)} sent output Slopify could not read. Update it to the latest version, then use Retry stage.`,
213
+ message: `The ${cliName(binary)} sent output Slopify could not read. Update it to the latest version, then use Try again.`,
209
214
  });
210
215
  }
211
216
  return parsed.data;
@@ -3,6 +3,7 @@ import { redact } from "../../kernel/log.js";
3
3
  import { providerError } from "../../kernel/ports/model.js";
4
4
  import { httpFailure, missingKey, noAudio, voiceFix } from "../explain.js";
5
5
  import { retryAfter } from "../retry-after.js";
6
+ import { cartesiaLanguages, lookUp } from "./voice-languages.js";
6
7
  // The HTTP gateway adapter for Cartesia. `fetch` and
7
8
  // nothing else; `/tts/bytes` streams the audio it renders, so the response body is
8
9
  // already the stream the port asks for.
@@ -32,6 +33,12 @@ export function cartesiaTts(deps) {
32
33
  return {
33
34
  id: "cartesia",
34
35
  capabilities: { streams: true },
36
+ voiceLanguages: async (voiceId, signal) => {
37
+ const key = deps.key();
38
+ if (key === undefined || key === "")
39
+ return undefined;
40
+ return cartesiaLanguages(await lookUp(deps.fetch, `${cartesiaBase}/voices/${encodeURIComponent(voiceId)}`, { "X-API-Key": key, "Cartesia-Version": cartesiaVersion }, signal));
41
+ },
35
42
  models: async () => cartesiaModels,
36
43
  synthesize: async (req) => {
37
44
  const response = await deps.fetch(`${cartesiaBase}/tts/bytes`, {
@@ -79,6 +86,9 @@ function kindOf(status) {
79
86
  return "rate_limit";
80
87
  }
81
88
  // ceiling: as in the other two adapters, everything else is `other` and is retried.
89
+ // The provider's own server failed; the same request may well succeed later.
90
+ if (status >= 500)
91
+ return "dropped";
82
92
  return "other";
83
93
  }
84
94
  async function failure(response, voiceId) {
@@ -3,6 +3,7 @@ import { redact } from "../../kernel/log.js";
3
3
  import { providerError } from "../../kernel/ports/model.js";
4
4
  import { httpFailure, missingKey, noAudio, refreshList, unreadable, voiceFix } from "../explain.js";
5
5
  import { retryAfter } from "../retry-after.js";
6
+ import { elevenLabsLanguages, lookUp } from "./voice-languages.js";
6
7
  // The HTTP gateway adapter for ElevenLabs: the platform's own `fetch` and nothing else. The
7
8
  // response body already is the `ReadableStream<Uint8Array>` the port asks for, so an SDK
8
9
  // would buy nothing but a dependency.
@@ -19,6 +20,7 @@ export const elevenLabsModels = [
19
20
  { id: "eleven_turbo_v2_5", name: "Eleven Turbo v2.5 (deprecated)" },
20
21
  { id: "eleven_turbo_v2", name: "Eleven Turbo v2 (deprecated)" },
21
22
  ];
23
+ export const elevenLabsDialogueModel = "eleven_v3";
22
24
  // mp3 at the port's container, 44.1 kHz, 128 kbps: `kernel/ports/tts.ts` fixes mp3 and the
23
25
  // concatenation keeps the provider's own sample rate.
24
26
  export const elevenLabsFormat = "mp3_44100_128";
@@ -38,7 +40,13 @@ const errorBody = z.object({
38
40
  export function elevenLabsTts(deps) {
39
41
  return {
40
42
  id: "elevenlabs",
41
- capabilities: { streams: true },
43
+ capabilities: { streams: true, dialogue: true },
44
+ voiceLanguages: async (voiceId, signal) => {
45
+ const key = deps.key();
46
+ if (key === undefined || key === "")
47
+ return undefined;
48
+ return elevenLabsLanguages(await lookUp(deps.fetch, `${elevenLabsBase}/voices/${encodeURIComponent(voiceId)}`, { "xi-api-key": key }, signal));
49
+ },
42
50
  models: async () => {
43
51
  const response = await deps.fetch(`${elevenLabsBase}/models`, {
44
52
  headers: { "xi-api-key": keyOf(deps) },
@@ -59,14 +67,25 @@ export function elevenLabsTts(deps) {
59
67
  },
60
68
  synthesize: async (req) => {
61
69
  const voice = encodeURIComponent(req.voiceId);
62
- const response = await deps.fetch(`${elevenLabsBase}/text-to-speech/${voice}/stream?output_format=${elevenLabsFormat}`, {
63
- method: "POST",
64
- signal: req.signal,
65
- headers: { "xi-api-key": keyOf(deps), "Content-Type": "application/json" },
66
- // No pre-check on length. A text past the model's limit comes
67
- // back as the provider's own 400 and that is what the stage shows.
68
- body: JSON.stringify({ text: req.text, model_id: req.model ?? elevenLabsModel }),
69
- });
70
+ const response = req.dialogue === undefined
71
+ ? await deps.fetch(`${elevenLabsBase}/text-to-speech/${voice}/stream?output_format=${elevenLabsFormat}`, {
72
+ method: "POST",
73
+ signal: req.signal,
74
+ headers: { "xi-api-key": keyOf(deps), "Content-Type": "application/json" },
75
+ // No pre-check on length. A text past the model's limit comes
76
+ // back as the provider's own 400 and that is what the stage shows.
77
+ body: JSON.stringify({ text: req.text, model_id: req.model ?? elevenLabsModel }),
78
+ })
79
+ : // Text to Dialogue: several voices in one conversation, on eleven_v3.
80
+ await deps.fetch(`${elevenLabsBase}/text-to-dialogue/stream?output_format=${elevenLabsFormat}`, {
81
+ method: "POST",
82
+ signal: req.signal,
83
+ headers: { "xi-api-key": keyOf(deps), "Content-Type": "application/json" },
84
+ body: JSON.stringify({
85
+ inputs: req.dialogue.map((line) => ({ text: line.text, voice_id: line.voiceId })),
86
+ model_id: req.model ?? elevenLabsDialogueModel,
87
+ }),
88
+ });
70
89
  if (!response.ok) {
71
90
  throw await failure(response, req.voiceId);
72
91
  }
@@ -96,6 +115,9 @@ function kindOf(status) {
96
115
  // ceiling: everything else is `other` and is retried, so a text past the character limit
97
116
  // fails the same way four times over. A terminal "this will never work" kind has to reach the
98
117
  // port's error contract first, which is not this adapter's to widen.
118
+ // The provider's own server failed; the same request may well succeed later.
119
+ if (status >= 500)
120
+ return "dropped";
99
121
  return "other";
100
122
  }
101
123
  async function failure(response, voiceId, next) {