@gentbajko/slopify 3.25.8 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (604) hide show
  1. package/dist/adapters/account-limits.js +30 -0
  2. package/dist/adapters/alignment/cache.js +3 -5
  3. package/dist/adapters/alignment/ctc.js +1 -2
  4. package/dist/adapters/alignment/lock.js +4 -3
  5. package/dist/adapters/alignment/multilingual.js +0 -3
  6. package/dist/adapters/alignment/prefetch.js +2 -1
  7. package/dist/adapters/alignment/worker.js +2 -1
  8. package/dist/adapters/explain.js +0 -3
  9. package/dist/adapters/ffmpeg.js +19 -1
  10. package/dist/adapters/host-cli/index.js +19 -3
  11. package/dist/adapters/http-failure.js +53 -0
  12. package/dist/adapters/image/codex.js +36 -4
  13. package/dist/adapters/image/fal.js +17 -50
  14. package/dist/adapters/image/google.js +31 -55
  15. package/dist/adapters/image/openai.js +20 -47
  16. package/dist/adapters/image/reference.js +36 -0
  17. package/dist/adapters/image/replicate.js +18 -47
  18. package/dist/adapters/interest/search-interest.js +113 -0
  19. package/dist/adapters/key-probes.js +53 -2
  20. package/dist/adapters/llm/antigravity.js +4 -0
  21. package/dist/adapters/llm/assistant-cli.js +388 -0
  22. package/dist/adapters/llm/child-session.js +83 -0
  23. package/dist/adapters/llm/claude-code-models.js +51 -98
  24. package/dist/adapters/llm/claude-code.js +13 -2
  25. package/dist/adapters/llm/codex-limits.js +27 -68
  26. package/dist/adapters/llm/codex-server-models.js +61 -109
  27. package/dist/adapters/llm/codex.js +12 -1
  28. package/dist/adapters/llm/openrouter.js +10 -47
  29. package/dist/adapters/llm/run-cli.js +6 -22
  30. package/dist/adapters/llm/sse-lines.js +5 -1
  31. package/dist/adapters/model-access.js +37 -0
  32. package/dist/adapters/tts/cartesia.js +15 -46
  33. package/dist/adapters/tts/elevenlabs.js +20 -52
  34. package/dist/adapters/tts/gemini.js +19 -40
  35. package/dist/adapters/tts/inworld-async.js +4 -1
  36. package/dist/adapters/tts/inworld.js +3 -0
  37. package/dist/adapters/tts/openai.js +20 -50
  38. package/dist/assets/models-lifecycle.yaml +6 -0
  39. package/dist/assistant-wiring.js +60 -0
  40. package/dist/autopilot-timers.js +59 -0
  41. package/dist/catalog/lifecycle.js +52 -0
  42. package/dist/catalog/propose.js +204 -0
  43. package/dist/catalog/registry.js +3 -1
  44. package/dist/catalog/schema.js +2 -0
  45. package/dist/catalog/store.js +39 -0
  46. package/dist/edge/autostart/native.js +3 -5
  47. package/dist/edge/autostart/prompt.js +2 -1
  48. package/dist/edge/autostart/service.js +3 -4
  49. package/dist/edge/cli-args.js +3 -4
  50. package/dist/edge/cli.js +2 -1
  51. package/dist/edge/docker-install/apply.js +11 -11
  52. package/dist/edge/docker-install/run.js +2 -1
  53. package/dist/edge/docker-install/state.js +4 -3
  54. package/dist/edge/docker-install/tree.js +2 -4
  55. package/dist/edge/docker-install/volume.js +3 -2
  56. package/dist/edge/docker.js +5 -4
  57. package/dist/edge/events/hub.js +5 -5
  58. package/dist/edge/http/actions.js +4 -25
  59. package/dist/edge/http/actor.js +5 -3
  60. package/dist/edge/http/app.js +85 -10
  61. package/dist/edge/http/assistant.js +127 -0
  62. package/dist/edge/http/audio-preview.js +4 -11
  63. package/dist/edge/http/auditions.js +1 -1
  64. package/dist/edge/http/autostart.js +5 -5
  65. package/dist/edge/http/backups.js +1 -3
  66. package/dist/edge/http/channel-memory.js +7 -20
  67. package/dist/edge/http/channels.js +13 -26
  68. package/dist/edge/http/checkpoints.js +33 -17
  69. package/dist/edge/http/design-templates.js +167 -0
  70. package/dist/edge/http/designs.js +178 -0
  71. package/dist/edge/http/diagnostics.js +5 -1
  72. package/dist/edge/http/document-themes.js +2 -11
  73. package/dist/edge/http/draft-files.js +1 -3
  74. package/dist/edge/http/draft-problem.js +4 -9
  75. package/dist/edge/http/drafts.js +3 -3
  76. package/dist/edge/http/entries.js +6 -9
  77. package/dist/edge/http/extension-auth.js +27 -0
  78. package/dist/edge/http/external-reviews.js +12 -11
  79. package/dist/edge/http/files.js +5 -11
  80. package/dist/edge/http/folder-location.js +1 -4
  81. package/dist/edge/http/fonts.js +0 -7
  82. package/dist/edge/http/home.js +38 -13
  83. package/dist/edge/http/host-cli.js +18 -8
  84. package/dist/edge/http/ids.js +8 -0
  85. package/dist/edge/http/keywords.js +35 -0
  86. package/dist/edge/http/model-checks.js +132 -0
  87. package/dist/edge/http/narration-peaks.js +5 -13
  88. package/dist/edge/http/onboarding.js +1 -14
  89. package/dist/edge/http/open-folder.js +5 -10
  90. package/dist/edge/http/outputs.js +4 -12
  91. package/dist/edge/http/patch-notes.js +4 -10
  92. package/dist/edge/http/planning.js +2 -6
  93. package/dist/edge/http/problem.js +2 -4
  94. package/dist/edge/http/project-create.js +1 -0
  95. package/dist/edge/http/project-templates.js +29 -17
  96. package/dist/edge/http/projects.js +2 -12
  97. package/dist/edge/http/prompts.js +7 -14
  98. package/dist/edge/http/pronunciations.js +1 -2
  99. package/dist/edge/http/providers.js +39 -24
  100. package/dist/edge/http/refusal.js +29 -0
  101. package/dist/edge/http/remote-access.js +40 -0
  102. package/dist/edge/http/remote-guard.js +71 -0
  103. package/dist/edge/http/request-guard.js +91 -0
  104. package/dist/edge/http/reviews.js +11 -24
  105. package/dist/edge/http/revision-files.js +5 -10
  106. package/dist/edge/http/revisions.js +10 -15
  107. package/dist/edge/http/run-cost.js +1 -2
  108. package/dist/edge/http/same-page.js +80 -0
  109. package/dist/edge/http/schedules.js +59 -32
  110. package/dist/edge/http/settings.js +22 -13
  111. package/dist/edge/http/social.js +282 -0
  112. package/dist/edge/http/staging.js +2 -4
  113. package/dist/edge/http/storage-files.js +5 -7
  114. package/dist/edge/http/storage.js +23 -23
  115. package/dist/edge/http/studio.js +23 -50
  116. package/dist/edge/http/style-preview.js +1 -4
  117. package/dist/edge/http/subtitles.js +3 -17
  118. package/dist/edge/http/template-studio.js +433 -0
  119. package/dist/edge/http/trash.js +10 -11
  120. package/dist/edge/http/tutorial.js +1 -2
  121. package/dist/edge/http/tutorials.js +5 -11
  122. package/dist/edge/http/update.js +7 -13
  123. package/dist/edge/http/waveform.js +3 -6
  124. package/dist/edge/http/youtube-edits.js +2 -3
  125. package/dist/edge/mcp/assistant.js +279 -0
  126. package/dist/edge/mcp/attribution.js +8 -2
  127. package/dist/edge/mcp/call-route.js +11 -2
  128. package/dist/edge/mcp/excluded-routes.js +25 -0
  129. package/dist/edge/mcp/http.js +11 -2
  130. package/dist/edge/mcp/instructions.js +5 -3
  131. package/dist/edge/mcp/library-tools.js +1 -0
  132. package/dist/edge/mcp/route-tool.js +35 -15
  133. package/dist/edge/mcp/server.js +13 -8
  134. package/dist/edge/mcp/tools-channels.js +2 -0
  135. package/dist/edge/mcp/tools-designs.js +165 -0
  136. package/dist/edge/mcp/tools-library.js +14 -2
  137. package/dist/edge/mcp/tools-planning.js +23 -4
  138. package/dist/edge/mcp/tools-projects.js +28 -1
  139. package/dist/edge/mcp/tools-runs.js +14 -1
  140. package/dist/edge/mcp/tools-schedules.js +28 -4
  141. package/dist/edge/mcp/tools-setup.js +61 -3
  142. package/dist/edge/mcp/tools-social.js +46 -0
  143. package/dist/edge/mcp/tools-storage.js +8 -0
  144. package/dist/edge/mcp/tools-template-studio.js +166 -0
  145. package/dist/edge/mcp/tools.js +6 -0
  146. package/dist/edge/remote-access/cli.js +71 -0
  147. package/dist/edge/remote-access/model.js +32 -0
  148. package/dist/edge/remote-access/rules.js +207 -0
  149. package/dist/edge/remote-access/service.js +203 -0
  150. package/dist/edge/remote-access/store.js +44 -0
  151. package/dist/edge/remote-access/views.js +43 -0
  152. package/dist/extension/slopify-studio-chrome.zip +0 -0
  153. package/dist/extension/slopify-studio-firefox.zip +0 -0
  154. package/dist/host-cli/install.js +2 -2
  155. package/dist/host-cli/open-folder.js +2 -2
  156. package/dist/host-cli/paths.js +2 -4
  157. package/dist/host-cli/server.js +4 -4
  158. package/dist/host-cli/service.js +3 -3
  159. package/dist/host-cli/status.js +7 -7
  160. package/dist/kernel/db/migrations/0063-model-checks.sql +20 -0
  161. package/dist/kernel/db/migrations/0064-template-studio.sql +46 -0
  162. package/dist/kernel/db/migrations/0065-promise-and-assistant-reviews.sql +58 -0
  163. package/dist/kernel/db/migrations/0066-assistant.sql +44 -0
  164. package/dist/kernel/db/migrations/0067-autopilot.sql +6 -0
  165. package/dist/kernel/db/migrations/0068-topic-scores.sql +17 -0
  166. package/dist/kernel/db/migrations/0070-designs.sql +53 -0
  167. package/dist/kernel/db/migrations/0071-design-usage.sql +33 -0
  168. package/dist/kernel/db/migrations/0072-design-templates.sql +33 -0
  169. package/dist/kernel/db/migrations/0073-schedule-design-kind.sql +5 -0
  170. package/dist/kernel/db/migrations/0074-template-bakeoffs.sql +44 -0
  171. package/dist/kernel/db/migrations/0075-social-posts.sql +25 -0
  172. package/dist/kernel/db/migrations/0076-channel-social.sql +5 -0
  173. package/dist/kernel/db/migrations/0077-post-prompts.sql +17 -0
  174. package/dist/kernel/db/tx.js +2 -4
  175. package/dist/kernel/errors.js +27 -1
  176. package/dist/kernel/json-answer.js +15 -0
  177. package/dist/kernel/lock.js +4 -9
  178. package/dist/kernel/paths.js +8 -0
  179. package/dist/kernel/ports/assistant-cli.js +4 -0
  180. package/dist/kernel/ports/host-cli.js +3 -0
  181. package/dist/kernel/ports/interest.js +4 -0
  182. package/dist/kernel/ports/languages.js +0 -3
  183. package/dist/kernel/ports/model.js +2 -0
  184. package/dist/kernel/runner/attempt.js +3 -1
  185. package/dist/kernel/runner/audition.js +3 -1
  186. package/dist/kernel/runner/checkpoint-authority.js +2 -1
  187. package/dist/kernel/runner/index.js +8 -10
  188. package/dist/kernel/runner/model-access.js +40 -0
  189. package/dist/kernel/runner/model-check.js +153 -0
  190. package/dist/kernel/runner/providers.js +15 -10
  191. package/dist/kernel/runner/standalone.js +9 -5
  192. package/dist/main.js +334 -19
  193. package/dist/model-checks.js +166 -0
  194. package/dist/patch-notes/3.25.9.md +5 -0
  195. package/dist/patch-notes/4.0.0.md +51 -0
  196. package/dist/patch-notes/index.json +12 -0
  197. package/dist/slices/admission/heads.js +2 -1
  198. package/dist/slices/admission/repo.js +2 -1
  199. package/dist/slices/admission/rules.js +43 -8
  200. package/dist/slices/admission/schema.js +8 -0
  201. package/dist/slices/admission/stage-writes.js +0 -3
  202. package/dist/slices/admission/start.js +48 -31
  203. package/dist/slices/admission/substitute.js +7 -4
  204. package/dist/slices/agents/attention-posts.js +31 -0
  205. package/dist/slices/agents/attention-templates.js +33 -0
  206. package/dist/slices/agents/attention.js +10 -1
  207. package/dist/slices/agents/connection.js +2 -6
  208. package/dist/slices/agents/model.js +6 -3
  209. package/dist/slices/agents/repo.js +15 -7
  210. package/dist/slices/article/continuation.js +0 -63
  211. package/dist/slices/article/plain.js +3 -2
  212. package/dist/slices/article/segments.js +2 -62
  213. package/dist/slices/assistant/access.js +1 -0
  214. package/dist/slices/assistant/asks.js +80 -0
  215. package/dist/slices/assistant/cost.js +54 -0
  216. package/dist/slices/assistant/gate.js +222 -0
  217. package/dist/slices/assistant/model.js +79 -0
  218. package/dist/slices/assistant/names.js +23 -0
  219. package/dist/slices/assistant/prompt.js +74 -0
  220. package/dist/slices/assistant/repo.js +74 -0
  221. package/dist/slices/assistant/tokens.js +18 -0
  222. package/dist/slices/assistant/turn.js +342 -0
  223. package/dist/slices/autopilot/buffer.js +76 -0
  224. package/dist/slices/autopilot/model.js +14 -0
  225. package/dist/slices/autopilot/notify.js +27 -0
  226. package/dist/slices/autopilot/repo.js +34 -0
  227. package/dist/slices/autopilot/signals.js +194 -0
  228. package/dist/slices/autopilot/warnings.js +85 -0
  229. package/dist/slices/autopilot/weekly.js +100 -0
  230. package/dist/slices/backups/files.js +2 -6
  231. package/dist/slices/backups/repo.js +3 -15
  232. package/dist/slices/backups/service.js +4 -3
  233. package/dist/slices/channels/cast-images.js +2 -1
  234. package/dist/slices/channels/model.js +0 -1
  235. package/dist/slices/channels/rebrand.js +0 -3
  236. package/dist/slices/channels/repo.js +4 -0
  237. package/dist/slices/channels/schema.js +6 -0
  238. package/dist/slices/channels/service.js +10 -0
  239. package/dist/slices/channels/videos.js +2 -14
  240. package/dist/slices/checkpoints/index.js +0 -1
  241. package/dist/slices/control/providers.js +0 -26
  242. package/dist/slices/designs/artwork.js +66 -0
  243. package/dist/slices/designs/budget.js +23 -0
  244. package/dist/slices/designs/calls.js +124 -0
  245. package/dist/slices/designs/check-context.js +38 -0
  246. package/dist/slices/designs/check-text.js +43 -0
  247. package/dist/slices/designs/fields.js +88 -0
  248. package/dist/slices/designs/frames.js +34 -0
  249. package/dist/slices/designs/gate.js +128 -0
  250. package/dist/slices/designs/layout.js +293 -0
  251. package/dist/slices/designs/model.js +35 -0
  252. package/dist/slices/designs/pdf-job.js +23 -0
  253. package/dist/slices/designs/pdf-pages.js +30 -0
  254. package/dist/slices/designs/pdf-runner.js +68 -0
  255. package/dist/slices/designs/pdf-worker.js +24 -0
  256. package/dist/slices/designs/picture-size.js +79 -0
  257. package/dist/slices/designs/pictures.js +55 -0
  258. package/dist/slices/designs/quote.js +24 -0
  259. package/dist/slices/designs/render-args.js +36 -0
  260. package/dist/slices/designs/render-input.js +59 -0
  261. package/dist/slices/designs/render.js +215 -0
  262. package/dist/slices/designs/repo.js +101 -0
  263. package/dist/slices/designs/requests.js +80 -0
  264. package/dist/slices/designs/runner.js +239 -0
  265. package/dist/slices/designs/sample-repo.js +91 -0
  266. package/dist/slices/designs/sample.js +222 -0
  267. package/dist/slices/designs/schema.js +87 -0
  268. package/dist/slices/designs/sections.js +44 -0
  269. package/dist/slices/designs/service.js +233 -0
  270. package/dist/slices/designs/steps-repo.js +102 -0
  271. package/dist/slices/designs/studio-kind.js +39 -0
  272. package/dist/slices/designs/template-checks.js +44 -0
  273. package/dist/slices/designs/template-make.js +118 -0
  274. package/dist/slices/designs/templates.js +218 -0
  275. package/dist/slices/document/fonts.js +2 -1
  276. package/dist/slices/episodes/summarize.js +5 -1
  277. package/dist/slices/estimate/index.js +45 -7
  278. package/dist/slices/fonts/catalog.js +3 -2
  279. package/dist/slices/fonts/discovery.js +3 -2
  280. package/dist/slices/fonts/files.js +2 -6
  281. package/dist/slices/fonts/index.js +0 -1
  282. package/dist/slices/fonts/sfnt.js +45 -10
  283. package/dist/slices/images/appearance.js +6 -15
  284. package/dist/slices/images/scenes.js +100 -27
  285. package/dist/slices/keywords/digest.js +84 -0
  286. package/dist/slices/keywords/facts.js +92 -0
  287. package/dist/slices/keywords/fill.js +80 -0
  288. package/dist/slices/keywords/model.js +75 -0
  289. package/dist/slices/keywords/rules.js +187 -0
  290. package/dist/slices/keywords/schema.js +28 -0
  291. package/dist/slices/keywords/specs.js +260 -0
  292. package/dist/slices/keywords/values.js +74 -0
  293. package/dist/slices/library/lint.js +29 -12
  294. package/dist/slices/library/model.js +2 -0
  295. package/dist/slices/library/photorealistic.js +4 -12
  296. package/dist/slices/library/rename.js +9 -7
  297. package/dist/slices/library/save.js +14 -10
  298. package/dist/slices/library/slots.js +37 -11
  299. package/dist/slices/library/transfer.js +2 -1
  300. package/dist/slices/library/used-by.js +25 -10
  301. package/dist/slices/loudness/loudnorm.js +51 -11
  302. package/dist/slices/model-checks/cost.js +61 -0
  303. package/dist/slices/model-checks/listing.js +124 -0
  304. package/dist/slices/model-checks/model.js +20 -0
  305. package/dist/slices/model-checks/record.js +32 -0
  306. package/dist/slices/model-checks/repo.js +120 -0
  307. package/dist/slices/model-checks/rules.js +112 -0
  308. package/dist/slices/model-checks/run.js +143 -0
  309. package/dist/slices/model-checks/service.js +198 -0
  310. package/dist/slices/model-upkeep/switch.js +70 -25
  311. package/dist/slices/model-upkeep/usage.js +26 -11
  312. package/dist/slices/narration/breaths.js +2 -1
  313. package/dist/slices/narration/clean-pieces.js +2 -1
  314. package/dist/slices/narration/concat.js +2 -1
  315. package/dist/slices/narration/peaks.js +3 -4
  316. package/dist/slices/narration/pronunciation-chunks.js +4 -3
  317. package/dist/slices/narration/pronunciation.js +3 -2
  318. package/dist/slices/narration/steering.js +4 -3
  319. package/dist/slices/notifications/notifier.js +4 -3
  320. package/dist/slices/notifications/rules.js +5 -2
  321. package/dist/slices/notifications/settings.js +4 -12
  322. package/dist/slices/notifications/subject.js +2 -1
  323. package/dist/slices/onboarding/copy.js +2 -1
  324. package/dist/slices/onboarding/first-run.js +3 -2
  325. package/dist/slices/onboarding/full-video.js +3 -2
  326. package/dist/slices/onboarding/install.js +4 -3
  327. package/dist/slices/onboarding/model.js +0 -16
  328. package/dist/slices/onboarding/packs.js +22 -22
  329. package/dist/slices/onboarding/quick-short.js +2 -1
  330. package/dist/slices/onboarding/sample.js +0 -1
  331. package/dist/slices/onboarding/state.js +2 -11
  332. package/dist/slices/onboarding/template.js +2 -1
  333. package/dist/slices/patch-notes/library.js +3 -2
  334. package/dist/slices/play-drafts/convert.js +4 -0
  335. package/dist/slices/play-drafts/review-inputs.js +8 -2
  336. package/dist/slices/play-drafts/review.js +5 -4
  337. package/dist/slices/play-drafts/schema.js +4 -0
  338. package/dist/slices/project-templates/from-project.js +9 -2
  339. package/dist/slices/project-templates/repo.js +6 -1
  340. package/dist/slices/project-templates/setup.js +8 -0
  341. package/dist/slices/project-templates/stand-ins.js +22 -0
  342. package/dist/slices/rebuild/activity.js +2 -0
  343. package/dist/slices/rebuild/admission-repo.js +3 -2
  344. package/dist/slices/rebuild/narration-history.js +4 -5
  345. package/dist/slices/rebuild/narration-retry.js +2 -1
  346. package/dist/slices/rebuild/preview-details.js +27 -9
  347. package/dist/slices/rebuild/preview-plan.js +3 -8
  348. package/dist/slices/rebuild/recipe-build.js +8 -3
  349. package/dist/slices/rebuild/recipe-describe.js +3 -3
  350. package/dist/slices/rebuild/recipe-digest.js +58 -0
  351. package/dist/slices/rebuild/recipe-input-schema.js +10 -0
  352. package/dist/slices/rebuild/recipe-keywords.js +71 -0
  353. package/dist/slices/rebuild/recipe-legacy.js +5 -1
  354. package/dist/slices/rebuild/recipe-model.js +4 -0
  355. package/dist/slices/rebuild/recipe-narration-text.js +2 -1
  356. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  357. package/dist/slices/rebuild/recipe-reference.js +8 -4
  358. package/dist/slices/rebuild/recipe-reviews.js +27 -7
  359. package/dist/slices/rebuild/recipe-save.js +6 -3
  360. package/dist/slices/rebuild/recipe-scenes.js +40 -11
  361. package/dist/slices/rebuild/recipe-shorts.js +8 -8
  362. package/dist/slices/rebuild/recipe-social.js +46 -0
  363. package/dist/slices/rebuild/recipe-text.js +64 -20
  364. package/dist/slices/rebuild/recipe-validation.js +98 -28
  365. package/dist/slices/rebuild/recipe-visual.js +55 -16
  366. package/dist/slices/rebuild/recipe-work.js +15 -3
  367. package/dist/slices/rebuild/recipe-youtube.js +4 -3
  368. package/dist/slices/rebuild/recovery-selection.js +3 -1
  369. package/dist/slices/rebuild/recovery.js +3 -2
  370. package/dist/slices/rebuild/repo.js +4 -3
  371. package/dist/slices/rebuild/review-promise.js +25 -0
  372. package/dist/slices/rebuild/review-redo.js +2 -1
  373. package/dist/slices/rebuild/runtime-admission.js +4 -3
  374. package/dist/slices/rebuild/runtime-agent-review.js +11 -5
  375. package/dist/slices/rebuild/runtime-animate.js +9 -8
  376. package/dist/slices/rebuild/runtime-article.js +3 -2
  377. package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
  378. package/dist/slices/rebuild/runtime-document.js +8 -11
  379. package/dist/slices/rebuild/runtime-export-bed.js +6 -11
  380. package/dist/slices/rebuild/runtime-export-edit.js +6 -15
  381. package/dist/slices/rebuild/runtime-export-inputs.js +7 -7
  382. package/dist/slices/rebuild/runtime-export-short.js +8 -4
  383. package/dist/slices/rebuild/runtime-export.js +34 -12
  384. package/dist/slices/rebuild/runtime-figure-card.js +2 -1
  385. package/dist/slices/rebuild/runtime-image.js +4 -9
  386. package/dist/slices/rebuild/runtime-instructions.js +2 -0
  387. package/dist/slices/rebuild/runtime-keywords.js +59 -0
  388. package/dist/slices/rebuild/runtime-lines.js +2 -11
  389. package/dist/slices/rebuild/runtime-local.js +14 -20
  390. package/dist/slices/rebuild/runtime-narration-publication.js +2 -5
  391. package/dist/slices/rebuild/runtime-narration-reuse.js +9 -13
  392. package/dist/slices/rebuild/runtime-narration-text.js +10 -15
  393. package/dist/slices/rebuild/runtime-pictures.js +2 -4
  394. package/dist/slices/rebuild/runtime-piece-label.js +10 -6
  395. package/dist/slices/rebuild/runtime-plan.js +31 -5
  396. package/dist/slices/rebuild/runtime-provider.js +60 -19
  397. package/dist/slices/rebuild/runtime-publication.js +10 -9
  398. package/dist/slices/rebuild/runtime-review.js +33 -28
  399. package/dist/slices/rebuild/runtime-run.js +4 -1
  400. package/dist/slices/rebuild/runtime-shorts.js +34 -50
  401. package/dist/slices/rebuild/runtime-store.js +3 -8
  402. package/dist/slices/rebuild/runtime-subtitles.js +16 -14
  403. package/dist/slices/rebuild/runtime-voices.js +8 -15
  404. package/dist/slices/rebuild/runtime-youtube.js +11 -29
  405. package/dist/slices/rebuild/transition-repo.js +6 -5
  406. package/dist/slices/rebuild/work-records.js +6 -0
  407. package/dist/slices/reruns/cascade.js +1 -2
  408. package/dist/slices/reruns/index.js +6 -9
  409. package/dist/slices/reviews/external-model.js +3 -0
  410. package/dist/slices/reviews/external-repo.js +2 -1
  411. package/dist/slices/reviews/external-rows.js +2 -3
  412. package/dist/slices/reviews/model.js +26 -8
  413. package/dist/slices/reviews/repo.js +2 -1
  414. package/dist/slices/reviews/rules.js +1 -0
  415. package/dist/slices/reviews/view.js +5 -2
  416. package/dist/slices/revisions/downloads.js +4 -5
  417. package/dist/slices/revisions/mutation-assets.js +7 -4
  418. package/dist/slices/revisions/mutation-cues.js +2 -5
  419. package/dist/slices/revisions/mutations.js +0 -1
  420. package/dist/slices/revisions/repo.js +3 -1
  421. package/dist/slices/revisions/schema.js +1 -0
  422. package/dist/slices/revisions/view.js +20 -0
  423. package/dist/slices/run-cost/limits.js +5 -2
  424. package/dist/slices/run-cost/meter.js +12 -5
  425. package/dist/slices/run-cost/panel.js +8 -74
  426. package/dist/slices/run-cost/plan-readings.js +127 -0
  427. package/dist/slices/run-cost/plan-use.js +169 -0
  428. package/dist/slices/run-cost/pricing.js +3 -3
  429. package/dist/slices/run-cost/week.js +32 -4
  430. package/dist/slices/schedules/agenda.js +11 -2
  431. package/dist/slices/schedules/by-date.js +42 -0
  432. package/dist/slices/schedules/calendar.js +5 -0
  433. package/dist/slices/schedules/design-agenda.js +36 -0
  434. package/dist/slices/schedules/design-check.js +41 -0
  435. package/dist/slices/schedules/design-run.js +126 -0
  436. package/dist/slices/schedules/next-run.js +62 -0
  437. package/dist/slices/schedules/prepare.js +18 -2
  438. package/dist/slices/schedules/repo.js +24 -16
  439. package/dist/slices/schedules/rescore.js +35 -0
  440. package/dist/slices/schedules/scheduler.js +13 -3
  441. package/dist/slices/schedules/schema.js +54 -3
  442. package/dist/slices/schedules/score.js +107 -0
  443. package/dist/slices/schedules/service.js +57 -24
  444. package/dist/slices/schedules/topic-list.js +15 -0
  445. package/dist/slices/schedules/topic-scores.js +104 -0
  446. package/dist/slices/schedules/topics.js +48 -36
  447. package/dist/slices/settings/cli-paths.js +4 -6
  448. package/dist/slices/settings/first-run.js +2 -11
  449. package/dist/slices/settings/model-reach.js +3 -17
  450. package/dist/slices/settings/model.js +10 -0
  451. package/dist/slices/settings/playback.js +33 -22
  452. package/dist/slices/settings/probe-read.js +16 -0
  453. package/dist/slices/settings/repo.js +33 -0
  454. package/dist/slices/settings/tutorial.js +2 -2
  455. package/dist/slices/settings/voices.js +4 -12
  456. package/dist/slices/shorts/pick.js +2 -15
  457. package/dist/slices/shorts/prompts.js +2 -13
  458. package/dist/slices/shorts/render.js +3 -0
  459. package/dist/slices/social/channel.js +44 -0
  460. package/dist/slices/social/defaults.js +17 -0
  461. package/dist/slices/social/due.js +36 -0
  462. package/dist/slices/social/extension.js +18 -0
  463. package/dist/slices/social/fit.js +115 -0
  464. package/dist/slices/social/limits.js +186 -0
  465. package/dist/slices/social/links.js +21 -0
  466. package/dist/slices/social/media.js +131 -0
  467. package/dist/slices/social/model.js +75 -0
  468. package/dist/slices/social/notice.js +19 -0
  469. package/dist/slices/social/pack.js +176 -0
  470. package/dist/slices/social/post-prompt.js +55 -0
  471. package/dist/slices/social/queue.js +31 -0
  472. package/dist/slices/social/repo.js +133 -0
  473. package/dist/slices/social/reshape.js +97 -0
  474. package/dist/slices/social/rules.js +5 -0
  475. package/dist/slices/social/schema.js +70 -0
  476. package/dist/slices/social/serve.js +51 -0
  477. package/dist/slices/social/service.js +128 -0
  478. package/dist/slices/social/settings.js +21 -0
  479. package/dist/slices/social/sources.js +114 -0
  480. package/dist/slices/social/state.js +110 -0
  481. package/dist/slices/social/text.js +149 -0
  482. package/dist/slices/storage/arrange.js +3 -1
  483. package/dist/slices/storage/assets.js +8 -10
  484. package/dist/slices/storage/backup-designs.js +74 -0
  485. package/dist/slices/storage/backup-export.js +8 -3
  486. package/dist/slices/storage/backup-format.js +10 -0
  487. package/dist/slices/storage/backup-import.js +20 -2
  488. package/dist/slices/storage/delete-project.js +6 -5
  489. package/dist/slices/storage/files-location.js +6 -8
  490. package/dist/slices/storage/layout.js +13 -2
  491. package/dist/slices/storage/model.js +3 -0
  492. package/dist/slices/storage/portable.js +16 -11
  493. package/dist/slices/storage/schema.js +2 -0
  494. package/dist/slices/storage/staging-refs.js +2 -1
  495. package/dist/slices/storage/staging.js +6 -9
  496. package/dist/slices/storage/trim.js +10 -3
  497. package/dist/slices/studio/calendar.js +4 -1
  498. package/dist/slices/studio/pick.js +2 -11
  499. package/dist/slices/studio/plan.js +6 -15
  500. package/dist/slices/studio/queue.js +3 -14
  501. package/dist/slices/studio/releases.js +26 -0
  502. package/dist/slices/studio/settings.js +14 -51
  503. package/dist/slices/studio/videos.js +12 -0
  504. package/dist/slices/style-preview/render.js +34 -13
  505. package/dist/slices/style-preview/schema.js +13 -0
  506. package/dist/slices/style-preview/service.js +2 -1
  507. package/dist/slices/style-preview/settings.js +33 -5
  508. package/dist/slices/telemetry/collector-client.js +2 -4
  509. package/dist/slices/telemetry/flush.js +2 -4
  510. package/dist/slices/telemetry/model.js +0 -7
  511. package/dist/slices/telemetry/record.js +10 -3
  512. package/dist/slices/template-studio/anatomy-view.js +118 -0
  513. package/dist/slices/template-studio/anatomy.js +73 -0
  514. package/dist/slices/template-studio/bakeoff-make.js +225 -0
  515. package/dist/slices/template-studio/bakeoff-repo.js +83 -0
  516. package/dist/slices/template-studio/bakeoff.js +333 -0
  517. package/dist/slices/template-studio/budget.js +103 -0
  518. package/dist/slices/template-studio/checks.js +273 -0
  519. package/dist/slices/template-studio/document-sample.js +56 -0
  520. package/dist/slices/template-studio/edit.js +15 -0
  521. package/dist/slices/template-studio/for-all.js +161 -0
  522. package/dist/slices/template-studio/free-video.js +31 -0
  523. package/dist/slices/template-studio/free.js +176 -0
  524. package/dist/slices/template-studio/gate.js +40 -0
  525. package/dist/slices/template-studio/hidden.js +20 -0
  526. package/dist/slices/template-studio/jobs.js +79 -0
  527. package/dist/slices/template-studio/loudness.js +111 -0
  528. package/dist/slices/template-studio/make.js +40 -0
  529. package/dist/slices/template-studio/model.js +54 -0
  530. package/dist/slices/template-studio/pack-import.js +253 -0
  531. package/dist/slices/template-studio/pack-samples.js +60 -0
  532. package/dist/slices/template-studio/pack.js +205 -0
  533. package/dist/slices/template-studio/paid-view.js +66 -0
  534. package/dist/slices/template-studio/paid.js +348 -0
  535. package/dist/slices/template-studio/quote.js +148 -0
  536. package/dist/slices/template-studio/ready.js +104 -0
  537. package/dist/slices/template-studio/repo.js +134 -0
  538. package/dist/slices/template-studio/reviews-path.js +58 -0
  539. package/dist/slices/template-studio/runner.js +84 -0
  540. package/dist/slices/template-studio/save.js +236 -0
  541. package/dist/slices/template-studio/say-it-as.js +248 -0
  542. package/dist/slices/template-studio/schedule-gate.js +34 -0
  543. package/dist/slices/template-studio/schedule-price.js +30 -0
  544. package/dist/slices/template-studio/schedule-sample.js +87 -0
  545. package/dist/slices/template-studio/schema.js +218 -0
  546. package/dist/slices/template-studio/scope.js +124 -0
  547. package/dist/slices/template-studio/sections.js +176 -0
  548. package/dist/slices/template-studio/service.js +173 -0
  549. package/dist/slices/template-studio/status.js +174 -0
  550. package/dist/slices/template-studio/topic.js +223 -0
  551. package/dist/slices/trash/designs.js +76 -0
  552. package/dist/slices/trash/model.js +13 -4
  553. package/dist/slices/trash/service.js +52 -2
  554. package/dist/slices/tutorials/library.js +3 -2
  555. package/dist/slices/video/ass-text.js +17 -0
  556. package/dist/slices/video/audio-export.js +2 -64
  557. package/dist/slices/video/encoding.js +160 -0
  558. package/dist/slices/video/ffmpeg.js +23 -21
  559. package/dist/slices/video/figure-card.js +1 -15
  560. package/dist/slices/video/slideshow.js +43 -18
  561. package/dist/slices/voices/palette.js +0 -3
  562. package/dist/slices/youtube/answer.js +2 -15
  563. package/dist/slices/youtube/edits-repo.js +2 -11
  564. package/dist/tutorials/Agents-MCP.md +2 -1
  565. package/dist/tutorials/Assistant.md +42 -0
  566. package/dist/tutorials/Channels.md +2 -0
  567. package/dist/tutorials/Home-and-Projects.md +19 -0
  568. package/dist/tutorials/Install.md +1 -0
  569. package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
  570. package/dist/tutorials/Models.md +33 -1
  571. package/dist/tutorials/Notifications.md +6 -0
  572. package/dist/tutorials/Play-Images.md +4 -0
  573. package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
  574. package/dist/tutorials/Prompts.md +10 -6
  575. package/dist/tutorials/Remote-Access.md +63 -0
  576. package/dist/tutorials/Reviews-and-Checkpoints.md +13 -2
  577. package/dist/tutorials/Schedules.md +21 -1
  578. package/dist/tutorials/Settings-Reference.md +50 -8
  579. package/dist/tutorials/Template-Studio.md +125 -0
  580. package/dist/tutorials/Templates.md +3 -0
  581. package/dist/tutorials/_Sidebar.md +5 -0
  582. package/dist/updater/work-in-progress.js +42 -12
  583. package/dist/web/assets/index-CkAI_2E7.css +1 -0
  584. package/dist/web/assets/index-DcD0cdfo.js +334 -0
  585. package/dist/web/assets/{pdf-T7MCwjIv.js → pdf-TODRwF31.js} +1 -1
  586. package/dist/web/index.html +2 -2
  587. package/package.json +5 -1
  588. package/dist/slices/article/provided-entries.js +0 -66
  589. package/dist/slices/article/run.js +0 -145
  590. package/dist/slices/images/run.js +0 -234
  591. package/dist/slices/narration/legacy-plan.js +0 -58
  592. package/dist/slices/narration/run.js +0 -270
  593. package/dist/slices/rebuild/runtime-actions.js +0 -90
  594. package/dist/slices/research/run.js +0 -224
  595. package/dist/slices/subtitles/prepare.js +0 -194
  596. package/dist/slices/subtitles/transcript.js +0 -34
  597. package/dist/slices/thumbnail/run.js +0 -219
  598. package/dist/slices/video/audio-inputs.js +0 -33
  599. package/dist/slices/video/reuse-audio.js +0 -62
  600. package/dist/slices/video/run.js +0 -78
  601. package/dist/slices/video/write-export.js +0 -158
  602. package/dist/slices/video/write-subtitles.js +0 -59
  603. package/dist/web/assets/index-BadsoORk.css +0 -1
  604. package/dist/web/assets/index-D8LqNsCl.js +0 -326
@@ -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,112 @@
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
+ }
@@ -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
+ }
@@ -0,0 +1,198 @@
1
+ import { errorText } from "../../kernel/errors.js";
2
+ import { choiceSites } from "../model-upkeep/usage.js";
3
+ import { isUncataloguedProvider, providerById, providerIds, providers, } from "../settings/model.js";
4
+ import { readSetting, writeSetting } from "../settings/repo.js";
5
+ import { keyOfModel, weeklyAtKey, } from "./model.js";
6
+ import { accountOf, readAuto, readChecks } from "./repo.js";
7
+ import { newForYou, standingOf } from "./rules.js";
8
+ import { needsConfirmation, planChecks, runChecks, } from "./run.js";
9
+ // ceiling: the weekly look runs once seven days have passed since the last one.
10
+ export const weeklyIntervalMs = 7 * 24 * 60 * 60_000;
11
+ function familyOfSlot(slot) {
12
+ return slot === "llm" ? "llm" : slot === "audio" ? "tts" : "image";
13
+ }
14
+ export function chosenModels(db) {
15
+ const chosen = new Set();
16
+ for (const site of choiceSites(db))
17
+ for (const [slot, choice] of Object.entries(site.choices)) {
18
+ if (choice === undefined || choice.provider === "" || choice.model.trim() === "")
19
+ continue;
20
+ chosen.add(keyOfModel({ provider: choice.provider, family: familyOfSlot(slot), model: choice.model }));
21
+ }
22
+ return chosen;
23
+ }
24
+ export function createModelChecks(deps) {
25
+ const running = new Set();
26
+ const stopping = new AbortController();
27
+ const offered = async (provider) => {
28
+ const { family } = providerById(provider);
29
+ if (isUncataloguedProvider(provider)) {
30
+ const models = await deps.providerModels(provider, family).catch(() => []);
31
+ return models.map((model) => ({ provider, family, model: model.id }));
32
+ }
33
+ return deps
34
+ .catalogue()[family].filter((row) => row.provider === provider && row.enabled && !row.deprecated)
35
+ .map((row) => ({ provider, family, model: row.id }));
36
+ };
37
+ const withAccount = () => providerIds.filter((id) => accountOf(deps.db, id) !== undefined);
38
+ // Named models of a CLI are taken as named: asking the CLI for its list would start it, and
39
+ // a picker asks this for its one chosen model whenever it opens.
40
+ const offeredFor = (provider, models) => {
41
+ if (models === undefined || !isUncataloguedProvider(provider))
42
+ return offered(provider);
43
+ const { family } = providerById(provider);
44
+ return Promise.resolve(models.map((model) => ({ provider, family, model })));
45
+ };
46
+ const plan = async (scope) => {
47
+ const scoped = scope.provider === undefined ? withAccount() : [scope.provider];
48
+ return planChecks({
49
+ catalogue: deps.catalogue(),
50
+ rows: readChecks(deps.db),
51
+ offered: (await Promise.all(scoped.map((id) => offeredFor(id, scope.models)))).flat(),
52
+ accounts: (id) => accountOf(deps.db, id),
53
+ chosen: chosenModels(deps.db),
54
+ }, scope);
55
+ };
56
+ // A provider is taken before the first await, so two starts that arrive together can't both
57
+ // pass the running check and pay for the same checks twice.
58
+ const reserve = (ids) => {
59
+ let held = [...ids];
60
+ for (const id of held)
61
+ running.add(id);
62
+ const release = () => {
63
+ for (const id of held)
64
+ running.delete(id);
65
+ held = [];
66
+ };
67
+ // Once planned, only the providers it checks stay taken.
68
+ const keepFor = (checks) => {
69
+ const owned = held.filter((id) => checks.targets.some((t) => t.provider === id));
70
+ for (const id of held)
71
+ if (!owned.includes(id))
72
+ running.delete(id);
73
+ held = owned;
74
+ };
75
+ return { release, keepFor };
76
+ };
77
+ const launch = (checks, release) => runChecks({ db: deps.db, clock: deps.clock, log: deps.log, check: deps.check }, checks,
78
+ // A manual plan's estimate was confirmed in `start`; an automatic one is free or cheap.
79
+ { confirmedDollars: checks.totalDollars, signal: stopping.signal })
80
+ .then((answer) => answer.ok
81
+ ? answer.summary
82
+ : { checked: 0, works: 0, noAccess: 0, failed: 0, skipped: 0, spent: 0 })
83
+ .finally(release);
84
+ const start = async (scope, confirmedDollars) => {
85
+ const scoped = scope.provider === undefined ? withAccount() : [scope.provider];
86
+ const busy = scoped.filter((id) => running.has(id));
87
+ if (busy.length > 0)
88
+ return { ok: false, reason: "running", providers: busy };
89
+ const { release, keepFor } = reserve(scoped);
90
+ let checks;
91
+ try {
92
+ checks = await plan(scope);
93
+ }
94
+ catch (error) {
95
+ release();
96
+ throw error;
97
+ }
98
+ keepFor(checks);
99
+ if (scope.trigger === "manual" && needsConfirmation(checks, confirmedDollars)) {
100
+ release();
101
+ return { ok: false, reason: "estimate", plan: checks };
102
+ }
103
+ return { ok: true, plan: checks, done: launch(checks, release) };
104
+ };
105
+ const automatic = async (trigger, provider) => {
106
+ const scoped = provider === undefined ? withAccount() : [provider];
107
+ for (const id of scoped)
108
+ await deps.listing.refresh(id, true).catch((error) => {
109
+ deps.log.write("warn", "model-checks.listing", {
110
+ detail: `${id}: ${errorText(error)}`,
111
+ });
112
+ });
113
+ if (readAuto(deps.db) === "off")
114
+ return;
115
+ const free = scoped.filter((id) => !running.has(id));
116
+ if (free.length === 0)
117
+ return;
118
+ const { release, keepFor } = reserve(free);
119
+ let kept;
120
+ try {
121
+ const checks = await plan(provider === undefined ? { trigger } : { trigger, provider });
122
+ kept = {
123
+ ...checks,
124
+ targets: checks.targets.filter((t) => free.some((id) => id === t.provider)),
125
+ };
126
+ keepFor(kept);
127
+ if (trigger === "weekly")
128
+ writeSetting(deps.db, weeklyAtKey, deps.clock.now().toISOString());
129
+ }
130
+ catch (error) {
131
+ release();
132
+ throw error;
133
+ }
134
+ if (kept.targets.length > 0)
135
+ await launch(kept, release);
136
+ else
137
+ release();
138
+ };
139
+ return {
140
+ offered,
141
+ plan,
142
+ start,
143
+ automatic,
144
+ weeklyDue: () => {
145
+ if (readAuto(deps.db) === "off")
146
+ return false;
147
+ const last = readSetting(deps.db, weeklyAtKey);
148
+ return (last === undefined || deps.clock.now().getTime() - Date.parse(last) >= weeklyIntervalMs);
149
+ },
150
+ standings: (provider, models) => {
151
+ const { family } = providerById(provider);
152
+ const account = accountOf(deps.db, provider);
153
+ const rows = new Map(readChecks(deps.db, provider)
154
+ .filter((row) => row.family === family)
155
+ .map((row) => [row.model, row]));
156
+ return Object.fromEntries(models.map((model) => [
157
+ model.id,
158
+ standingOf(rows.get(model.id), account, deps.retiring?.({ provider, family, model: model.id })),
159
+ ]));
160
+ },
161
+ overview: () => {
162
+ const rows = readChecks(deps.db);
163
+ const catalogue = deps.catalogue();
164
+ const nameOf = (key) => catalogue[key.family].find((m) => m.provider === key.provider && m.id === key.model)
165
+ ?.name ?? key.model;
166
+ const states = providers.map((provider) => {
167
+ const account = accountOf(deps.db, provider.id);
168
+ const own = rows.filter((row) => row.provider === provider.id && row.account === account);
169
+ const times = own.flatMap((row) => (row.checkedAt === null ? [] : [row.checkedAt])).sort();
170
+ return {
171
+ provider: provider.id,
172
+ family: provider.family,
173
+ running: running.has(provider.id),
174
+ lastCheckedAt: times.at(-1) ?? null,
175
+ spent: own.reduce((sum, row) => sum + row.cost, 0),
176
+ works: own.filter((row) => row.verdict === "works").length,
177
+ noAccess: own.filter((row) => row.verdict === "no-access").length,
178
+ listed: own.filter((row) => row.listed === true).length,
179
+ };
180
+ });
181
+ const current = rows.filter((row) => providerIds.includes(row.provider) &&
182
+ row.account === accountOf(deps.db, row.provider));
183
+ return {
184
+ auto: readAuto(deps.db),
185
+ providers: states,
186
+ newForYou: newForYou(current).map((row) => ({
187
+ provider: row.provider,
188
+ family: row.family,
189
+ model: row.model,
190
+ name: nameOf(row),
191
+ firstWorksAt: row.firstWorksAt ?? "",
192
+ })),
193
+ };
194
+ },
195
+ running: () => [...running],
196
+ stop: () => stopping.abort(),
197
+ };
198
+ }