@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,30 +1,79 @@
1
1
  import { z } from "zod";
2
+ import { jsonBetween } from "../../kernel/json-answer.js";
2
3
  import { detectSlots, render, sceneKeyword } from "../admission/substitute.js";
3
4
  // Scenes from the article (Images → Scenes from the article): each image is drawn from its
4
5
  // own scene instead of the same text every time. One call to the project's AI model reads the
5
6
  // article and writes every scene, following the article from its opening to its end in the
6
7
  // order the images are shown.
8
+ // The keywords Slopify fills per picture from the scenes call (`keywords/specs.ts`): any of them
9
+ // turns scenes on for that picture, whatever the Images switch says.
10
+ export const slopifySceneKeywords = ["Slopify_Scene", "Slopify_Setting", "Slopify_Mood"];
11
+ const sceneNames = [sceneKeyword, "Slopify_Scene"];
12
+ const detailNames = ["Slopify_Setting", "Slopify_Mood"];
13
+ // `{{Scene}}` or `{{Slopify_Scene}}`: where the scene goes.
7
14
  export function usesScene(body) {
15
+ return body != null && detectSlots(body).names.some((name) => sceneNames.includes(name));
16
+ }
17
+ // The old name, which follows Images → Scenes from the article as it always did.
18
+ export function usesLegacyScene(body) {
8
19
  return body != null && detectSlots(body).names.includes(sceneKeyword);
9
20
  }
10
- // The prompt with its scene: where the prompt has `{{Scene}}`, there; otherwise as a line of
11
- // its own after the prompt's first paragraph, so any prompt takes a scene without editing.
12
- export function withScene(body, scene) {
13
- if (usesScene(body))
14
- return render(body, { [sceneKeyword]: scene });
15
- const at = body.indexOf("\n\n");
21
+ // `{{Slopify_Scene}}`, `{{Slopify_Setting}}` or `{{Slopify_Mood}}`: the picture gets its scene.
22
+ export function usesSlopifyScene(body) {
23
+ return (body != null &&
24
+ detectSlots(body).names.some((name) => slopifySceneKeywords.includes(name)));
25
+ }
26
+ // `{{Slopify_Setting}}` or `{{Slopify_Mood}}`: the scenes call writes them too.
27
+ export function wantsSceneDetails(body) {
28
+ return body != null && detectSlots(body).names.some((name) => detailNames.includes(name));
29
+ }
30
+ // The prompt with its scene: where the prompt has `{{Scene}}` or `{{Slopify_Scene}}`, there;
31
+ // otherwise, while the Images switch is on (`legacy`), as a line of its own after the prompt's
32
+ // first paragraph, so any prompt takes a scene without editing. With the switch off a picture is
33
+ // here only for its `Slopify_` keywords, and a `{{Scene}}` line is left out as it always was.
34
+ // The setting and mood go where the prompt asks; a line asking for one not written is left out.
35
+ export function withScene(body, scene, options = {}) {
36
+ const legacy = options.legacy !== false;
37
+ const text = legacy ? body : withoutLines(body, [sceneKeyword]);
38
+ const detailed = withDetails(text, options);
39
+ if (usesScene(detailed))
40
+ return render(detailed, { [sceneKeyword]: scene, Slopify_Scene: scene });
41
+ if (!legacy)
42
+ return detailed;
43
+ const at = detailed.indexOf("\n\n");
16
44
  return at === -1
17
- ? `${body}\n\nScene: ${scene}`
18
- : `${body.slice(0, at)}\n\nScene: ${scene}${body.slice(at)}`;
45
+ ? `${detailed}\n\nScene: ${scene}`
46
+ : `${detailed.slice(0, at)}\n\nScene: ${scene}${detailed.slice(at)}`;
19
47
  }
20
- // The prompt with the switch off: a line that holds `{{Scene}}` is left out, so the image is
21
- // drawn from the rest of the prompt as it always was.
48
+ // A picture that is drawn without scenes (the establishing image): a line holding a `Slopify_`
49
+ // scene keyword is left out, and `{{Scene}}` stays as it always did.
50
+ export function withoutSlopifyScene(body) {
51
+ return withoutLines(body, slopifySceneKeywords);
52
+ }
53
+ // The prompt with no scene: a line that holds a scene keyword is left out, so the image is drawn
54
+ // from the rest of the prompt as it always was.
22
55
  export function withoutScene(body) {
23
- if (!usesScene(body))
56
+ return withoutLines(body, [...sceneNames, ...detailNames]);
57
+ }
58
+ function withDetails(body, details) {
59
+ if (!wantsSceneDetails(body))
60
+ return body;
61
+ const missing = [
62
+ ...(details.setting?.trim() ? [] : ["Slopify_Setting"]),
63
+ ...(details.mood?.trim() ? [] : ["Slopify_Mood"]),
64
+ ];
65
+ return render(withoutLines(body, missing), {
66
+ ...(details.setting?.trim() ? { Slopify_Setting: details.setting.trim() } : {}),
67
+ ...(details.mood?.trim() ? { Slopify_Mood: details.mood.trim() } : {}),
68
+ });
69
+ }
70
+ function withoutLines(body, names) {
71
+ const holds = (line) => detectSlots(line).names.some((name) => names.includes(name));
72
+ if (!body.split("\n").some(holds))
24
73
  return body;
25
74
  return body
26
75
  .split("\n")
27
- .filter((line) => !usesScene(line))
76
+ .filter((line) => !holds(line))
28
77
  .join("\n")
29
78
  .replace(/\n{3,}/g, "\n\n")
30
79
  .trim();
@@ -42,7 +91,25 @@ const countLine = /^Pictures: (\d+)$/m;
42
91
  // prompt asks for one with `{{Scene}}`. They are not tied to a stretch of the article: each is
43
92
  // the moment that would make someone click, a different one for each thumbnail.
44
93
  const thumbnailLine = /^Thumbnails: (\d+)$/m;
94
+ // With `details`, each scene comes with its setting and mood for `{{Slopify_Setting}}` and
95
+ // `{{Slopify_Mood}}`; without, the request is the one it always was.
45
96
  export function sceneMessages(brief) {
97
+ const plain = plainSceneMessages(brief);
98
+ return brief.details === true ? plain.map(askingDetails) : plain;
99
+ }
100
+ const answerLine = /^Answer with a JSON array of exactly (\d+) strings, (.*), and nothing else\.$/m;
101
+ function askingDetails(message) {
102
+ if (message.role !== "system")
103
+ return message;
104
+ return {
105
+ ...message,
106
+ content: message.content.replace(answerLine, (_whole, total, order) => [
107
+ "For each scene also write its setting: the era and place of that moment in a few words (period, region, season), so the picture stays true to its time; and its mood: the feeling of that moment in one to three words (calm, tense, sad), for the picture's light and colour.",
108
+ `Answer with a JSON array of exactly ${total} objects, ${order}, each {"scene": "…", "setting": "…", "mood": "…"}, and nothing else.`,
109
+ ].join("\n\n")),
110
+ };
111
+ }
112
+ function plainSceneMessages(brief) {
46
113
  const count = brief.pictures.length;
47
114
  const thumbnails = brief.thumbnails ?? 0;
48
115
  if (thumbnails > 0)
@@ -120,15 +187,33 @@ function withThumbnails(brief, count, thumbnails) {
120
187
  },
121
188
  ];
122
189
  }
190
+ const sceneAnswer = z.array(z.union([
191
+ z.string(),
192
+ z.object({
193
+ scene: z.string(),
194
+ setting: z.string().optional(),
195
+ mood: z.string().optional(),
196
+ }),
197
+ ]));
198
+ // A list of scenes, or of scenes with their setting and mood (`details`, only then present).
123
199
  export function checkScenes(text, count) {
124
200
  const fix = "Use Try again; if it keeps happening, choose another model in the Providers section of Edit project.";
125
- const parsed = z.array(z.string()).safeParse(jsonArrayOf(text));
201
+ const parsed = sceneAnswer.safeParse(jsonBetween(text, "[", "]"));
126
202
  if (!parsed.success)
127
203
  return {
128
204
  ok: false,
129
205
  reason: `The AI model didn't answer with a list of image scenes. ${fix}`,
130
206
  };
131
- const scenes = parsed.data.map((scene) => scene.replace(/\s+/g, " ").trim());
207
+ const tidy = (value) => value.replace(/\s+/g, " ").trim();
208
+ const scenes = parsed.data.map((one) => tidy(typeof one === "string" ? one : one.scene));
209
+ const details = parsed.data.some((one) => typeof one !== "string")
210
+ ? parsed.data.map((one) => typeof one === "string"
211
+ ? {}
212
+ : {
213
+ ...(one.setting === undefined ? {} : { setting: tidy(one.setting) }),
214
+ ...(one.mood === undefined ? {} : { mood: tidy(one.mood) }),
215
+ })
216
+ : undefined;
132
217
  if (scenes.length !== count)
133
218
  return {
134
219
  ok: false,
@@ -136,17 +221,5 @@ export function checkScenes(text, count) {
136
221
  };
137
222
  if (scenes.some((scene) => scene === ""))
138
223
  return { ok: false, reason: `One of the AI model's image scenes is empty. ${fix}` };
139
- return { ok: true, scenes };
140
- }
141
- function jsonArrayOf(text) {
142
- const start = text.indexOf("[");
143
- const end = text.lastIndexOf("]");
144
- if (start === -1 || end <= start)
145
- return undefined;
146
- try {
147
- return JSON.parse(text.slice(start, end + 1));
148
- }
149
- catch {
150
- return undefined;
151
- }
224
+ return { ok: true, scenes, ...(details === undefined ? {} : { details }) };
152
225
  }
@@ -0,0 +1,84 @@
1
+ import { z } from "zod";
2
+ import { keywordSpecs } from "./specs.js";
3
+ // The keyword digest: one call to the project's AI model reads the article once and writes every
4
+ // digest keyword (`{{Slopify_Summary}}`, `{{Slopify_Hook}}`…), so a run that uses several pays for
5
+ // one call. It asks for all six every time, so adding one more digest keyword to a prompt later
6
+ // reuses the same answer.
7
+ export const digestNames = keywordSpecs
8
+ .filter((spec) => spec.filler === "digest")
9
+ .map((spec) => spec.name);
10
+ export function digestMessages(brief) {
11
+ return [
12
+ {
13
+ role: "system",
14
+ content: [
15
+ "You write short texts about a narrated video from its article, for its pictures, thumbnail, description, shorts and reviews.",
16
+ "summary: two or three sentences on what the video covers.",
17
+ "hook: the single most click-worthy idea of the video, in one sentence.",
18
+ "thumbnailText: two to four words to put on the thumbnail, in the video's language.",
19
+ "keyFacts: three to five striking facts the article states, each one sentence.",
20
+ "quote: one quotable line from the article, as it is written there.",
21
+ "tags: five to fifteen search terms someone would type to find this video.",
22
+ "Use only what the article says. Write in the article's language.",
23
+ 'Answer with JSON and nothing else: {"summary": "…", "hook": "…", "thumbnailText": "…", "keyFacts": ["…"], "quote": "…", "tags": ["…"]}.',
24
+ ].join("\n\n"),
25
+ },
26
+ {
27
+ role: "user",
28
+ content: [`Video: ${brief.title}`, `Article:\n\n${brief.article}`].join("\n\n"),
29
+ },
30
+ ];
31
+ }
32
+ const listOrText = z.union([z.array(z.string()), z.string()]);
33
+ const digestSchema = z.object({
34
+ summary: z.string(),
35
+ hook: z.string(),
36
+ thumbnailText: z.string(),
37
+ keyFacts: listOrText,
38
+ quote: z.string(),
39
+ tags: listOrText,
40
+ });
41
+ export function checkDigest(text) {
42
+ const fix = "Use Try again; if it keeps happening, choose another model in the Providers section of Edit project.";
43
+ const parsed = digestSchema.safeParse(jsonObjectOf(text));
44
+ if (!parsed.success)
45
+ return {
46
+ ok: false,
47
+ reason: `The AI model didn't answer with the keyword digest (summary, hook, thumbnail text, key facts, quote and tags). ${fix}`,
48
+ };
49
+ const tidy = (value) => value.replace(/\s+/g, " ").trim();
50
+ const list = (value) => (typeof value === "string" ? value.split("\n") : value)
51
+ .map((one) => tidy(one).replace(/^[-•*]\s*/, ""))
52
+ .filter((one) => one !== "");
53
+ const { summary, hook, thumbnailText, keyFacts, quote, tags } = parsed.data;
54
+ const digest = {
55
+ Slopify_Summary: tidy(summary),
56
+ Slopify_Hook: tidy(hook),
57
+ Slopify_Thumbnail_Text: tidy(thumbnailText),
58
+ Slopify_Key_Facts: list(keyFacts).join("\n"),
59
+ Slopify_Quote: tidy(quote),
60
+ Slopify_Tags: (typeof tags === "string" ? tags.split(",") : tags)
61
+ .map(tidy)
62
+ .filter((one) => one !== "")
63
+ .join(", "),
64
+ };
65
+ const empty = digestNames.filter((name) => (digest[name] ?? "") === "");
66
+ if (empty.length > 0)
67
+ return {
68
+ ok: false,
69
+ reason: `The AI model left part of the keyword digest empty (${empty.map((name) => `{{${name}}}`).join(", ")}). ${fix}`,
70
+ };
71
+ return { ok: true, digest };
72
+ }
73
+ function jsonObjectOf(text) {
74
+ const start = text.indexOf("{");
75
+ const end = text.lastIndexOf("}");
76
+ if (start === -1 || end <= start)
77
+ return undefined;
78
+ try {
79
+ return JSON.parse(text.slice(start, end + 1));
80
+ }
81
+ catch {
82
+ return undefined;
83
+ }
84
+ }
@@ -0,0 +1,92 @@
1
+ // The facts a run copies when it starts, for the keywords Slopify fills from them: what the
2
+ // channel, its links, its cast, its brand kit and its other projects are now. Later edits there
3
+ // never change a project already made; Edit project's Refresh keyword facts copies them again.
4
+ import { readFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { listProjects } from "../admission/repo.js";
7
+ import { castOfChannel, channelById, projectChannels, resolveChannelId } from "../channels/repo.js";
8
+ import { readFontMetadata } from "../fonts/sfnt.js";
9
+ import { seriesOf } from "../studio/plan-model.js";
10
+ import { allReleases } from "../studio/releases.js";
11
+ import { videoOf } from "../studio/videos.js";
12
+ import { readChannelLinksFor } from "../youtube/edits-repo.js";
13
+ // ceiling: the titles a prompt is told about. A channel with hundreds of videos would fill the
14
+ // prompt with titles; the newest are the ones it is likeliest to repeat.
15
+ export const coveredBeforeMax = 50;
16
+ // `projectId` is the project the facts are for, when it exists already (a refresh): it is
17
+ // neither its own previous video nor one it covered before.
18
+ export function keywordFactsFor(deps, draft, now, projectId) {
19
+ const { db } = deps;
20
+ const channelId = resolveChannelId(db, draft.channelId);
21
+ const channel = channelById(db, channelId);
22
+ const channels = projectChannels(db);
23
+ const projects = listProjects(db);
24
+ // A refresh sees the channel as it was before this project, never the episodes made after it.
25
+ const made = projects.find((project) => project.id === projectId)?.createdAt;
26
+ const own = projects.filter((project) => project.id !== projectId &&
27
+ (made === undefined || project.createdAt < made) &&
28
+ (channels.get(project.id) ?? channelId) === channelId);
29
+ const series = seriesOf(draft);
30
+ const sameSeries = own.filter((project) => seriesOf(project.config) === series);
31
+ const later = projects.filter((project) => project.id !== projectId &&
32
+ seriesOf(project.config) === series &&
33
+ (channels.get(project.id) ?? channelId) === channelId);
34
+ const previous = sameSeries[0];
35
+ const previousVideo = previous === undefined ? undefined : videoOf(db, previous.id, null)?.videoId;
36
+ const upcoming = allReleases(db).find((release) => release.short === 0 &&
37
+ Date.parse(release.at) > now.getTime() &&
38
+ later.some((project) => project.id === release.projectId));
39
+ const next = upcoming === undefined ? undefined : later.find((one) => one.id === upcoming.projectId);
40
+ const links = readChannelLinksFor(db, channelId);
41
+ const cast = castOfChannel(db, channelId);
42
+ const brand = draft.useBrandKit === false ? undefined : channel?.brand;
43
+ const colours = unique([brand?.captionColor, brand?.captionOutlineColor, brand?.titleColor]);
44
+ const fonts = unique([brand?.captionFontId, brand?.titleFontId].map((id) => id === undefined ? undefined : fontName(deps.paths, id)));
45
+ return {
46
+ ...(channel === undefined ? {} : { channel: channel.name }),
47
+ ...(channel?.seriesBrief.trim() ? { series: channel.seriesBrief.trim() } : {}),
48
+ ...(links.length === 0 ? {} : { links: links.map(({ name, url }) => ({ name, url })) }),
49
+ ...(previous === undefined
50
+ ? {}
51
+ : {
52
+ previousVideo: {
53
+ title: previous.title,
54
+ ...(previousVideo === undefined ? {} : { url: `https://youtu.be/${previousVideo}` }),
55
+ },
56
+ }),
57
+ ...(next === undefined ? {} : { nextVideo: { title: next.title } }),
58
+ ...(cast.length === 0
59
+ ? {}
60
+ : { cast: cast.map(({ name, description }) => ({ name, description })) }),
61
+ ...(own.length === 0
62
+ ? {}
63
+ : { coveredBefore: own.slice(0, coveredBeforeMax).map((project) => project.title) }),
64
+ episode: sameSeries.length + 1,
65
+ ...(colours.length === 0 && fonts.length === 0 ? {} : { brand: { colours, fonts } }),
66
+ };
67
+ }
68
+ // A brand-kit font's name: the bundled one's, or an uploaded font's own. A system font is
69
+ // found only by scanning the computer's font folders, which a run's start never waits for, so
70
+ // it is left out.
71
+ function fontName(paths, id) {
72
+ if (id === "default")
73
+ return "Barlow";
74
+ if (!/^uploaded-[a-f0-9]{64}$/.test(id))
75
+ return undefined;
76
+ for (const extension of [".ttf", ".otf"]) {
77
+ let bytes;
78
+ try {
79
+ bytes = readFileSync(join(paths.dataDir, "fonts", `${id}${extension}`));
80
+ }
81
+ catch (error) {
82
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
83
+ continue;
84
+ throw error;
85
+ }
86
+ return readFontMetadata(bytes)?.[0]?.family;
87
+ }
88
+ return undefined;
89
+ }
90
+ function unique(values) {
91
+ return [...new Set(values.filter((value) => value !== undefined))];
92
+ }
@@ -0,0 +1,80 @@
1
+ // Filling the keywords Slopify fills into a prompt that `render` has already filled with the
2
+ // person's own values, so a value they gave a `Slopify_` name still wins. A body with no
3
+ // keyword comes back as the very same string. Browser-safe.
4
+ import { keywordOf } from "./specs.js";
5
+ const slot = /\{\{([^{}\n]*)\}\}/g;
6
+ // Filled elsewhere: each picture's own keywords by the pictures, the release date and the length
7
+ // when the step runs.
8
+ const elsewhere = ["scenes", "appearance", "release", "duration"];
9
+ export function mentionsKeyword(body) {
10
+ for (const match of body.matchAll(slot))
11
+ if (keywordOf((match[1] ?? "").trim())?.name === (match[1] ?? "").trim())
12
+ return true;
13
+ return false;
14
+ }
15
+ export function fillKeywords(body, place, values) {
16
+ return fillWith(body, (name) => {
17
+ const spec = keywordOf(name);
18
+ if (spec === undefined || spec.name !== name || !spec.places.includes(place))
19
+ return undefined;
20
+ if (elsewhere.includes(spec.filler))
21
+ return undefined;
22
+ return Object.hasOwn(values, name) ? (values[name] ?? "") : null;
23
+ });
24
+ }
25
+ // The release date and the length, filled when the step runs: they are left in the planned
26
+ // request, so a later change to either never makes anything outdated.
27
+ export function fillLateKeywords(text, late) {
28
+ const filled = fillWith(text, (name) => name === "Slopify_Release_Date"
29
+ ? (late.releaseDate ?? "")
30
+ : name === "Slopify_Duration"
31
+ ? (late.duration ?? "")
32
+ : undefined);
33
+ return filled.ok ? filled.text : text;
34
+ }
35
+ export function mentionsLateKeyword(text) {
36
+ return text.includes("Slopify_Release_Date") || text.includes("Slopify_Duration");
37
+ }
38
+ // `valueFor` answers a string to fill (an empty one leaves its line out), null for a keyword
39
+ // still waiting, and undefined for a name to leave as written.
40
+ export function fillWith(body, valueFor) {
41
+ const waiting = [];
42
+ const blank = [];
43
+ let touched = false;
44
+ for (const match of body.matchAll(slot)) {
45
+ const name = (match[1] ?? "").trim();
46
+ const value = valueFor(name);
47
+ if (value === undefined)
48
+ continue;
49
+ touched = true;
50
+ if (value === null && !waiting.includes(name))
51
+ waiting.push(name);
52
+ if (value === "" && !blank.includes(name))
53
+ blank.push(name);
54
+ }
55
+ if (!touched)
56
+ return { ok: true, text: body };
57
+ if (waiting.length > 0)
58
+ return { ok: false, waiting };
59
+ const kept = blank.length === 0
60
+ ? body
61
+ : body
62
+ .split("\n")
63
+ .filter((line) => !blank.some((name) => holds(line, name)))
64
+ .join("\n")
65
+ .replace(/\n{3,}/g, "\n\n")
66
+ .trim();
67
+ return {
68
+ ok: true,
69
+ text: kept.replace(slot, (whole, inside) => {
70
+ const value = valueFor(inside.trim());
71
+ return value === undefined || value === null ? whole : value;
72
+ }),
73
+ };
74
+ }
75
+ function holds(line, name) {
76
+ for (const match of line.matchAll(slot))
77
+ if ((match[1] ?? "").trim() === name)
78
+ return true;
79
+ return false;
80
+ }
@@ -0,0 +1,75 @@
1
+ // The keywords Slopify fills itself: never fields for the person, always written with the
2
+ // reserved `Slopify_` prefix so they never take a name a template already uses. One table
3
+ // says, for each, what fills it, where it works and what it costs; the editor's Insert bar,
4
+ // its hover cards, the help tips, the lint and the fillers all read it. Browser-safe: the
5
+ // editor imports it.
6
+ // Where a keyword can appear: the prompt kinds, the intro and outro entries, and the title.
7
+ export const keywordPlaces = [
8
+ "title",
9
+ "article",
10
+ "script",
11
+ "narration",
12
+ "intro",
13
+ "outro",
14
+ "image",
15
+ "thumbnail",
16
+ "description",
17
+ "shorts",
18
+ "review",
19
+ // A Library prompt that writes a post for another site (`slices/social`).
20
+ "post",
21
+ ];
22
+ // What fills a keyword: facts copied when the run starts, the release date read when the step
23
+ // runs, the article's chapter titles, the finished length, the shared digest call, the scenes
24
+ // call and the appearance lookup.
25
+ export const fillerKinds = [
26
+ "facts",
27
+ "release",
28
+ "chapters",
29
+ "duration",
30
+ "digest",
31
+ "scenes",
32
+ "appearance",
33
+ ];
34
+ // Free, or part of one AI call per run (shared by every keyword with the same cost).
35
+ export const keywordCosts = ["free", "digest", "scenes", "appearance"];
36
+ export const reservedPrefix = "Slopify_";
37
+ // The whole prefix is Slopify's, whatever its case, so a near miss never turns into a field
38
+ // that a later keyword would take over.
39
+ export function isReserved(name) {
40
+ return name.toLowerCase().startsWith(reservedPrefix.toLowerCase());
41
+ }
42
+ // What a keyword's cost says in the Insert bar.
43
+ export function costLabel(cost) {
44
+ switch (cost) {
45
+ case "free":
46
+ return "Free: no AI call.";
47
+ case "digest":
48
+ return "Part of one AI call per run: the keyword digest, shared by every digest keyword.";
49
+ case "scenes":
50
+ return "Part of one AI call per run: the scenes call, shared by every picture.";
51
+ case "appearance":
52
+ return "Part of one AI call per run: the appearance lookup, with web search.";
53
+ }
54
+ }
55
+ // How the editor names a place: the prompt kinds and the two entry categories.
56
+ export const placeLabels = {
57
+ title: "Title",
58
+ article: "Article",
59
+ script: "Script",
60
+ narration: "Narration",
61
+ intro: "Intro",
62
+ outro: "Outro",
63
+ image: "Image",
64
+ thumbnail: "Thumbnail",
65
+ description: "Description",
66
+ shorts: "Shorts",
67
+ review: "Review",
68
+ post: "Post",
69
+ };
70
+ // "A, B and C".
71
+ export function listOf(words) {
72
+ if (words.length <= 1)
73
+ return words.join("");
74
+ return `${words.slice(0, -1).join(", ")} and ${words.at(-1) ?? ""}`;
75
+ }