@gentbajko/slopify 3.25.9 → 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 (603) 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/4.0.0.md +51 -0
  195. package/dist/patch-notes/index.json +6 -0
  196. package/dist/slices/admission/heads.js +2 -1
  197. package/dist/slices/admission/repo.js +2 -1
  198. package/dist/slices/admission/rules.js +43 -8
  199. package/dist/slices/admission/schema.js +8 -0
  200. package/dist/slices/admission/stage-writes.js +0 -3
  201. package/dist/slices/admission/start.js +48 -31
  202. package/dist/slices/admission/substitute.js +7 -4
  203. package/dist/slices/agents/attention-posts.js +31 -0
  204. package/dist/slices/agents/attention-templates.js +33 -0
  205. package/dist/slices/agents/attention.js +10 -1
  206. package/dist/slices/agents/connection.js +2 -6
  207. package/dist/slices/agents/model.js +6 -3
  208. package/dist/slices/agents/repo.js +15 -7
  209. package/dist/slices/article/continuation.js +0 -63
  210. package/dist/slices/article/plain.js +3 -2
  211. package/dist/slices/article/segments.js +2 -62
  212. package/dist/slices/assistant/access.js +1 -0
  213. package/dist/slices/assistant/asks.js +80 -0
  214. package/dist/slices/assistant/cost.js +54 -0
  215. package/dist/slices/assistant/gate.js +222 -0
  216. package/dist/slices/assistant/model.js +79 -0
  217. package/dist/slices/assistant/names.js +23 -0
  218. package/dist/slices/assistant/prompt.js +74 -0
  219. package/dist/slices/assistant/repo.js +74 -0
  220. package/dist/slices/assistant/tokens.js +18 -0
  221. package/dist/slices/assistant/turn.js +342 -0
  222. package/dist/slices/autopilot/buffer.js +76 -0
  223. package/dist/slices/autopilot/model.js +14 -0
  224. package/dist/slices/autopilot/notify.js +27 -0
  225. package/dist/slices/autopilot/repo.js +34 -0
  226. package/dist/slices/autopilot/signals.js +194 -0
  227. package/dist/slices/autopilot/warnings.js +85 -0
  228. package/dist/slices/autopilot/weekly.js +100 -0
  229. package/dist/slices/backups/files.js +2 -6
  230. package/dist/slices/backups/repo.js +3 -15
  231. package/dist/slices/backups/service.js +4 -3
  232. package/dist/slices/channels/cast-images.js +2 -1
  233. package/dist/slices/channels/model.js +0 -1
  234. package/dist/slices/channels/rebrand.js +0 -3
  235. package/dist/slices/channels/repo.js +4 -0
  236. package/dist/slices/channels/schema.js +6 -0
  237. package/dist/slices/channels/service.js +10 -0
  238. package/dist/slices/channels/videos.js +2 -14
  239. package/dist/slices/checkpoints/index.js +0 -1
  240. package/dist/slices/control/providers.js +0 -26
  241. package/dist/slices/designs/artwork.js +66 -0
  242. package/dist/slices/designs/budget.js +23 -0
  243. package/dist/slices/designs/calls.js +124 -0
  244. package/dist/slices/designs/check-context.js +38 -0
  245. package/dist/slices/designs/check-text.js +43 -0
  246. package/dist/slices/designs/fields.js +88 -0
  247. package/dist/slices/designs/frames.js +34 -0
  248. package/dist/slices/designs/gate.js +128 -0
  249. package/dist/slices/designs/layout.js +293 -0
  250. package/dist/slices/designs/model.js +35 -0
  251. package/dist/slices/designs/pdf-job.js +23 -0
  252. package/dist/slices/designs/pdf-pages.js +30 -0
  253. package/dist/slices/designs/pdf-runner.js +68 -0
  254. package/dist/slices/designs/pdf-worker.js +24 -0
  255. package/dist/slices/designs/picture-size.js +79 -0
  256. package/dist/slices/designs/pictures.js +55 -0
  257. package/dist/slices/designs/quote.js +24 -0
  258. package/dist/slices/designs/render-args.js +36 -0
  259. package/dist/slices/designs/render-input.js +59 -0
  260. package/dist/slices/designs/render.js +215 -0
  261. package/dist/slices/designs/repo.js +101 -0
  262. package/dist/slices/designs/requests.js +80 -0
  263. package/dist/slices/designs/runner.js +239 -0
  264. package/dist/slices/designs/sample-repo.js +91 -0
  265. package/dist/slices/designs/sample.js +222 -0
  266. package/dist/slices/designs/schema.js +87 -0
  267. package/dist/slices/designs/sections.js +44 -0
  268. package/dist/slices/designs/service.js +233 -0
  269. package/dist/slices/designs/steps-repo.js +102 -0
  270. package/dist/slices/designs/studio-kind.js +39 -0
  271. package/dist/slices/designs/template-checks.js +44 -0
  272. package/dist/slices/designs/template-make.js +118 -0
  273. package/dist/slices/designs/templates.js +218 -0
  274. package/dist/slices/document/fonts.js +2 -1
  275. package/dist/slices/episodes/summarize.js +5 -1
  276. package/dist/slices/estimate/index.js +45 -7
  277. package/dist/slices/fonts/catalog.js +3 -2
  278. package/dist/slices/fonts/discovery.js +3 -2
  279. package/dist/slices/fonts/files.js +2 -6
  280. package/dist/slices/fonts/index.js +0 -1
  281. package/dist/slices/fonts/sfnt.js +45 -10
  282. package/dist/slices/images/appearance.js +6 -15
  283. package/dist/slices/images/scenes.js +100 -27
  284. package/dist/slices/keywords/digest.js +84 -0
  285. package/dist/slices/keywords/facts.js +92 -0
  286. package/dist/slices/keywords/fill.js +80 -0
  287. package/dist/slices/keywords/model.js +75 -0
  288. package/dist/slices/keywords/rules.js +187 -0
  289. package/dist/slices/keywords/schema.js +28 -0
  290. package/dist/slices/keywords/specs.js +260 -0
  291. package/dist/slices/keywords/values.js +74 -0
  292. package/dist/slices/library/lint.js +29 -12
  293. package/dist/slices/library/model.js +2 -0
  294. package/dist/slices/library/photorealistic.js +4 -12
  295. package/dist/slices/library/rename.js +9 -7
  296. package/dist/slices/library/save.js +14 -10
  297. package/dist/slices/library/slots.js +37 -11
  298. package/dist/slices/library/transfer.js +2 -1
  299. package/dist/slices/library/used-by.js +25 -10
  300. package/dist/slices/loudness/loudnorm.js +51 -11
  301. package/dist/slices/model-checks/cost.js +61 -0
  302. package/dist/slices/model-checks/listing.js +124 -0
  303. package/dist/slices/model-checks/model.js +20 -0
  304. package/dist/slices/model-checks/record.js +32 -0
  305. package/dist/slices/model-checks/repo.js +120 -0
  306. package/dist/slices/model-checks/rules.js +112 -0
  307. package/dist/slices/model-checks/run.js +143 -0
  308. package/dist/slices/model-checks/service.js +198 -0
  309. package/dist/slices/model-upkeep/switch.js +70 -25
  310. package/dist/slices/model-upkeep/usage.js +26 -11
  311. package/dist/slices/narration/breaths.js +2 -1
  312. package/dist/slices/narration/clean-pieces.js +2 -1
  313. package/dist/slices/narration/concat.js +2 -1
  314. package/dist/slices/narration/peaks.js +3 -4
  315. package/dist/slices/narration/pronunciation-chunks.js +4 -3
  316. package/dist/slices/narration/pronunciation.js +3 -2
  317. package/dist/slices/narration/steering.js +4 -3
  318. package/dist/slices/notifications/notifier.js +4 -3
  319. package/dist/slices/notifications/rules.js +5 -2
  320. package/dist/slices/notifications/settings.js +4 -12
  321. package/dist/slices/notifications/subject.js +2 -1
  322. package/dist/slices/onboarding/copy.js +2 -1
  323. package/dist/slices/onboarding/first-run.js +3 -2
  324. package/dist/slices/onboarding/full-video.js +3 -2
  325. package/dist/slices/onboarding/install.js +4 -3
  326. package/dist/slices/onboarding/model.js +0 -16
  327. package/dist/slices/onboarding/packs.js +22 -22
  328. package/dist/slices/onboarding/quick-short.js +2 -1
  329. package/dist/slices/onboarding/sample.js +0 -1
  330. package/dist/slices/onboarding/state.js +2 -11
  331. package/dist/slices/onboarding/template.js +2 -1
  332. package/dist/slices/patch-notes/library.js +3 -2
  333. package/dist/slices/play-drafts/convert.js +4 -0
  334. package/dist/slices/play-drafts/review-inputs.js +8 -2
  335. package/dist/slices/play-drafts/review.js +5 -4
  336. package/dist/slices/play-drafts/schema.js +4 -0
  337. package/dist/slices/project-templates/from-project.js +9 -2
  338. package/dist/slices/project-templates/repo.js +6 -1
  339. package/dist/slices/project-templates/setup.js +8 -0
  340. package/dist/slices/project-templates/stand-ins.js +22 -0
  341. package/dist/slices/rebuild/activity.js +2 -0
  342. package/dist/slices/rebuild/admission-repo.js +3 -2
  343. package/dist/slices/rebuild/narration-history.js +4 -5
  344. package/dist/slices/rebuild/narration-retry.js +2 -1
  345. package/dist/slices/rebuild/preview-details.js +27 -9
  346. package/dist/slices/rebuild/preview-plan.js +3 -8
  347. package/dist/slices/rebuild/recipe-build.js +8 -3
  348. package/dist/slices/rebuild/recipe-describe.js +3 -3
  349. package/dist/slices/rebuild/recipe-digest.js +58 -0
  350. package/dist/slices/rebuild/recipe-input-schema.js +10 -0
  351. package/dist/slices/rebuild/recipe-keywords.js +71 -0
  352. package/dist/slices/rebuild/recipe-legacy.js +5 -1
  353. package/dist/slices/rebuild/recipe-model.js +4 -0
  354. package/dist/slices/rebuild/recipe-narration-text.js +2 -1
  355. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  356. package/dist/slices/rebuild/recipe-reference.js +8 -4
  357. package/dist/slices/rebuild/recipe-reviews.js +27 -7
  358. package/dist/slices/rebuild/recipe-save.js +6 -3
  359. package/dist/slices/rebuild/recipe-scenes.js +40 -11
  360. package/dist/slices/rebuild/recipe-shorts.js +8 -8
  361. package/dist/slices/rebuild/recipe-social.js +46 -0
  362. package/dist/slices/rebuild/recipe-text.js +64 -20
  363. package/dist/slices/rebuild/recipe-validation.js +98 -28
  364. package/dist/slices/rebuild/recipe-visual.js +55 -16
  365. package/dist/slices/rebuild/recipe-work.js +15 -3
  366. package/dist/slices/rebuild/recipe-youtube.js +4 -3
  367. package/dist/slices/rebuild/recovery-selection.js +3 -1
  368. package/dist/slices/rebuild/recovery.js +3 -2
  369. package/dist/slices/rebuild/repo.js +4 -3
  370. package/dist/slices/rebuild/review-promise.js +25 -0
  371. package/dist/slices/rebuild/review-redo.js +2 -1
  372. package/dist/slices/rebuild/runtime-admission.js +4 -3
  373. package/dist/slices/rebuild/runtime-agent-review.js +11 -5
  374. package/dist/slices/rebuild/runtime-animate.js +9 -8
  375. package/dist/slices/rebuild/runtime-article.js +3 -2
  376. package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
  377. package/dist/slices/rebuild/runtime-document.js +8 -11
  378. package/dist/slices/rebuild/runtime-export-bed.js +6 -11
  379. package/dist/slices/rebuild/runtime-export-edit.js +6 -15
  380. package/dist/slices/rebuild/runtime-export-inputs.js +7 -7
  381. package/dist/slices/rebuild/runtime-export-short.js +8 -4
  382. package/dist/slices/rebuild/runtime-export.js +34 -12
  383. package/dist/slices/rebuild/runtime-figure-card.js +2 -1
  384. package/dist/slices/rebuild/runtime-image.js +4 -9
  385. package/dist/slices/rebuild/runtime-instructions.js +2 -0
  386. package/dist/slices/rebuild/runtime-keywords.js +59 -0
  387. package/dist/slices/rebuild/runtime-lines.js +2 -11
  388. package/dist/slices/rebuild/runtime-local.js +14 -20
  389. package/dist/slices/rebuild/runtime-narration-publication.js +2 -5
  390. package/dist/slices/rebuild/runtime-narration-reuse.js +9 -13
  391. package/dist/slices/rebuild/runtime-narration-text.js +10 -15
  392. package/dist/slices/rebuild/runtime-pictures.js +2 -4
  393. package/dist/slices/rebuild/runtime-piece-label.js +10 -6
  394. package/dist/slices/rebuild/runtime-plan.js +31 -5
  395. package/dist/slices/rebuild/runtime-provider.js +60 -19
  396. package/dist/slices/rebuild/runtime-publication.js +10 -9
  397. package/dist/slices/rebuild/runtime-review.js +33 -28
  398. package/dist/slices/rebuild/runtime-run.js +4 -1
  399. package/dist/slices/rebuild/runtime-shorts.js +25 -45
  400. package/dist/slices/rebuild/runtime-store.js +3 -8
  401. package/dist/slices/rebuild/runtime-subtitles.js +16 -14
  402. package/dist/slices/rebuild/runtime-voices.js +8 -15
  403. package/dist/slices/rebuild/runtime-youtube.js +11 -29
  404. package/dist/slices/rebuild/transition-repo.js +6 -5
  405. package/dist/slices/rebuild/work-records.js +6 -0
  406. package/dist/slices/reruns/cascade.js +1 -2
  407. package/dist/slices/reruns/index.js +6 -9
  408. package/dist/slices/reviews/external-model.js +3 -0
  409. package/dist/slices/reviews/external-repo.js +2 -1
  410. package/dist/slices/reviews/external-rows.js +2 -3
  411. package/dist/slices/reviews/model.js +26 -8
  412. package/dist/slices/reviews/repo.js +2 -1
  413. package/dist/slices/reviews/rules.js +1 -0
  414. package/dist/slices/reviews/view.js +5 -2
  415. package/dist/slices/revisions/downloads.js +4 -5
  416. package/dist/slices/revisions/mutation-assets.js +7 -4
  417. package/dist/slices/revisions/mutation-cues.js +2 -5
  418. package/dist/slices/revisions/mutations.js +0 -1
  419. package/dist/slices/revisions/repo.js +3 -1
  420. package/dist/slices/revisions/schema.js +1 -0
  421. package/dist/slices/revisions/view.js +20 -0
  422. package/dist/slices/run-cost/limits.js +5 -2
  423. package/dist/slices/run-cost/meter.js +12 -5
  424. package/dist/slices/run-cost/panel.js +8 -74
  425. package/dist/slices/run-cost/plan-readings.js +127 -0
  426. package/dist/slices/run-cost/plan-use.js +169 -0
  427. package/dist/slices/run-cost/pricing.js +3 -3
  428. package/dist/slices/run-cost/week.js +32 -4
  429. package/dist/slices/schedules/agenda.js +11 -2
  430. package/dist/slices/schedules/by-date.js +42 -0
  431. package/dist/slices/schedules/calendar.js +5 -0
  432. package/dist/slices/schedules/design-agenda.js +36 -0
  433. package/dist/slices/schedules/design-check.js +41 -0
  434. package/dist/slices/schedules/design-run.js +126 -0
  435. package/dist/slices/schedules/next-run.js +62 -0
  436. package/dist/slices/schedules/prepare.js +18 -2
  437. package/dist/slices/schedules/repo.js +24 -16
  438. package/dist/slices/schedules/rescore.js +35 -0
  439. package/dist/slices/schedules/scheduler.js +13 -3
  440. package/dist/slices/schedules/schema.js +54 -3
  441. package/dist/slices/schedules/score.js +107 -0
  442. package/dist/slices/schedules/service.js +57 -24
  443. package/dist/slices/schedules/topic-list.js +15 -0
  444. package/dist/slices/schedules/topic-scores.js +104 -0
  445. package/dist/slices/schedules/topics.js +48 -36
  446. package/dist/slices/settings/cli-paths.js +4 -6
  447. package/dist/slices/settings/first-run.js +2 -11
  448. package/dist/slices/settings/model-reach.js +3 -17
  449. package/dist/slices/settings/model.js +10 -0
  450. package/dist/slices/settings/playback.js +33 -22
  451. package/dist/slices/settings/probe-read.js +16 -0
  452. package/dist/slices/settings/repo.js +33 -0
  453. package/dist/slices/settings/tutorial.js +2 -2
  454. package/dist/slices/settings/voices.js +4 -12
  455. package/dist/slices/shorts/pick.js +2 -15
  456. package/dist/slices/shorts/prompts.js +2 -13
  457. package/dist/slices/shorts/render.js +3 -0
  458. package/dist/slices/social/channel.js +44 -0
  459. package/dist/slices/social/defaults.js +17 -0
  460. package/dist/slices/social/due.js +36 -0
  461. package/dist/slices/social/extension.js +18 -0
  462. package/dist/slices/social/fit.js +115 -0
  463. package/dist/slices/social/limits.js +186 -0
  464. package/dist/slices/social/links.js +21 -0
  465. package/dist/slices/social/media.js +131 -0
  466. package/dist/slices/social/model.js +75 -0
  467. package/dist/slices/social/notice.js +19 -0
  468. package/dist/slices/social/pack.js +176 -0
  469. package/dist/slices/social/post-prompt.js +55 -0
  470. package/dist/slices/social/queue.js +31 -0
  471. package/dist/slices/social/repo.js +133 -0
  472. package/dist/slices/social/reshape.js +97 -0
  473. package/dist/slices/social/rules.js +5 -0
  474. package/dist/slices/social/schema.js +70 -0
  475. package/dist/slices/social/serve.js +51 -0
  476. package/dist/slices/social/service.js +128 -0
  477. package/dist/slices/social/settings.js +21 -0
  478. package/dist/slices/social/sources.js +114 -0
  479. package/dist/slices/social/state.js +110 -0
  480. package/dist/slices/social/text.js +149 -0
  481. package/dist/slices/storage/arrange.js +3 -1
  482. package/dist/slices/storage/assets.js +8 -10
  483. package/dist/slices/storage/backup-designs.js +74 -0
  484. package/dist/slices/storage/backup-export.js +8 -3
  485. package/dist/slices/storage/backup-format.js +10 -0
  486. package/dist/slices/storage/backup-import.js +20 -2
  487. package/dist/slices/storage/delete-project.js +6 -5
  488. package/dist/slices/storage/files-location.js +6 -8
  489. package/dist/slices/storage/layout.js +13 -2
  490. package/dist/slices/storage/model.js +3 -0
  491. package/dist/slices/storage/portable.js +16 -11
  492. package/dist/slices/storage/schema.js +2 -0
  493. package/dist/slices/storage/staging-refs.js +2 -1
  494. package/dist/slices/storage/staging.js +6 -9
  495. package/dist/slices/storage/trim.js +10 -3
  496. package/dist/slices/studio/calendar.js +4 -1
  497. package/dist/slices/studio/pick.js +2 -11
  498. package/dist/slices/studio/plan.js +6 -15
  499. package/dist/slices/studio/queue.js +3 -14
  500. package/dist/slices/studio/releases.js +26 -0
  501. package/dist/slices/studio/settings.js +14 -51
  502. package/dist/slices/studio/videos.js +12 -0
  503. package/dist/slices/style-preview/render.js +34 -13
  504. package/dist/slices/style-preview/schema.js +13 -0
  505. package/dist/slices/style-preview/service.js +2 -1
  506. package/dist/slices/style-preview/settings.js +33 -5
  507. package/dist/slices/telemetry/collector-client.js +2 -4
  508. package/dist/slices/telemetry/flush.js +2 -4
  509. package/dist/slices/telemetry/model.js +0 -7
  510. package/dist/slices/telemetry/record.js +10 -3
  511. package/dist/slices/template-studio/anatomy-view.js +118 -0
  512. package/dist/slices/template-studio/anatomy.js +73 -0
  513. package/dist/slices/template-studio/bakeoff-make.js +225 -0
  514. package/dist/slices/template-studio/bakeoff-repo.js +83 -0
  515. package/dist/slices/template-studio/bakeoff.js +333 -0
  516. package/dist/slices/template-studio/budget.js +103 -0
  517. package/dist/slices/template-studio/checks.js +273 -0
  518. package/dist/slices/template-studio/document-sample.js +56 -0
  519. package/dist/slices/template-studio/edit.js +15 -0
  520. package/dist/slices/template-studio/for-all.js +161 -0
  521. package/dist/slices/template-studio/free-video.js +31 -0
  522. package/dist/slices/template-studio/free.js +176 -0
  523. package/dist/slices/template-studio/gate.js +40 -0
  524. package/dist/slices/template-studio/hidden.js +20 -0
  525. package/dist/slices/template-studio/jobs.js +79 -0
  526. package/dist/slices/template-studio/loudness.js +111 -0
  527. package/dist/slices/template-studio/make.js +40 -0
  528. package/dist/slices/template-studio/model.js +54 -0
  529. package/dist/slices/template-studio/pack-import.js +253 -0
  530. package/dist/slices/template-studio/pack-samples.js +60 -0
  531. package/dist/slices/template-studio/pack.js +205 -0
  532. package/dist/slices/template-studio/paid-view.js +66 -0
  533. package/dist/slices/template-studio/paid.js +348 -0
  534. package/dist/slices/template-studio/quote.js +148 -0
  535. package/dist/slices/template-studio/ready.js +104 -0
  536. package/dist/slices/template-studio/repo.js +134 -0
  537. package/dist/slices/template-studio/reviews-path.js +58 -0
  538. package/dist/slices/template-studio/runner.js +84 -0
  539. package/dist/slices/template-studio/save.js +236 -0
  540. package/dist/slices/template-studio/say-it-as.js +248 -0
  541. package/dist/slices/template-studio/schedule-gate.js +34 -0
  542. package/dist/slices/template-studio/schedule-price.js +30 -0
  543. package/dist/slices/template-studio/schedule-sample.js +87 -0
  544. package/dist/slices/template-studio/schema.js +218 -0
  545. package/dist/slices/template-studio/scope.js +124 -0
  546. package/dist/slices/template-studio/sections.js +176 -0
  547. package/dist/slices/template-studio/service.js +173 -0
  548. package/dist/slices/template-studio/status.js +174 -0
  549. package/dist/slices/template-studio/topic.js +223 -0
  550. package/dist/slices/trash/designs.js +76 -0
  551. package/dist/slices/trash/model.js +13 -4
  552. package/dist/slices/trash/service.js +52 -2
  553. package/dist/slices/tutorials/library.js +3 -2
  554. package/dist/slices/video/ass-text.js +17 -0
  555. package/dist/slices/video/audio-export.js +2 -64
  556. package/dist/slices/video/encoding.js +160 -0
  557. package/dist/slices/video/ffmpeg.js +23 -21
  558. package/dist/slices/video/figure-card.js +1 -15
  559. package/dist/slices/video/slideshow.js +43 -18
  560. package/dist/slices/voices/palette.js +0 -3
  561. package/dist/slices/youtube/answer.js +2 -15
  562. package/dist/slices/youtube/edits-repo.js +2 -11
  563. package/dist/tutorials/Agents-MCP.md +2 -1
  564. package/dist/tutorials/Assistant.md +42 -0
  565. package/dist/tutorials/Channels.md +2 -0
  566. package/dist/tutorials/Home-and-Projects.md +19 -0
  567. package/dist/tutorials/Install.md +1 -0
  568. package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
  569. package/dist/tutorials/Models.md +33 -1
  570. package/dist/tutorials/Notifications.md +6 -0
  571. package/dist/tutorials/Play-Images.md +4 -0
  572. package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
  573. package/dist/tutorials/Prompts.md +10 -6
  574. package/dist/tutorials/Remote-Access.md +63 -0
  575. package/dist/tutorials/Reviews-and-Checkpoints.md +13 -2
  576. package/dist/tutorials/Schedules.md +21 -1
  577. package/dist/tutorials/Settings-Reference.md +50 -8
  578. package/dist/tutorials/Template-Studio.md +125 -0
  579. package/dist/tutorials/Templates.md +3 -0
  580. package/dist/tutorials/_Sidebar.md +5 -0
  581. package/dist/updater/work-in-progress.js +42 -12
  582. package/dist/web/assets/index-CkAI_2E7.css +1 -0
  583. package/dist/web/assets/index-DcD0cdfo.js +334 -0
  584. package/dist/web/assets/{pdf-T7MCwjIv.js → pdf-TODRwF31.js} +1 -1
  585. package/dist/web/index.html +2 -2
  586. package/package.json +5 -1
  587. package/dist/slices/article/provided-entries.js +0 -66
  588. package/dist/slices/article/run.js +0 -145
  589. package/dist/slices/images/run.js +0 -234
  590. package/dist/slices/narration/legacy-plan.js +0 -58
  591. package/dist/slices/narration/run.js +0 -270
  592. package/dist/slices/rebuild/runtime-actions.js +0 -90
  593. package/dist/slices/research/run.js +0 -224
  594. package/dist/slices/subtitles/prepare.js +0 -194
  595. package/dist/slices/subtitles/transcript.js +0 -34
  596. package/dist/slices/thumbnail/run.js +0 -219
  597. package/dist/slices/video/audio-inputs.js +0 -33
  598. package/dist/slices/video/reuse-audio.js +0 -62
  599. package/dist/slices/video/run.js +0 -78
  600. package/dist/slices/video/write-export.js +0 -158
  601. package/dist/slices/video/write-subtitles.js +0 -59
  602. package/dist/web/assets/index-BadsoORk.css +0 -1
  603. package/dist/web/assets/index-D8LqNsCl.js +0 -326
@@ -1,8 +1,12 @@
1
1
  // Renaming a Library prompt or intro/outro. Everything names the Library by name, so a rename
2
- // rewrites the name where a future run reads it: each live template's current version (its
3
- // form and the copy it saved of the Library) and every Play draft still being edited.
4
- // Schedules run their template as it is now, so they follow. A project revision is the record
5
- // of what its run used and keeps the old name; "Used by" finds it through `formerNames`.
2
+ // rewrites the name where a future run reads it: every version of every template (its form and
3
+ // the copy it saved of the Library) and every Play draft still being edited. Every version, not
4
+ // only the newest: a gated template's schedules run its newest ready version (`template-studio/
5
+ // gate.ts`), Export pack carries that one, a version's samples stay current only while the
6
+ // versions since differ in what they changed, and a trashed template or an old version can be
7
+ // put back. The name is a pointer, so rewriting it changes nothing a version means. A project
8
+ // revision is the record of what its run used and keeps the old name; "Used by" finds it
9
+ // through `formerNames`.
6
10
  import { z } from "zod";
7
11
  import { renamedIn } from "./used-by.js";
8
12
  const documentRow = z.object({ key: z.string(), version: z.number(), document_json: z.string() });
@@ -56,9 +60,7 @@ export function renameReferences(db, ref, id, to) {
56
60
  if (ref.name === to)
57
61
  return;
58
62
  const templates = db
59
- .prepare(`SELECT t.id AS key,r.version,r.document_json FROM project_templates t
60
- JOIN project_template_revisions r ON r.template_id=t.id AND r.version=t.head_version
61
- WHERE t.deleted_at IS NULL`)
63
+ .prepare("SELECT template_id AS key,version,document_json FROM project_template_revisions")
62
64
  .all()
63
65
  .map((row) => documentRow.parse(row));
64
66
  const updateTemplate = db.prepare("UPDATE project_template_revisions SET document_json=? WHERE template_id=? AND version=?");
@@ -8,8 +8,10 @@ import { lintEntry, lintPrompt } from "./lint.js";
8
8
  import { entryCategories, promptKinds } from "./model.js";
9
9
  import { renameReferences } from "./rename.js";
10
10
  import { entryById, insertEntry, insertPrompt, promptById, replaceEntry, replacePrompt, trashEntry, trashPrompt, } from "./repo.js";
11
- export function createPrompt(deps, draft) {
12
- const fields = lintPrompt(draft);
11
+ // Duplicate names the prompt it copies (`copyOf`): a copy of one saved before the `Slopify_`
12
+ // prefix was reserved keeps the names that one already has, as an edit of it would.
13
+ export function createPrompt(deps, draft, copyOf) {
14
+ const fields = lintPrompt(draft, copyOf === undefined ? [] : (promptById(deps.db, copyOf)?.slots ?? []));
13
15
  if (fields.length > 0) {
14
16
  return { ok: false, reason: "invalid", fields };
15
17
  }
@@ -21,8 +23,10 @@ export function createPrompt(deps, draft) {
21
23
  }, prompt);
22
24
  }
23
25
  // A save replaces the row whole and keeps what was saved as a new version (`history.ts`).
24
- export function updatePrompt(deps, id, draft, restoredFrom) {
25
- const fields = lintPrompt(draft);
26
+ export function updatePrompt(deps, id, draft, restoredFrom,
27
+ // Slot names a restored version had, which it may keep like the stored prompt's.
28
+ restoredSlots = []) {
29
+ const fields = lintPrompt(draft, [...(promptById(deps.db, id)?.slots ?? []), ...restoredSlots]);
26
30
  if (fields.length > 0) {
27
31
  return { ok: false, reason: "invalid", fields };
28
32
  }
@@ -47,7 +51,7 @@ export function restorePrompt(deps, id, version) {
47
51
  const kind = promptKinds.find((one) => one === saved?.kind);
48
52
  if (saved === undefined || kind === undefined || promptById(deps.db, id) === undefined)
49
53
  return { ok: false, reason: "not-found" };
50
- return updatePrompt(deps, id, { kind, name: saved.name, body: saved.body }, version);
54
+ return updatePrompt(deps, id, { kind, name: saved.name, body: saved.body }, version, detectSlots(saved.body).names);
51
55
  }
52
56
  // Delete moves the prompt to the trash with its history; Settings → Trash puts it back or
53
57
  // removes it for good (`slices/trash`).
@@ -56,8 +60,8 @@ export function removePrompt(deps, id) {
56
60
  ? { ok: true, value: null }
57
61
  : { ok: false, reason: "not-found" };
58
62
  }
59
- export function createEntry(deps, draft) {
60
- const fields = lintEntry(draft);
63
+ export function createEntry(deps, draft, copyOf) {
64
+ const fields = lintEntry(draft, copyOf === undefined ? [] : (entryById(deps.db, copyOf)?.slots ?? []));
61
65
  if (fields.length > 0) {
62
66
  return { ok: false, reason: "invalid", fields };
63
67
  }
@@ -68,8 +72,8 @@ export function createEntry(deps, draft) {
68
72
  return true;
69
73
  }, entry);
70
74
  }
71
- export function updateEntry(deps, id, draft, restoredFrom) {
72
- const fields = lintEntry(draft);
75
+ export function updateEntry(deps, id, draft, restoredFrom, restoredSlots = []) {
76
+ const fields = lintEntry(draft, [...(entryById(deps.db, id)?.slots ?? []), ...restoredSlots]);
73
77
  if (fields.length > 0) {
74
78
  return { ok: false, reason: "invalid", fields };
75
79
  }
@@ -95,7 +99,7 @@ export function restoreEntry(deps, id, version) {
95
99
  saved.mode === null ||
96
100
  entryById(deps.db, id) === undefined)
97
101
  return { ok: false, reason: "not-found" };
98
- return updateEntry(deps, id, { category, mode: saved.mode, name: saved.name, body: saved.body }, version);
102
+ return updateEntry(deps, id, { category, mode: saved.mode, name: saved.name, body: saved.body }, version, detectSlots(saved.body).names);
99
103
  }
100
104
  export function removeEntry(deps, id) {
101
105
  return trashEntry(deps.db, id, deps.clock.now().toISOString())
@@ -4,8 +4,10 @@
4
4
  // is the one that runs.
5
5
  import { normaliseDraft, usesNarrationPreparation, usesReference, usesShorts, usesYoutubeDescription, } from "../admission/rules.js";
6
6
  import { collectFields, render } from "../admission/substitute.js";
7
+ import { keywordUsesOf, placedProblems, textEntryProblems, } from "../keywords/rules.js";
7
8
  import { reviewPromptKey } from "../reviews/model.js";
8
9
  import { activeReviewStages } from "../reviews/rules.js";
10
+ import { socialPlatforms } from "../social/model.js";
9
11
  import { usesScriptPrompt } from "../voices/model.js";
10
12
  import { snapshotEntry, snapshotPrompt } from "./snapshot.js";
11
13
  export function pickTemplates(db, input, snapshot) {
@@ -16,40 +18,63 @@ export function pickTemplates(db, input, snapshot) {
16
18
  // entries. A prompt left selected on a stage set to Provide asks for no field.
17
19
  const text = [];
18
20
  const image = [];
21
+ // Each picked body with the field that picked it and where it runs, for the keyword checks.
22
+ const placed = [];
23
+ const body = (kind, name, field, into, key) => {
24
+ const at = into.length;
25
+ pickBody(db, kind, name, field, missing, into, key, snapshot);
26
+ const picked = into[at];
27
+ if (picked !== undefined)
28
+ placed.push({ field, place: kind, body: picked.body });
29
+ };
19
30
  if (sources.article === "generate") {
20
- body(db, usesScriptPrompt(draft) ? "script" : "article", draft.articlePrompt, "articlePrompt", missing, text, "article", snapshot);
31
+ body(usesScriptPrompt(draft) ? "script" : "article", draft.articlePrompt, "articlePrompt", text, "article");
21
32
  }
22
33
  const intro = pickEntry(db, "intro", draft.intro, missing, snapshot);
23
34
  if (usesNarrationPreparation(draft))
24
- body(db, "narration", draft.narrationPrompt, "narrationPrompt", missing, text, "narration", snapshot);
35
+ body("narration", draft.narrationPrompt, "narrationPrompt", text, "narration");
25
36
  const outro = pickEntry(db, "outro", draft.outro, missing, snapshot);
26
37
  push(text, "intro", intro);
27
38
  push(text, "outro", outro);
39
+ if (intro !== undefined)
40
+ placed.push({ field: "intro", place: "intro", body: intro.body });
41
+ if (outro !== undefined)
42
+ placed.push({ field: "outro", place: "outro", body: outro.body });
43
+ for (const entry of [intro, outro])
44
+ if (entry?.mode === "text")
45
+ missing.push(...textEntryProblems(entry.body, entry.category));
28
46
  // None picked is the built-in prompt, which asks for no keyword.
29
47
  if (usesYoutubeDescription(draft))
30
- body(db, "description", draft.descriptionPrompt, "descriptionPrompt", missing, text, "description", snapshot);
48
+ body("description", draft.descriptionPrompt, "descriptionPrompt", text, "description");
31
49
  // The Shorts step's two prompts; none picked is the built-in one, which asks for no keyword.
32
50
  if (usesShorts(draft)) {
33
- body(db, "shorts", draft.shorts?.prompt, "shorts.prompt", missing, text, "shorts", snapshot);
34
- body(db, "image", draft.shorts?.imagePrompt, "shorts.imagePrompt", missing, image, "shortsImage", snapshot);
51
+ body("shorts", draft.shorts?.prompt, "shorts.prompt", text, "shorts");
52
+ body("image", draft.shorts?.imagePrompt, "shorts.imagePrompt", image, "shortsImage");
35
53
  }
36
54
  // Each reviewed stage's Review prompt; none picked is the stage's built-in one.
37
55
  for (const stage of activeReviewStages(draft))
38
- body(db, "review", draft.reviews?.stages[stage]?.prompt, `reviews.stages.${stage}.prompt`, missing, text, reviewPromptKey(stage), snapshot);
56
+ body("review", draft.reviews?.stages[stage]?.prompt, `reviews.stages.${stage}.prompt`, text, reviewPromptKey(stage));
57
+ // Each site's post prompt, for the sites whose post is written by one; none picked is the
58
+ // built-in one, which asks for no keyword.
59
+ for (const platform of socialPlatforms) {
60
+ const choice = draft.social?.[platform];
61
+ if (choice?.text === "prompt")
62
+ body("post", choice.prompt, `social.${platform}.prompt`, text, `social.${platform}`);
63
+ }
39
64
  // The establishing image's prompt: an image prompt like the others, its keywords filled the
40
65
  // same way.
41
66
  if (usesReference(draft) && draft.reference?.source === "prompt")
42
- body(db, "image", draft.reference.prompt, "reference.prompt", missing, image, "referencePrompt", snapshot);
67
+ body("image", draft.reference.prompt, "reference.prompt", image, "referencePrompt");
43
68
  if (sources.images === "generate") {
44
69
  for (const [index, picked] of draft.imagePrompts.entries()) {
45
70
  const field = `imagePrompts.${String(index)}.name`;
46
- body(db, "image", picked.name, field, missing, image, `imagePrompts.${String(index)}`, snapshot);
71
+ body("image", picked.name, field, image, `imagePrompts.${String(index)}`);
47
72
  }
48
73
  }
49
74
  // Both Generate modes read the same thumbnail template; Prompt by LLM sends it as the
50
75
  // instruction rather than to the image provider.
51
76
  if (sources.thumbnail === "from_prompt" || sources.thumbnail === "prompt_by_llm") {
52
- body(db, "thumbnail", draft.thumbnailPrompt, "thumbnailPrompt", missing, image, "thumbnailPrompt", snapshot);
77
+ body("thumbnail", draft.thumbnailPrompt, "thumbnailPrompt", image, "thumbnailPrompt");
53
78
  }
54
79
  const fields = collectFields(
55
80
  // The title's keywords are asked for like a prompt's: it is filled when the run starts.
@@ -58,7 +83,8 @@ export function pickTemplates(db, input, snapshot) {
58
83
  draft: withSavedModes(draft, intro, outro),
59
84
  requiredSlots: fields.map((field) => field.name),
60
85
  bodies: [...text, ...image],
61
- missing,
86
+ missing: [...missing, ...placedProblems(placed, draft.values)],
87
+ keywordUses: keywordUsesOf(placed),
62
88
  };
63
89
  }
64
90
  // Every picked body rendered once, with the trimmed values
@@ -70,7 +96,7 @@ export function renderPicked(picked, values) {
70
96
  }
71
97
  return rendered;
72
98
  }
73
- function body(db, kind, name, field, missing, into, key, snapshot) {
99
+ function pickBody(db, kind, name, field, missing, into, key, snapshot) {
74
100
  // An unpicked prompt is admission's rule to state ("Pick an article prompt."), so this
75
101
  // says nothing about it and the form marks the field once.
76
102
  if (name === undefined || name.trim() === "") {
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { errorText } from "../../kernel/errors.js";
2
3
  import { documentThemeNameMax } from "../document/model.js";
3
4
  import { documentThemeSchema } from "../document/theme-schema.js";
4
5
  import { narrationAliasSchema } from "../narration/aliases-schema.js";
@@ -202,7 +203,7 @@ export async function importEach(items, options) {
202
203
  break;
203
204
  }
204
205
  catch (error) {
205
- reason = error instanceof Error ? error.message : String(error);
206
+ reason = errorText(error);
206
207
  break;
207
208
  }
208
209
  }
@@ -5,6 +5,8 @@
5
5
  // `snapshot.ts` use.
6
6
  import { z } from "zod";
7
7
  import { liveProject } from "../admission/repo.js";
8
+ import { runnableVersion } from "../template-studio/gate.js";
9
+ import { gatesOf, notTemplateSample } from "../template-studio/repo.js";
8
10
  function record(value) {
9
11
  return typeof value === "object" && value !== null && !Array.isArray(value)
10
12
  ? value
@@ -40,6 +42,8 @@ function slotsIn(value, ref) {
40
42
  return field(shorts, "prompt");
41
43
  case "review":
42
44
  return Object.values(record(record(value.reviews)?.stages) ?? {}).flatMap((stage) => field(record(stage), "prompt"));
45
+ case "post":
46
+ return Object.values(record(value.social) ?? {}).flatMap((one) => field(record(one), "prompt"));
43
47
  case "image":
44
48
  return [
45
49
  ...(Array.isArray(value.imagePrompts) ? value.imagePrompts : []).flatMap((one) => field(record(one), "name")),
@@ -106,18 +110,28 @@ export function usedBy(db, ref, formerNames = []) {
106
110
  const parsed = templateRow.parse(row);
107
111
  return matches(formOf(parsed.document_json)) ? [{ id: parsed.id, name: parsed.name }] : [];
108
112
  });
109
- // A schedule runs its template as it is now (`schedules/scheduler.ts`), so the template's
110
- // current version is what it uses.
113
+ // A schedule runs the version of its template the studio gate lets it run
114
+ // (`template-studio/schedule-gate.ts`), which can be older than the newest one.
115
+ const runsOf = new Map();
116
+ const formRun = (templateId, head) => {
117
+ if (!runsOf.has(templateId)) {
118
+ const version = runnableVersion(gatesOf(db, templateId), head) ?? head;
119
+ const row = db
120
+ .prepare("SELECT document_json FROM project_template_revisions WHERE template_id=? AND version=?")
121
+ .get(templateId, version);
122
+ runsOf.set(templateId, row === undefined ? undefined : formOf(String(row.document_json)));
123
+ }
124
+ return runsOf.get(templateId);
125
+ };
111
126
  const schedules = db
112
- .prepare(`SELECT s.id,s.name,s.status,r.document_json FROM schedules s
127
+ .prepare(`SELECT s.id,s.name,s.status,s.template_id,t.head_version FROM schedules s
113
128
  JOIN project_templates t ON t.id=s.template_id
114
- JOIN project_template_revisions r ON r.template_id=t.id AND r.version=t.head_version
115
- WHERE s.deleted_at IS NULL AND s.status IN ('active','paused') AND ${mentions("r.document_json")}
129
+ WHERE s.deleted_at IS NULL AND s.status IN ('active','paused') AND s.template_kind='project'
116
130
  ORDER BY lower(s.name),s.id`)
117
- .all(...plain)
131
+ .all()
118
132
  .flatMap((row) => {
119
133
  const parsed = scheduleRow.parse(row);
120
- return matches(formOf(parsed.document_json))
134
+ return matches(formRun(parsed.template_id, parsed.head_version))
121
135
  ? [{ id: parsed.id, name: parsed.name, status: parsed.status }]
122
136
  : [];
123
137
  });
@@ -126,7 +140,7 @@ export function usedBy(db, ref, formerNames = []) {
126
140
  (SELECT count(*) FROM project_revisions x WHERE x.project_id=r.project_id) AS total
127
141
  FROM project_revisions r JOIN projects p ON p.id=r.project_id
128
142
  LEFT JOIN project_heads h ON h.project_id=r.project_id
129
- WHERE ${liveProject("p")} AND ${mentions("r.config")}
143
+ WHERE ${liveProject("p")} AND ${notTemplateSample("p")} AND ${mentions("r.config")}
130
144
  ORDER BY p.created_at DESC,p.id,r.created_at`)
131
145
  .all(...plain)
132
146
  .map((row) => revisionRow.parse(row));
@@ -146,7 +160,7 @@ export function usedBy(db, ref, formerNames = []) {
146
160
  // A project from before revisions existed has none yet; its own config is what it ran with.
147
161
  for (const row of db
148
162
  .prepare(`SELECT p.id,p.title,p.config FROM projects p
149
- WHERE ${liveProject("p")}
163
+ WHERE ${liveProject("p")} AND ${notTemplateSample("p")}
150
164
  AND NOT EXISTS (SELECT 1 FROM project_revisions r WHERE r.project_id=p.id)
151
165
  AND ${mentions("p.config")} ORDER BY p.created_at DESC,p.id`)
152
166
  .all(...plain)
@@ -167,7 +181,8 @@ const scheduleRow = z.object({
167
181
  id: z.string(),
168
182
  name: z.string(),
169
183
  status: z.string(),
170
- document_json: z.string(),
184
+ template_id: z.string(),
185
+ head_version: z.number(),
171
186
  });
172
187
  const revisionRow = z.object({
173
188
  project_id: z.string(),
@@ -1,4 +1,4 @@
1
- import { renameSync } from "node:fs";
1
+ import { renameSync, rmSync } from "node:fs";
2
2
  import { z } from "zod";
3
3
  import { runFfmpeg } from "../video/ffmpeg.js";
4
4
  // ceiling: below -70 LUFS (the filter's absolute gate) or under 0.4 s (one measuring block)
@@ -89,6 +89,7 @@ export async function measure(run, input, goal, chain = "") {
89
89
  args: measureArgs(input, goal, chain),
90
90
  signal: run.signal,
91
91
  log: run.log,
92
+ priority: run.priority,
92
93
  onProgress: () => { },
93
94
  onStderr: (text) => {
94
95
  stderr += text;
@@ -109,8 +110,15 @@ export async function normalizeFile(run, input, output, goal, format) {
109
110
  // The channels are set before anything is measured: a stereo file mixed down to mono is
110
111
  // louder per channel (the mix keeps its loudness, so each sample is about 3 dB higher), and
111
112
  // the gain and the peak ceiling have to hold for what is written.
112
- const channels = `aformat=channel_layouts=${format.channels === 1 ? "mono" : "stereo"}`;
113
- const measured = await measure(run, ["-i", input], goal, channels);
113
+ const measured = await measure(run, ["-i", input], goal, channelsOf(format));
114
+ await writeLevelled(run, input, output, goal, measured, format);
115
+ return measured;
116
+ }
117
+ function channelsOf(format) {
118
+ return `aformat=channel_layouts=${format.channels === 1 ? "mono" : "stereo"}`;
119
+ }
120
+ // `input` written to `output` with the gain that takes `measured` to `goal` (`gainFilter`).
121
+ async function writeLevelled(run, input, output, goal, measured, format) {
114
122
  await runFfmpeg({
115
123
  bin: run.bin,
116
124
  args: [
@@ -125,7 +133,7 @@ export async function normalizeFile(run, input, output, goal, format) {
125
133
  "-map",
126
134
  "0:a:0",
127
135
  "-af",
128
- `${channels},${gainFilter(goal, measured, format.sampleRate)}`,
136
+ `${channelsOf(format)},${gainFilter(goal, measured, format.sampleRate)}`,
129
137
  "-c:a",
130
138
  "pcm_f32le",
131
139
  "-f",
@@ -134,23 +142,55 @@ export async function normalizeFile(run, input, output, goal, format) {
134
142
  ],
135
143
  signal: run.signal,
136
144
  log: run.log,
145
+ priority: run.priority,
137
146
  onProgress: () => { },
138
147
  });
139
- return measured;
140
148
  }
141
149
  // ceiling: within a fifth of a LU of the level no one hears the difference.
142
150
  const correctAbove = 0.2;
143
- // One file brought to `goal` (`normalizeFile`), measured again, and corrected once when the
144
- // limiter left it more than a fifth of a LU under the level. Returns the first measurement and
145
- // the last.
151
+ // ceiling: two corrections land a voice whose peaks stand 17 dB over its loudness (raw TTS)
152
+ // within a few tenths of a LU; a third would only chase the last tenth.
153
+ const corrections = 2;
154
+ // ceiling: the most gain a correction adds over the plain gain. Past it the limiter would
155
+ // flatten the voice; the file lands short instead, and its report says how far.
156
+ const extraGainMax = 6;
157
+ // One file brought to `goal` (`normalizeFile`), measured again, and corrected when the limiter
158
+ // left it more than a fifth of a LU under the level. Each correction writes the input again
159
+ // with more gain (so the limiter holds the peaks once, never twice), the gain found from the
160
+ // levels the earlier gains gave. Returns the first measurement and the last.
146
161
  export async function levelFile(run, input, output, goal, format) {
147
162
  const before = await normalizeFile(run, input, output, goal, format);
148
163
  let after = await measureFile(run, output, goal);
149
- if (measurable(after) && Math.abs(after.integrated - goal.lufs) > correctAbove) {
164
+ if (!measurable(before))
165
+ return { before, after };
166
+ const plain = goal.lufs - before.integrated;
167
+ const tried = [
168
+ { gain: plain, lufs: after.integrated },
169
+ ];
170
+ for (let pass = 0; pass < corrections; pass += 1) {
171
+ if (!measurable(after) || Math.abs(after.integrated - goal.lufs) <= correctAbove)
172
+ break;
173
+ const last = tried[tried.length - 1];
174
+ const prior = tried[tried.length - 2];
175
+ if (last === undefined)
176
+ break;
177
+ const slope = prior === undefined || last.lufs === prior.lufs
178
+ ? 1
179
+ : (last.lufs - prior.lufs) / (last.gain - prior.gain);
180
+ const gain = Math.min(plain + extraGainMax, last.gain + (goal.lufs - last.lufs) / (slope > 0.05 ? slope : 1));
181
+ if (Math.abs(gain - last.gain) < 0.05)
182
+ break;
150
183
  const corrected = `${output}.corrected.wav`;
151
- await normalizeFile(run, output, corrected, goal, format);
184
+ await writeLevelled(run, input, corrected, { ...goal, lufs: before.integrated + gain }, before, format);
185
+ const next = await measureFile(run, corrected, goal);
186
+ // A correction that landed further off than the file it would replace is dropped.
187
+ if (Math.abs(next.integrated - goal.lufs) >= Math.abs(after.integrated - goal.lufs)) {
188
+ rmSync(corrected, { force: true });
189
+ break;
190
+ }
152
191
  renameSync(corrected, output);
153
- after = await measureFile(run, output, goal);
192
+ after = next;
193
+ tried.push({ gain, lufs: next.integrated });
154
194
  }
155
195
  return { before, after };
156
196
  }
@@ -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
+ }