@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
@@ -0,0 +1,187 @@
1
+ // What the editor and Play say about the keywords in one body: a `Slopify_` name that is no
2
+ // keyword, a keyword where it can't be filled, and an old name that still works. Browser-safe.
3
+ import { detectSlots } from "../admission/substitute.js";
4
+ import { isReserved, listOf, placeLabels, reservedPrefix, } from "./model.js";
5
+ import { keywordOf, keywordSpecs, legacyNames } from "./specs.js";
6
+ export const keywordProblemKinds = ["unknown", "wrong-place", "legacy"];
7
+ // The same well-formed slot the parser reads (`admission/substitute.ts`).
8
+ const slot = /\{\{([^{}\n]*)\}\}/g;
9
+ // Unknown names already in the stored prompt are let through, so a prompt saved before the
10
+ // prefix was reserved still saves.
11
+ export function keywordProblems(body, place, grandfathered = []) {
12
+ const problems = [];
13
+ for (const match of body.matchAll(slot)) {
14
+ const name = (match[1] ?? "").trim();
15
+ if (name === "")
16
+ continue;
17
+ const at = match.index;
18
+ const where = `{{${name}}} at ${positionOf(body, at)}`;
19
+ const spec = keywordOf(name);
20
+ if (spec === undefined) {
21
+ if (isReserved(name) && !grandfathered.includes(name))
22
+ problems.push({
23
+ kind: "unknown",
24
+ name,
25
+ at,
26
+ message: `${where} isn't a keyword Slopify fills: names starting with ${reservedPrefix} are Slopify's own. Rename it, e.g. {{${name.slice(reservedPrefix.length) || "Topic"}}}, or insert a keyword from the Insert bar above the text.`,
27
+ });
28
+ continue;
29
+ }
30
+ const legacy = spec.legacy === name;
31
+ if (!spec.places.includes(place)) {
32
+ // An old name outside the picture prompts was plain text before the keywords, and still is.
33
+ if (legacy)
34
+ continue;
35
+ problems.push({ kind: "wrong-place", name, at, message: wrongPlace(where, spec, place) });
36
+ continue;
37
+ }
38
+ if (legacy)
39
+ problems.push({
40
+ kind: "legacy",
41
+ name,
42
+ at,
43
+ message: `${where} is the old name of {{${spec.name}}}. It keeps working; use Update to new names in the editor to rename it.`,
44
+ });
45
+ }
46
+ return problems;
47
+ }
48
+ function wrongPlace(where, spec, place) {
49
+ if (place === "title")
50
+ return `${where} can't be used in the title: Slopify fills its keywords from the title, so the title can't use them. Remove it, or use a keyword of your own such as {{${spec.name.slice(reservedPrefix.length)}}}.`;
51
+ const works = listOf(spec.places.map((one) => placeLabels[one]));
52
+ return `${where} can't be used in ${placeName(place)}: ${why(spec, place)}. Remove it; it works in ${works}.`;
53
+ }
54
+ function why(spec, place) {
55
+ if ((place === "article" || place === "script") && spec.places.includes("narration"))
56
+ return "it exists once the article is written, so never in the Article or Script prompt";
57
+ switch (spec.filler) {
58
+ case "digest":
59
+ return place === "article" || place === "script"
60
+ ? "it is written from the article, which doesn't exist yet when this prompt runs"
61
+ : "Slopify writes it for pictures, descriptions, shorts and reviews only";
62
+ case "duration":
63
+ return "the video's length is known only once the narration is finished";
64
+ case "scenes":
65
+ case "appearance":
66
+ return "it describes one picture, so it works only in picture prompts";
67
+ default:
68
+ return "it describes how pictures look, so it works only in picture prompts";
69
+ }
70
+ }
71
+ function placeName(place) {
72
+ const label = placeLabels[place];
73
+ const article = /^[AEIOU]/.test(label) ? "an" : "a";
74
+ return place === "intro" || place === "outro"
75
+ ? `${article} ${label} entry`
76
+ : `${article} ${label} prompt`;
77
+ }
78
+ // The old names a body uses, in first-appearance order.
79
+ export function usesLegacy(body) {
80
+ const found = [];
81
+ for (const match of body.matchAll(slot)) {
82
+ const name = (match[1] ?? "").trim();
83
+ if (legacyNames.includes(name) && !found.includes(name))
84
+ found.push(name);
85
+ }
86
+ return found;
87
+ }
88
+ // The body with one old name written as its new one. Appearance means the same under both
89
+ // names; Scene's new name turns scenes on for that picture, which is why each is its own choice.
90
+ export function upgradeLegacy(body, which) {
91
+ const spec = keywordOf(which);
92
+ if (spec === undefined || spec.legacy !== which)
93
+ return body;
94
+ return body.replace(slot, (whole, inside) => inside.trim() === which ? `{{${spec.name}}}` : whole);
95
+ }
96
+ // The place a rendered prompt key fills (`library/slots.ts` names them).
97
+ export function placeOfKey(key, options = {}) {
98
+ if (key === "article")
99
+ return options.script === true ? "script" : "article";
100
+ if (key === "narration" || key === "intro" || key === "outro")
101
+ return key;
102
+ if (key === "description" || key === "shorts")
103
+ return key;
104
+ if (key === "shortsImage" || key === "referencePrompt" || /^imagePrompts\.\d+$/.test(key))
105
+ return "image";
106
+ if (key === "thumbnailPrompt")
107
+ return "thumbnail";
108
+ if (key.startsWith("review."))
109
+ return "review";
110
+ if (key.startsWith("social."))
111
+ return "post";
112
+ return undefined;
113
+ }
114
+ // The parser reports a character offset; a person editing a textarea counts lines and columns.
115
+ export function positionOf(body, at) {
116
+ const before = body.slice(0, at);
117
+ const lastBreak = before.lastIndexOf("\n");
118
+ const line = before.split("\n").length;
119
+ const column = at - lastBreak;
120
+ return `line ${String(line)}, column ${String(column)}`;
121
+ }
122
+ // The keywords filled from the facts a run copies when it starts (`facts.ts`).
123
+ const factNames = keywordSpecs
124
+ .filter((spec) => spec.filler === "facts" && spec.name !== "Slopify_Title")
125
+ .map((spec) => spec.name);
126
+ // Whether any of the bodies asks for a fact Slopify copies (the title is the project's own).
127
+ export function usesFacts(bodies) {
128
+ return bodies.some((body) => detectSlots(body).names.some((name) => factNames.includes(name)));
129
+ }
130
+ export function keywordUsesOf(bodies) {
131
+ let digest;
132
+ let chapters;
133
+ let scenes;
134
+ for (const { field, place, body } of bodies)
135
+ for (const name of detectSlots(body).names) {
136
+ const spec = keywordOf(name);
137
+ if (spec === undefined || spec.name !== name || !spec.places.includes(place))
138
+ continue;
139
+ if (spec.filler === "digest")
140
+ digest ??= field;
141
+ if (spec.filler === "chapters")
142
+ chapters ??= field;
143
+ if (spec.filler === "scenes")
144
+ scenes ??= field;
145
+ }
146
+ return {
147
+ ...(digest === undefined ? {} : { digest }),
148
+ ...(chapters === undefined ? {} : { chapters }),
149
+ ...(scenes === undefined ? {} : { scenes }),
150
+ };
151
+ }
152
+ // What stops a run: a keyword in a prompt that runs where it can't be filled. A name the person
153
+ // gave a value of their own is theirs (a prompt saved before the prefix was reserved), and an
154
+ // unknown `Slopify_` name stays an ordinary keyword to fill in.
155
+ export function placedProblems(bodies, values) {
156
+ const problems = [];
157
+ const seen = new Set();
158
+ for (const { field, place, body } of bodies)
159
+ for (const problem of keywordProblems(body, place)) {
160
+ if (problem.kind !== "wrong-place" || Object.hasOwn(values, problem.name))
161
+ continue;
162
+ const key = `${field}\0${problem.name}`;
163
+ if (seen.has(key))
164
+ continue;
165
+ seen.add(key);
166
+ problems.push({
167
+ field,
168
+ message: place === "title"
169
+ ? problem.message
170
+ : `${problem.message} Change the prompt in Library, or pick another one.`,
171
+ });
172
+ }
173
+ return problems;
174
+ }
175
+ // A Text-mode intro or outro is narrated as written, and the release date is only filled into a
176
+ // request as it is sent to the AI model (`rebuild/runtime-keywords.ts`), so a Text entry can't
177
+ // say it.
178
+ export function textEntryProblems(body, category) {
179
+ return detectSlots(body).names.includes("Slopify_Release_Date")
180
+ ? [
181
+ {
182
+ field: category,
183
+ message: `{{Slopify_Release_Date}} can't be used in a Text ${category}: Text is narrated as written, and the release date is only filled in when a step asks the AI model. Set this ${category}'s Mode to LLM in Library → Intros & Outros, or remove the keyword.`,
184
+ },
185
+ ]
186
+ : [];
187
+ }
@@ -0,0 +1,28 @@
1
+ import { z } from "zod";
2
+ // `values.ts`'s KeywordFacts, as a saved config holds it. Edit project sends it back with the
3
+ // rest of the config, so it is bounded like any other part of a draft.
4
+ const text = z.string().max(10_000);
5
+ export const keywordFactsSchema = z.object({
6
+ channel: text.optional(),
7
+ series: text.optional(),
8
+ links: z
9
+ .array(z.object({ name: text, url: text }))
10
+ .max(200)
11
+ .readonly()
12
+ .optional(),
13
+ previousVideo: z.object({ title: text, url: text.optional() }).optional(),
14
+ nextVideo: z.object({ title: text }).optional(),
15
+ cast: z
16
+ .array(z.object({ name: text, description: text }))
17
+ .max(500)
18
+ .readonly()
19
+ .optional(),
20
+ coveredBefore: z.array(text).max(500).readonly().optional(),
21
+ episode: z.number().int().min(1).optional(),
22
+ brand: z
23
+ .object({
24
+ colours: z.array(text).max(10).readonly(),
25
+ fonts: z.array(text).max(10).readonly(),
26
+ })
27
+ .optional(),
28
+ });
@@ -0,0 +1,260 @@
1
+ // The keywords themselves, in the order the Insert bar and the wiki list them.
2
+ import { keywordPlaces } from "./model.js";
3
+ const everywhere = keywordPlaces.filter((place) => place !== "title");
4
+ const afterArticle = everywhere.filter((place) => place !== "article" && place !== "script");
5
+ const pictures = ["image", "thumbnail"];
6
+ const fromDigest = [
7
+ "image",
8
+ "thumbnail",
9
+ "description",
10
+ "shorts",
11
+ "review",
12
+ "post",
13
+ ];
14
+ const atStart = "When the run starts; later changes don't change this project.";
15
+ const digestAvailable = "Once the article is written: one shared AI call writes every digest keyword the run uses.";
16
+ const sceneAvailable = "Once the article is written: the scenes call writes one for each picture, even with Scenes from the article off.";
17
+ export const keywordSpecs = [
18
+ {
19
+ name: "Slopify_Title",
20
+ filler: "facts",
21
+ fills: "The video's title, with its keywords filled in.",
22
+ example: "How Lighthouses Were Built on Bare Rock",
23
+ available: atStart,
24
+ places: everywhere,
25
+ cost: "free",
26
+ batch: 1,
27
+ },
28
+ {
29
+ name: "Slopify_Channel",
30
+ filler: "facts",
31
+ fills: "The name of the project's channel.",
32
+ example: "Quiet Histories",
33
+ available: atStart,
34
+ places: everywhere,
35
+ cost: "free",
36
+ batch: 1,
37
+ },
38
+ {
39
+ name: "Slopify_Series",
40
+ filler: "facts",
41
+ fills: "The channel's series brief from Channels → Edit. A line with an empty brief is left out.",
42
+ example: "Calm histories of how things were made, one invention at a time.",
43
+ available: atStart,
44
+ places: everywhere,
45
+ cost: "free",
46
+ batch: 2,
47
+ },
48
+ {
49
+ name: "Slopify_Chapters",
50
+ filler: "chapters",
51
+ fills: "The article's chapter titles, one per line.",
52
+ example: "The first lamps\nBuilding on bare rock\nThe last keepers",
53
+ available: "Once the article is written, so never in the Article or Script prompt.",
54
+ places: afterArticle,
55
+ cost: "free",
56
+ batch: 1,
57
+ },
58
+ {
59
+ name: "Slopify_Links",
60
+ filler: "facts",
61
+ fills: "The channel's links, one per line as name and address. In a Description prompt they stay links the page fills when it shows the description.",
62
+ example: "Website: https://example.com\nPatreon: https://patreon.com/example",
63
+ available: atStart,
64
+ places: everywhere,
65
+ cost: "free",
66
+ batch: 1,
67
+ },
68
+ {
69
+ name: "Slopify_Previous_Video",
70
+ filler: "facts",
71
+ fills: "The title and link of the channel's previous video in the same series. A line with none is left out; in a Description prompt it stays the Previous video link.",
72
+ example: "The Canal Age – https://youtu.be/abc123",
73
+ available: atStart,
74
+ places: everywhere,
75
+ cost: "free",
76
+ batch: 1,
77
+ },
78
+ {
79
+ name: "Slopify_Next_Video",
80
+ filler: "facts",
81
+ fills: "The title of the next planned video in the same series and posting plan line. A line with none is left out; in a Description prompt it stays the Next video link.",
82
+ example: "Tea Clippers and the Race Home",
83
+ available: atStart,
84
+ places: everywhere,
85
+ cost: "free",
86
+ batch: 1,
87
+ },
88
+ {
89
+ name: "Slopify_Release_Date",
90
+ filler: "release",
91
+ fills: "The release date from the posting plan. A line with no date is left out; moving the date later doesn't remake anything.",
92
+ example: "Thursday 22 October 2026",
93
+ available: "When the step runs.",
94
+ places: everywhere,
95
+ cost: "free",
96
+ batch: 2,
97
+ },
98
+ {
99
+ name: "Slopify_Episode",
100
+ filler: "facts",
101
+ fills: "This video's number in its series on the channel.",
102
+ example: "7",
103
+ available: atStart,
104
+ places: everywhere,
105
+ cost: "free",
106
+ batch: 2,
107
+ },
108
+ {
109
+ name: "Slopify_Duration",
110
+ filler: "duration",
111
+ fills: "The finished video's length.",
112
+ example: "42 min 10 s",
113
+ available: "Once the narration is finished.",
114
+ places: ["description", "shorts", "review", "post"],
115
+ cost: "free",
116
+ batch: 2,
117
+ },
118
+ {
119
+ name: "Slopify_Cast",
120
+ filler: "facts",
121
+ fills: "The channel's cast members, each with their description. A line with no cast is left out.",
122
+ example: "Mira: a keeper's daughter who kept the lighthouse logbook",
123
+ available: atStart,
124
+ places: everywhere,
125
+ cost: "free",
126
+ batch: 2,
127
+ },
128
+ {
129
+ name: "Slopify_Covered_Before",
130
+ filler: "facts",
131
+ fills: "The titles of the videos the channel already made, so the article avoids repeating them. A line with none is left out.",
132
+ example: "The Canal Age\nHow Bread Was Baked in Rome",
133
+ available: atStart,
134
+ places: everywhere,
135
+ cost: "free",
136
+ batch: 2,
137
+ },
138
+ {
139
+ name: "Slopify_Brand",
140
+ filler: "facts",
141
+ fills: "The brand kit's caption and title colours and font names. A line with an empty kit is left out.",
142
+ example: "Colours: #f5c542, #101010. Fonts: Barlow Condensed",
143
+ available: atStart,
144
+ places: pictures,
145
+ cost: "free",
146
+ batch: 2,
147
+ },
148
+ {
149
+ name: "Slopify_Summary",
150
+ filler: "digest",
151
+ fills: "Two or three sentences on what the video covers.",
152
+ example: "How engineers raised towers on rocks the sea covered twice a day, and how their keepers lived.",
153
+ available: digestAvailable,
154
+ places: fromDigest,
155
+ cost: "digest",
156
+ batch: 1,
157
+ },
158
+ {
159
+ name: "Slopify_Hook",
160
+ filler: "digest",
161
+ fills: "The video's most click-worthy idea.",
162
+ example: "The rock was under water half of every day. They built on it anyway.",
163
+ available: digestAvailable,
164
+ places: fromDigest,
165
+ cost: "digest",
166
+ batch: 1,
167
+ },
168
+ {
169
+ name: "Slopify_Thumbnail_Text",
170
+ filler: "digest",
171
+ fills: "Two to four words to put on the thumbnail.",
172
+ example: "BUILT UNDER WATER",
173
+ available: digestAvailable,
174
+ places: fromDigest,
175
+ cost: "digest",
176
+ batch: 1,
177
+ },
178
+ {
179
+ name: "Slopify_Key_Facts",
180
+ filler: "digest",
181
+ fills: "Three to five striking facts from the article, one per line.",
182
+ example: "The tower stands 35 metres high.\nIts lamp was first lit in 1811.",
183
+ available: digestAvailable,
184
+ places: fromDigest,
185
+ cost: "digest",
186
+ batch: 2,
187
+ },
188
+ {
189
+ name: "Slopify_Quote",
190
+ filler: "digest",
191
+ fills: "One quotable line from the article.",
192
+ example: "A light is only as good as the person who keeps it.",
193
+ available: digestAvailable,
194
+ places: fromDigest,
195
+ cost: "digest",
196
+ batch: 2,
197
+ },
198
+ {
199
+ name: "Slopify_Tags",
200
+ filler: "digest",
201
+ fills: "Search terms for the video, separated by commas.",
202
+ example: "lighthouses, engineering history, maritime history, history for sleep",
203
+ available: digestAvailable,
204
+ places: fromDigest,
205
+ cost: "digest",
206
+ batch: 2,
207
+ },
208
+ {
209
+ name: "Slopify_Scene",
210
+ filler: "scenes",
211
+ fills: "What this picture shows: a moment of the article, written for each picture.",
212
+ example: "A keeper climbs the spiral stair at dusk, a lamp held high.",
213
+ available: sceneAvailable,
214
+ places: pictures,
215
+ cost: "scenes",
216
+ legacy: "Scene",
217
+ batch: 1,
218
+ },
219
+ {
220
+ name: "Slopify_Setting",
221
+ filler: "scenes",
222
+ fills: "The era and place of this picture's moment, so it stays period-correct.",
223
+ example: "A rocky Scottish coast, early 19th century, late autumn",
224
+ available: sceneAvailable,
225
+ places: pictures,
226
+ cost: "scenes",
227
+ batch: 1,
228
+ },
229
+ {
230
+ name: "Slopify_Mood",
231
+ filler: "scenes",
232
+ fills: "The feeling of this picture's moment, for its light and colour.",
233
+ example: "tense",
234
+ available: sceneAvailable,
235
+ places: pictures,
236
+ cost: "scenes",
237
+ batch: 2,
238
+ },
239
+ {
240
+ name: "Slopify_Appearance",
241
+ filler: "appearance",
242
+ fills: "How the video's subject and the characters in this picture look, looked up on the web.",
243
+ example: "Mira: a tall woman in a wool coat and oilskin hat, a brass lamp at her belt.",
244
+ available: "Once the article is written: one AI call with web search looks them up.",
245
+ places: pictures,
246
+ cost: "appearance",
247
+ legacy: "Appearance",
248
+ batch: 1,
249
+ },
250
+ ];
251
+ export const keywordNames = keywordSpecs.map((spec) => spec.name);
252
+ export const legacyNames = keywordSpecs.flatMap((spec) => spec.legacy === undefined ? [] : [spec.legacy]);
253
+ // The keyword a slot name stands for: its own name, or Scene's and Appearance's old ones. Names
254
+ // are case-sensitive, like every slot name.
255
+ export function keywordOf(name) {
256
+ return keywordSpecs.find((spec) => spec.name === name || spec.legacy === name);
257
+ }
258
+ export function keywordsFor(place) {
259
+ return keywordSpecs.filter((spec) => spec.places.includes(place));
260
+ }
@@ -0,0 +1,74 @@
1
+ // The facts a run copies when it starts, for the keywords Slopify fills from them, and how each
2
+ // becomes a keyword's text. Browser-safe.
3
+ import { previousVideoLink } from "../youtube/placeholders.js";
4
+ // The description's own link placeholder for the next video, filled when it is shown like
5
+ // "Previous video".
6
+ export const nextVideoLink = "Next video";
7
+ // Every keyword the facts fill, for one place. A Description prompt keeps the links as the
8
+ // placeholders the description page fills when it shows the description, so they stay live.
9
+ export function factValues(facts, title, place) {
10
+ const described = place === "description";
11
+ const links = facts?.links ?? [];
12
+ const previous = facts?.previousVideo;
13
+ return {
14
+ Slopify_Title: title,
15
+ Slopify_Channel: facts?.channel ?? "",
16
+ Slopify_Series: facts?.series?.trim() ?? "",
17
+ Slopify_Links: links
18
+ .map((link) => `${link.name}: ${described ? `{{${link.name}}}` : link.url}`)
19
+ .join("\n"),
20
+ Slopify_Previous_Video: described
21
+ ? `{{${previousVideoLink}}}`
22
+ : previous === undefined
23
+ ? ""
24
+ : previous.url === undefined || previous.url === ""
25
+ ? previous.title
26
+ : `${previous.title} – ${previous.url}`,
27
+ Slopify_Next_Video: described ? `{{${nextVideoLink}}}` : (facts?.nextVideo?.title ?? ""),
28
+ Slopify_Cast: (facts?.cast ?? [])
29
+ .map((member) => member.description.trim() === "" ? member.name : `${member.name}: ${member.description}`)
30
+ .join("\n"),
31
+ Slopify_Covered_Before: (facts?.coveredBefore ?? []).join("\n"),
32
+ Slopify_Episode: facts?.episode === undefined ? "" : String(facts.episode),
33
+ Slopify_Brand: brandText(facts?.brand),
34
+ };
35
+ }
36
+ function brandText(brand) {
37
+ if (brand === undefined)
38
+ return "";
39
+ const parts = [
40
+ ...(brand.colours.length === 0 ? [] : [`Colours: ${brand.colours.join(", ")}.`]),
41
+ ...(brand.fonts.length === 0 ? [] : [`Fonts: ${brand.fonts.join(", ")}.`]),
42
+ ];
43
+ return parts.join(" ");
44
+ }
45
+ // The finished length as the description and post prompts say it: "42 min 10 s".
46
+ export function durationText(seconds) {
47
+ const total = Math.max(0, Math.round(seconds));
48
+ const hours = Math.floor(total / 3600);
49
+ const minutes = Math.floor((total % 3600) / 60);
50
+ const rest = total % 60;
51
+ return [
52
+ ...(hours > 0 ? [`${String(hours)} h`] : []),
53
+ ...(hours > 0 || minutes > 0 ? [`${String(minutes)} min`] : []),
54
+ `${String(rest)} s`,
55
+ ].join(" ");
56
+ }
57
+ // The release date as a prompt says it: "Thursday 22 October 2026", in the computer's time zone
58
+ // like every other release time Slopify shows. Blank when the video has no release time.
59
+ export function releaseDateText(at, timeZone) {
60
+ if (at === undefined || at.trim() === "")
61
+ return "";
62
+ const date = new Date(at);
63
+ if (Number.isNaN(date.getTime()))
64
+ return "";
65
+ return new Intl.DateTimeFormat("en-GB", {
66
+ weekday: "long",
67
+ day: "numeric",
68
+ month: "long",
69
+ year: "numeric",
70
+ ...(timeZone === undefined ? {} : { timeZone }),
71
+ })
72
+ .format(date)
73
+ .replace(",", "");
74
+ }
@@ -2,14 +2,35 @@
2
2
  // restated here - it is admission/substitute.ts's, imported so the editor and the run see
3
3
  // the same parse of the same body.
4
4
  import { detectSlots } from "../admission/substitute.js";
5
+ import { keywordProblems, positionOf, textEntryProblems } from "../keywords/rules.js";
5
6
  import { bodyMax, nameMax } from "./model.js";
6
- export function lintPrompt(draft) {
7
- return lint(draft.name, draft.body);
7
+ // `grandfathered` is the slot names the stored template already has: an unknown `Slopify_`
8
+ // name saved before the prefix was reserved does not stop it saving again.
9
+ export function lintPrompt(draft, grandfathered = []) {
10
+ return lint(draft.name, draft.body, draft.kind, grandfathered);
8
11
  }
9
- export function lintEntry(draft) {
10
- return lint(draft.name, draft.body);
12
+ export function lintEntry(draft, grandfathered = []) {
13
+ return lint(draft.name, draft.body, draft.category, grandfathered);
11
14
  }
12
- function lint(name, body) {
15
+ // What the editor warns about without refusing the save: a keyword where it can't be filled
16
+ // (Play refuses that run) and an old keyword name.
17
+ export function keywordWarnings(draft) {
18
+ return [
19
+ ...keywordProblems(draft.body, placeOf(draft))
20
+ .filter((problem) => problem.kind !== "unknown")
21
+ .map((problem) => ({ field: "body", message: problem.message })),
22
+ ...("category" in draft && draft.mode === "text"
23
+ ? textEntryProblems(draft.body, draft.category).map((problem) => ({
24
+ field: "body",
25
+ message: problem.message,
26
+ }))
27
+ : []),
28
+ ];
29
+ }
30
+ function placeOf(draft) {
31
+ return "kind" in draft ? draft.kind : draft.category;
32
+ }
33
+ function lint(name, body, place, grandfathered) {
13
34
  const fields = [];
14
35
  const trimmed = name.trim();
15
36
  if (trimmed === "") {
@@ -35,6 +56,9 @@ function lint(name, body) {
35
56
  for (const error of detectSlots(body).errors) {
36
57
  fields.push({ field: "body", message: describe(error, body) });
37
58
  }
59
+ for (const problem of keywordProblems(body, place, grandfathered))
60
+ if (problem.kind === "unknown")
61
+ fields.push({ field: "body", message: problem.message });
38
62
  return fields;
39
63
  }
40
64
  // The parser reports a character offset; a person editing a textarea counts lines and
@@ -50,10 +74,3 @@ function describe(error, body) {
50
74
  return `The keyword at ${where} contains a brace. Keywords cannot be placed inside other keywords.`;
51
75
  }
52
76
  }
53
- function positionOf(body, at) {
54
- const before = body.slice(0, at);
55
- const lastBreak = before.lastIndexOf("\n");
56
- const line = before.split("\n").length;
57
- const column = at - lastBreak;
58
- return `line ${String(line)}, column ${String(column)}`;
59
- }
@@ -12,6 +12,8 @@ export const promptKinds = [
12
12
  "review",
13
13
  // Speaker turns for a multi-voice run (`slices/voices/script.ts`), in place of an article.
14
14
  "script",
15
+ // A post for another site: X, LinkedIn, Instagram, TikTok, Reddit or Bluesky (`slices/social`).
16
+ "post",
15
17
  ];
16
18
  export const entryCategories = ["intro", "outro"];
17
19
  // The name is what the Play pickers show, so it shares admission's title bound.
@@ -1,4 +1,4 @@
1
- import { readSetting, writeSetting } from "../settings/repo.js";
1
+ import { deleteSetting, readJsonSetting, stringListSchema, writeSetting, } from "../settings/repo.js";
2
2
  import { promptById, promptByName } from "./repo.js";
3
3
  // An Image prompt's "Draws photorealistic pictures" tick (Library → Prompts → the prompt).
4
4
  // Pictures an image model draws in such a style can pass for real scenes, which is YouTube's
@@ -9,16 +9,8 @@ import { promptById, promptByName } from "./repo.js";
9
9
  // for when it is restored.
10
10
  export const photorealisticPromptsKey = "library.photorealisticPrompts";
11
11
  export function photorealisticPromptIds(db) {
12
- const stored = readSetting(db, photorealisticPromptsKey);
13
- if (stored === undefined)
14
- return new Set();
15
- try {
16
- const value = JSON.parse(stored);
17
- return new Set(Array.isArray(value) ? value.filter((one) => typeof one === "string") : []);
18
- }
19
- catch {
20
- return new Set();
21
- }
12
+ const value = readJsonSetting(db, photorealisticPromptsKey, stringListSchema, []);
13
+ return new Set(value);
22
14
  }
23
15
  // The prompts as the Library lists them, the ticked ones marked.
24
16
  export function withPhotorealistic(db, prompts) {
@@ -34,7 +26,7 @@ export function setPromptPhotorealistic(db, id, on) {
34
26
  const others = [...photorealisticPromptIds(db)].filter((one) => one !== id);
35
27
  const next = on ? [...others, id].sort() : others;
36
28
  if (next.length === 0)
37
- db.prepare("DELETE FROM settings WHERE key = ?").run(photorealisticPromptsKey);
29
+ deleteSetting(db, photorealisticPromptsKey);
38
30
  else
39
31
  writeSetting(db, photorealisticPromptsKey, JSON.stringify(next));
40
32
  return { ok: true };