@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,166 @@
1
+ import { keyProbes } from "./adapters/key-probes.js";
2
+ import { emptyLifecycle, retirementOf } from "./catalog/lifecycle.js";
3
+ import { thinkingModes } from "./kernel/ports/llm.js";
4
+ import { readinessIsUsable } from "./kernel/ports/model.js";
5
+ import { checkModel } from "./kernel/runner/model-check.js";
6
+ import { modelSources } from "./model-catalog.js";
7
+ import { isVideoTarget, spentOn } from "./slices/model-checks/cost.js";
8
+ import { createListing } from "./slices/model-checks/listing.js";
9
+ import { createModelChecks } from "./slices/model-checks/service.js";
10
+ import { isLocalCliProvider, systemVoiceProvider, } from "./slices/settings/model.js";
11
+ import { listVoices } from "./slices/settings/repo.js";
12
+ // The composition layer's check recipes: what the smallest request is for each kind of model.
13
+ // A reply of one word at the lowest thinking setting, a voice saying "Hi", a one-to-one grey
14
+ // dot. Image-to-video models are listed only, never generated.
15
+ export const checkPrompt = "Reply with OK.";
16
+ export const checkSpeech = "Hi";
17
+ export const checkPicture = "a small grey dot";
18
+ // A voice every account of these providers has, for a check made before any voice is saved.
19
+ const defaultVoices = {
20
+ "openai-tts": "alloy",
21
+ "google-tts": "Kore",
22
+ // ElevenLabs' premade "Rachel".
23
+ elevenlabs: "21m00Tcm4TlvDq8ikWAM",
24
+ };
25
+ function lowest(modes) {
26
+ if (modes === undefined || modes.length === 0)
27
+ return undefined;
28
+ return thinkingModes.find((mode) => modes.includes(mode));
29
+ }
30
+ export async function recipeFor(deps, target) {
31
+ const catalogue = deps.catalogue();
32
+ const { provider, model } = target;
33
+ if (target.family === "llm") {
34
+ let modes;
35
+ if (isLocalCliProvider(provider))
36
+ modes = (await deps.cliModels(provider, "llm").catch(() => [])).find((m) => m.id === model)?.thinkingModes;
37
+ else {
38
+ const row = catalogue.llm.find((m) => m.provider === provider && m.id === model);
39
+ modes = row?.llm.thinking === undefined ? undefined : Object.keys(row.llm.thinking);
40
+ }
41
+ const thinking = lowest(modes);
42
+ return {
43
+ kind: "call",
44
+ call: {
45
+ kind: "llm",
46
+ provider,
47
+ model,
48
+ prompt: checkPrompt,
49
+ ...(thinking === undefined ? {} : { thinking }),
50
+ },
51
+ };
52
+ }
53
+ if (target.family === "tts") {
54
+ if (provider === systemVoiceProvider)
55
+ return (await deps.systemVoiceReady())
56
+ ? { kind: "free-works" }
57
+ : { kind: "skip", reason: "No speech program was found on this computer." };
58
+ const voiceId = deps.voices().find((voice) => voice.provider === provider)?.voiceId ??
59
+ (Object.hasOwn(defaultVoices, provider) ? defaultVoices[provider] : undefined);
60
+ if (voiceId === undefined)
61
+ return {
62
+ kind: "skip",
63
+ reason: "Add a voice for this provider in Settings → Voices, then choose Check models.",
64
+ };
65
+ return { kind: "call", call: { kind: "tts", provider, model, voiceId, text: checkSpeech } };
66
+ }
67
+ if (isVideoTarget(catalogue, target))
68
+ return { kind: "skip", reason: "Image-to-video models are listed, never checked." };
69
+ const row = catalogue.image.find((m) => m.provider === provider && m.id === model);
70
+ const ratios = row?.image.aspectRatios ?? ["1:1"];
71
+ const aspect = ratios.includes("1:1") ? "1:1" : (ratios[0] ?? "1:1");
72
+ let thinking;
73
+ if (isLocalCliProvider(provider))
74
+ thinking = lowest((await deps.cliModels(provider, "image").catch(() => [])).find((m) => m.id === model)
75
+ ?.thinkingModes);
76
+ return {
77
+ kind: "call",
78
+ call: {
79
+ kind: "image",
80
+ provider,
81
+ model,
82
+ prompt: checkPicture,
83
+ aspect,
84
+ ...(thinking === undefined ? {} : { thinking }),
85
+ },
86
+ };
87
+ }
88
+ export function modelChecker(deps) {
89
+ return async (target, signal) => {
90
+ const recipe = await recipeFor(deps, target);
91
+ if (recipe.kind === "skip")
92
+ return { kind: "skipped", reason: recipe.reason };
93
+ if (recipe.kind === "free-works")
94
+ return { kind: "checked", verdict: "works", sentence: null, stopProvider: false, spent: 0 };
95
+ const answer = await checkModel(deps, recipe.call, signal);
96
+ return {
97
+ kind: "checked",
98
+ verdict: answer.verdict,
99
+ sentence: answer.sentence,
100
+ stopProvider: answer.stopProvider,
101
+ // A failure the provider may have billed is counted at the estimate the check showed.
102
+ spent: answer.verdict === "works"
103
+ ? spentOn(deps.catalogue(), target, answer.use)
104
+ : answer.mayHaveBilled
105
+ ? spentOn(deps.catalogue(), target, null)
106
+ : 0,
107
+ };
108
+ };
109
+ }
110
+ // The model-checks service as main.ts runs it: free listings from the providers' own model
111
+ // endpoints, and checks made through the recipes above.
112
+ export function wireModelChecks(deps) {
113
+ const { modelsFor } = modelSources(deps.registry);
114
+ const listing = createListing({
115
+ db: deps.db,
116
+ clock: deps.clock,
117
+ fetch: deps.fetch,
118
+ probes: keyProbes,
119
+ known: (provider, family) => deps
120
+ .catalogue()[family].filter((row) => row.provider === provider && row.enabled && !row.deprecated)
121
+ .map((row) => row.id),
122
+ cliModels: async (provider, family) => (await modelsFor(provider, family)).map((m) => m.id),
123
+ });
124
+ const checks = createModelChecks({
125
+ db: deps.db,
126
+ clock: deps.clock,
127
+ log: deps.log,
128
+ catalogue: deps.catalogue,
129
+ listing,
130
+ providerModels: modelsFor,
131
+ retiring: (model) => retirementOf(deps.lifecycle?.() ?? emptyLifecycle, deps.catalogue(), model),
132
+ check: modelChecker({
133
+ registry: deps.registry,
134
+ clock: deps.clock,
135
+ log: deps.log,
136
+ catalogue: deps.catalogue,
137
+ voices: () => listVoices(deps.db),
138
+ cliModels: modelsFor,
139
+ systemVoiceReady: async () => (await deps.statuses()).some((status) => status.id === systemVoiceProvider && readinessIsUsable(status.readiness)),
140
+ }),
141
+ });
142
+ return { checks, listing };
143
+ }
144
+ // A CLI that has become usable - installed and signed in - since the last look, or one that is
145
+ // usable and was never listed, is checked as if it had just been added.
146
+ export function cliWatcher(checks, statuses, known) {
147
+ const usable = new Map();
148
+ return async () => {
149
+ for (const status of await statuses()) {
150
+ if (status.readiness.kind !== "cli")
151
+ continue;
152
+ const now = readinessIsUsable(status.readiness);
153
+ const before = usable.get(status.id);
154
+ usable.set(status.id, now);
155
+ if (now && (before === false || (before === undefined && !known(status.id))))
156
+ await checks.automatic("cli-added", status.id);
157
+ }
158
+ };
159
+ }
160
+ // The hourly look. The weekly look goes first: it lists and checks every provider, a CLI
161
+ // among them, so a CLI it has just checked is known and isn't checked again as newly added.
162
+ export async function lookAtModels(checks, lookForClis) {
163
+ if (checks.weeklyDue())
164
+ await checks.automatic("weekly");
165
+ await lookForClis();
166
+ }
@@ -0,0 +1,5 @@
1
+ # Slopify 3.25.9
2
+
3
+ Released 17 October 2026.
4
+
5
+ - **An older project's shorts render again.** Remaking the shorts of a project made before an earlier change to how Slopify recognises finished work failed with "The clip this short was cut from is no longer among the picked shorts". The shorts now render from the moments already picked.
@@ -0,0 +1,51 @@
1
+ # Slopify 4.0.0
2
+
3
+ Released 18 October 2026.
4
+
5
+ ## Template Studio
6
+
7
+ - **Set a template up once, with real samples.** Library → Templates → Open studio shows a template section by section: Subject, Script, Voice, Pictures, Video & captions, Thumbnail & title, Shorts, Document, Reviews & checkpoints, Upload pack and Schedule. Each Save makes a short sample of the sections it changed with your own providers. The sample is the opening of the script, about 30 seconds of narration, 4 pictures, a 30-second clip, a short, the PDF and the upload text, so you see and hear the result before a schedule uses it.
8
+ - Every paid Save shows its price on the button. A daily sample budget (Settings → Template studio, $2 by default) holds samples that would go over it. CLI plans are used only when you turn on "Samples may use my CLI plans".
9
+ - **Mark ready.** Once you mark a version ready in the studio, schedules run the newest ready version and later edits wait until you mark them ready. Templates you never mark ready keep following their newest version, as before.
10
+ - **Compare models** makes one section's sample with 2–3 models side by side, with cost and time. **Say it as…** fixes a name the voice mispronounces. **Export pack** and **Import pack** share a ready template with its samples.
11
+ - Create opens on template + topic + Start. More settings opens the full form.
12
+
13
+ ## Posting to X, LinkedIn, Instagram, TikTok, Bluesky and Reddit
14
+
15
+ - Prepare upload has a **Where** choice: YouTube plus each site the project posts to. Each post shows its text, its media in the site's shape, a live character count and plain warnings. Templates set the defaults in Template Studio → Upload pack.
16
+ - **Fill in <site>** opens the site and the Slopify Studio extension fills the post: text, media, the site's AI label, and its own scheduler where it has one (X, LinkedIn, TikTok). You press Post. On Instagram, Bluesky and Reddit, which have no scheduler, the extension fills the post at its release time and notifies you.
17
+ - The extension never presses Post, Share or Publish, and it only fills tabs Slopify opened. Allow each site in the extension's options with "Allow posting on …". Reddit has not been checked live yet.
18
+
19
+ ## Pictures and designed pieces
20
+
21
+ - **Edit a picture** you upload with an instruction, and keep editing the result. Every step is a version you can go back to.
22
+ - **Design posters and flyers.** The AI makes the artwork and Slopify sets the names, dates and prices in real fonts, so they are always spelled right. Download PNG or a print-ready PDF in A4, A3, A5 and social sizes. A design template with fields (event, date, venue…) makes a piece per row ahead of its date, or one at a time with Make a piece.
23
+
24
+ ## Keywords Slopify fills
25
+
26
+ - New built-in keywords such as {{Slopify_Title}}, {{Slopify_Summary}}, {{Slopify_Hook}}, {{Slopify_Chapters}}, {{Slopify_Setting}} and {{Slopify_Thumbnail_Text}}. The Insert bar above each prompt lists the ones that work there, with an example and what they cost. {{Scene}} and {{Appearance}} keep working; the editor offers to update them to {{Slopify_Scene}} and {{Slopify_Appearance}}.
27
+
28
+ ## Assistant, health and checks
29
+
30
+ - **Assistant.** A chat panel that runs on your Claude Code or Codex and can only use Slopify's own tools. It asks before anything that costs money, deletes or sends something, and shows what it will do. Its changes show as "by assistant".
31
+ - **Autopilot on Home** says how many days of videos are ready, when a schedule's topics run out, and warns before something breaks: a CLI signed out, a plan near its limit, disk filling, a model being retired. A weekly report can go to your notification URL (off by default).
32
+ - **Models that work for you.** Model pickers show which models your key or plan can actually run, with Check models and a weekly free check. Models that are being retired show their replacement.
33
+ - **Promise check.** An optional cheap review before narration: does the opening deliver what the title and thumbnail promise.
34
+ - **Topic ranking.** Schedules can rank new topics by what your library already covers, or also by public search interest (sends topic titles to Google, DuckDuckGo and Wikipedia; off by default).
35
+
36
+ ## Remote access and rendering
37
+
38
+ - **Open Slopify on your phone or laptop** through Tailscale (Settings → General → Remote access). Only devices on your own tailnet can open it, never the public internet. Keys, imports and updates can only be changed on this computer.
39
+ - **Render settings** (Settings → Production defaults → Rendering): video encoder (processor or NVIDIA graphics card), clip encoding speed and render priority.
40
+
41
+ ## Fixes
42
+
43
+ - A run's share of your Claude or Codex plan is now its own use only, never above 100%.
44
+ - Narration levelling now reaches the target loudness (−14 LUFS lands within 0.1 LU).
45
+ - The extension reads A/B test results again after it sets up A/B Testing.
46
+
47
+ ## Upgrading from 3.25.9
48
+
49
+ - Your projects, templates and schedules carry over unchanged; existing projects are not remade. Every template version you had counts as "Not checked in the studio" and keeps running.
50
+ - The extension goes to 2.0.0. Posting needs its new optional permissions, granted in its options; YouTube works as before without them.
51
+ - `/mcp` now answers only on this computer: connect agents to `http://127.0.0.1:<port>/mcp`, not through a proxy.
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "id": "4.0.0",
4
+ "title": "Slopify 4.0.0",
5
+ "version": "4.0.0",
6
+ "date": "2026-10-18"
7
+ },
8
+ {
9
+ "id": "3.25.9",
10
+ "title": "Slopify 3.25.9",
11
+ "version": "3.25.9",
12
+ "date": "2026-10-17"
13
+ },
2
14
  {
3
15
  "id": "3.25.8",
4
16
  "title": "Slopify 3.25.8",
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { notTemplateSample } from "../template-studio/repo.js";
2
3
  import { bookSchema } from "../voices/model.js";
3
4
  import { formats } from "./model.js";
4
5
  import { liveProject } from "./repo.js";
@@ -33,7 +34,7 @@ const headSelect = `SELECT projects.id, projects.title, projects.format, project
33
34
  FROM projects LEFT JOIN project_controls ON project_controls.project_id = projects.id`;
34
35
  export function listProjectHeads(db) {
35
36
  return db
36
- .prepare(`${headSelect} WHERE ${liveProject()} ORDER BY created_at DESC, id DESC`)
37
+ .prepare(`${headSelect} WHERE ${liveProject()} AND ${notTemplateSample()} ORDER BY created_at DESC, id DESC`)
37
38
  .all()
38
39
  .map((raw) => {
39
40
  const row = headRow.parse(raw);
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { failureId, stageKinds, stageStates } from "../../kernel/pipeline.js";
3
+ import { notTemplateSample } from "../template-studio/repo.js";
3
4
  import { admittedInItsState, parkedOnReview } from "./admitted-work.js";
4
5
  import { formats, stageSources } from "./model.js";
5
6
  import { runConfigSchema } from "./schema.js";
@@ -78,7 +79,7 @@ export function projectExists(db, id) {
78
79
  // Newest first.
79
80
  export function listProjects(db) {
80
81
  return db
81
- .prepare(`${projectSelect} WHERE ${liveProject()} ORDER BY created_at DESC, id DESC`)
82
+ .prepare(`${projectSelect} WHERE ${liveProject()} AND ${notTemplateSample()} ORDER BY created_at DESC, id DESC`)
82
83
  .all()
83
84
  .map((row) => toProject(projectRow.parse(row)));
84
85
  }
@@ -1,9 +1,11 @@
1
1
  import { stageKinds } from "../../kernel/pipeline.js";
2
2
  import { imageScaleProblem } from "../images/scale.js";
3
+ import { placedProblems } from "../keywords/rules.js";
3
4
  import { loudnessFields } from "../loudness/model.js";
4
5
  import { pauseFields } from "../narration/pauses-model.js";
5
6
  import { reviewFields } from "../reviews/rules.js";
6
7
  import { shortsSettingsProblems } from "../shorts/model.js";
8
+ import { usesPostPrompt } from "../social/rules.js";
7
9
  import { ambientBedProblems, usesAmbientBed } from "../video/ambient-bed.js";
8
10
  import { usesAnimation, videoEditProblems } from "../video/edit-settings.js";
9
11
  import { usesVoices, voicesProblems } from "../voices/model.js";
@@ -75,6 +77,8 @@ export function admit(input) {
75
77
  else if (draft.title.length > titleMax) {
76
78
  fields.push({ field: "title", message: `Keep the title to ${titleMax} characters or fewer.` });
77
79
  }
80
+ // Slopify fills its keywords from the title, so the title can't use them.
81
+ fields.push(...placedProblems([{ field: "title", place: "title", body: draft.title }], draft.values));
78
82
  for (const kind of stageKinds) {
79
83
  if (!allowedSources[kind].includes(sourceOf(sources, kind))) {
80
84
  fields.push({
@@ -83,7 +87,7 @@ export function admit(input) {
83
87
  });
84
88
  }
85
89
  }
86
- if (needsLlmFor(draft) && !chosen(draft.llm)) {
90
+ if (needsLlmFor(draft, input.keywordUses) && !chosen(draft.llm)) {
87
91
  fields.push({ field: "llm", message: "Choose a text (LLM) provider and model." });
88
92
  }
89
93
  if (sources.article === "generate" && blank(draft.articlePrompt)) {
@@ -132,7 +136,7 @@ export function admit(input) {
132
136
  fields.push(...reviewFields(draft));
133
137
  if (usesReference(draft) && draft.reference?.source === "provide")
134
138
  checkFile(input.staged, draft.provided.reference, "images", "provided.reference", "Upload the establishing image, or set Establishing image to Off in the Images section.", fields);
135
- checkProvided(draft, input.staged, fields);
139
+ checkProvided(draft, input.staged, fields, input.keywordUses);
136
140
  checkValues(draft, input.requiredSlots, fields);
137
141
  if (!Number.isFinite(draft.silenceGapSeconds) ||
138
142
  draft.silenceGapSeconds < 0 ||
@@ -219,7 +223,7 @@ function checkImagePrompts(draft, fields) {
219
223
  if (scaleProblem !== undefined)
220
224
  fields.push({ field: "imageScale", message: scaleProblem });
221
225
  }
222
- function checkProvided(draft, staged, fields) {
226
+ function checkProvided(draft, staged, fields, uses = {}) {
223
227
  const { sources, provided } = draft;
224
228
  if (sources.research === "provide" && blank(provided.research)) {
225
229
  fields.push({
@@ -230,7 +234,7 @@ function checkProvided(draft, staged, fields) {
230
234
  if (sources.article === "provide" && blank(provided.article)) {
231
235
  fields.push({ field: "provided.article", message: "Paste the article." });
232
236
  }
233
- fields.push(...articleOffFields(draft));
237
+ fields.push(...articleOffFields(draft, uses));
234
238
  if (sources.audio === "provide") {
235
239
  checkFile(staged, provided.audio, "audio", "provided.audio", "Pick an audio file.", fields);
236
240
  }
@@ -318,7 +322,7 @@ function checkValues(draft, requiredSlots, fields) {
318
322
  function blank(value) {
319
323
  return value === undefined || value.trim() === "";
320
324
  }
321
- export function llmUses(draft) {
325
+ export function llmUses(draft, keywords = {}) {
322
326
  const sources = draft.sources;
323
327
  const uses = [];
324
328
  if (sources.research === "generate")
@@ -341,10 +345,16 @@ export function llmUses(draft) {
341
345
  uses.push({ section: "outputs", label: "writing the YouTube description" });
342
346
  if (usesShorts(draft))
343
347
  uses.push({ section: "outputs", label: "picking the shorts" });
348
+ if (usesPostPrompt(draft))
349
+ uses.push({ section: "outputs", label: "writing the posts for other sites" });
350
+ if (keywords.scenes !== undefined)
351
+ uses.push({ section: "outputs", label: "writing image scenes" });
352
+ if (keywords.digest !== undefined)
353
+ uses.push({ section: "outputs", label: "writing the keyword digest" });
344
354
  return uses;
345
355
  }
346
- export function needsLlmFor(draft) {
347
- return llmUses(draft).length > 0;
356
+ export function needsLlmFor(draft, keywords = {}) {
357
+ return llmUses(draft, keywords).length > 0;
348
358
  }
349
359
  export function usesNarrationPreparation(draft) {
350
360
  return draft.sources.audio === "generate" && !blank(draft.narrationPrompt);
@@ -506,7 +516,7 @@ export function usesPronunciationGlossary(draft) {
506
516
  }
507
517
  // Article Off: a project of images or a thumbnail from prompts, with no text at all. What reads
508
518
  // the article can't be on with it.
509
- export function articleOffFields(draft) {
519
+ export function articleOffFields(draft, keywords = {}) {
510
520
  const { sources } = draft;
511
521
  if (sources.article !== "off")
512
522
  return [];
@@ -569,5 +579,30 @@ export function articleOffFields(draft) {
569
579
  },
570
580
  ]
571
581
  : []),
582
+ // The keywords Slopify writes from the article (`keywords/specs.ts`).
583
+ ...(keywords.digest === undefined
584
+ ? []
585
+ : [
586
+ {
587
+ field: keywords.digest,
588
+ message: `{{Slopify_Summary}}, {{Slopify_Hook}} and the other digest keywords are written from the article, and Article is Off. ${fix}, or remove them from this prompt.`,
589
+ },
590
+ ]),
591
+ ...(keywords.chapters === undefined
592
+ ? []
593
+ : [
594
+ {
595
+ field: keywords.chapters,
596
+ message: `{{Slopify_Chapters}} lists the article's chapters, and Article is Off. ${fix}, or remove it from this prompt.`,
597
+ },
598
+ ]),
599
+ ...(keywords.scenes === undefined
600
+ ? []
601
+ : [
602
+ {
603
+ field: keywords.scenes,
604
+ message: `{{Slopify_Scene}}, {{Slopify_Setting}} and {{Slopify_Mood}} are written from the article, and Article is Off. ${fix}, or remove them from this prompt.`,
605
+ },
606
+ ]),
572
607
  ];
573
608
  }
@@ -4,9 +4,11 @@ import { thinkingModes } from "../../kernel/ports/llm.js";
4
4
  import { castSnapshotSchema } from "../channels/schema.js";
5
5
  import { checkpointStageSchema } from "../checkpoints/schema.js";
6
6
  import { documentSettingsSchema } from "../document/theme-schema.js";
7
+ import { keywordFactsSchema } from "../keywords/schema.js";
7
8
  import { narrationAliasesSchema } from "../narration/aliases-schema.js";
8
9
  import { chunkModes } from "../narration/chunk.js";
9
10
  import { reviewModes, reviewStages } from "../reviews/model.js";
11
+ import { socialSettingsSchema } from "../social/schema.js";
10
12
  import { subtitleConfigSchema } from "../subtitles/model.js";
11
13
  import { ambientBedSchema } from "../video/ambient-bed-schema.js";
12
14
  import { animateModes, atmospheres, colorGrades, cutModes, lookLevels, transitionKinds, } from "../video/edit-settings.js";
@@ -156,6 +158,7 @@ export const runDraftSchema = z.object({
156
158
  .array(z.object({ title: z.string(), summary: z.string() }))
157
159
  .readonly()
158
160
  .optional(),
161
+ keywordFacts: keywordFactsSchema.optional(),
159
162
  titleStyle: z
160
163
  .object({
161
164
  fontId: z
@@ -193,6 +196,11 @@ export const runDraftSchema = z.object({
193
196
  levelSentences: z.boolean().optional(),
194
197
  // Absent is English (`kernel/ports/languages.ts`).
195
198
  language: languageSchema.optional(),
199
+ // Posting to other sites (`slices/social`); absent is posting nowhere.
200
+ social: socialSettingsSchema.optional(),
201
+ // A template studio sample (`slices/template-studio/scope.ts`): its steps make a short taste
202
+ // of the video, not all of it. Absent on every other project, whose steps it never changes.
203
+ sample: z.object({ fullScript: z.boolean() }).strict().optional(),
196
204
  });
197
205
  export const runConfigSchema = runDraftSchema.extend({
198
206
  rendered: z.record(z.string(), z.string()),
@@ -16,6 +16,3 @@ export function finishStage(db, stageId, state, failureReason, at) {
16
16
  export function resetStage(db, stageId) {
17
17
  db.prepare("UPDATE stages SET state = 'pending', failure_reason = NULL, failure_kind = NULL, retry_at = NULL, attempt_count = 0, progress_current = NULL, progress_total = NULL, started_at = NULL, finished_at = NULL WHERE id = ?").run(stageId);
18
18
  }
19
- export function setStageProgress(db, stageId, current, total) {
20
- db.prepare("UPDATE stages SET progress_current = ?, progress_total = ? WHERE id = ?").run(current, total, stageId);
21
- }
@@ -4,6 +4,8 @@ import { storeArticleText } from "../article/store.js";
4
4
  import { resolveChannelId, setProjectChannel } from "../channels/repo.js";
5
5
  import { defaultDocumentTheme } from "../document/model.js";
6
6
  import { earlierEpisodesFor } from "../episodes/related.js";
7
+ import { keywordFactsFor } from "../keywords/facts.js";
8
+ import { usesFacts } from "../keywords/rules.js";
7
9
  import { listNarrationAliases } from "../narration/aliases-library.js";
8
10
  import { currentPronunciationRules } from "../narration/pronunciation.js";
9
11
  import { collectSharedGlossary } from "../narration/shared-glossary.js";
@@ -33,37 +35,8 @@ export function initialState(source) {
33
35
  export function startRun(deps, draft, rendered, retainStaged = false, templates) {
34
36
  const id = deps.ids.next();
35
37
  const at = deps.clock.now().toISOString();
36
- const shared = usesPronunciationGlossary(draft) && draft.audio?.shareGlossary === true
37
- ? collectSharedGlossary(deps).entries
38
- : [];
39
- // Library → Aliases as they are now: later edits there never change this project.
40
- const aliases = draft.sources.audio === "generate" && draft.audio?.useNarrationAliases === true
41
- ? listNarrationAliases(deps.db)
42
- : [];
43
- // The title may name keywords too ("History: {{Topic}}"), filled like a prompt's, so a
44
- // template or Play keeps the pattern and each project gets its own title.
45
- const title = render(draft.title, draft.values).trim() || draft.title;
46
- // The channel's related earlier episodes as they are now (`slices/episodes`): later edits
47
- // there never change this project. A draft never brings its own.
48
- const { earlierEpisodes: _carried, ...fresh } = draft;
49
- const earlier = earlierEpisodesFor(deps.db, draft, title);
50
- const config = {
51
- ...fresh,
52
- title,
53
- ...(title !== draft.title && detectSlots(draft.title).names.length > 0
54
- ? { titlePattern: draft.title }
55
- : {}),
56
- // A new project always names its document theme: a config without one reads as the
57
- // DiceMaster of older projects (see documentThemeOf), which no new project should get.
58
- ...(sourceOf(draft.sources, "document") === "generate" && draft.document === undefined
59
- ? { document: { theme: defaultDocumentTheme } }
60
- : {}),
61
- rendered,
62
- ...(shared.length === 0 ? {} : { sharedGlossary: shared }),
63
- ...(aliases.length === 0 ? {} : { narrationAliases: aliases }),
64
- pronunciationRules: currentPronunciationRules,
65
- ...(earlier === undefined ? {} : { earlierEpisodes: earlier }),
66
- };
38
+ const config = configOf(deps, draft, rendered, deps.clock.now(), templates);
39
+ const { title } = config;
67
40
  const project = {
68
41
  id,
69
42
  title,
@@ -130,6 +103,50 @@ export function startRun(deps, draft, rendered, retainStaged = false, templates)
130
103
  }
131
104
  return { project, stages };
132
105
  }
106
+ // The config a run starts with: the draft as filled, with what Slopify copies in when it starts
107
+ // (the shared glossary, Library → Aliases, the pronunciation rules, the channel's earlier
108
+ // episodes) as they are now. The template studio saves its sample's next revision from it.
109
+ export function configOf(deps, draft, rendered, now, templates,
110
+ // The project the config is for when it exists already (a template sample's next
111
+ // revision): it is neither its own previous video nor one it covered before.
112
+ projectId) {
113
+ const shared = usesPronunciationGlossary(draft) && draft.audio?.shareGlossary === true
114
+ ? collectSharedGlossary(deps).entries
115
+ : [];
116
+ // Library → Aliases as they are now: later edits there never change this project.
117
+ const aliases = draft.sources.audio === "generate" && draft.audio?.useNarrationAliases === true
118
+ ? listNarrationAliases(deps.db)
119
+ : [];
120
+ // The title may name keywords too ("History: {{Topic}}"), filled like a prompt's, so a
121
+ // template or Play keeps the pattern and each project gets its own title.
122
+ const title = render(draft.title, draft.values).trim() || draft.title;
123
+ // The channel's related earlier episodes as they are now (`slices/episodes`): later edits
124
+ // there never change this project. A draft never brings its own.
125
+ const { earlierEpisodes: _carried, keywordFacts: _facts, ...fresh } = draft;
126
+ const earlier = earlierEpisodesFor(deps.db, draft, title);
127
+ const titlePattern = title !== draft.title && detectSlots(draft.title).names.length > 0 ? draft.title : undefined;
128
+ // The facts the keywords Slopify fills come from, as they are now, and only when a picked
129
+ // prompt asks for one: a run that uses none saves the config it always did.
130
+ const facts = usesFacts(Object.values(templates ?? rendered))
131
+ ? keywordFactsFor(deps, { ...draft, title, titlePattern }, now, projectId)
132
+ : undefined;
133
+ return {
134
+ ...fresh,
135
+ title,
136
+ ...(titlePattern === undefined ? {} : { titlePattern }),
137
+ // A new project always names its document theme: a config without one reads as the
138
+ // DiceMaster of older projects (see documentThemeOf), which no new project should get.
139
+ ...(sourceOf(draft.sources, "document") === "generate" && draft.document === undefined
140
+ ? { document: { theme: defaultDocumentTheme } }
141
+ : {}),
142
+ rendered,
143
+ ...(shared.length === 0 ? {} : { sharedGlossary: shared }),
144
+ ...(aliases.length === 0 ? {} : { narrationAliases: aliases }),
145
+ pronunciationRules: currentPronunciationRules,
146
+ ...(earlier === undefined ? {} : { earlierEpisodes: earlier }),
147
+ ...(facts === undefined ? {} : { keywordFacts: facts }),
148
+ };
149
+ }
133
150
  function attachProvided(deps, projectId, draft, collected, retainStaged) {
134
151
  const { sources, provided } = draft;
135
152
  if (sources.research === "provide" && provided.research !== undefined) {
@@ -2,6 +2,7 @@
2
2
  // name case-sensitive and free to hold anything but `{`, `}` and a newline (so
3
3
  // `{{Middle of Words}}` is one name). There is no escape syntax. Hand-rolled on purpose: a
4
4
  // template library would bring its own grammar.
5
+ import { keywordNames, legacyNames } from "../keywords/specs.js";
5
6
  export const slotLintKinds = ["unclosed", "empty", "nested"];
6
7
  export const fieldGroups = ["common", "text", "image"];
7
8
  // A well-formed slot: no brace and no newline between the delimiters.
@@ -38,16 +39,18 @@ export function detectSlots(body) {
38
39
  return { names, errors };
39
40
  }
40
41
  // Filled by Slopify, never by the person: each image's own scene, which the project's AI model
41
- // writes from the article when an image prompt asks for it (`images/scenes.ts`).
42
+ // writes from the article when an image prompt asks for it (`images/scenes.ts`). The old name
43
+ // of {{Slopify_Scene}}, which keeps its own meaning (`keywords/specs.ts`).
42
44
  export const sceneKeyword = "Scene";
43
45
  // Filled by Slopify too: how the video's subject and the characters in an image look, which the
44
46
  // project's AI model looks up on the web when a prompt asks for it (`images/appearance.ts`).
45
47
  export const appearanceKeyword = "Appearance";
46
- // The keywords Slopify fills itself, which are never fields for the person.
47
- export const filledKeywords = [sceneKeyword, appearanceKeyword];
48
+ // The keywords Slopify fills itself, which are never fields for the person: every `Slopify_`
49
+ // keyword and the two old names. An unknown `Slopify_` name is not one of them.
50
+ export const filledKeywords = [...keywordNames, ...legacyNames];
48
51
  // One field per distinct name; Common when a name is used on both sides, otherwise Text or
49
52
  // Image. The order is first appearance, which is why both sides arrive as ordered lists of
50
- // bodies rather than as sets. The scene and appearance keywords are not fields: Slopify fills them.
53
+ // bodies rather than as sets. The keywords Slopify fills are not fields.
51
54
  export function collectFields(textBodies, imageBodies) {
52
55
  const text = namesOf(textBodies);
53
56
  const image = namesOf(imageBodies);
@@ -0,0 +1,31 @@
1
+ import { projectById } from "../admission/repo.js";
2
+ import { platformLabels } from "../social/model.js";
3
+ import { postsIn } from "../social/repo.js";
4
+ // Posts the extension filled in a site's form, waiting for the person to press Post (or
5
+ // Schedule) there, and fills that stopped. Posting is the person's: an agent reports them.
6
+ export function postsWaiting(deps) {
7
+ return postsIn(deps.db, ["filled", "failed"]).flatMap((post) => {
8
+ const project = projectById(deps.db, post.projectId);
9
+ if (project === undefined)
10
+ return [];
11
+ const site = platformLabels[post.platform];
12
+ const filled = post.state === "filled";
13
+ return [
14
+ {
15
+ kind: filled ? "post-due" : "extension-failure",
16
+ since: (filled ? post.filledAt : post.updatedAt) ?? post.updatedAt ?? "",
17
+ projectId: post.projectId,
18
+ title: project.title,
19
+ stage: "video",
20
+ itemKey: `social:${post.platform}`,
21
+ reason: filled
22
+ ? `The ${site} post is filled in ${site}'s form and waits for the person to press Post there. Posting is the person's: report it.`
23
+ : `Filling the ${site} post failed: ${post.message ?? "the extension gave no reason."} Fixing it is the extension's or the person's: report it.`,
24
+ autoRetryInFlight: false,
25
+ agentCanAct: false,
26
+ ids: { projectId: post.projectId, platform: post.platform },
27
+ tools: ["get_post_packs"],
28
+ },
29
+ ];
30
+ });
31
+ }
@@ -0,0 +1,33 @@
1
+ import { z } from "zod";
2
+ import { notReadySentence } from "../template-studio/schedule-gate.js";
3
+ import { rowsOf } from "./attention-work.js";
4
+ const scheduleRow = z.object({ id: z.string(), name: z.string(), template_id: z.string() });
5
+ // An active schedule whose gated template has no version it may run skips every run until the
6
+ // person marks one ready: the agent reports it. The gate keeps no time it began, so it is dated
7
+ // when it is read.
8
+ export function templatesNotReady(db, now, skip, notReady = (id) => notReadySentence(db, id)) {
9
+ const rows = db
10
+ .prepare("SELECT id, name, template_id FROM schedules WHERE deleted_at IS NULL AND status='active' AND template_kind='project' ORDER BY lower(name), id")
11
+ .all();
12
+ return rowsOf(rows, "schedules", skip, (value) => {
13
+ const row = scheduleRow.parse(value);
14
+ const sentence = notReady(row.template_id);
15
+ return sentence === undefined
16
+ ? []
17
+ : [
18
+ {
19
+ kind: "template-not-ready",
20
+ since: now.toISOString(),
21
+ projectId: null,
22
+ title: row.name,
23
+ stage: null,
24
+ itemKey: null,
25
+ reason: `${sentence} Marking a version ready is the person's: report it.`,
26
+ autoRetryInFlight: false,
27
+ agentCanAct: false,
28
+ ids: { id: row.id, templateId: row.template_id },
29
+ tools: ["get_schedule", "get_template_checks"],
30
+ },
31
+ ];
32
+ });
33
+ }