@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
@@ -1,6 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { mkdirSync } from "node:fs";
3
3
  import { isAbsolute, join, resolve as resolvePath } from "node:path";
4
+ import { editSettings } from "../../kernel/ports/settings-places.js";
4
5
  import { measurePiece } from "../narration/pauses.js";
5
6
  import { runFfmpeg } from "../video/ffmpeg.js";
6
7
  import { sentenceLevel } from "./sentence-level-model.js";
@@ -243,7 +244,7 @@ export async function meterFile(run, file) {
243
244
  else if (code === 0)
244
245
  resolve();
245
246
  else
246
- reject(new Error(`Slopify couldn't measure a narration piece's sentences (ffmpeg said: ${errors.trim().split("\n").at(-1) ?? "nothing"}). Try again; if it happens again, turn off Level each sentence in Edit project → Pauses and volume, then use Download diagnostics in Settings and report it.`));
247
+ reject(new Error(`Slopify couldn't measure a narration piece's sentences (ffmpeg said: ${errors.trim().split("\n").at(-1) ?? "nothing"}). Try again; if it happens again, turn off Level each sentence in ${editSettings.sound}, then use Download diagnostics in Settings and report it.`));
247
248
  });
248
249
  });
249
250
  return meter.readings();
@@ -0,0 +1,61 @@
1
+ import { isVideoModel } from "../../catalog/schema.js";
2
+ import { priceCall } from "../run-cost/pricing.js";
3
+ import { isLocalCliProvider, systemVoiceProvider } from "../settings/model.js";
4
+ // ceiling: an automatic check may cost at most this much per call (decision 1). Anything
5
+ // dearer - and every image - waits for Check models and a confirmed estimate.
6
+ export const automaticCeilingDollars = 0.001;
7
+ // What a check is assumed to use before it runs: a short prompt and a one-word reply, two
8
+ // characters of speech, one small image.
9
+ const assumed = { tokensIn: 40, tokensOut: 40, characters: 2, images: 1 };
10
+ export function isVideoTarget(catalogue, target) {
11
+ return (target.family === "image" &&
12
+ catalogue.image.some((row) => row.provider === target.provider && row.id === target.model && isVideoModel(row)));
13
+ }
14
+ export function costOf(catalogue, target) {
15
+ if (target.provider === systemVoiceProvider)
16
+ return { dollars: 0, planUsage: false };
17
+ if (isLocalCliProvider(target.provider))
18
+ return { dollars: 0, planUsage: true };
19
+ return { dollars: priced(catalogue, target, assumed), planUsage: false };
20
+ }
21
+ // What a finished check cost, from what it reported; the estimate when it reported nothing.
22
+ export function spentOn(catalogue, target, use) {
23
+ if (isLocalCliProvider(target.provider) || target.provider === systemVoiceProvider)
24
+ return 0;
25
+ const reported = use === null
26
+ ? null
27
+ : priced(catalogue, target, {
28
+ tokensIn: use.tokensIn ?? assumed.tokensIn,
29
+ tokensOut: use.tokensOut ?? assumed.tokensOut,
30
+ characters: use.characters ?? assumed.characters,
31
+ images: use.images ?? assumed.images,
32
+ });
33
+ return reported ?? costOf(catalogue, target).dollars ?? 0;
34
+ }
35
+ function priced(catalogue, target, use) {
36
+ const call = {
37
+ kind: target.family,
38
+ provider: target.provider,
39
+ model: target.model,
40
+ wallMs: 0,
41
+ ...use,
42
+ };
43
+ const price = priceCall(call, catalogue);
44
+ if (price.cost !== null)
45
+ return price.cost;
46
+ // A voice priced by the minute: two characters are well under a second.
47
+ const perMinute = target.family === "tts"
48
+ ? catalogue.tts.find((row) => row.provider === target.provider && row.id === target.model)
49
+ ?.pricing.perMinute
50
+ : undefined;
51
+ return perMinute === undefined ? null : perMinute / 60;
52
+ }
53
+ // Automatic checks (a key or CLI added, the weekly look) run only what costs nothing, a CLI
54
+ // plan's allowance, or a text or voice call under the ceiling. Images and clips never.
55
+ export function automaticAllowed(catalogue, target, cost) {
56
+ if (target.family === "image" || isVideoTarget(catalogue, target))
57
+ return false;
58
+ if (cost.planUsage)
59
+ return true;
60
+ return cost.dollars !== null && cost.dollars < automaticCeilingDollars;
61
+ }
@@ -0,0 +1,124 @@
1
+ import { keyWithShared } from "../settings/keys.js";
2
+ import { providerById } from "../settings/model.js";
3
+ import { probeRead } from "../settings/probe-read.js";
4
+ import { accountOf, writeListed } from "./repo.js";
5
+ // ceiling: a list longer than this many pages is read no further.
6
+ const maxPages = 10;
7
+ async function liveList(deps, probe, key) {
8
+ const list = probe.list;
9
+ if (list === undefined)
10
+ return undefined;
11
+ const ids = new Set();
12
+ let page;
13
+ for (let n = 0; n < maxPages; n += 1) {
14
+ const answer = await probeRead(deps.fetch, list.url(page), probe.headers(key), true);
15
+ if (answer === undefined || answer.status < 200 || answer.status >= 300)
16
+ return undefined;
17
+ const read = list.read(answer.body);
18
+ if (read === undefined)
19
+ return undefined;
20
+ for (const id of read.ids)
21
+ ids.add(id);
22
+ if (read.next === undefined)
23
+ break;
24
+ page = read.next;
25
+ }
26
+ return ids;
27
+ }
28
+ // One read per model's own page: found, missing, or (no answer) left as it was.
29
+ async function modelPages(deps, probe, key, models) {
30
+ const found = new Map();
31
+ const model = probe.model;
32
+ if (model === undefined)
33
+ return found;
34
+ for (const id of models) {
35
+ const answer = await probeRead(deps.fetch, model.url(id), probe.headers(key), false);
36
+ if (answer === undefined)
37
+ continue;
38
+ if (answer.status >= 200 && answer.status < 300)
39
+ found.set(id, true);
40
+ else if (answer.status === 404)
41
+ found.set(id, false);
42
+ }
43
+ return found;
44
+ }
45
+ export async function listedFor(deps, provider) {
46
+ const { family, auth } = providerById(provider);
47
+ if (auth === "local")
48
+ return { kind: "skipped", reason: "no-list" };
49
+ const account = accountOf(deps.db, provider);
50
+ if (account === undefined)
51
+ return { kind: "skipped", reason: "no-key" };
52
+ let models;
53
+ if (auth === "cli") {
54
+ let ids;
55
+ try {
56
+ ids = await deps.cliModels(provider, family);
57
+ }
58
+ catch {
59
+ return { kind: "skipped", reason: "unreadable" };
60
+ }
61
+ models = new Map(ids.map((id) => [id, true]));
62
+ }
63
+ else {
64
+ const probe = Object.hasOwn(deps.probes, provider) ? deps.probes[provider] : undefined;
65
+ const key = keyWithShared(deps.db, provider);
66
+ if (key === undefined)
67
+ return { kind: "skipped", reason: "no-key" };
68
+ const known = deps.known(provider, family);
69
+ if (probe?.list !== undefined) {
70
+ const live = await liveList(deps, probe, key);
71
+ if (live === undefined)
72
+ return { kind: "skipped", reason: "unreadable" };
73
+ models = new Map(known.map((id) => [id, live.has(id)]));
74
+ }
75
+ else if (probe?.model !== undefined && probe.model.lists === undefined) {
76
+ models = await modelPages(deps, probe, key, known);
77
+ }
78
+ else
79
+ return { kind: "skipped", reason: "no-list" };
80
+ }
81
+ writeListed(deps.db, {
82
+ provider,
83
+ family,
84
+ account,
85
+ at: deps.clock.now().toISOString(),
86
+ models,
87
+ });
88
+ return {
89
+ kind: "listed",
90
+ listed: [...models.values()].filter(Boolean).length,
91
+ of: models.size,
92
+ };
93
+ }
94
+ // ceiling: a provider's list is read at most this often for the same key or login, however
95
+ // many pickers open; a key saved, a CLI added or Check models forces a new read.
96
+ export const listingFreshMs = 6 * 60 * 60_000;
97
+ export function createListing(deps) {
98
+ const last = new Map();
99
+ const running = new Map();
100
+ return {
101
+ refresh: (provider, force = false) => {
102
+ const inFlight = running.get(provider);
103
+ if (inFlight !== undefined)
104
+ return inFlight;
105
+ const account = accountOf(deps.db, provider);
106
+ const seen = last.get(provider);
107
+ const now = deps.clock.now().getTime();
108
+ if (!force &&
109
+ seen !== undefined &&
110
+ seen.account === account &&
111
+ now - seen.at < listingFreshMs)
112
+ return Promise.resolve(undefined);
113
+ const started = listedFor(deps, provider)
114
+ .then((outcome) => {
115
+ if (account !== undefined)
116
+ last.set(provider, { account, at: now });
117
+ return outcome;
118
+ })
119
+ .finally(() => running.delete(provider));
120
+ running.set(provider, started);
121
+ return started;
122
+ },
123
+ };
124
+ }
@@ -0,0 +1,20 @@
1
+ // What Slopify knows about one model for the person's own key or CLI login. Only the pickers
2
+ // read it: a run never asks, so no standing ever changes what a run does.
3
+ // works: a check call or a real run succeeded. listed: the key's or CLI's live list names it.
4
+ // known: only Slopify's model list (models.yaml) names it. no-access: the provider said the
5
+ // account can't use it.
6
+ export const modelStandings = ["works", "listed", "known", "no-access"];
7
+ export const checkTriggers = ["manual", "key-added", "cli-added", "weekly", "run"];
8
+ // failed: the check could not tell (rate limit, network, the provider down); it keeps
9
+ // whatever was known before.
10
+ export const checkVerdicts = ["works", "no-access", "failed"];
11
+ export const verdictSources = ["check", "run"];
12
+ // Settings → Models → Automatic checks. free: only checks that cost nothing or well under a
13
+ // tenth of a cent each; off: only Check models.
14
+ export const autoModes = ["free", "off"];
15
+ export const weeklyAtKey = "model-checks.weekly-at";
16
+ export const autoKey = "model-checks.auto";
17
+ export const baselineKey = "model-checks.baseline-done";
18
+ export function keyOfModel(key) {
19
+ return `${key.family}:${key.provider}:${key.model}`;
20
+ }
@@ -0,0 +1,32 @@
1
+ import { providerIds } from "../settings/model.js";
2
+ import { accountOf, writeVerdict } from "./repo.js";
3
+ function known(provider) {
4
+ return providerIds.includes(provider);
5
+ }
6
+ // Learn from real runs: a model a run used works, and one the provider refused as not on the
7
+ // plan is marked so, with the provider's sentence, the moment it happens. A call made without
8
+ // a key on file (none to tie the row to) teaches nothing.
9
+ export function accessRecorder(deps) {
10
+ const write = (use, verdict, sentence) => {
11
+ if (!known(use.provider))
12
+ return;
13
+ const account = accountOf(deps.db, use.provider);
14
+ if (account === undefined)
15
+ return;
16
+ writeVerdict(deps.db, {
17
+ provider: use.provider,
18
+ family: use.family,
19
+ model: use.model,
20
+ account,
21
+ verdict,
22
+ source: "run",
23
+ sentence,
24
+ at: deps.clock.now().toISOString(),
25
+ cost: 0,
26
+ });
27
+ };
28
+ return {
29
+ worked: (use) => write(use, "works", null),
30
+ denied: (use) => write(use, "no-access", use.sentence),
31
+ };
32
+ }
@@ -0,0 +1,120 @@
1
+ import { z } from "zod";
2
+ import { providerFamilies } from "../../kernel/ports/model.js";
3
+ import { isLocalCliProvider, providerById, sharedKeyOf, } from "../settings/model.js";
4
+ import { readSetting, writeSetting } from "../settings/repo.js";
5
+ import { autoKey, autoModes, checkVerdicts, verdictSources, } from "./model.js";
6
+ import { mergeVerdict } from "./rules.js";
7
+ const checkRow = z.object({
8
+ provider: z.string(),
9
+ family: z.enum(providerFamilies),
10
+ model: z.string(),
11
+ account: z.string(),
12
+ listed: z.number().nullable(),
13
+ listed_at: z.string().nullable(),
14
+ verdict: z.enum(checkVerdicts).nullable(),
15
+ verdict_source: z.enum(verdictSources).nullable(),
16
+ sentence: z.string().nullable(),
17
+ checked_at: z.string().nullable(),
18
+ cost: z.number(),
19
+ first_works_at: z.string().nullable(),
20
+ notice_seen: z.number(),
21
+ });
22
+ const generationRow = z.object({ credential_generation: z.string() });
23
+ function toRow(raw) {
24
+ const row = checkRow.parse(raw);
25
+ return {
26
+ provider: row.provider,
27
+ family: row.family,
28
+ model: row.model,
29
+ account: row.account,
30
+ listed: row.listed === null ? null : row.listed === 1,
31
+ listedAt: row.listed_at,
32
+ verdict: row.verdict,
33
+ verdictSource: row.verdict_source,
34
+ sentence: row.sentence,
35
+ checkedAt: row.checked_at,
36
+ cost: row.cost,
37
+ firstWorksAt: row.first_works_at,
38
+ noticeSeen: row.notice_seen === 1,
39
+ };
40
+ }
41
+ export function readChecks(db, provider) {
42
+ const rows = provider === undefined
43
+ ? db.prepare("SELECT * FROM model_checks ORDER BY provider, family, model").all()
44
+ : db
45
+ .prepare("SELECT * FROM model_checks WHERE provider = ? ORDER BY family, model")
46
+ .all(provider);
47
+ return rows.map(toRow);
48
+ }
49
+ function readOne(db, key) {
50
+ const raw = db
51
+ .prepare("SELECT * FROM model_checks WHERE provider = ? AND family = ? AND model = ?")
52
+ .get(key.provider, key.family, key.model);
53
+ return raw === undefined ? undefined : toRow(raw);
54
+ }
55
+ // A row of another key or login is started over before anything is written to it.
56
+ function ensure(db, key, account) {
57
+ const found = readOne(db, key);
58
+ if (found !== undefined && found.account === account)
59
+ return found;
60
+ db.prepare("INSERT INTO model_checks (provider, family, model, account) VALUES (?, ?, ?, ?)" +
61
+ " ON CONFLICT(provider, family, model) DO UPDATE SET account = excluded.account," +
62
+ " listed = NULL, listed_at = NULL, verdict = NULL, verdict_source = NULL, sentence = NULL," +
63
+ " checked_at = NULL, cost = 0, first_works_at = NULL, notice_seen = 0").run(key.provider, key.family, key.model, account);
64
+ return undefined;
65
+ }
66
+ export function writeListed(db, write) {
67
+ const update = db.prepare("UPDATE model_checks SET listed = ?, listed_at = ? WHERE provider = ? AND family = ? AND model = ?");
68
+ for (const [model, listed] of write.models) {
69
+ ensure(db, { provider: write.provider, family: write.family, model }, write.account);
70
+ update.run(listed ? 1 : 0, write.at, write.provider, write.family, model);
71
+ }
72
+ }
73
+ export function writeVerdict(db, write) {
74
+ const previous = ensure(db, write, write.account);
75
+ const merged = mergeVerdict(previous, write);
76
+ const firstWorks = previous?.firstWorksAt ?? (merged.verdict === "works" ? merged.checkedAt : null);
77
+ // A model a run already used is no news to the person who chose it.
78
+ const seen = (previous?.noticeSeen ?? false) ||
79
+ (previous?.firstWorksAt == null && firstWorks !== null && write.source === "run");
80
+ db.prepare("UPDATE model_checks SET verdict = ?, verdict_source = ?, sentence = ?, checked_at = ?," +
81
+ " cost = cost + ?, first_works_at = ?, notice_seen = ?" +
82
+ " WHERE provider = ? AND family = ? AND model = ?").run(merged.verdict, merged.verdictSource, merged.sentence, merged.checkedAt, write.cost, firstWorks, seen ? 1 : 0, write.provider, write.family, write.model);
83
+ const stored = readOne(db, write);
84
+ if (stored === undefined)
85
+ throw new Error("model_checks row vanished during its write");
86
+ return stored;
87
+ }
88
+ export function markNoticesSeen(db, keys) {
89
+ if (keys === "all") {
90
+ db.prepare("UPDATE model_checks SET notice_seen = 1 WHERE first_works_at IS NOT NULL").run();
91
+ return;
92
+ }
93
+ const update = db.prepare("UPDATE model_checks SET notice_seen = 1 WHERE provider = ? AND family = ? AND model = ?");
94
+ for (const key of keys)
95
+ update.run(key.provider, key.family, key.model);
96
+ }
97
+ // Whose models a row describes. A keyed provider's is its key's generation (a new key starts
98
+ // over), the shared key's for Gemini speech without its own; a CLI's and the system voice's is
99
+ // the provider itself, since the CLI's login is not Slopify's to tell apart. Undefined: no key.
100
+ export function accountOf(db, provider) {
101
+ if (isLocalCliProvider(provider) || providerById(provider).auth !== "key")
102
+ return provider;
103
+ const generation = (id) => {
104
+ const raw = db
105
+ .prepare("SELECT credential_generation FROM provider_keys WHERE provider = ?")
106
+ .get(id);
107
+ return raw === undefined
108
+ ? undefined
109
+ : `${id}:${generationRow.parse(raw).credential_generation}`;
110
+ };
111
+ const shared = sharedKeyOf[provider];
112
+ return generation(provider) ?? (shared === undefined ? undefined : generation(shared));
113
+ }
114
+ export function readAuto(db) {
115
+ const value = readSetting(db, autoKey);
116
+ return autoModes.find((mode) => mode === value) ?? "free";
117
+ }
118
+ export function writeAuto(db, mode) {
119
+ writeSetting(db, autoKey, mode);
120
+ }
@@ -0,0 +1,125 @@
1
+ import { modelStandings, } from "./model.js";
2
+ // The newer of two decisive answers wins, whichever it is; an answer older than the stored one
3
+ // (a slow call finishing late) changes nothing; "failed" tells nothing about access, so it
4
+ // never replaces a decisive one.
5
+ export function mergeVerdict(previous, next) {
6
+ const decisive = previous?.verdict === "works" || previous?.verdict === "no-access";
7
+ if (previous?.checkedAt != null && Date.parse(next.at) < Date.parse(previous.checkedAt))
8
+ return previous;
9
+ if (next.verdict === "failed" && decisive && previous !== undefined)
10
+ return previous;
11
+ return {
12
+ verdict: next.verdict,
13
+ verdictSource: next.source,
14
+ sentence: next.sentence,
15
+ checkedAt: next.at,
16
+ };
17
+ }
18
+ // A row learned with another key or login says nothing about this one.
19
+ export function standingOf(row, account, retiring) {
20
+ const extra = retiring === undefined ? {} : { retiring };
21
+ if (row === undefined || account === undefined || row.account !== account)
22
+ return { standing: "known", checkedAt: null, sentence: null, source: null, ...extra };
23
+ const standing = row.verdict === "works" || row.verdict === "no-access"
24
+ ? row.verdict
25
+ : row.listed === true
26
+ ? "listed"
27
+ : "known";
28
+ return {
29
+ standing,
30
+ checkedAt: row.checkedAt,
31
+ sentence: row.sentence,
32
+ source: row.verdict === null ? null : row.verdictSource,
33
+ ...extra,
34
+ };
35
+ }
36
+ const order = (view) => modelStandings.indexOf(view?.standing ?? "known");
37
+ // The picker's list. By default only models that work for you, plus the one already chosen
38
+ // (never hidden, whatever its standing); before anything works, the listed models, then the
39
+ // known ones, so a picker is never empty before the first check.
40
+ export function pickerEntries(models, standings, selected, showAll) {
41
+ const viewOf = (model) => standings !== undefined && Object.hasOwn(standings, model.id) ? standings[model.id] : undefined;
42
+ const all = models
43
+ .map((model, index) => ({ model, view: viewOf(model), index }))
44
+ .sort((a, b) => order(a.view) - order(b.view) || a.index - b.index);
45
+ const has = (standing) => all.some((entry) => (entry.view?.standing ?? "known") === standing);
46
+ const shown = has("works") ? "works" : has("listed") ? "listed" : "known";
47
+ const keep = all.filter((entry) => showAll || entry.model.id === selected || (entry.view?.standing ?? "known") === shown);
48
+ return {
49
+ entries: keep.map(({ model, view }) => ({ model, view })),
50
+ hidden: all.length - keep.length,
51
+ fallback: shown !== "works",
52
+ };
53
+ }
54
+ const monthNames = [
55
+ "Jan",
56
+ "Feb",
57
+ "Mar",
58
+ "Apr",
59
+ "May",
60
+ "Jun",
61
+ "Jul",
62
+ "Aug",
63
+ "Sep",
64
+ "Oct",
65
+ "Nov",
66
+ "Dec",
67
+ ];
68
+ export function ago(at, now) {
69
+ const minutes = Math.max(0, Math.round((now.getTime() - Date.parse(at)) / 60_000));
70
+ if (minutes < 1)
71
+ return "just now";
72
+ if (minutes < 60)
73
+ return `${String(minutes)} min ago`;
74
+ const hours = Math.round(minutes / 60);
75
+ if (hours < 24)
76
+ return `${String(hours)} h ago`;
77
+ const days = Math.round(hours / 24);
78
+ return days === 1 ? "yesterday" : `${String(days)} days ago`;
79
+ }
80
+ function shortDate(day) {
81
+ const [, month, date] = day.split("-");
82
+ const name = monthNames[Number(month) - 1];
83
+ return name === undefined ? day : `${String(Number(date))} ${name}`;
84
+ }
85
+ // The badge beside a model. Retiring outranks the rest: it is the one that needs doing.
86
+ export function badgeOf(view, now) {
87
+ if (view?.retiring !== undefined)
88
+ return `⚠ retiring ${shortDate(view.retiring.on)}${view.retiring.replacement === null ? "" : ` → ${view.retiring.replacement.name}`}`;
89
+ if (view === undefined)
90
+ return "? not checked";
91
+ if (view.standing === "works")
92
+ return view.checkedAt === null
93
+ ? "✓ works"
94
+ : `✓ works · ${view.source === "run" ? "used" : "checked"} ${ago(view.checkedAt, now)}`;
95
+ if (view.standing === "no-access")
96
+ return "✕ not on your plan";
97
+ return view.standing === "listed" ? "? listed for you · not checked" : "? not checked";
98
+ }
99
+ // Models that started working for you since you last looked: Settings → Models' New for you.
100
+ export function newForYou(rows) {
101
+ return rows
102
+ .filter((row) => row.verdict === "works" && row.firstWorksAt !== null && !row.noticeSeen)
103
+ .sort((a, b) => (b.firstWorksAt ?? "").localeCompare(a.firstWorksAt ?? ""));
104
+ }
105
+ // A check estimate in words: "free", "less than a cent", "about $0.04".
106
+ export function dollarsText(dollars, unpriced = 0) {
107
+ const base = dollars <= 0 ? "free" : dollars < 0.01 ? "less than a cent" : `about $${dollars.toFixed(2)}`;
108
+ if (unpriced === 0)
109
+ return base;
110
+ const more = `${String(unpriced)} model${unpriced === 1 ? "" : "s"} with no listed price`;
111
+ return dollars <= 0 ? `${more} (price unknown)` : `${base}, plus ${more}`;
112
+ }
113
+ // The same estimate as the end of a sentence, "this costs …": "about $0.04 at list price",
114
+ // and for models with no listed price "an unknown amount: 3 models have no listed price".
115
+ export function costPhrase(dollars, unpriced = 0) {
116
+ const base = dollars <= 0
117
+ ? "nothing"
118
+ : `${dollars < 0.01 ? "less than a cent" : `about $${dollars.toFixed(2)}`} at list price`;
119
+ if (unpriced === 0)
120
+ return base;
121
+ const models = `${String(unpriced)} model${unpriced === 1 ? "" : "s"}`;
122
+ return dollars <= 0
123
+ ? `an unknown amount: ${models} ${unpriced === 1 ? "has" : "have"} no listed price`
124
+ : `${base}, plus an unknown amount for ${models} with no listed price`;
125
+ }
@@ -0,0 +1,143 @@
1
+ import { errorText } from "../../kernel/errors.js";
2
+ import { isLocalCliProvider, providerIds } from "../settings/model.js";
3
+ import { readSetting, writeSetting } from "../settings/repo.js";
4
+ import { automaticAllowed, costOf, isVideoTarget } from "./cost.js";
5
+ import { baselineKey, keyOfModel, } from "./model.js";
6
+ import { markNoticesSeen, writeVerdict } from "./repo.js";
7
+ // ceiling: an automatic run checks at most this many models of one provider (decision 7).
8
+ export const automaticPerProvider = 25;
9
+ const isProvider = (id) => providerIds.includes(id);
10
+ export function planChecks(input, scope) {
11
+ const manual = scope.trigger === "manual";
12
+ const rowOf = new Map(input.rows.map((row) => [keyOfModel(row), row]));
13
+ const skipped = [];
14
+ const picked = [];
15
+ const perProvider = new Map();
16
+ const offered = input.offered.filter((model) => (scope.provider === undefined || model.provider === scope.provider) &&
17
+ (scope.models === undefined || scope.models.includes(model.model)));
18
+ // Automatic runs take what is in use first, then what already worked, then what is new.
19
+ const rank = (model) => {
20
+ const row = rowOf.get(keyOfModel(model));
21
+ if (input.chosen.has(keyOfModel(model)))
22
+ return 0;
23
+ return row?.verdict === "works" ? 1 : 2;
24
+ };
25
+ const ordered = manual ? offered : [...offered].sort((a, b) => rank(a) - rank(b));
26
+ for (const model of ordered) {
27
+ if (!isProvider(model.provider))
28
+ continue;
29
+ const account = input.accounts(model.provider);
30
+ // An automatic run says nothing about what it was never going to check.
31
+ if (account === undefined) {
32
+ if (manual)
33
+ skipped.push({ model, reason: "No key is saved for this provider." });
34
+ continue;
35
+ }
36
+ if (isVideoTarget(input.catalogue, model)) {
37
+ if (manual)
38
+ skipped.push({ model, reason: "Image-to-video models are listed, never checked." });
39
+ continue;
40
+ }
41
+ const cost = costOf(input.catalogue, model);
42
+ if (!manual) {
43
+ const row = rowOf.get(keyOfModel(model));
44
+ const sameAccount = row !== undefined && row.account === account;
45
+ const inScope = input.chosen.has(keyOfModel(model)) ||
46
+ (sameAccount && row.verdict === "works") ||
47
+ (sameAccount && row.listed === true && row.verdict === null);
48
+ if (!inScope)
49
+ continue;
50
+ if (!automaticAllowed(input.catalogue, model, cost)) {
51
+ skipped.push({ model, reason: "It costs money, so only Check models runs it." });
52
+ continue;
53
+ }
54
+ const count = perProvider.get(model.provider) ?? 0;
55
+ if (count >= automaticPerProvider)
56
+ continue;
57
+ perProvider.set(model.provider, count + 1);
58
+ }
59
+ picked.push({ ...model, account, cost });
60
+ }
61
+ return {
62
+ trigger: scope.trigger,
63
+ targets: picked,
64
+ totalDollars: picked.reduce((sum, target) => sum + (target.cost.dollars ?? 0), 0),
65
+ unpriced: picked.filter((target) => target.cost.dollars === null).length,
66
+ planAccounts: [
67
+ ...new Set(picked.filter((t) => isLocalCliProvider(t.provider)).map((t) => t.provider)),
68
+ ],
69
+ skipped,
70
+ };
71
+ }
72
+ // A plan that costs money runs only with the person's confirmed estimate: the dollars they
73
+ // were shown must cover the plan's.
74
+ export function needsConfirmation(plan, confirmedDollars) {
75
+ if (plan.totalDollars <= 0 && plan.unpriced === 0)
76
+ return false;
77
+ return confirmedDollars === undefined || confirmedDollars + 1e-9 < plan.totalDollars;
78
+ }
79
+ export async function runChecks(deps, plan, options = {}) {
80
+ if (plan.trigger === "manual" && needsConfirmation(plan, options.confirmedDollars))
81
+ return {
82
+ ok: false,
83
+ reason: "estimate",
84
+ totalDollars: plan.totalDollars,
85
+ unpriced: plan.unpriced,
86
+ };
87
+ const signal = options.signal ?? new AbortController().signal;
88
+ const tally = { checked: 0, works: 0, noAccess: 0, failed: 0, skipped: 0, spent: 0 };
89
+ const byProvider = new Map();
90
+ for (const target of plan.targets)
91
+ byProvider.set(target.provider, [...(byProvider.get(target.provider) ?? []), target]);
92
+ await Promise.all([...byProvider.values()].map(async (targets) => {
93
+ for (const target of targets) {
94
+ if (signal.aborted)
95
+ return;
96
+ let outcome;
97
+ try {
98
+ outcome = await deps.check(target, signal);
99
+ }
100
+ catch (error) {
101
+ if (signal.aborted)
102
+ return;
103
+ deps.log.write("warn", "model-checks.check", {
104
+ detail: `${target.provider} ${target.model}: ${errorText(error)}`,
105
+ });
106
+ tally.failed += 1;
107
+ continue;
108
+ }
109
+ if (outcome.kind === "skipped") {
110
+ tally.skipped += 1;
111
+ continue;
112
+ }
113
+ tally.checked += 1;
114
+ tally.spent += outcome.spent;
115
+ if (outcome.verdict === "works")
116
+ tally.works += 1;
117
+ else if (outcome.verdict === "no-access")
118
+ tally.noAccess += 1;
119
+ else
120
+ tally.failed += 1;
121
+ writeVerdict(deps.db, {
122
+ ...target,
123
+ verdict: outcome.verdict,
124
+ source: "check",
125
+ sentence: outcome.sentence,
126
+ at: deps.clock.now().toISOString(),
127
+ cost: outcome.spent,
128
+ });
129
+ // The rest of this provider's checks would fail the same way (a rejected key, a
130
+ // used-up limit), and each might cost something.
131
+ if (outcome.stopProvider) {
132
+ tally.skipped += targets.length - targets.indexOf(target) - 1;
133
+ return;
134
+ }
135
+ }
136
+ }));
137
+ // What works at the first run is what the person already had: none of it is news.
138
+ if (readSetting(deps.db, baselineKey) === undefined) {
139
+ markNoticesSeen(deps.db, "all");
140
+ writeSetting(deps.db, baselineKey, deps.clock.now().toISOString());
141
+ }
142
+ return { ok: true, summary: tally };
143
+ }