@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,14 +1,18 @@
1
1
  import { z } from "zod";
2
2
  import { transact } from "../../kernel/db/tx.js";
3
+ import { errorText } from "../../kernel/errors.js";
4
+ import { jsonBetween } from "../../kernel/json-answer.js";
3
5
  import { liveProject } from "../admission/repo.js";
4
6
  import { defaultChannelId } from "../channels/model.js";
5
7
  import { projectChannels, scheduleChannel } from "../channels/repo.js";
6
8
  import { channelVideoTitles } from "../channels/videos.js";
7
- import { readSetting, writeSetting } from "../settings/repo.js";
9
+ import { readJsonSetting, writeSetting } from "../settings/repo.js";
10
+ import { notTemplateSample } from "../template-studio/repo.js";
8
11
  import { scheduleById } from "./repo.js";
9
12
  import { heldTopicEditSchema, heldTopicSchema, queueMax, topicMoveSchema, topicQueueSchema, topicTitleSchema, topicTransferSchema, } from "./schema.js";
10
13
  import { newTopics } from "./similar.js";
11
- import { templateKeywords, topicRowProblems } from "./topic-list.js";
14
+ import { scheduleKeywords, topicRowProblems } from "./topic-list.js";
15
+ import { pinTopic, rankedItems, saveScores, scoreTopics } from "./topic-scores.js";
12
16
  // A schedule that finds its own topics asks an LLM whenever its queue holds fewer than
13
17
  // `keepAtLeast`. Only one generation per schedule runs at a time: a lease on the schedule row
14
18
  // (`topics_generating_at`) is taken before the LLM is asked and cleared after, so a tick and a
@@ -129,26 +133,40 @@ async function ask(deps, schedule, options) {
129
133
  catch (error) {
130
134
  if (options.signal?.aborted === true)
131
135
  return { ok: false, reason: "failed", message: "Slopify was closing." };
132
- const detail = error instanceof Error ? error.message : String(error);
136
+ const detail = errorText(error);
133
137
  return fail(`${llm.provider} (${llm.model}) did not answer: ${detail.trim().slice(0, 300)}. Check it in Settings → Providers, or pick another LLM under Edit → Topic generation.`);
134
138
  }
135
139
  const suggested = parseTopics(answer);
136
140
  if (suggested.length === 0)
137
141
  return fail(`${llm.provider} (${llm.model}) answered without a list of topics. Try again, or pick another LLM under Edit → Topic generation.`);
142
+ // Scored outside the transaction: search may take seconds, and never holds the database.
143
+ const scores = schedule.topicScoring === "off"
144
+ ? undefined
145
+ : await scoreTopics(deps, schedule.topicScoring, newTopics(suggested, known), known, template?.document.form.language ?? "en", options.signal);
138
146
  return transact(deps.db, () => {
139
147
  const current = scheduleById(deps.db, schedule.id);
140
148
  if (current === undefined || current.deletedAt !== null)
141
149
  return { ok: false, reason: "not-found", message: "This schedule no longer exists." };
142
150
  // Checked again against what is there now: a topic added while the LLM was thinking counts.
143
- const fresh = newTopics(suggested, knownTitles(deps, schedule.id)).slice(0, Math.max(0, Math.min(wanted, queueMax - current.items.length)));
151
+ const candidates = newTopics(suggested, knownTitles(deps, schedule.id));
152
+ // With scoring, the best-scored are kept; without, the LLM's own order, as before.
153
+ const fresh = (scores === undefined
154
+ ? candidates
155
+ : candidates.toSorted((a, b) => (scores.get(b)?.score ?? -1) - (scores.get(a)?.score ?? -1))).slice(0, Math.max(0, Math.min(wanted, queueMax - current.items.length)));
144
156
  if (fresh.length === 0)
145
157
  return fail("every topic the LLM suggested was already made, queued or turned down for this schedule. Add more detail to the series brief under Edit, so it has new ground to cover.");
146
158
  const at = deps.clock.now().toISOString();
147
159
  const mode = current.topicGeneration.mode === "hold" ? "hold" : "queue";
160
+ if (scores !== undefined)
161
+ saveScores(deps.db, schedule.id, new Map(fresh.flatMap((title) => {
162
+ const score = scores.get(title);
163
+ return score === undefined ? [] : [[title, score]];
164
+ })), at);
148
165
  if (mode === "queue") {
166
+ const items = [...current.items, ...fresh.map((title) => ({ title, values: {} }))];
149
167
  deps.db
150
168
  .prepare("UPDATE schedules SET items_json=?,version=version+1,updated_at=? WHERE id=? AND deleted_at IS NULL")
151
- .run(JSON.stringify([...current.items, ...fresh.map((title) => ({ title, values: {} }))]), at, schedule.id);
169
+ .run(JSON.stringify(scores === undefined ? items : rankedItems(deps.db, schedule.id, items)), at, schedule.id);
152
170
  }
153
171
  else {
154
172
  const start = nextRank(deps, schedule.id);
@@ -158,6 +176,8 @@ async function ask(deps, schedule, options) {
158
176
  VALUES (?,?,?,'held',?,?)`)
159
177
  .run(deps.uuid(), schedule.id, title, start + index, at);
160
178
  });
179
+ if (scores !== undefined)
180
+ rankHeld(deps, schedule.id);
161
181
  }
162
182
  deps.db
163
183
  .prepare("UPDATE schedules SET topics_generated_at=?,topics_failed_at=NULL,topics_error=NULL WHERE id=?")
@@ -180,6 +200,17 @@ async function ask(deps, schedule, options) {
180
200
  return { ok: true, added: fresh.length, mode };
181
201
  });
182
202
  }
203
+ // Held topics in score order: unscored ones first as they were, then the scored, best first.
204
+ export function rankHeld(deps, scheduleId) {
205
+ const held = deps.db
206
+ .prepare("SELECT id,title FROM schedule_topics WHERE schedule_id=? AND state='held' ORDER BY rank,created_at,id")
207
+ .all(scheduleId)
208
+ .map((row) => z.object({ id: z.string(), title: z.string() }).parse(row));
209
+ const rank = deps.db.prepare("UPDATE schedule_topics SET rank=? WHERE id=?");
210
+ rankedItems(deps.db, scheduleId, held).forEach((topic, index) => {
211
+ rank.run(index, topic.id);
212
+ });
213
+ }
183
214
  function nextRank(deps, scheduleId) {
184
215
  const row = deps.db
185
216
  .prepare("SELECT max(rank) AS rank FROM schedule_topics WHERE schedule_id=? AND state='held'")
@@ -200,7 +231,8 @@ export function knownTitles(deps, scheduleId) {
200
231
  const channel = scheduleChannel(deps.db, scheduleId);
201
232
  const channels = projectChannels(deps.db);
202
233
  const projects = deps.db
203
- .prepare(`SELECT id,title FROM projects WHERE ${liveProject()} ORDER BY created_at DESC`)
234
+ // A template's studio sample is no video of the channel's.
235
+ .prepare(`SELECT id,title FROM projects WHERE ${liveProject()} AND ${notTemplateSample()} ORDER BY created_at DESC`)
204
236
  .all()
205
237
  .map((row) => z.object({ id: z.string(), title: z.string() }).parse(row))
206
238
  .filter((row) => channel === undefined || (channels.get(row.id) ?? defaultChannelId) === channel.id)
@@ -254,20 +286,8 @@ export function topicMessages(schedule, templateTitle, known, count) {
254
286
  }
255
287
  // A JSON array of strings, or failing that one topic per line with bullets and numbers dropped.
256
288
  export function parseTopics(answer) {
257
- const start = answer.indexOf("[");
258
- const end = answer.lastIndexOf("]");
259
- let lines;
260
- if (start !== -1 && end > start) {
261
- try {
262
- const parsed = JSON.parse(answer.slice(start, end + 1));
263
- if (Array.isArray(parsed))
264
- lines = parsed;
265
- }
266
- catch {
267
- lines = undefined;
268
- }
269
- }
270
- lines ??= answer.split(/\r?\n/);
289
+ const parsed = jsonBetween(answer, "[", "]");
290
+ const lines = Array.isArray(parsed) ? parsed : answer.split(/\r?\n/);
271
291
  return lines
272
292
  .map((line) => typeof line === "string"
273
293
  ? line
@@ -286,16 +306,7 @@ export function parseTopics(answer) {
286
306
  export const heldValuesKey = "schedules.held-topic-values";
287
307
  const heldValuesSchema = z.record(z.string(), z.record(z.string(), z.string()));
288
308
  function readHeldValues(db) {
289
- const raw = readSetting(db, heldValuesKey);
290
- if (raw === undefined)
291
- return {};
292
- try {
293
- const parsed = heldValuesSchema.safeParse(JSON.parse(raw));
294
- return parsed.success ? parsed.data : {};
295
- }
296
- catch {
297
- return {};
298
- }
309
+ return readJsonSetting(db, heldValuesKey, heldValuesSchema, {});
299
310
  }
300
311
  // Entries of topics that are no longer held (approved, turned down, or gone with their
301
312
  // schedule) are dropped on every write.
@@ -394,11 +405,11 @@ export function editHeldTopic(deps, scheduleId, topicId, input) {
394
405
  const schedule = scheduleById(deps.db, scheduleId);
395
406
  if (schedule === undefined)
396
407
  return { ok: false, reason: "not-found" };
397
- const template = deps.template(schedule.templateId);
398
- if (template === undefined)
408
+ const known = scheduleKeywords(deps, schedule.templateId);
409
+ if (known === undefined)
399
410
  return { ok: false, reason: "missing-template" };
400
411
  const problems = topicRowProblems([{ title: parsed.data, values }], {
401
- keywords: templateKeywords(template.document.form).filter((name) => name !== schedule.topicKeyword),
412
+ keywords: known.filter((name) => name !== schedule.topicKeyword),
402
413
  topicKeyword: schedule.topicKeyword,
403
414
  });
404
415
  if (problems.length > 0)
@@ -447,6 +458,7 @@ export function moveTopic(deps, scheduleId, input) {
447
458
  return { ok: false, reason: "invalid-input" };
448
459
  items.splice(Math.min(parsed.data.to, items.length), 0, topic);
449
460
  writeItems(deps, scheduleId, schedule.version, items);
461
+ pinTopic(deps.db, scheduleId, topic.title);
450
462
  return { ok: true, value: scheduleById(deps.db, scheduleId) };
451
463
  });
452
464
  }
@@ -463,11 +475,11 @@ export function replaceTopics(deps, scheduleId, input) {
463
475
  const schedule = open.value;
464
476
  if (schedule.version !== parsed.data.baseVersion)
465
477
  return { ok: false, reason: "conflict" };
466
- const template = deps.template(schedule.templateId);
467
- if (!template)
478
+ const keywords = scheduleKeywords(deps, schedule.templateId);
479
+ if (keywords === undefined)
468
480
  return { ok: false, reason: "missing-template" };
469
481
  const problems = topicRowProblems(parsed.data.items, {
470
- keywords: templateKeywords(template.document.form),
482
+ keywords,
471
483
  topicKeyword: schedule.topicKeyword,
472
484
  kept: schedule.items,
473
485
  });
@@ -2,9 +2,10 @@ import { constants } from "node:fs";
2
2
  import { access, stat } from "node:fs/promises";
3
3
  import { extname, isAbsolute } from "node:path";
4
4
  import { z } from "zod";
5
+ import { isUnreachablePath } from "../../kernel/errors.js";
5
6
  import { cliProbeTimeoutMs, readinessFromProbe } from "./cli-status.js";
6
7
  import { providerById } from "./model.js";
7
- import { readSetting, writeSetting } from "./repo.js";
8
+ import { readJsonSetting, writeSetting } from "./repo.js";
8
9
  export const cliPathMaxLength = 4096;
9
10
  const pathSchema = z
10
11
  .string()
@@ -20,8 +21,7 @@ export function cliPathStatus(db, id) {
20
21
  const provider = providerById(id);
21
22
  if (provider.auth !== "cli")
22
23
  throw new Error(`${id} is not a CLI provider`);
23
- const stored = readSetting(db, `cli.path.${installationId(id)}`);
24
- const configured = stored === undefined ? null : storedPathSchema.parse(JSON.parse(stored));
24
+ const configured = readJsonSetting(db, `cli.path.${installationId(id)}`, storedPathSchema, null);
25
25
  return { configured, command: configured ?? provider.binary };
26
26
  }
27
27
  // Read on each attempt, so a saved path reaches the next invocation without a restart.
@@ -82,9 +82,7 @@ async function save(deps, id, raw) {
82
82
  : constants.X_OK);
83
83
  }
84
84
  catch (error) {
85
- if (error instanceof Error &&
86
- "code" in error &&
87
- ["ENOENT", "ENOTDIR", "EACCES", "EPERM", "ELOOP"].includes(String(error.code)))
85
+ if (isUnreachablePath(error))
88
86
  return {
89
87
  ok: false,
90
88
  message: "Slopify cannot find or run a program at this path. Check the path is correct and the file is allowed to run.",
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { readinessIsUsable } from "../../kernel/ports/model.js";
3
- import { keyedProviders, readSetting, writeSetting } from "./repo.js";
3
+ import { keyedProviders, readJsonSetting, readSetting, writeSetting } from "./repo.js";
4
4
  // What a fresh Play form picks before the user picks anything. Written once, on the first
5
5
  // launch that finds a command-line tool, and read by Play for every new draft.
6
6
  const choiceSchema = z.object({ provider: z.string().min(1), model: z.string() }).strict();
@@ -13,16 +13,7 @@ const doneKey = "first-run.done";
13
13
  // The order a text tool is preferred in when several are found.
14
14
  const textPreference = ["claude-code", "codex", "gemini"];
15
15
  export function readProviderDefaults(db) {
16
- const raw = readSetting(db, defaultsKey);
17
- if (raw === undefined)
18
- return {};
19
- try {
20
- const parsed = providerDefaultsSchema.safeParse(JSON.parse(raw));
21
- return parsed.success ? parsed.data : {};
22
- }
23
- catch {
24
- return {};
25
- }
16
+ return readJsonSetting(db, defaultsKey, providerDefaultsSchema, {});
26
17
  }
27
18
  export function dismissFirstRun(db) {
28
19
  writeSetting(db, doneKey, "1");
@@ -1,3 +1,4 @@
1
+ import { probeRead } from "./probe-read.js";
1
2
  import { keyOf } from "./repo.js";
2
3
  // ceiling: a provider chosen in many templates still costs a handful of reads, not one per
3
4
  // model anywhere in the library.
@@ -12,23 +13,8 @@ export async function modelReach(deps, provider, models) {
12
13
  const known = reads.get(url);
13
14
  if (known !== undefined)
14
15
  return known;
15
- const started = (async () => {
16
- try {
17
- const response = await deps.fetch(url, {
18
- headers: probe.headers(key),
19
- signal: AbortSignal.timeout(15_000),
20
- redirect: "error",
21
- });
22
- // A list is read whole; a single model's page only for its status.
23
- const body = probe.model?.lists === undefined ? "" : await response.text();
24
- if (probe.model?.lists === undefined)
25
- await response.body?.cancel().catch(() => { });
26
- return { status: response.status, body };
27
- }
28
- catch {
29
- return undefined;
30
- }
31
- })();
16
+ // A list is read whole; a single model's page only for its status.
17
+ const started = probeRead(deps.fetch, url, probe.headers(key), probe.model?.lists !== undefined);
32
18
  reads.set(url, started);
33
19
  return started;
34
20
  };
@@ -37,6 +37,13 @@ export const systemVoiceProvider = "system-voice";
37
37
  export function isUncataloguedProvider(id) {
38
38
  return isLocalCliProvider(id) || id === systemVoiceProvider;
39
39
  }
40
+ // The local stand-ins the bundled samples were made with (`src/sample-build/`): no catalogue
41
+ // lists them, they cost nothing, and they exist only while the sample archives are built, so
42
+ // a sample project names them but nothing new can run on them.
43
+ export const sampleStandInProviders = ["sample-writer", "sample-artist", "sample-voice"];
44
+ export function isSampleStandIn(id) {
45
+ return sampleStandInProviders.includes(id);
46
+ }
40
47
  export const providers = [
41
48
  { id: "openrouter", family: "llm", displayName: "OpenRouter", auth: "key" },
42
49
  {
@@ -137,4 +144,7 @@ export const defaultSettings = {
137
144
  appearance: "system",
138
145
  loudness: defaultLoudness,
139
146
  subtitleModel: defaultEnglishSubtitleModel,
147
+ videoEncoder: "cpu",
148
+ clipEncodeSpeed: "thorough",
149
+ renderPriority: "background",
140
150
  };
@@ -2,17 +2,24 @@ import { z } from "zod";
2
2
  import { englishSubtitleModels } from "../../kernel/ports/subtitles.js";
3
3
  import { silenceGapSecondsMax } from "../admission/rules.js";
4
4
  import { loudnessFields } from "../loudness/model.js";
5
+ import { clipEncodeSpeeds, renderPriorities, videoEncoders } from "../video/encoding.js";
5
6
  import { appearances, defaultSettings } from "./model.js";
6
- import { readSetting, writeSetting } from "./repo.js";
7
+ import { readJsonSetting, writeSetting } from "./repo.js";
7
8
  // The `settings` table is key/value, so each setting is its own row and a value the app
8
9
  // does not understand costs only that one setting.
9
10
  const silenceGapKey = "silenceGapSeconds";
10
11
  const appearanceKey = "appearance";
11
12
  const loudnessKey = "loudness";
12
13
  const subtitleModelKey = "subtitleModel";
14
+ const videoEncoderKey = "videoEncoder";
15
+ const clipEncodeSpeedKey = "clipEncodeSpeed";
16
+ const renderPriorityKey = "renderPriority";
13
17
  const silenceGapValue = z.number().int().min(0).max(silenceGapSecondsMax);
14
18
  const appearanceValue = z.enum(appearances);
15
19
  const subtitleModelValue = z.enum(englishSubtitleModels);
20
+ const videoEncoderValue = z.enum(videoEncoders);
21
+ const clipEncodeSpeedValue = z.enum(clipEncodeSpeeds);
22
+ const renderPriorityValue = z.enum(renderPriorities);
16
23
  const loudnessValue = z.object({
17
24
  enabled: z.boolean(),
18
25
  videoLufs: z.number(),
@@ -24,6 +31,9 @@ export function readSettings(deps) {
24
31
  appearance: parsed(deps, appearanceKey, appearanceValue, defaultSettings.appearance),
25
32
  loudness: parsed(deps, loudnessKey, loudnessValue, defaultSettings.loudness),
26
33
  subtitleModel: parsed(deps, subtitleModelKey, subtitleModelValue, defaultSettings.subtitleModel),
34
+ videoEncoder: parsed(deps, videoEncoderKey, videoEncoderValue, defaultSettings.videoEncoder),
35
+ clipEncodeSpeed: parsed(deps, clipEncodeSpeedKey, clipEncodeSpeedValue, defaultSettings.clipEncodeSpeed),
36
+ renderPriority: parsed(deps, renderPriorityKey, renderPriorityValue, defaultSettings.renderPriority),
27
37
  };
28
38
  }
29
39
  export function saveSettings(deps, settings) {
@@ -49,6 +59,24 @@ export function saveSettings(deps, settings) {
49
59
  message: "Choose Most accurate, Balanced or Smallest in Settings → Production defaults → Subtitle timing model.",
50
60
  });
51
61
  }
62
+ if (!videoEncoderValue.safeParse(settings.videoEncoder).success) {
63
+ fields.push({
64
+ field: "videoEncoder",
65
+ message: "Choose Processor (x264) or NVIDIA graphics card (NVENC) in Settings → Production defaults → Video encoder.",
66
+ });
67
+ }
68
+ if (!clipEncodeSpeedValue.safeParse(settings.clipEncodeSpeed).success) {
69
+ fields.push({
70
+ field: "clipEncodeSpeed",
71
+ message: "Choose Thorough or Fast in Settings → Production defaults → Clip encoding.",
72
+ });
73
+ }
74
+ if (!renderPriorityValue.safeParse(settings.renderPriority).success) {
75
+ fields.push({
76
+ field: "renderPriority",
77
+ message: "Choose Normal or Background in Settings → Production defaults → Render priority.",
78
+ });
79
+ }
52
80
  if (fields.length > 0) {
53
81
  return { ok: false, fields };
54
82
  }
@@ -56,31 +84,14 @@ export function saveSettings(deps, settings) {
56
84
  writeSetting(deps.db, appearanceKey, JSON.stringify(settings.appearance));
57
85
  writeSetting(deps.db, loudnessKey, JSON.stringify(settings.loudness));
58
86
  writeSetting(deps.db, subtitleModelKey, JSON.stringify(settings.subtitleModel));
87
+ writeSetting(deps.db, videoEncoderKey, JSON.stringify(settings.videoEncoder));
88
+ writeSetting(deps.db, clipEncodeSpeedKey, JSON.stringify(settings.clipEncodeSpeed));
89
+ writeSetting(deps.db, renderPriorityKey, JSON.stringify(settings.renderPriority));
59
90
  return { ok: true, settings };
60
91
  }
61
92
  // A stored value is JSON, and a hand-edited database is the only way it can be anything
62
93
  // but what was saved. Such a row falls back to the default and says so in the log rather
63
94
  // than reaching the app or taking the settings page down.
64
95
  function parsed(deps, key, schema, fallback) {
65
- const stored = readSetting(deps.db, key);
66
- if (stored === undefined) {
67
- return fallback;
68
- }
69
- const result = schema.safeParse(jsonOf(stored));
70
- if (result.success) {
71
- return result.data;
72
- }
73
- deps.log.write("warn", "settings.invalid", {
74
- detail: `setting ${key} is not a value this build understands; using the default`,
75
- });
76
- return fallback;
77
- }
78
- function jsonOf(stored) {
79
- try {
80
- return JSON.parse(stored);
81
- }
82
- catch {
83
- // Not JSON at all: the same answer as JSON of the wrong shape, decided by the caller.
84
- return undefined;
85
- }
96
+ return readJsonSetting(deps.db, key, schema, fallback, deps.log);
86
97
  }
@@ -0,0 +1,16 @@
1
+ export async function probeRead(fetch, url, headers, withBody) {
2
+ try {
3
+ const response = await fetch(url, {
4
+ headers,
5
+ signal: AbortSignal.timeout(15_000),
6
+ redirect: "error",
7
+ });
8
+ const body = withBody ? await response.text() : "";
9
+ if (!withBody)
10
+ await response.body?.cancel().catch(() => { });
11
+ return { status: response.status, body };
12
+ }
13
+ catch {
14
+ return undefined;
15
+ }
16
+ }
@@ -59,6 +59,39 @@ export function readSetting(db, key) {
59
59
  const row = db.prepare("SELECT value FROM settings WHERE key = ?").get(key);
60
60
  return row === undefined ? undefined : settingRow.parse(row).value;
61
61
  }
62
+ // A list of strings that keeps its strings when something else sits among them.
63
+ export const stringListSchema = z
64
+ .array(z.unknown())
65
+ .transform((items) => items.filter((one) => typeof one === "string"));
66
+ const warnedSettings = new Set();
67
+ // A row that is absent reads as `fallback`. So does a row that is not JSON or does not fit the
68
+ // schema (a hand-edited database, or a newer Slopify's), which also leaves one warning naming the
69
+ // setting, never its value, so a damaged row neither stops the app nor goes unnoticed. With no
70
+ // `log` to hand the warning goes to the process warnings, once per setting.
71
+ export function readJsonSetting(db, key, schema, fallback, log) {
72
+ const stored = readSetting(db, key);
73
+ if (stored === undefined)
74
+ return fallback;
75
+ try {
76
+ const parsed = schema.safeParse(JSON.parse(stored));
77
+ if (parsed.success)
78
+ return parsed.data;
79
+ }
80
+ catch {
81
+ // Not JSON: reported below with the rows that parse but do not fit.
82
+ }
83
+ const detail = `setting ${key} is not a value this build understands; using the default`;
84
+ if (log !== undefined)
85
+ log.write("warn", "settings.invalid", { detail });
86
+ else if (!warnedSettings.has(key)) {
87
+ warnedSettings.add(key);
88
+ process.emitWarning(detail, { code: "SLOPIFY_SETTING_INVALID" });
89
+ }
90
+ return fallback;
91
+ }
92
+ export function deleteSetting(db, key) {
93
+ db.prepare("DELETE FROM settings WHERE key = ?").run(key);
94
+ }
62
95
  export function writeSetting(db, key, value) {
63
96
  db.prepare("INSERT INTO settings (key, value) VALUES (?, ?)" +
64
97
  " ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(key, value);
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { transact } from "../../kernel/db/tx.js";
3
3
  import { fingerprint } from "../../kernel/runner/work.js";
4
- import { readSetting, writeSetting } from "./repo.js";
4
+ import { deleteSetting, readSetting, writeSetting } from "./repo.js";
5
5
  import { tutorialSessionSchema, tutorialWriteSchema, } from "./tutorial-schema.js";
6
6
  export { tutorialSessionSchema, tutorialWriteSchema, } from "./tutorial-schema.js";
7
7
  const recordSchema = z
@@ -61,6 +61,6 @@ export function saveTutorial(db, input) {
61
61
  }
62
62
  export function resetTutorial(db) {
63
63
  transact(db, () => {
64
- db.prepare("DELETE FROM settings WHERE key = ?").run("tutorial.session");
64
+ deleteSetting(db, "tutorial.session");
65
65
  });
66
66
  }
@@ -1,6 +1,6 @@
1
1
  import { isUniqueConstraint } from "../../kernel/db/index.js";
2
2
  import { providerById } from "./model.js";
3
- import { deleteVoice, insertVoice, listVoices, readSetting, updateVoiceLanguages, writeSetting, } from "./repo.js";
3
+ import { deleteSetting, deleteVoice, insertVoice, listVoices, readJsonSetting, stringListSchema, updateVoiceLanguages, writeSetting, } from "./repo.js";
4
4
  export const voiceNameMax = 200;
5
5
  export const voiceIdMax = 200;
6
6
  // A non-empty name and a non-empty voice ID, the ID unique within its provider, names free to
@@ -80,20 +80,12 @@ export function voices(deps) {
80
80
  // their ids, so the flag needs no column of its own and travels with a backup.
81
81
  export const realPersonVoicesKey = "voices.realPerson";
82
82
  export function realPersonVoiceIds(db) {
83
- const stored = readSetting(db, realPersonVoicesKey);
84
- if (stored === undefined)
85
- return new Set();
86
- try {
87
- const value = JSON.parse(stored);
88
- return new Set(Array.isArray(value) ? value.filter((one) => typeof one === "string") : []);
89
- }
90
- catch {
91
- return new Set();
92
- }
83
+ const value = readJsonSetting(db, realPersonVoicesKey, stringListSchema, []);
84
+ return new Set(value);
93
85
  }
94
86
  function writeRealPersonVoices(db, ids) {
95
87
  if (ids.length === 0)
96
- db.prepare("DELETE FROM settings WHERE key = ?").run(realPersonVoicesKey);
88
+ deleteSetting(db, realPersonVoicesKey);
97
89
  else
98
90
  writeSetting(db, realPersonVoicesKey, JSON.stringify([...new Set(ids)].sort()));
99
91
  }
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { jsonBetween } from "../../kernel/json-answer.js";
2
3
  import { youtubeTimestamp } from "../youtube/timestamps.js";
3
4
  import { shortHashtagsMax, shortHashtagsMin, shortTitleMax } from "./model.js";
4
5
  export const shortPickSchema = z.object({
@@ -128,7 +129,7 @@ const tolerance = 0.01;
128
129
  // Not being a JSON array of clips is the one failure; everything else is a problem to tell
129
130
  // the model about.
130
131
  export function checkPicks(text, sentences, limits) {
131
- const parsed = answerSchema.safeParse(jsonArrayOf(text));
132
+ const parsed = answerSchema.safeParse(jsonBetween(text, "[", "]"));
132
133
  if (!parsed.success)
133
134
  return {
134
135
  ok: false,
@@ -226,17 +227,3 @@ export function noPicksMessage(problems, limits) {
226
227
  function round(seconds) {
227
228
  return Math.round(seconds * 1000) / 1000;
228
229
  }
229
- // The array in the answer, wherever the model put it: bare, in a ```json fence, or after a
230
- // sentence of its own.
231
- function jsonArrayOf(text) {
232
- const start = text.indexOf("[");
233
- const end = text.lastIndexOf("]");
234
- if (start === -1 || end <= start)
235
- return undefined;
236
- try {
237
- return JSON.parse(text.slice(start, end + 1));
238
- }
239
- catch {
240
- return undefined;
241
- }
242
- }
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { jsonBetween } from "../../kernel/json-answer.js";
2
3
  export function imagePromptMessages(brief) {
3
4
  const plural = brief.count === 1 ? "prompt" : "prompts";
4
5
  return [
@@ -35,7 +36,7 @@ const fix = "Try again, or choose another model in Edit project → Providers.";
35
36
  // The reason is the sentence the stage shows; the provider wrapper's `check` asks the model
36
37
  // again while attempts remain.
37
38
  export function checkImagePrompts(text, count) {
38
- const parsed = z.array(z.string()).safeParse(jsonArrayOf(text));
39
+ const parsed = z.array(z.string()).safeParse(jsonBetween(text, "[", "]"));
39
40
  if (!parsed.success)
40
41
  return {
41
42
  ok: false,
@@ -54,15 +55,3 @@ export function checkImagePrompts(text, count) {
54
55
  };
55
56
  return { ok: true, prompts };
56
57
  }
57
- function jsonArrayOf(text) {
58
- const start = text.indexOf("[");
59
- const end = text.lastIndexOf("]");
60
- if (start === -1 || end <= start)
61
- return undefined;
62
- try {
63
- return JSON.parse(text.slice(start, end + 1));
64
- }
65
- catch {
66
- return undefined;
67
- }
68
- }
@@ -167,6 +167,7 @@ export async function renderShort(run) {
167
167
  }),
168
168
  signal: run.signal,
169
169
  log: run.log,
170
+ priority: run.machine?.priority,
170
171
  onProgress: () => undefined,
171
172
  });
172
173
  writeFileSync(join(workspace, "subtitles.ass"), shortCaptionsAss(fasterWords(run.words, speed), {
@@ -205,6 +206,8 @@ export async function renderShort(run) {
205
206
  signal: run.signal,
206
207
  log: run.log,
207
208
  onProgress: run.onProgress,
209
+ machine: run.machine,
210
+ notice: run.notice,
208
211
  });
209
212
  }
210
213
  finally {
@@ -0,0 +1,44 @@
1
+ import { z } from "zod";
2
+ import { accountExamples, accountPatterns } from "./limits.js";
3
+ import { platformLabels, socialPlatforms, xTiers, } from "./model.js";
4
+ const accountsSchema = z.partialRecord(z.enum(socialPlatforms), z.string().max(260));
5
+ export function channelSocial(db, channelId) {
6
+ const row = db.prepare("SELECT x_tier, social_accounts FROM channels WHERE id=?").get(channelId);
7
+ return channelSocialOf(row?.x_tier, row?.social_accounts);
8
+ }
9
+ // The two columns as a channel row holds them; anything unreadable is Free and no accounts.
10
+ export function channelSocialOf(tier, accounts) {
11
+ const xTier = xTiers.find((one) => one === tier) ?? "free";
12
+ try {
13
+ const parsed = accountsSchema.safeParse(JSON.parse(typeof accounts === "string" ? accounts : "{}"));
14
+ return { xTier, accounts: parsed.success ? parsed.data : {} };
15
+ }
16
+ catch {
17
+ return { xTier, accounts: {} };
18
+ }
19
+ }
20
+ // An account name a site wouldn't take, as the sentence the Posting block shows; undefined
21
+ // when every name fits. A blank name is no account.
22
+ export function accountsProblem(accounts) {
23
+ for (const platform of socialPlatforms) {
24
+ const name = accounts[platform]?.trim() ?? "";
25
+ if (name !== "" && !accountPatterns[platform].test(name))
26
+ return `"${name}" isn't ${/^[AEIOU]/u.test(platformLabels[platform]) ? "an" : "a"} ${platformLabels[platform]} account name. Type it the way ${platformLabels[platform]} shows it (like ${accountExamples[platform]}), without the site's address, then Save.`;
27
+ }
28
+ return undefined;
29
+ }
30
+ export function tidyAccounts(accounts) {
31
+ const tidy = {};
32
+ for (const platform of socialPlatforms) {
33
+ const name = accounts[platform]?.trim() ?? "";
34
+ if (name !== "")
35
+ tidy[platform] = name;
36
+ }
37
+ return tidy;
38
+ }
39
+ export function writeChannelSocial(db, channelId, change) {
40
+ if (change.xTier !== undefined)
41
+ db.prepare("UPDATE channels SET x_tier=? WHERE id=?").run(change.xTier, channelId);
42
+ if (change.accounts !== undefined)
43
+ db.prepare("UPDATE channels SET social_accounts=? WHERE id=?").run(JSON.stringify(tidyAccounts(change.accounts)), channelId);
44
+ }
@@ -0,0 +1,17 @@
1
+ // What each site gets when a template turns it on, as the owner chose: the summary with the
2
+ // video on X and LinkedIn, a 4:5 picture carousel on Instagram, the first short on TikTok, the
3
+ // summary with up to four pictures on Bluesky, and a link post on Reddit.
4
+ export const defaultChoices = {
5
+ x: { text: "summary", media: { video: "long" }, hashtags: 2, link: false },
6
+ linkedin: { text: "summary", media: { video: "long" }, hashtags: 3, link: false },
7
+ instagram: { text: "summary", media: { pictures: 10 }, hashtags: 5, link: false },
8
+ tiktok: { text: "summary", media: { video: "short", short: 1 }, hashtags: 5, link: false },
9
+ bluesky: { text: "summary", media: { pictures: 4 }, hashtags: 2, link: false },
10
+ reddit: { text: "none", media: {}, hashtags: 0, link: true },
11
+ };
12
+ export const defaultReddit = (title) => ({
13
+ subreddit: "",
14
+ title,
15
+ flair: "",
16
+ kind: "link",
17
+ });