@gentbajko/slopify 3.25.9 → 4.1.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 (718) hide show
  1. package/dist/adapters/account-limits.js +30 -0
  2. package/dist/adapters/alignment/audio.js +1 -1
  3. package/dist/adapters/alignment/cache.js +4 -6
  4. package/dist/adapters/alignment/ctc.js +1 -2
  5. package/dist/adapters/alignment/lock.js +4 -3
  6. package/dist/adapters/alignment/multilingual.js +2 -4
  7. package/dist/adapters/alignment/prefetch.js +2 -1
  8. package/dist/adapters/alignment/runner.js +1 -1
  9. package/dist/adapters/alignment/worker.js +3 -2
  10. package/dist/adapters/explain.js +10 -12
  11. package/dist/adapters/ffmpeg.js +19 -1
  12. package/dist/adapters/host-cli/index.js +21 -4
  13. package/dist/adapters/host-cli/transport.js +2 -1
  14. package/dist/adapters/http-failure.js +53 -0
  15. package/dist/adapters/image/bytes.js +2 -1
  16. package/dist/adapters/image/codex.js +42 -9
  17. package/dist/adapters/image/fal.js +21 -53
  18. package/dist/adapters/image/google.js +33 -56
  19. package/dist/adapters/image/models.js +2 -2
  20. package/dist/adapters/image/openai.js +22 -48
  21. package/dist/adapters/image/reference.js +36 -0
  22. package/dist/adapters/image/replicate.js +21 -49
  23. package/dist/adapters/image/video.js +2 -1
  24. package/dist/adapters/interest/search-interest.js +113 -0
  25. package/dist/adapters/key-probes.js +54 -3
  26. package/dist/adapters/llm/antigravity-models.js +1 -1
  27. package/dist/adapters/llm/antigravity.js +11 -6
  28. package/dist/adapters/llm/assistant-cli.js +388 -0
  29. package/dist/adapters/llm/child-session.js +83 -0
  30. package/dist/adapters/llm/claude-code-models.js +52 -99
  31. package/dist/adapters/llm/claude-code.js +16 -4
  32. package/dist/adapters/llm/codex-limits.js +27 -68
  33. package/dist/adapters/llm/codex-models.js +1 -1
  34. package/dist/adapters/llm/codex-server-models.js +61 -109
  35. package/dist/adapters/llm/codex.js +12 -1
  36. package/dist/adapters/llm/document-workspace.js +3 -2
  37. package/dist/adapters/llm/image-workspace.js +3 -2
  38. package/dist/adapters/llm/openrouter.js +12 -48
  39. package/dist/adapters/llm/run-cli.js +9 -24
  40. package/dist/adapters/llm/sse-lines.js +5 -1
  41. package/dist/adapters/model-access.js +37 -0
  42. package/dist/adapters/tts/cartesia.js +15 -46
  43. package/dist/adapters/tts/elevenlabs.js +20 -52
  44. package/dist/adapters/tts/gemini.js +21 -41
  45. package/dist/adapters/tts/inworld-async.js +6 -2
  46. package/dist/adapters/tts/inworld.js +6 -2
  47. package/dist/adapters/tts/openai.js +20 -50
  48. package/dist/adapters/tts/system.js +2 -1
  49. package/dist/assets/models-lifecycle.yaml +6 -0
  50. package/dist/assistant-wiring.js +60 -0
  51. package/dist/autopilot-timers.js +59 -0
  52. package/dist/catalog/lifecycle.js +52 -0
  53. package/dist/catalog/propose.js +204 -0
  54. package/dist/catalog/registry.js +23 -14
  55. package/dist/catalog/schema.js +4 -1
  56. package/dist/catalog/store.js +39 -0
  57. package/dist/catalog/validate.js +5 -2
  58. package/dist/edge/autostart/native.js +3 -5
  59. package/dist/edge/autostart/prompt.js +2 -1
  60. package/dist/edge/autostart/service.js +3 -4
  61. package/dist/edge/cli-args.js +3 -4
  62. package/dist/edge/cli.js +2 -1
  63. package/dist/edge/docker-install/apply.js +11 -11
  64. package/dist/edge/docker-install/run.js +2 -1
  65. package/dist/edge/docker-install/state.js +4 -3
  66. package/dist/edge/docker-install/tree.js +2 -4
  67. package/dist/edge/docker-install/volume.js +3 -2
  68. package/dist/edge/docker.js +5 -4
  69. package/dist/edge/events/hub.js +5 -5
  70. package/dist/edge/http/actions.js +11 -32
  71. package/dist/edge/http/actor.js +5 -3
  72. package/dist/edge/http/app.js +86 -11
  73. package/dist/edge/http/assistant.js +129 -0
  74. package/dist/edge/http/audio-preview.js +4 -11
  75. package/dist/edge/http/auditions.js +1 -1
  76. package/dist/edge/http/autostart.js +5 -5
  77. package/dist/edge/http/backups.js +2 -4
  78. package/dist/edge/http/channel-memory.js +9 -22
  79. package/dist/edge/http/channels.js +14 -27
  80. package/dist/edge/http/checkpoints.js +39 -21
  81. package/dist/edge/http/design-templates.js +167 -0
  82. package/dist/edge/http/designs.js +178 -0
  83. package/dist/edge/http/diagnostics.js +5 -1
  84. package/dist/edge/http/document-themes.js +5 -14
  85. package/dist/edge/http/draft-files.js +1 -3
  86. package/dist/edge/http/draft-problem.js +5 -10
  87. package/dist/edge/http/drafts.js +3 -3
  88. package/dist/edge/http/entries.js +6 -9
  89. package/dist/edge/http/extension-auth.js +27 -0
  90. package/dist/edge/http/external-reviews.js +12 -11
  91. package/dist/edge/http/files.js +5 -11
  92. package/dist/edge/http/folder-location.js +1 -4
  93. package/dist/edge/http/fonts.js +0 -7
  94. package/dist/edge/http/home.js +47 -14
  95. package/dist/edge/http/host-cli.js +18 -8
  96. package/dist/edge/http/ids.js +8 -0
  97. package/dist/edge/http/keywords.js +35 -0
  98. package/dist/edge/http/model-checks.js +132 -0
  99. package/dist/edge/http/narration-peaks.js +5 -13
  100. package/dist/edge/http/onboarding.js +30 -26
  101. package/dist/edge/http/open-folder.js +5 -10
  102. package/dist/edge/http/outputs.js +7 -14
  103. package/dist/edge/http/patch-notes.js +6 -12
  104. package/dist/edge/http/planning.js +3 -7
  105. package/dist/edge/http/problem.js +2 -4
  106. package/dist/edge/http/project-create.js +2 -1
  107. package/dist/edge/http/project-templates.js +31 -19
  108. package/dist/edge/http/projects.js +24 -16
  109. package/dist/edge/http/prompts.js +9 -16
  110. package/dist/edge/http/pronunciations.js +1 -2
  111. package/dist/edge/http/providers.js +48 -34
  112. package/dist/edge/http/refusal.js +29 -0
  113. package/dist/edge/http/remote-access.js +40 -0
  114. package/dist/edge/http/remote-guard.js +71 -0
  115. package/dist/edge/http/request-guard.js +91 -0
  116. package/dist/edge/http/reviews.js +12 -25
  117. package/dist/edge/http/revision-files.js +6 -11
  118. package/dist/edge/http/revisions.js +14 -19
  119. package/dist/edge/http/run-cost.js +1 -2
  120. package/dist/edge/http/same-page.js +80 -0
  121. package/dist/edge/http/schedules.js +69 -40
  122. package/dist/edge/http/settings.js +27 -14
  123. package/dist/edge/http/social.js +283 -0
  124. package/dist/edge/http/staging.js +3 -5
  125. package/dist/edge/http/storage-files.js +5 -7
  126. package/dist/edge/http/storage.js +33 -25
  127. package/dist/edge/http/studio.js +94 -69
  128. package/dist/edge/http/style-preview.js +3 -6
  129. package/dist/edge/http/subtitles.js +3 -17
  130. package/dist/edge/http/template-studio.js +436 -0
  131. package/dist/edge/http/trash.js +11 -12
  132. package/dist/edge/http/tutorial.js +1 -2
  133. package/dist/edge/http/tutorials.js +5 -11
  134. package/dist/edge/http/update.js +7 -13
  135. package/dist/edge/http/waveform.js +3 -6
  136. package/dist/edge/http/youtube-edits.js +3 -4
  137. package/dist/edge/mcp/assistant.js +279 -0
  138. package/dist/edge/mcp/attribution.js +8 -2
  139. package/dist/edge/mcp/call-route.js +11 -2
  140. package/dist/edge/mcp/excluded-routes.js +29 -3
  141. package/dist/edge/mcp/http.js +11 -2
  142. package/dist/edge/mcp/instructions.js +6 -4
  143. package/dist/edge/mcp/library-tools.js +9 -5
  144. package/dist/edge/mcp/remake-tools.js +7 -4
  145. package/dist/edge/mcp/route-tool.js +35 -15
  146. package/dist/edge/mcp/server.js +13 -8
  147. package/dist/edge/mcp/tools-channels.js +11 -9
  148. package/dist/edge/mcp/tools-designs.js +165 -0
  149. package/dist/edge/mcp/tools-library.js +19 -7
  150. package/dist/edge/mcp/tools-planning.js +40 -21
  151. package/dist/edge/mcp/tools-projects.js +38 -11
  152. package/dist/edge/mcp/tools-publishing.js +9 -9
  153. package/dist/edge/mcp/tools-runs.js +23 -10
  154. package/dist/edge/mcp/tools-schedules.js +32 -8
  155. package/dist/edge/mcp/tools-setup.js +75 -11
  156. package/dist/edge/mcp/tools-social.js +47 -0
  157. package/dist/edge/mcp/tools-storage.js +11 -3
  158. package/dist/edge/mcp/tools-template-studio.js +166 -0
  159. package/dist/edge/mcp/tools.js +6 -0
  160. package/dist/edge/remote-access/cli.js +71 -0
  161. package/dist/edge/remote-access/model.js +32 -0
  162. package/dist/edge/remote-access/rules.js +207 -0
  163. package/dist/edge/remote-access/service.js +203 -0
  164. package/dist/edge/remote-access/store.js +44 -0
  165. package/dist/edge/remote-access/views.js +43 -0
  166. package/dist/extension/slopify-studio-chrome.zip +0 -0
  167. package/dist/extension/slopify-studio-firefox.zip +0 -0
  168. package/dist/host-cli/install.js +2 -2
  169. package/dist/host-cli/open-folder.js +2 -2
  170. package/dist/host-cli/paths.js +2 -4
  171. package/dist/host-cli/server.js +4 -4
  172. package/dist/host-cli/service.js +3 -3
  173. package/dist/host-cli/status.js +7 -7
  174. package/dist/kernel/db/migrations/0063-model-checks.sql +20 -0
  175. package/dist/kernel/db/migrations/0064-template-studio.sql +46 -0
  176. package/dist/kernel/db/migrations/0065-promise-and-assistant-reviews.sql +58 -0
  177. package/dist/kernel/db/migrations/0066-assistant.sql +44 -0
  178. package/dist/kernel/db/migrations/0067-autopilot.sql +6 -0
  179. package/dist/kernel/db/migrations/0068-topic-scores.sql +17 -0
  180. package/dist/kernel/db/migrations/0070-designs.sql +53 -0
  181. package/dist/kernel/db/migrations/0071-design-usage.sql +33 -0
  182. package/dist/kernel/db/migrations/0072-design-templates.sql +33 -0
  183. package/dist/kernel/db/migrations/0073-schedule-design-kind.sql +5 -0
  184. package/dist/kernel/db/migrations/0074-template-bakeoffs.sql +44 -0
  185. package/dist/kernel/db/migrations/0075-social-posts.sql +25 -0
  186. package/dist/kernel/db/migrations/0076-channel-social.sql +5 -0
  187. package/dist/kernel/db/migrations/0077-post-prompts.sql +17 -0
  188. package/dist/kernel/db/migrations/0078-uploaded-from-links.sql +7 -0
  189. package/dist/kernel/db/tx.js +2 -4
  190. package/dist/kernel/errors.js +27 -1
  191. package/dist/kernel/json-answer.js +15 -0
  192. package/dist/kernel/lock.js +4 -9
  193. package/dist/kernel/paths.js +8 -0
  194. package/dist/kernel/pipeline.js +12 -0
  195. package/dist/kernel/ports/assistant-cli.js +4 -0
  196. package/dist/kernel/ports/host-cli.js +3 -0
  197. package/dist/kernel/ports/interest.js +4 -0
  198. package/dist/kernel/ports/languages.js +0 -3
  199. package/dist/kernel/ports/model.js +2 -0
  200. package/dist/kernel/ports/settings-places.js +31 -0
  201. package/dist/kernel/ports/system-speech.js +31 -3
  202. package/dist/kernel/runner/attempt.js +14 -4
  203. package/dist/kernel/runner/audition.js +3 -1
  204. package/dist/kernel/runner/checkpoint-authority.js +2 -1
  205. package/dist/kernel/runner/index.js +8 -10
  206. package/dist/kernel/runner/model-access.js +40 -0
  207. package/dist/kernel/runner/model-check.js +153 -0
  208. package/dist/kernel/runner/providers.js +18 -12
  209. package/dist/kernel/runner/standalone.js +11 -7
  210. package/dist/main.js +338 -21
  211. package/dist/model-catalog.js +20 -0
  212. package/dist/model-checks.js +166 -0
  213. package/dist/patch-notes/4.0.0.md +51 -0
  214. package/dist/patch-notes/4.1.0.md +42 -0
  215. package/dist/patch-notes/index.json +12 -0
  216. package/dist/slices/admission/heads.js +2 -1
  217. package/dist/slices/admission/listing-facts.js +95 -0
  218. package/dist/slices/admission/repo.js +11 -7
  219. package/dist/slices/admission/rules.js +50 -15
  220. package/dist/slices/admission/schema.js +8 -0
  221. package/dist/slices/admission/stage-writes.js +0 -3
  222. package/dist/slices/admission/start.js +48 -31
  223. package/dist/slices/admission/state-words.js +34 -5
  224. package/dist/slices/admission/substitute.js +7 -4
  225. package/dist/slices/agents/attention-posts.js +31 -0
  226. package/dist/slices/agents/attention-templates.js +33 -0
  227. package/dist/slices/agents/attention.js +10 -1
  228. package/dist/slices/agents/connection.js +2 -6
  229. package/dist/slices/agents/model.js +6 -3
  230. package/dist/slices/agents/repo.js +15 -7
  231. package/dist/slices/article/continuation.js +0 -63
  232. package/dist/slices/article/plain.js +3 -2
  233. package/dist/slices/article/segments.js +2 -62
  234. package/dist/slices/assistant/access.js +1 -0
  235. package/dist/slices/assistant/asks.js +80 -0
  236. package/dist/slices/assistant/cost.js +54 -0
  237. package/dist/slices/assistant/gate.js +222 -0
  238. package/dist/slices/assistant/model.js +86 -0
  239. package/dist/slices/assistant/names.js +23 -0
  240. package/dist/slices/assistant/prompt.js +74 -0
  241. package/dist/slices/assistant/repo.js +74 -0
  242. package/dist/slices/assistant/tokens.js +18 -0
  243. package/dist/slices/assistant/turn.js +344 -0
  244. package/dist/slices/autopilot/buffer.js +76 -0
  245. package/dist/slices/autopilot/model.js +14 -0
  246. package/dist/slices/autopilot/notify.js +27 -0
  247. package/dist/slices/autopilot/repo.js +34 -0
  248. package/dist/slices/autopilot/signals.js +194 -0
  249. package/dist/slices/autopilot/warnings.js +85 -0
  250. package/dist/slices/autopilot/weekly.js +100 -0
  251. package/dist/slices/backups/files.js +2 -6
  252. package/dist/slices/backups/repo.js +3 -15
  253. package/dist/slices/backups/service.js +8 -7
  254. package/dist/slices/channels/cast-images.js +3 -2
  255. package/dist/slices/channels/model.js +0 -1
  256. package/dist/slices/channels/rebrand.js +0 -3
  257. package/dist/slices/channels/repo.js +4 -0
  258. package/dist/slices/channels/schema.js +7 -1
  259. package/dist/slices/channels/service.js +10 -0
  260. package/dist/slices/channels/videos.js +3 -15
  261. package/dist/slices/checkpoints/index.js +0 -1
  262. package/dist/slices/control/providers.js +2 -28
  263. package/dist/slices/designs/artwork.js +66 -0
  264. package/dist/slices/designs/budget.js +23 -0
  265. package/dist/slices/designs/calls.js +124 -0
  266. package/dist/slices/designs/check-context.js +38 -0
  267. package/dist/slices/designs/check-text.js +43 -0
  268. package/dist/slices/designs/fields.js +88 -0
  269. package/dist/slices/designs/frames.js +34 -0
  270. package/dist/slices/designs/gate.js +130 -0
  271. package/dist/slices/designs/layout.js +293 -0
  272. package/dist/slices/designs/model.js +35 -0
  273. package/dist/slices/designs/pdf-job.js +23 -0
  274. package/dist/slices/designs/pdf-pages.js +30 -0
  275. package/dist/slices/designs/pdf-runner.js +68 -0
  276. package/dist/slices/designs/pdf-worker.js +24 -0
  277. package/dist/slices/designs/picture-size.js +79 -0
  278. package/dist/slices/designs/pictures.js +55 -0
  279. package/dist/slices/designs/quote.js +24 -0
  280. package/dist/slices/designs/render-args.js +36 -0
  281. package/dist/slices/designs/render-input.js +59 -0
  282. package/dist/slices/designs/render.js +215 -0
  283. package/dist/slices/designs/repo.js +101 -0
  284. package/dist/slices/designs/requests.js +80 -0
  285. package/dist/slices/designs/runner.js +239 -0
  286. package/dist/slices/designs/sample-repo.js +91 -0
  287. package/dist/slices/designs/sample.js +223 -0
  288. package/dist/slices/designs/schema.js +87 -0
  289. package/dist/slices/designs/sections.js +44 -0
  290. package/dist/slices/designs/service.js +233 -0
  291. package/dist/slices/designs/steps-repo.js +102 -0
  292. package/dist/slices/designs/studio-kind.js +39 -0
  293. package/dist/slices/designs/template-checks.js +44 -0
  294. package/dist/slices/designs/template-make.js +118 -0
  295. package/dist/slices/designs/templates.js +218 -0
  296. package/dist/slices/document/fonts.js +2 -1
  297. package/dist/slices/document/model.js +7 -4
  298. package/dist/slices/document/render.js +2 -1
  299. package/dist/slices/document/theme-schema.js +1 -1
  300. package/dist/slices/document/theme.js +49 -3
  301. package/dist/slices/episodes/summarize.js +6 -2
  302. package/dist/slices/estimate/index.js +72 -28
  303. package/dist/slices/estimate/requests.js +4 -4
  304. package/dist/slices/fixes/rules.js +3 -1
  305. package/dist/slices/fonts/catalog.js +5 -3
  306. package/dist/slices/fonts/coverage.js +2 -1
  307. package/dist/slices/fonts/discovery.js +3 -2
  308. package/dist/slices/fonts/files.js +2 -6
  309. package/dist/slices/fonts/index.js +0 -1
  310. package/dist/slices/fonts/sfnt.js +45 -10
  311. package/dist/slices/images/appearance.js +8 -16
  312. package/dist/slices/images/scenes.js +102 -28
  313. package/dist/slices/keywords/digest.js +85 -0
  314. package/dist/slices/keywords/facts.js +92 -0
  315. package/dist/slices/keywords/fill.js +80 -0
  316. package/dist/slices/keywords/model.js +75 -0
  317. package/dist/slices/keywords/rules.js +187 -0
  318. package/dist/slices/keywords/schema.js +28 -0
  319. package/dist/slices/keywords/specs.js +260 -0
  320. package/dist/slices/keywords/values.js +74 -0
  321. package/dist/slices/library/lint.js +29 -12
  322. package/dist/slices/library/model.js +2 -0
  323. package/dist/slices/library/photorealistic.js +4 -12
  324. package/dist/slices/library/rename.js +9 -7
  325. package/dist/slices/library/save.js +14 -10
  326. package/dist/slices/library/slots.js +37 -11
  327. package/dist/slices/library/transfer.js +5 -4
  328. package/dist/slices/library/used-by.js +25 -10
  329. package/dist/slices/loudness/loudnorm.js +53 -12
  330. package/dist/slices/loudness/model.js +3 -2
  331. package/dist/slices/loudness/sentence-level.js +2 -1
  332. package/dist/slices/model-checks/cost.js +61 -0
  333. package/dist/slices/model-checks/listing.js +124 -0
  334. package/dist/slices/model-checks/model.js +20 -0
  335. package/dist/slices/model-checks/record.js +32 -0
  336. package/dist/slices/model-checks/repo.js +120 -0
  337. package/dist/slices/model-checks/rules.js +125 -0
  338. package/dist/slices/model-checks/run.js +143 -0
  339. package/dist/slices/model-checks/service.js +198 -0
  340. package/dist/slices/model-upkeep/model.js +1 -1
  341. package/dist/slices/model-upkeep/switch.js +72 -27
  342. package/dist/slices/model-upkeep/usage.js +39 -13
  343. package/dist/slices/narration/breaths.js +3 -1
  344. package/dist/slices/narration/clean-pieces.js +3 -1
  345. package/dist/slices/narration/concat.js +2 -1
  346. package/dist/slices/narration/pauses-model.js +3 -2
  347. package/dist/slices/narration/peaks.js +3 -4
  348. package/dist/slices/narration/pronunciation-chunks.js +4 -3
  349. package/dist/slices/narration/pronunciation.js +6 -4
  350. package/dist/slices/narration/steering.js +4 -3
  351. package/dist/slices/notifications/notifier.js +4 -3
  352. package/dist/slices/notifications/rules.js +5 -2
  353. package/dist/slices/notifications/settings.js +4 -12
  354. package/dist/slices/notifications/subject.js +4 -3
  355. package/dist/slices/onboarding/copy.js +21 -3
  356. package/dist/slices/onboarding/first-run.js +16 -40
  357. package/dist/slices/onboarding/full-video.js +3 -2
  358. package/dist/slices/onboarding/install.js +6 -4
  359. package/dist/slices/onboarding/model.js +26 -37
  360. package/dist/slices/onboarding/packs.js +22 -22
  361. package/dist/slices/onboarding/preferred-model.js +14 -0
  362. package/dist/slices/onboarding/quick-short.js +105 -65
  363. package/dist/slices/onboarding/sample.js +4 -2
  364. package/dist/slices/onboarding/state.js +9 -11
  365. package/dist/slices/onboarding/template.js +8 -6
  366. package/dist/slices/outputs/add.js +3 -2
  367. package/dist/slices/outputs/service.js +2 -2
  368. package/dist/slices/patch-notes/library.js +4 -3
  369. package/dist/slices/play-drafts/convert.js +4 -0
  370. package/dist/slices/play-drafts/readiness.js +3 -3
  371. package/dist/slices/play-drafts/review-inputs.js +9 -3
  372. package/dist/slices/play-drafts/review.js +5 -4
  373. package/dist/slices/play-drafts/schema.js +4 -0
  374. package/dist/slices/play-drafts/service.js +17 -7
  375. package/dist/slices/project-templates/from-project.js +9 -2
  376. package/dist/slices/project-templates/next-chapter.js +2 -1
  377. package/dist/slices/project-templates/provider-defaults.js +22 -0
  378. package/dist/slices/project-templates/repo.js +6 -1
  379. package/dist/slices/project-templates/setup-sections.js +26 -0
  380. package/dist/slices/project-templates/setup.js +14 -1
  381. package/dist/slices/project-templates/stand-ins.js +22 -0
  382. package/dist/slices/rebuild/activity.js +2 -0
  383. package/dist/slices/rebuild/admission-repo.js +3 -2
  384. package/dist/slices/rebuild/narration-history.js +4 -5
  385. package/dist/slices/rebuild/narration-retry.js +2 -1
  386. package/dist/slices/rebuild/preview-details.js +32 -14
  387. package/dist/slices/rebuild/preview-plan.js +3 -8
  388. package/dist/slices/rebuild/recipe-build.js +8 -3
  389. package/dist/slices/rebuild/recipe-describe.js +3 -3
  390. package/dist/slices/rebuild/recipe-digest.js +58 -0
  391. package/dist/slices/rebuild/recipe-input-schema.js +10 -0
  392. package/dist/slices/rebuild/recipe-keywords.js +71 -0
  393. package/dist/slices/rebuild/recipe-legacy.js +5 -1
  394. package/dist/slices/rebuild/recipe-model.js +4 -0
  395. package/dist/slices/rebuild/recipe-narration-text.js +2 -1
  396. package/dist/slices/rebuild/recipe-preparation.js +4 -4
  397. package/dist/slices/rebuild/recipe-reference.js +8 -4
  398. package/dist/slices/rebuild/recipe-reviews.js +27 -7
  399. package/dist/slices/rebuild/recipe-save.js +8 -5
  400. package/dist/slices/rebuild/recipe-scenes.js +40 -11
  401. package/dist/slices/rebuild/recipe-shorts.js +8 -8
  402. package/dist/slices/rebuild/recipe-social.js +46 -0
  403. package/dist/slices/rebuild/recipe-text.js +64 -20
  404. package/dist/slices/rebuild/recipe-validation.js +102 -31
  405. package/dist/slices/rebuild/recipe-visual.js +55 -16
  406. package/dist/slices/rebuild/recipe-voices.js +3 -2
  407. package/dist/slices/rebuild/recipe-work.js +19 -7
  408. package/dist/slices/rebuild/recipe-youtube.js +4 -3
  409. package/dist/slices/rebuild/recovery-selection.js +3 -1
  410. package/dist/slices/rebuild/recovery.js +9 -8
  411. package/dist/slices/rebuild/remake-outdated.js +5 -5
  412. package/dist/slices/rebuild/repo.js +4 -3
  413. package/dist/slices/rebuild/review-promise.js +25 -0
  414. package/dist/slices/rebuild/review-redo.js +3 -2
  415. package/dist/slices/rebuild/runtime-admission.js +4 -3
  416. package/dist/slices/rebuild/runtime-agent-review.js +11 -5
  417. package/dist/slices/rebuild/runtime-animate.js +10 -9
  418. package/dist/slices/rebuild/runtime-article.js +7 -5
  419. package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
  420. package/dist/slices/rebuild/runtime-document.js +11 -13
  421. package/dist/slices/rebuild/runtime-export-bed.js +8 -12
  422. package/dist/slices/rebuild/runtime-export-edit.js +8 -16
  423. package/dist/slices/rebuild/runtime-export-inputs.js +12 -11
  424. package/dist/slices/rebuild/runtime-export-short.js +9 -5
  425. package/dist/slices/rebuild/runtime-export.js +38 -15
  426. package/dist/slices/rebuild/runtime-figure-card.js +2 -1
  427. package/dist/slices/rebuild/runtime-image.js +8 -12
  428. package/dist/slices/rebuild/runtime-instructions.js +2 -0
  429. package/dist/slices/rebuild/runtime-keywords.js +59 -0
  430. package/dist/slices/rebuild/runtime-lines.js +2 -11
  431. package/dist/slices/rebuild/runtime-local.js +17 -22
  432. package/dist/slices/rebuild/runtime-narration-publication.js +2 -5
  433. package/dist/slices/rebuild/runtime-narration-reuse.js +9 -13
  434. package/dist/slices/rebuild/runtime-narration-text.js +13 -17
  435. package/dist/slices/rebuild/runtime-pictures.js +2 -4
  436. package/dist/slices/rebuild/runtime-piece-label.js +10 -6
  437. package/dist/slices/rebuild/runtime-plan.js +31 -5
  438. package/dist/slices/rebuild/runtime-provider.js +67 -25
  439. package/dist/slices/rebuild/runtime-publication.js +10 -9
  440. package/dist/slices/rebuild/runtime-review.js +39 -33
  441. package/dist/slices/rebuild/runtime-run.js +4 -1
  442. package/dist/slices/rebuild/runtime-shorts.js +31 -50
  443. package/dist/slices/rebuild/runtime-store.js +20 -10
  444. package/dist/slices/rebuild/runtime-subtitles.js +28 -25
  445. package/dist/slices/rebuild/runtime-voices.js +10 -16
  446. package/dist/slices/rebuild/runtime-youtube.js +14 -31
  447. package/dist/slices/rebuild/service-readiness.js +11 -3
  448. package/dist/slices/rebuild/transition-repo.js +6 -5
  449. package/dist/slices/rebuild/work-records.js +6 -0
  450. package/dist/slices/reruns/cascade.js +1 -2
  451. package/dist/slices/reruns/index.js +6 -9
  452. package/dist/slices/research/synthesis.js +3 -2
  453. package/dist/slices/reviews/external-model.js +4 -1
  454. package/dist/slices/reviews/external-repo.js +2 -1
  455. package/dist/slices/reviews/external-rows.js +2 -3
  456. package/dist/slices/reviews/holds.js +5 -4
  457. package/dist/slices/reviews/model.js +32 -11
  458. package/dist/slices/reviews/repo.js +2 -1
  459. package/dist/slices/reviews/rules.js +4 -3
  460. package/dist/slices/reviews/verdict.js +2 -1
  461. package/dist/slices/reviews/view.js +5 -2
  462. package/dist/slices/revisions/downloads.js +4 -5
  463. package/dist/slices/revisions/image-plan.js +1 -1
  464. package/dist/slices/revisions/mutation-assets.js +9 -5
  465. package/dist/slices/revisions/mutation-cues.js +2 -5
  466. package/dist/slices/revisions/mutations.js +3 -3
  467. package/dist/slices/revisions/repo.js +3 -1
  468. package/dist/slices/revisions/schema.js +1 -0
  469. package/dist/slices/revisions/view.js +20 -0
  470. package/dist/slices/run-cost/limits.js +5 -2
  471. package/dist/slices/run-cost/meter.js +12 -5
  472. package/dist/slices/run-cost/panel.js +8 -74
  473. package/dist/slices/run-cost/plan-readings.js +127 -0
  474. package/dist/slices/run-cost/plan-use.js +169 -0
  475. package/dist/slices/run-cost/pricing.js +3 -3
  476. package/dist/slices/run-cost/week.js +32 -4
  477. package/dist/slices/schedules/agenda.js +18 -7
  478. package/dist/slices/schedules/by-date.js +42 -0
  479. package/dist/slices/schedules/calendar.js +5 -0
  480. package/dist/slices/schedules/design-agenda.js +36 -0
  481. package/dist/slices/schedules/design-check.js +41 -0
  482. package/dist/slices/schedules/design-run.js +126 -0
  483. package/dist/slices/schedules/next-run.js +62 -0
  484. package/dist/slices/schedules/prepare.js +18 -2
  485. package/dist/slices/schedules/repo.js +30 -21
  486. package/dist/slices/schedules/rescore.js +35 -0
  487. package/dist/slices/schedules/scheduler.js +13 -3
  488. package/dist/slices/schedules/schema.js +57 -3
  489. package/dist/slices/schedules/score.js +107 -0
  490. package/dist/slices/schedules/service.js +57 -24
  491. package/dist/slices/schedules/topic-list.js +15 -0
  492. package/dist/slices/schedules/topic-scores.js +104 -0
  493. package/dist/slices/schedules/topics.js +53 -41
  494. package/dist/slices/settings/cli-paths.js +4 -6
  495. package/dist/slices/settings/first-run.js +8 -43
  496. package/dist/slices/settings/health.js +30 -15
  497. package/dist/slices/settings/key-test.js +1 -1
  498. package/dist/slices/settings/model-reach.js +3 -17
  499. package/dist/slices/settings/model.js +14 -2
  500. package/dist/slices/settings/playback.js +34 -23
  501. package/dist/slices/settings/probe-read.js +16 -0
  502. package/dist/slices/settings/repo.js +33 -0
  503. package/dist/slices/settings/tutorial.js +2 -2
  504. package/dist/slices/settings/voices.js +4 -12
  505. package/dist/slices/shorts/keep.js +2 -1
  506. package/dist/slices/shorts/pick.js +6 -18
  507. package/dist/slices/shorts/prompts.js +4 -14
  508. package/dist/slices/shorts/render.js +6 -2
  509. package/dist/slices/social/channel.js +44 -0
  510. package/dist/slices/social/defaults.js +17 -0
  511. package/dist/slices/social/due.js +36 -0
  512. package/dist/slices/social/extension.js +18 -0
  513. package/dist/slices/social/fit.js +115 -0
  514. package/dist/slices/social/limits.js +186 -0
  515. package/dist/slices/social/links.js +21 -0
  516. package/dist/slices/social/media.js +132 -0
  517. package/dist/slices/social/model.js +75 -0
  518. package/dist/slices/social/notice.js +19 -0
  519. package/dist/slices/social/pack.js +176 -0
  520. package/dist/slices/social/post-prompt.js +56 -0
  521. package/dist/slices/social/queue.js +31 -0
  522. package/dist/slices/social/repo.js +133 -0
  523. package/dist/slices/social/reshape.js +97 -0
  524. package/dist/slices/social/rules.js +5 -0
  525. package/dist/slices/social/schema.js +70 -0
  526. package/dist/slices/social/serve.js +51 -0
  527. package/dist/slices/social/service.js +129 -0
  528. package/dist/slices/social/settings.js +21 -0
  529. package/dist/slices/social/sources.js +114 -0
  530. package/dist/slices/social/state.js +110 -0
  531. package/dist/slices/social/text.js +149 -0
  532. package/dist/slices/storage/arrange.js +3 -1
  533. package/dist/slices/storage/assets.js +8 -10
  534. package/dist/slices/storage/backup-designs.js +74 -0
  535. package/dist/slices/storage/backup-export.js +8 -3
  536. package/dist/slices/storage/backup-format.js +10 -0
  537. package/dist/slices/storage/backup-import.js +22 -4
  538. package/dist/slices/storage/delete-project.js +6 -5
  539. package/dist/slices/storage/files-location.js +6 -8
  540. package/dist/slices/storage/layout.js +13 -2
  541. package/dist/slices/storage/model.js +3 -0
  542. package/dist/slices/storage/portable.js +17 -12
  543. package/dist/slices/storage/schema.js +2 -0
  544. package/dist/slices/storage/staging-refs.js +2 -1
  545. package/dist/slices/storage/staging.js +6 -9
  546. package/dist/slices/storage/trim.js +26 -9
  547. package/dist/slices/studio/calendar.js +7 -2
  548. package/dist/slices/studio/disclosure.js +4 -4
  549. package/dist/slices/studio/model.js +1 -1
  550. package/dist/slices/studio/pack.js +9 -8
  551. package/dist/slices/studio/pick.js +2 -11
  552. package/dist/slices/studio/plan.js +7 -16
  553. package/dist/slices/studio/queue.js +3 -14
  554. package/dist/slices/studio/releases.js +26 -0
  555. package/dist/slices/studio/settings.js +20 -57
  556. package/dist/slices/studio/videos.js +35 -4
  557. package/dist/slices/style-preview/render.js +37 -16
  558. package/dist/slices/style-preview/schema.js +14 -1
  559. package/dist/slices/style-preview/service.js +2 -1
  560. package/dist/slices/style-preview/settings.js +33 -5
  561. package/dist/slices/subtitles/captions.js +1 -1
  562. package/dist/slices/telemetry/collector-client.js +2 -4
  563. package/dist/slices/telemetry/flush.js +2 -4
  564. package/dist/slices/telemetry/model.js +0 -7
  565. package/dist/slices/telemetry/record.js +10 -3
  566. package/dist/slices/template-studio/anatomy-view.js +161 -0
  567. package/dist/slices/template-studio/anatomy.js +73 -0
  568. package/dist/slices/template-studio/bakeoff-make.js +225 -0
  569. package/dist/slices/template-studio/bakeoff-repo.js +83 -0
  570. package/dist/slices/template-studio/bakeoff.js +333 -0
  571. package/dist/slices/template-studio/budget.js +106 -0
  572. package/dist/slices/template-studio/cards.js +78 -0
  573. package/dist/slices/template-studio/checks.js +322 -0
  574. package/dist/slices/template-studio/document-sample.js +56 -0
  575. package/dist/slices/template-studio/edit.js +15 -0
  576. package/dist/slices/template-studio/for-all.js +161 -0
  577. package/dist/slices/template-studio/free-video.js +31 -0
  578. package/dist/slices/template-studio/free.js +176 -0
  579. package/dist/slices/template-studio/gate.js +40 -0
  580. package/dist/slices/template-studio/hidden.js +20 -0
  581. package/dist/slices/template-studio/jobs.js +79 -0
  582. package/dist/slices/template-studio/loudness.js +111 -0
  583. package/dist/slices/template-studio/make.js +40 -0
  584. package/dist/slices/template-studio/model.js +58 -0
  585. package/dist/slices/template-studio/pack-import.js +253 -0
  586. package/dist/slices/template-studio/pack-samples.js +60 -0
  587. package/dist/slices/template-studio/pack.js +207 -0
  588. package/dist/slices/template-studio/paid-view.js +66 -0
  589. package/dist/slices/template-studio/paid.js +355 -0
  590. package/dist/slices/template-studio/quote.js +155 -0
  591. package/dist/slices/template-studio/ready.js +104 -0
  592. package/dist/slices/template-studio/repo.js +134 -0
  593. package/dist/slices/template-studio/reviews-path.js +58 -0
  594. package/dist/slices/template-studio/runner.js +84 -0
  595. package/dist/slices/template-studio/save.js +236 -0
  596. package/dist/slices/template-studio/say-it-as.js +248 -0
  597. package/dist/slices/template-studio/schedule-gate.js +34 -0
  598. package/dist/slices/template-studio/schedule-price.js +32 -0
  599. package/dist/slices/template-studio/schedule-sample.js +91 -0
  600. package/dist/slices/template-studio/schema.js +243 -0
  601. package/dist/slices/template-studio/scope.js +124 -0
  602. package/dist/slices/template-studio/sections.js +176 -0
  603. package/dist/slices/template-studio/service.js +175 -0
  604. package/dist/slices/template-studio/status.js +191 -0
  605. package/dist/slices/template-studio/topic.js +223 -0
  606. package/dist/slices/thumbnail/by-llm.js +1 -1
  607. package/dist/slices/trash/designs.js +76 -0
  608. package/dist/slices/trash/model.js +13 -4
  609. package/dist/slices/trash/service.js +52 -2
  610. package/dist/slices/tutorials/library.js +3 -2
  611. package/dist/slices/uploads/repo.js +37 -1
  612. package/dist/slices/uploads/rules.js +3 -2
  613. package/dist/slices/video/ass-text.js +17 -0
  614. package/dist/slices/video/audio-export.js +2 -64
  615. package/dist/slices/video/edit-list.js +2 -2
  616. package/dist/slices/video/edit-settings.js +1 -1
  617. package/dist/slices/video/encoding.js +169 -0
  618. package/dist/slices/video/ffmpeg.js +23 -21
  619. package/dist/slices/video/figure-card.js +1 -15
  620. package/dist/slices/video/plan.js +2 -1
  621. package/dist/slices/video/slideshow.js +43 -18
  622. package/dist/slices/voices/attribution.js +3 -2
  623. package/dist/slices/voices/model.js +9 -8
  624. package/dist/slices/voices/palette.js +0 -3
  625. package/dist/slices/voices/script.js +2 -1
  626. package/dist/slices/youtube/answer.js +4 -16
  627. package/dist/slices/youtube/edits-repo.js +3 -12
  628. package/dist/tutorials/AI-CLIs.md +25 -25
  629. package/dist/tutorials/Agents-MCP.md +34 -33
  630. package/dist/tutorials/Assistant.md +42 -0
  631. package/dist/tutorials/Backups.md +7 -7
  632. package/dist/tutorials/Calendar.md +17 -25
  633. package/dist/tutorials/Cast-Library.md +19 -19
  634. package/dist/tutorials/Channels.md +42 -35
  635. package/dist/tutorials/Costs-and-Run-Cost.md +38 -40
  636. package/dist/tutorials/Create-Article.md +114 -0
  637. package/dist/tutorials/Create-Extras.md +112 -0
  638. package/dist/tutorials/Create-Images.md +165 -0
  639. package/dist/tutorials/Create-Narration.md +178 -0
  640. package/dist/tutorials/Create-Overview.md +244 -0
  641. package/dist/tutorials/Create-Video-and-Style.md +144 -0
  642. package/dist/tutorials/Docker.md +3 -3
  643. package/dist/tutorials/Document-Themes.md +32 -32
  644. package/dist/tutorials/Editing-a-Project.md +94 -94
  645. package/dist/tutorials/FAQ.md +18 -18
  646. package/dist/tutorials/First-Launch-and-Welcome.md +50 -64
  647. package/dist/tutorials/Home-and-Projects.md +115 -71
  648. package/dist/tutorials/Home.md +33 -33
  649. package/dist/tutorials/Install.md +14 -13
  650. package/dist/tutorials/Intros-and-Outros.md +33 -34
  651. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +62 -58
  652. package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
  653. package/dist/tutorials/Models.md +53 -21
  654. package/dist/tutorials/Multiple-Voices.md +41 -40
  655. package/dist/tutorials/Narration-Aliases-and-Glossary.md +14 -14
  656. package/dist/tutorials/Notifications.md +27 -17
  657. package/dist/tutorials/Other-Languages.md +15 -15
  658. package/dist/tutorials/PDF-Documents.md +38 -29
  659. package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
  660. package/dist/tutorials/Project-Page.md +118 -120
  661. package/dist/tutorials/Prompts-and-Templates.md +84 -0
  662. package/dist/tutorials/Prompts.md +48 -44
  663. package/dist/tutorials/Providers-and-Keys.md +105 -98
  664. package/dist/tutorials/Publishing-to-YouTube.md +31 -31
  665. package/dist/tutorials/Recovery-and-Retries.md +31 -31
  666. package/dist/tutorials/Remote-Access.md +63 -0
  667. package/dist/tutorials/Reviews-and-Checkpoints.md +98 -90
  668. package/dist/tutorials/Schedules.md +46 -27
  669. package/dist/tutorials/Settings-Reference.md +194 -174
  670. package/dist/tutorials/Shorts.md +34 -33
  671. package/dist/tutorials/Start-at-Login.md +3 -3
  672. package/dist/tutorials/Studio-Extension.md +22 -24
  673. package/dist/tutorials/Template-Studio.md +128 -0
  674. package/dist/tutorials/Templates.md +51 -77
  675. package/dist/tutorials/Trash.md +4 -4
  676. package/dist/tutorials/Troubleshooting.md +44 -44
  677. package/dist/tutorials/Uninstalling-and-Moving.md +5 -5
  678. package/dist/tutorials/Updating-and-Patch-Notes.md +18 -18
  679. package/dist/tutorials/Video-Editing.md +34 -34
  680. package/dist/tutorials/Where-Your-Files-Live.md +10 -10
  681. package/dist/tutorials/YouTube-Description.md +26 -26
  682. package/dist/tutorials/Your-First-Short.md +35 -35
  683. package/dist/tutorials/_Sidebar.md +19 -14
  684. package/dist/updater/work-in-progress.js +42 -12
  685. package/dist/web/assets/index-BNftKQrA.css +1 -0
  686. package/dist/web/assets/index-Cpa58woP.js +459 -0
  687. package/dist/web/assets/pdf-BKh8f7R6.js +56 -0
  688. package/dist/web/assets/pdf.worker.min-BmVo14Nb.mjs +28 -0
  689. package/dist/web/assets/pdf.worker.min-D7i8o3wR.js +1 -0
  690. package/dist/web/index.html +2 -2
  691. package/package.json +6 -2
  692. package/dist/slices/article/provided-entries.js +0 -66
  693. package/dist/slices/article/run.js +0 -145
  694. package/dist/slices/images/run.js +0 -234
  695. package/dist/slices/narration/legacy-plan.js +0 -58
  696. package/dist/slices/narration/run.js +0 -270
  697. package/dist/slices/rebuild/runtime-actions.js +0 -90
  698. package/dist/slices/research/run.js +0 -224
  699. package/dist/slices/subtitles/prepare.js +0 -194
  700. package/dist/slices/subtitles/transcript.js +0 -34
  701. package/dist/slices/thumbnail/run.js +0 -219
  702. package/dist/slices/video/audio-inputs.js +0 -33
  703. package/dist/slices/video/reuse-audio.js +0 -62
  704. package/dist/slices/video/run.js +0 -78
  705. package/dist/slices/video/write-export.js +0 -158
  706. package/dist/slices/video/write-subtitles.js +0 -59
  707. package/dist/tutorials/Library-Overview.md +0 -76
  708. package/dist/tutorials/Play-Images.md +0 -161
  709. package/dist/tutorials/Play-Narration.md +0 -167
  710. package/dist/tutorials/Play-Outputs.md +0 -109
  711. package/dist/tutorials/Play-Overview.md +0 -227
  712. package/dist/tutorials/Play-Title-and-Article.md +0 -136
  713. package/dist/tutorials/Play-Video-and-Style.md +0 -143
  714. package/dist/web/assets/index-BadsoORk.css +0 -1
  715. package/dist/web/assets/index-D8LqNsCl.js +0 -326
  716. package/dist/web/assets/pdf-T7MCwjIv.js +0 -54
  717. package/dist/web/assets/pdf.worker.min-Dswkl-cV.mjs +0 -29
  718. package/dist/web/assets/pdf.worker.min-zC6nfbxG.js +0 -1
@@ -0,0 +1,113 @@
1
+ import { z } from "zod";
2
+ // Public search interest without a key: Google's search suggestions (DuckDuckGo's when Google
3
+ // doesn't answer) and the Wikipedia article's views last month. Checked keyless on 2026-10-18;
4
+ // Google Trends answers 429/404 to such calls, so it isn't asked.
5
+ // ceiling: one service that hangs costs a topic this long at most.
6
+ export const interestRequestMs = 4000;
7
+ const suggestAnswer = z.tuple([z.string(), z.array(z.string())]).rest(z.unknown());
8
+ const openSearchAnswer = z.tuple([z.string(), z.array(z.string())]).rest(z.unknown());
9
+ const pageviewsAnswer = z.object({ items: z.array(z.object({ views: z.number() }).loose()) });
10
+ class Unanswered extends Error {
11
+ reason;
12
+ constructor(reason) {
13
+ super(reason);
14
+ this.reason = reason;
15
+ }
16
+ }
17
+ // "en" from "en-GB"; a language Wikipedia names by its two letters.
18
+ function languageOf(language) {
19
+ const code = language.trim().toLowerCase().slice(0, 2);
20
+ return /^[a-z]{2}$/.test(code) ? code : "en";
21
+ }
22
+ // The last whole month before `now`, as the pageviews API names its days.
23
+ export function lastMonth(now) {
24
+ const first = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - 1, 1));
25
+ const last = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 0));
26
+ const day = (date) => `${String(date.getUTCFullYear())}${String(date.getUTCMonth() + 1).padStart(2, "0")}${String(date.getUTCDate()).padStart(2, "0")}00`;
27
+ return { start: day(first), end: day(last) };
28
+ }
29
+ export function createSearchInterest(deps) {
30
+ const headers = {
31
+ "User-Agent": `Slopify/${deps.version} (https://github.com/gentbajko/slopify)`,
32
+ Accept: "application/json",
33
+ };
34
+ const json = async (url, signal) => {
35
+ const timeout = AbortSignal.timeout(interestRequestMs);
36
+ let response;
37
+ try {
38
+ response = await deps.fetch(url, { headers, signal: AbortSignal.any([signal, timeout]) });
39
+ }
40
+ catch {
41
+ throw new Unanswered(timeout.aborted ? "timeout" : "offline");
42
+ }
43
+ if (!response.ok)
44
+ throw new Unanswered("refused");
45
+ try {
46
+ return await response.json();
47
+ }
48
+ catch {
49
+ throw new Unanswered("refused");
50
+ }
51
+ };
52
+ const reasonOf = (error) => error instanceof Unanswered ? error.reason : "offline";
53
+ const suggestions = async (topic, language, signal) => {
54
+ const q = encodeURIComponent(topic);
55
+ let failure = "offline";
56
+ for (const url of [
57
+ `https://suggestqueries.google.com/complete/search?client=firefox&hl=${language}&q=${q}`,
58
+ `https://duckduckgo.com/ac/?q=${q}&type=list&kl=wt-wt`,
59
+ ]) {
60
+ try {
61
+ const parsed = suggestAnswer.safeParse(await json(url, signal));
62
+ if (parsed.success)
63
+ return parsed.data[1].slice(0, 10);
64
+ failure = "refused";
65
+ }
66
+ catch (error) {
67
+ failure = reasonOf(error);
68
+ }
69
+ }
70
+ return failure;
71
+ };
72
+ const wikiViews = async (topic, language, signal) => {
73
+ try {
74
+ const found = openSearchAnswer.safeParse(await json(`https://${language}.wikipedia.org/w/api.php?action=opensearch&format=json&namespace=0&limit=1&search=${encodeURIComponent(topic)}`, signal));
75
+ if (!found.success)
76
+ return "refused";
77
+ const title = found.data[1][0];
78
+ if (title === undefined)
79
+ return null;
80
+ const { start, end } = lastMonth(deps.now());
81
+ let answer;
82
+ try {
83
+ answer = await json(`https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/${language}.wikipedia/all-access/user/${encodeURIComponent(title.replaceAll(" ", "_"))}/monthly/${start}/${end}`, signal);
84
+ }
85
+ catch (error) {
86
+ // An article too new to have a month of views answers 404.
87
+ if (reasonOf(error) === "refused")
88
+ return null;
89
+ throw error;
90
+ }
91
+ const views = pageviewsAnswer.safeParse(answer);
92
+ return views.success ? views.data.items.reduce((sum, item) => sum + item.views, 0) : null;
93
+ }
94
+ catch (error) {
95
+ return reasonOf(error);
96
+ }
97
+ };
98
+ return async (topic, rawLanguage, signal) => {
99
+ const language = languageOf(rawLanguage);
100
+ const [offered, views] = await Promise.all([
101
+ suggestions(topic, language, signal),
102
+ wikiViews(topic, language, signal),
103
+ ]);
104
+ const failed = (value) => typeof value === "string";
105
+ if (failed(offered) && failed(views))
106
+ return { ok: false, reason: offered };
107
+ return {
108
+ ok: true,
109
+ suggestions: failed(offered) ? null : offered,
110
+ wikiViews: failed(views) ? null : views,
111
+ };
112
+ };
113
+ }
@@ -1,5 +1,6 @@
1
+ import { safeJson } from "./http-failure.js";
1
2
  import { cartesiaVersion } from "./tts/cartesia.js";
2
- // Settings → Providers' Test button, per provider. Some answer a key they do not recognise
3
+ // Settings → Keys & services' Test button, per provider. Some answer a key they do not recognise
3
4
  // with something other than 401: Google with 400 API_KEY_INVALID, ElevenLabs with 400
4
5
  // invalid_api_key, Inworld with 403 "does not exist". Observed on 2026-09-27; their
5
6
  // documentation says 401.
@@ -21,6 +22,37 @@ function listed(ids) {
21
22
  };
22
23
  }
23
24
  const field = (value, key) => typeof value === "object" && value !== null ? value[key] : undefined;
25
+ function idsOf(rows, key, strip) {
26
+ if (!Array.isArray(rows))
27
+ return undefined;
28
+ return rows.flatMap((row) => {
29
+ const id = field(row, key);
30
+ return typeof id === "string" && id !== "" ? [strip ? id.replace(strip, "") : id] : [];
31
+ });
32
+ }
33
+ // `{ data: [{ id }] }`: OpenAI's and OpenRouter's lists.
34
+ function dataList(url) {
35
+ return {
36
+ url: () => url,
37
+ read: (body) => {
38
+ const ids = idsOf(field(safeJson(body), "data"), "id");
39
+ return ids === undefined ? undefined : { ids };
40
+ },
41
+ };
42
+ }
43
+ const googleBase = "https://generativelanguage.googleapis.com/v1beta/models?pageSize=1000";
44
+ const googleList = {
45
+ url: (page) => page === undefined ? googleBase : `${googleBase}&pageToken=${encodeURIComponent(page)}`,
46
+ read: (body) => {
47
+ const value = safeJson(body);
48
+ const ids = idsOf(field(value, "models"), "name", /^models\//);
49
+ if (ids === undefined)
50
+ return undefined;
51
+ const next = field(value, "nextPageToken");
52
+ return typeof next === "string" && next !== "" ? { ids, next } : { ids };
53
+ },
54
+ };
55
+ const openAiList = dataList("https://api.openai.com/v1/models");
24
56
  const google = {
25
57
  url: "https://generativelanguage.googleapis.com/v1beta/models?pageSize=1",
26
58
  headers: (key) => ({ "x-goog-api-key": key }),
@@ -28,6 +60,7 @@ const google = {
28
60
  model: {
29
61
  url: (model) => `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model.replace(/^models\//, ""))}`,
30
62
  },
63
+ list: googleList,
31
64
  };
32
65
  export const keyProbes = {
33
66
  openrouter: {
@@ -40,9 +73,20 @@ export const keyProbes = {
40
73
  return Array.isArray(data) ? data.map((one) => field(one, "id")) : [];
41
74
  }),
42
75
  },
76
+ list: dataList("https://openrouter.ai/api/v1/models"),
77
+ },
78
+ "openai-tts": {
79
+ url: "https://api.openai.com/v1/models",
80
+ headers: bearer,
81
+ model: openAiModel,
82
+ list: openAiList,
83
+ },
84
+ "openai-image": {
85
+ url: "https://api.openai.com/v1/models",
86
+ headers: bearer,
87
+ model: openAiModel,
88
+ list: openAiList,
43
89
  },
44
- "openai-tts": { url: "https://api.openai.com/v1/models", headers: bearer, model: openAiModel },
45
- "openai-image": { url: "https://api.openai.com/v1/models", headers: bearer, model: openAiModel },
46
90
  "google-image": google,
47
91
  "google-tts": google,
48
92
  // fal.ai's model list answers without a key; its price lookup requires one.
@@ -72,6 +116,13 @@ export const keyProbes = {
72
116
  url: () => "https://api.elevenlabs.io/v1/models",
73
117
  lists: listed((body) => Array.isArray(body) ? body.map((one) => field(one, "model_id")) : []),
74
118
  },
119
+ list: {
120
+ url: () => "https://api.elevenlabs.io/v1/models",
121
+ read: (body) => {
122
+ const ids = idsOf(safeJson(body), "model_id");
123
+ return ids === undefined ? undefined : { ids };
124
+ },
125
+ },
75
126
  },
76
127
  cartesia: {
77
128
  url: "https://api.cartesia.ai/voices?limit=1",
@@ -32,6 +32,6 @@ export async function nodeAntigravityModels(binary, read = nodeReadCliOutput) {
32
32
  }
33
33
  catch {
34
34
  // Do not pass the CLI's raw output or home paths to the browser.
35
- throw new Error('Slopify could not get the model list from the Antigravity CLI (it ran "agy models"). Check agy is installed and signed in (Settings → Providers), then refresh the list, or type a model slug that "agy models" shows.');
35
+ throw new Error('Slopify could not get the model list from the Antigravity CLI (it ran "agy models"). Check agy is installed and signed in (Settings → Keys & services), then refresh the list, or type a model slug that "agy models" shows.');
36
36
  }
37
37
  }
@@ -5,7 +5,9 @@ import { z } from "zod";
5
5
  import { redact } from "../../kernel/log.js";
6
6
  import { documentMessages } from "../../kernel/ports/llm-documents.js";
7
7
  import { providerError } from "../../kernel/ports/model.js";
8
+ import { editSettings } from "../../kernel/ports/settings-places.js";
8
9
  import { cliCheck, cliName, cliReported, commandOf } from "../explain.js";
10
+ import { accessField } from "../model-access.js";
9
11
  import { nodeAntigravityModels } from "./antigravity-models.js";
10
12
  import { imageWorkspace, readFileName, unseenImages } from "./image-workspace.js";
11
13
  import { cliInput, cliShaped, deliveredInput, endedWithout, promptOf, stopCliRun, stuckCli, } from "./run-cli.js";
@@ -76,7 +78,7 @@ export function antigravityLlm(deps) {
76
78
  if (/^gemini$/i.test(commandOf(binary)))
77
79
  throw providerError({
78
80
  kind: "unsupported",
79
- message: `The saved program path for the Antigravity CLI (${binary}) still points at the Gemini CLI, which Google has retired. In Settings → Providers, clear the Antigravity CLI path or set it to the agy program, then use Try again.`,
81
+ message: `The saved program path for the Antigravity CLI (${binary}) still points at the Gemini CLI, which Google has retired. In Settings → Keys & services, clear the Antigravity CLI path or set it to the agy program, then use Try again.`,
80
82
  });
81
83
  // The documents travel inline, as with OpenRouter: headless agy refuses MCP tools it was
82
84
  // not pre-approved for, so a research reader tool would be silently unavailable.
@@ -159,7 +161,7 @@ export function antigravityLlm(deps) {
159
161
  }
160
162
  req.signal.throwIfAborted();
161
163
  if (run === undefined)
162
- throw failure(binary, req.model, "", `The ${cliName(binary)} could not be started. Check it is installed and set up in Settings → Providers, then use Try again.`);
164
+ throw failure(binary, req.model, "", `The ${cliName(binary)} could not be started. Check it is installed and set up in Settings → Keys & services, then use Try again.`);
163
165
  if (authRequired(run.stderr()))
164
166
  throw authFailure();
165
167
  throw failure(binary, req.model, run.stderr(), ended === undefined ? stuckCli(binary) : endedWithout(binary, ended, run.stderr()));
@@ -190,11 +192,11 @@ function emptyAnswer(binary, stderr) {
190
192
  if (/no output produced|auto-denied|permission/i.test(stderr))
191
193
  return providerError({
192
194
  kind: "other",
193
- message: `The ${cliName(binary)} tried to use a tool Slopify does not allow (it said: ${redact(stderr.trim()).slice(0, 300)}), so it gave no answer. Use Try again; if it keeps happening, choose another model or text provider in the Providers section of Edit project.`,
195
+ message: `The ${cliName(binary)} tried to use a tool Slopify does not allow (it said: ${redact(stderr.trim()).slice(0, 300)}), so it gave no answer. Use Try again; if it keeps happening, choose another service or model in ${editSettings.writer}.`,
194
196
  });
195
197
  return providerError({
196
198
  kind: "other",
197
- message: `The ${cliName(binary)} answered with no text. Use Try again; if it keeps happening, run agy in a terminal to check it works, or choose another model in the Providers section of Edit project.`,
199
+ message: `The ${cliName(binary)} answered with no text. Use Try again; if it keeps happening, run agy in a terminal to check it works, or choose another model in ${editSettings.writer}.`,
198
200
  });
199
201
  }
200
202
  // `message` is what the CLI said; `sentence` replaces the default wording when Slopify
@@ -205,7 +207,9 @@ function failure(binary, model, message, sentence, now = Date.now) {
205
207
  if (/invalid model selection/i.test(message))
206
208
  return providerError({
207
209
  kind: "unsupported",
208
- message: `The ${cliName(binary)} does not offer the model "${model}". Run "agy models" in a terminal to see the ones it has, pick one in the Edit tab (Edit project → Providers), then use Continue the run.`,
210
+ // agy lists models a plan can't run and refuses them with this sentence.
211
+ access: "no-access",
212
+ message: `The ${cliName(binary)} does not offer the model "${model}". Run "agy models" in a terminal to see the ones it has, pick one in ${editSettings.writer}, then use Continue the run.`,
209
213
  });
210
214
  if (/quota|resource.?exhausted|capacity/i.test(message) &&
211
215
  /exhaust|exceed|used up|reached|limit/i.test(message)) {
@@ -219,7 +223,7 @@ function failure(binary, model, message, sentence, now = Date.now) {
219
223
  if (waitMs !== undefined && waitMs <= 5 * 60_000)
220
224
  return providerError({
221
225
  kind: "rate_limit",
222
- message: `The ${cliName(binary)} is busy: Google's capacity for this model is used for the moment (it said: ${redact(message)}). Slopify tries again by itself shortly; if it keeps happening, run fewer projects at once or choose another model in the Providers section of Edit project.`,
226
+ message: `The ${cliName(binary)} is busy: Google's capacity for this model is used for the moment (it said: ${redact(message)}). Slopify tries again by itself shortly; if it keeps happening, run fewer projects at once or choose another model in ${editSettings.writer}.`,
223
227
  retryAfterMs: Math.max(1000, waitMs),
224
228
  });
225
229
  return providerError({
@@ -233,6 +237,7 @@ function failure(binary, model, message, sentence, now = Date.now) {
233
237
  }
234
238
  return providerError({
235
239
  kind: "other",
240
+ ...accessField(null, message),
236
241
  message: sentence ?? cliReported(binary, redact(message), cliCheck(binary)),
237
242
  });
238
243
  }
@@ -0,0 +1,388 @@
1
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { z } from "zod";
5
+ import { redact } from "../../kernel/log.js";
6
+ import { cliName } from "../explain.js";
7
+ import { cliLoginError } from "./cli-login-error.js";
8
+ import { codexBinary, hardenedCodexBase } from "./codex.js";
9
+ import { cliInput, endedWithout, stopCliRun, stuckCli } from "./run-cli.js";
10
+ import { lines } from "./sse-lines.js";
11
+ // The in-app assistant's CLI turn: Claude Code or Codex with no tool of its own (no shell, no
12
+ // files, no web), none of the person's settings, hooks, plugins, skills, memory or MCP
13
+ // servers, and one MCP server: Slopify's `/mcp/assistant`, opened with the turn's token.
14
+ //
15
+ // The token is never an argument: argv is readable by every process on the computer. It is in
16
+ // the CLI's environment only, and the CLIs put it in the header themselves:
17
+ // - Claude Code 2.1.302 expands ${VAR} in an --mcp-config file's headers (checked on this
18
+ // machine: a stand-in server saw "Bearer <the token>" from `"Bearer
19
+ // ${SLOPIFY_ASSISTANT_TOKEN}"`). The file names the variable, not the token.
20
+ // - Codex 0.155.1 reads `mcp_servers.<name>.bearer_token_env_var` (codex-rs config
21
+ // `McpServerTransportConfig::StreamableHttp`; also checked: the server saw the bearer).
22
+ export const assistantServerName = "slopify";
23
+ export const assistantTokenVariable = "SLOPIFY_ASSISTANT_TOKEN";
24
+ const slopifyTool = `mcp__${assistantServerName}__`;
25
+ export function claudeAssistantArgs(options) {
26
+ return [
27
+ "-p",
28
+ "--output-format",
29
+ "stream-json",
30
+ "--verbose",
31
+ "--include-partial-messages",
32
+ // `claude --help` 2.1.302: --restricted drops the code-running tools and WebFetch and
33
+ // ignores the user, project and local settings files; --setting-sources "" loads none of
34
+ // them either. --bare would discard the OAuth sign-in the assistant runs on.
35
+ "--restricted",
36
+ "--setting-sources",
37
+ "",
38
+ "--disable-slash-commands",
39
+ "--no-chrome",
40
+ "--no-session-persistence",
41
+ "--settings",
42
+ JSON.stringify({
43
+ disableAllHooks: true,
44
+ autoMemoryEnabled: false,
45
+ claudeMdExcludes: ["**"],
46
+ enabledPlugins: {},
47
+ }),
48
+ "--system-prompt",
49
+ options.system,
50
+ "--strict-mcp-config",
51
+ "--mcp-config",
52
+ options.mcpConfig,
53
+ // No built-in tool at all; only Slopify's server is allowed, and the web tools are named
54
+ // as refused in case a later version brings them back by default.
55
+ "--tools",
56
+ "",
57
+ "--allowedTools",
58
+ `mcp__${assistantServerName}`,
59
+ "--disallowedTools",
60
+ "WebSearch,WebFetch,Bash",
61
+ "--permission-mode",
62
+ "dontAsk",
63
+ ...(options.model === undefined || options.model === "" ? [] : ["--model", options.model]),
64
+ ];
65
+ }
66
+ export function claudeAssistantConfig(url) {
67
+ return JSON.stringify({
68
+ mcpServers: {
69
+ [assistantServerName]: {
70
+ type: "http",
71
+ url,
72
+ headers: { Authorization: `Bearer \${${assistantTokenVariable}}` },
73
+ },
74
+ },
75
+ });
76
+ }
77
+ export function codexAssistantArgs(options) {
78
+ const server = `mcp_servers.${assistantServerName}`;
79
+ return [
80
+ ...hardenedCodexBase(options.directory),
81
+ "-c",
82
+ `instructions=${JSON.stringify(options.system)}`,
83
+ "-c",
84
+ 'web_search="disabled"',
85
+ "-c",
86
+ "tool_output_token_limit=32768",
87
+ "-c",
88
+ `${server}.url=${JSON.stringify(options.url)}`,
89
+ "-c",
90
+ `${server}.bearer_token_env_var=${JSON.stringify(assistantTokenVariable)}`,
91
+ "-c",
92
+ `${server}.required=true`,
93
+ "-c",
94
+ `${server}.default_tools_approval_mode="auto"`,
95
+ ...(options.model === undefined || options.model === "" ? [] : ["-m", options.model]),
96
+ "--",
97
+ "-",
98
+ ];
99
+ }
100
+ // What the CLI is started with: enough to find itself, its sign-in and the network, and
101
+ // nothing else of Slopify's environment. A provider key there would make the CLI bill that key
102
+ // instead of the person's plan, and hand it a secret it never needs.
103
+ const passedNames = new Set([
104
+ "PATH",
105
+ "HOME",
106
+ "USER",
107
+ "LOGNAME",
108
+ "SHELL",
109
+ "LANG",
110
+ "LANGUAGE",
111
+ "TZ",
112
+ "TMPDIR",
113
+ "TEMP",
114
+ "TMP",
115
+ "TERM",
116
+ // Where each CLI keeps its sign-in and settings, when moved from the default.
117
+ "CLAUDE_CONFIG_DIR",
118
+ "CODEX_HOME",
119
+ // A network that needs a proxy or its own certificates.
120
+ "HTTP_PROXY",
121
+ "HTTPS_PROXY",
122
+ "NO_PROXY",
123
+ "http_proxy",
124
+ "https_proxy",
125
+ "no_proxy",
126
+ "NODE_EXTRA_CA_CERTS",
127
+ "SSL_CERT_FILE",
128
+ "SSL_CERT_DIR",
129
+ // Windows: the CLI can't start, or find its folders, without these.
130
+ "SYSTEMROOT",
131
+ "SystemRoot",
132
+ "WINDIR",
133
+ "COMSPEC",
134
+ "PATHEXT",
135
+ "USERPROFILE",
136
+ "APPDATA",
137
+ "LOCALAPPDATA",
138
+ "HOMEDRIVE",
139
+ "HOMEPATH",
140
+ "PROGRAMDATA",
141
+ "ProgramData",
142
+ "PROGRAMFILES",
143
+ "ProgramFiles",
144
+ "Path",
145
+ ]);
146
+ export function assistantEnv(sourceEnv) {
147
+ const env = {};
148
+ for (const [name, value] of Object.entries(sourceEnv))
149
+ if (value !== undefined && (passedNames.has(name) || /^(LC|XDG)_[A-Z_]+$/.test(name)))
150
+ env[name] = value;
151
+ return env;
152
+ }
153
+ // A private folder (0700) that is the CLI's working directory, so nothing of the person's
154
+ // projects is its cwd, holding the 0600 config file; removed when the turn ends.
155
+ export function assistantWorkspace(url, token, sourceEnv) {
156
+ const directory = mkdtempSync(join(tmpdir(), "slopify-assistant-"));
157
+ const remove = () => rmSync(directory, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
158
+ try {
159
+ const config = join(directory, "mcp.json");
160
+ writeFileSync(config, claudeAssistantConfig(url), { mode: 0o600, flag: "wx" });
161
+ const env = assistantEnv(sourceEnv);
162
+ env.PWD = directory;
163
+ env[assistantTokenVariable] = token;
164
+ return { directory, config, env, remove };
165
+ }
166
+ catch (error) {
167
+ remove();
168
+ throw error;
169
+ }
170
+ }
171
+ const typed = z.object({ type: z.string() }).loose();
172
+ const claudeInit = z
173
+ .object({
174
+ subtype: z.literal("init"),
175
+ tools: z.array(z.string()).optional(),
176
+ mcp_servers: z.array(z.object({ name: z.string(), status: z.string() }).loose()).optional(),
177
+ })
178
+ .loose();
179
+ const claudeAssistant = z
180
+ .object({
181
+ message: z.object({
182
+ content: z.array(z
183
+ .object({ type: z.string(), text: z.string().optional(), name: z.string().optional() })
184
+ .loose()),
185
+ }),
186
+ })
187
+ .loose();
188
+ const claudePartial = z
189
+ .object({
190
+ event: z.object({
191
+ type: z.literal("content_block_delta"),
192
+ delta: z.object({ type: z.literal("text_delta"), text: z.string() }),
193
+ }),
194
+ })
195
+ .loose();
196
+ const claudeResult = z
197
+ .object({ subtype: z.string(), is_error: z.boolean().optional(), result: z.string().optional() })
198
+ .loose();
199
+ const codexItem = z
200
+ .object({
201
+ item: z
202
+ .object({
203
+ id: z.string().optional(),
204
+ type: z.string(),
205
+ text: z.string().optional(),
206
+ server: z.string().optional(),
207
+ tool: z.string().optional(),
208
+ })
209
+ .loose(),
210
+ })
211
+ .loose();
212
+ const codexFailed = z.object({ error: z.object({ message: z.string() }) }).loose();
213
+ const codexError = z.object({ message: z.string() }).loose();
214
+ // Codex items that are no part of a Slopify chat: a shell command, a file edit, a web
215
+ // search, another agent.
216
+ const codexForeign = new Set([
217
+ "command_execution",
218
+ "file_change",
219
+ "web_search",
220
+ "collab_tool_call",
221
+ ]);
222
+ function failed(cli, said) {
223
+ const login = cliLoginError(cli, said);
224
+ if (login !== undefined)
225
+ return { type: "failed", message: login.message };
226
+ return {
227
+ type: "failed",
228
+ message: `The ${cliName(cli === "claude-code" ? "claude" : "codex")} stopped with an error: ${redact(said).slice(0, 500)}. Send your message again; if it keeps happening, check the CLI in Settings → Keys & services.`,
229
+ };
230
+ }
231
+ // One JSONL line of the CLI's stream as what the chat shows. A stateful reader: Codex reports
232
+ // a message's text so far, and only what is new is passed on.
233
+ export function assistantStream(cli) {
234
+ const typedSoFar = new Map();
235
+ return (line) => {
236
+ if (line.trim() === "")
237
+ return [];
238
+ let value;
239
+ try {
240
+ value = JSON.parse(line);
241
+ }
242
+ catch {
243
+ return [];
244
+ }
245
+ const event = typed.safeParse(value);
246
+ if (!event.success)
247
+ return [];
248
+ return cli === "claude-code"
249
+ ? claudeEvents(event.data.type, value)
250
+ : codexEvents(event.data.type, value);
251
+ };
252
+ function claudeEvents(type, value) {
253
+ if (type === "system") {
254
+ const init = claudeInit.safeParse(value);
255
+ if (!init.success)
256
+ return [];
257
+ const foreign = (init.data.tools ?? []).filter((name) => !name.startsWith(slopifyTool));
258
+ if (foreign.length > 0)
259
+ return [{ type: "foreign", name: foreign.join(", ") }];
260
+ const server = init.data.mcp_servers?.find((one) => one.name === assistantServerName);
261
+ if (server !== undefined && (server.status === "failed" || server.status === "needs-auth"))
262
+ return [
263
+ {
264
+ type: "failed",
265
+ message: "Claude Code couldn't reach Slopify's assistant tools, so it answered nothing. Send your message again; if it keeps happening, restart Slopify.",
266
+ },
267
+ ];
268
+ return [];
269
+ }
270
+ if (type === "stream_event") {
271
+ const partial = claudePartial.safeParse(value);
272
+ return partial.success && partial.data.event.delta.text !== ""
273
+ ? [{ type: "partial", text: partial.data.event.delta.text }]
274
+ : [];
275
+ }
276
+ if (type === "assistant") {
277
+ const message = claudeAssistant.safeParse(value);
278
+ if (!message.success)
279
+ return [];
280
+ return message.data.message.content.flatMap((block) => {
281
+ if (block.type === "text" && block.text !== undefined && block.text !== "")
282
+ return [{ type: "text", text: block.text }];
283
+ if (block.type !== "tool_use" || block.name === undefined)
284
+ return [];
285
+ return block.name.startsWith(slopifyTool)
286
+ ? [{ type: "tool", name: block.name.slice(slopifyTool.length) }]
287
+ : [{ type: "foreign", name: block.name }];
288
+ });
289
+ }
290
+ if (type === "result") {
291
+ const result = claudeResult.safeParse(value);
292
+ if (!result.success)
293
+ return [];
294
+ return result.data.is_error === true || result.data.subtype !== "success"
295
+ ? [failed("claude-code", result.data.result ?? result.data.subtype)]
296
+ : [{ type: "done" }];
297
+ }
298
+ return [];
299
+ }
300
+ function codexEvents(type, value) {
301
+ if (type === "item.started" || type === "item.updated" || type === "item.completed") {
302
+ const parsed = codexItem.safeParse(value);
303
+ if (!parsed.success)
304
+ return [];
305
+ const { item } = parsed.data;
306
+ if (codexForeign.has(item.type))
307
+ return [{ type: "foreign", name: item.type }];
308
+ if (item.type === "mcp_tool_call") {
309
+ if (item.server !== assistantServerName)
310
+ return [{ type: "foreign", name: `${item.server ?? "?"}.${item.tool ?? "?"}` }];
311
+ return type === "item.started" && item.tool !== undefined
312
+ ? [{ type: "tool", name: item.tool }]
313
+ : [];
314
+ }
315
+ if (item.type !== "agent_message")
316
+ return [];
317
+ const id = item.id ?? "";
318
+ const seen = typedSoFar.get(id) ?? "";
319
+ const text = item.text ?? "";
320
+ if (type === "item.completed") {
321
+ typedSoFar.delete(id);
322
+ return text === "" ? [] : [{ type: "text", text }];
323
+ }
324
+ if (text.length > seen.length && text.startsWith(seen)) {
325
+ typedSoFar.set(id, text);
326
+ return [{ type: "partial", text: text.slice(seen.length) }];
327
+ }
328
+ return [];
329
+ }
330
+ if (type === "turn.completed")
331
+ return [{ type: "done" }];
332
+ if (type === "turn.failed") {
333
+ const parsed = codexFailed.safeParse(value);
334
+ return [failed("codex", parsed.success ? parsed.data.error.message : "the turn failed")];
335
+ }
336
+ if (type === "error") {
337
+ const parsed = codexError.safeParse(value);
338
+ return [failed("codex", parsed.success ? parsed.data.message : "unknown error")];
339
+ }
340
+ return [];
341
+ }
342
+ }
343
+ // The turn itself: a private workspace, the CLI started in it with the prompt on stdin, its
344
+ // stream read into the chat's events, and everything stopped and removed at the end.
345
+ export function assistantCliRunner(run, sourceEnv = process.env) {
346
+ return async function* turn(request) {
347
+ const space = assistantWorkspace(request.url, request.token, sourceEnv);
348
+ let child;
349
+ let ended;
350
+ try {
351
+ child = run(request.binary, request.cli === "claude-code"
352
+ ? claudeAssistantArgs({ mcpConfig: space.config, system: request.system })
353
+ : codexAssistantArgs({
354
+ directory: space.directory,
355
+ url: request.url,
356
+ system: request.system,
357
+ }), request.signal, { cwd: space.directory, env: space.env, stdin: cliInput(request.prompt) });
358
+ const read = assistantStream(request.cli);
359
+ for await (const line of lines(child.stdout, request.signal))
360
+ for (const event of read(line)) {
361
+ yield event;
362
+ if (event.type === "done" || event.type === "failed" || event.type === "foreign")
363
+ return;
364
+ }
365
+ }
366
+ finally {
367
+ // Also when the reader stops early (Stop, the time limit): a CLI left running would
368
+ // keep spending the person's plan.
369
+ try {
370
+ if (child !== undefined)
371
+ ended = await stopCliRun(child);
372
+ }
373
+ finally {
374
+ space.remove();
375
+ }
376
+ }
377
+ if (request.signal.aborted || child === undefined)
378
+ return;
379
+ // The stream ended with neither an answer nor a failure.
380
+ const binary = request.cli === "claude-code" ? "claude" : codexBinary;
381
+ const said = child.stderr();
382
+ yield {
383
+ type: "failed",
384
+ message: cliLoginError(request.cli, said)?.message ??
385
+ (ended === undefined ? stuckCli(binary) : endedWithout(binary, ended, said)),
386
+ };
387
+ };
388
+ }