@gentbajko/slopify 3.25.9 → 4.1.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 (718) hide show
  1. package/dist/adapters/account-limits.js +30 -0
  2. package/dist/adapters/alignment/audio.js +1 -1
  3. package/dist/adapters/alignment/cache.js +4 -6
  4. package/dist/adapters/alignment/ctc.js +1 -2
  5. package/dist/adapters/alignment/lock.js +4 -3
  6. package/dist/adapters/alignment/multilingual.js +2 -4
  7. package/dist/adapters/alignment/prefetch.js +2 -1
  8. package/dist/adapters/alignment/runner.js +1 -1
  9. package/dist/adapters/alignment/worker.js +3 -2
  10. package/dist/adapters/explain.js +10 -12
  11. package/dist/adapters/ffmpeg.js +19 -1
  12. package/dist/adapters/host-cli/index.js +21 -4
  13. package/dist/adapters/host-cli/transport.js +2 -1
  14. package/dist/adapters/http-failure.js +53 -0
  15. package/dist/adapters/image/bytes.js +2 -1
  16. package/dist/adapters/image/codex.js +42 -9
  17. package/dist/adapters/image/fal.js +21 -53
  18. package/dist/adapters/image/google.js +33 -56
  19. package/dist/adapters/image/models.js +2 -2
  20. package/dist/adapters/image/openai.js +22 -48
  21. package/dist/adapters/image/reference.js +36 -0
  22. package/dist/adapters/image/replicate.js +21 -49
  23. package/dist/adapters/image/video.js +2 -1
  24. package/dist/adapters/interest/search-interest.js +113 -0
  25. package/dist/adapters/key-probes.js +54 -3
  26. package/dist/adapters/llm/antigravity-models.js +1 -1
  27. package/dist/adapters/llm/antigravity.js +11 -6
  28. package/dist/adapters/llm/assistant-cli.js +388 -0
  29. package/dist/adapters/llm/child-session.js +83 -0
  30. package/dist/adapters/llm/claude-code-models.js +52 -99
  31. package/dist/adapters/llm/claude-code.js +16 -4
  32. package/dist/adapters/llm/codex-limits.js +27 -68
  33. package/dist/adapters/llm/codex-models.js +1 -1
  34. package/dist/adapters/llm/codex-server-models.js +61 -109
  35. package/dist/adapters/llm/codex.js +12 -1
  36. package/dist/adapters/llm/document-workspace.js +3 -2
  37. package/dist/adapters/llm/image-workspace.js +3 -2
  38. package/dist/adapters/llm/openrouter.js +12 -48
  39. package/dist/adapters/llm/run-cli.js +9 -24
  40. package/dist/adapters/llm/sse-lines.js +5 -1
  41. package/dist/adapters/model-access.js +37 -0
  42. package/dist/adapters/tts/cartesia.js +15 -46
  43. package/dist/adapters/tts/elevenlabs.js +20 -52
  44. package/dist/adapters/tts/gemini.js +21 -41
  45. package/dist/adapters/tts/inworld-async.js +6 -2
  46. package/dist/adapters/tts/inworld.js +6 -2
  47. package/dist/adapters/tts/openai.js +20 -50
  48. package/dist/adapters/tts/system.js +2 -1
  49. package/dist/assets/models-lifecycle.yaml +6 -0
  50. package/dist/assistant-wiring.js +60 -0
  51. package/dist/autopilot-timers.js +59 -0
  52. package/dist/catalog/lifecycle.js +52 -0
  53. package/dist/catalog/propose.js +204 -0
  54. package/dist/catalog/registry.js +23 -14
  55. package/dist/catalog/schema.js +4 -1
  56. package/dist/catalog/store.js +39 -0
  57. package/dist/catalog/validate.js +5 -2
  58. package/dist/edge/autostart/native.js +3 -5
  59. package/dist/edge/autostart/prompt.js +2 -1
  60. package/dist/edge/autostart/service.js +3 -4
  61. package/dist/edge/cli-args.js +3 -4
  62. package/dist/edge/cli.js +2 -1
  63. package/dist/edge/docker-install/apply.js +11 -11
  64. package/dist/edge/docker-install/run.js +2 -1
  65. package/dist/edge/docker-install/state.js +4 -3
  66. package/dist/edge/docker-install/tree.js +2 -4
  67. package/dist/edge/docker-install/volume.js +3 -2
  68. package/dist/edge/docker.js +5 -4
  69. package/dist/edge/events/hub.js +5 -5
  70. package/dist/edge/http/actions.js +11 -32
  71. package/dist/edge/http/actor.js +5 -3
  72. package/dist/edge/http/app.js +86 -11
  73. package/dist/edge/http/assistant.js +129 -0
  74. package/dist/edge/http/audio-preview.js +4 -11
  75. package/dist/edge/http/auditions.js +1 -1
  76. package/dist/edge/http/autostart.js +5 -5
  77. package/dist/edge/http/backups.js +2 -4
  78. package/dist/edge/http/channel-memory.js +9 -22
  79. package/dist/edge/http/channels.js +14 -27
  80. package/dist/edge/http/checkpoints.js +39 -21
  81. package/dist/edge/http/design-templates.js +167 -0
  82. package/dist/edge/http/designs.js +178 -0
  83. package/dist/edge/http/diagnostics.js +5 -1
  84. package/dist/edge/http/document-themes.js +5 -14
  85. package/dist/edge/http/draft-files.js +1 -3
  86. package/dist/edge/http/draft-problem.js +5 -10
  87. package/dist/edge/http/drafts.js +3 -3
  88. package/dist/edge/http/entries.js +6 -9
  89. package/dist/edge/http/extension-auth.js +27 -0
  90. package/dist/edge/http/external-reviews.js +12 -11
  91. package/dist/edge/http/files.js +5 -11
  92. package/dist/edge/http/folder-location.js +1 -4
  93. package/dist/edge/http/fonts.js +0 -7
  94. package/dist/edge/http/home.js +47 -14
  95. package/dist/edge/http/host-cli.js +18 -8
  96. package/dist/edge/http/ids.js +8 -0
  97. package/dist/edge/http/keywords.js +35 -0
  98. package/dist/edge/http/model-checks.js +132 -0
  99. package/dist/edge/http/narration-peaks.js +5 -13
  100. package/dist/edge/http/onboarding.js +30 -26
  101. package/dist/edge/http/open-folder.js +5 -10
  102. package/dist/edge/http/outputs.js +7 -14
  103. package/dist/edge/http/patch-notes.js +6 -12
  104. package/dist/edge/http/planning.js +3 -7
  105. package/dist/edge/http/problem.js +2 -4
  106. package/dist/edge/http/project-create.js +2 -1
  107. package/dist/edge/http/project-templates.js +31 -19
  108. package/dist/edge/http/projects.js +24 -16
  109. package/dist/edge/http/prompts.js +9 -16
  110. package/dist/edge/http/pronunciations.js +1 -2
  111. package/dist/edge/http/providers.js +48 -34
  112. package/dist/edge/http/refusal.js +29 -0
  113. package/dist/edge/http/remote-access.js +40 -0
  114. package/dist/edge/http/remote-guard.js +71 -0
  115. package/dist/edge/http/request-guard.js +91 -0
  116. package/dist/edge/http/reviews.js +12 -25
  117. package/dist/edge/http/revision-files.js +6 -11
  118. package/dist/edge/http/revisions.js +14 -19
  119. package/dist/edge/http/run-cost.js +1 -2
  120. package/dist/edge/http/same-page.js +80 -0
  121. package/dist/edge/http/schedules.js +69 -40
  122. package/dist/edge/http/settings.js +27 -14
  123. package/dist/edge/http/social.js +283 -0
  124. package/dist/edge/http/staging.js +3 -5
  125. package/dist/edge/http/storage-files.js +5 -7
  126. package/dist/edge/http/storage.js +33 -25
  127. package/dist/edge/http/studio.js +94 -69
  128. package/dist/edge/http/style-preview.js +3 -6
  129. package/dist/edge/http/subtitles.js +3 -17
  130. package/dist/edge/http/template-studio.js +436 -0
  131. package/dist/edge/http/trash.js +11 -12
  132. package/dist/edge/http/tutorial.js +1 -2
  133. package/dist/edge/http/tutorials.js +5 -11
  134. package/dist/edge/http/update.js +7 -13
  135. package/dist/edge/http/waveform.js +3 -6
  136. package/dist/edge/http/youtube-edits.js +3 -4
  137. package/dist/edge/mcp/assistant.js +279 -0
  138. package/dist/edge/mcp/attribution.js +8 -2
  139. package/dist/edge/mcp/call-route.js +11 -2
  140. package/dist/edge/mcp/excluded-routes.js +29 -3
  141. package/dist/edge/mcp/http.js +11 -2
  142. package/dist/edge/mcp/instructions.js +6 -4
  143. package/dist/edge/mcp/library-tools.js +9 -5
  144. package/dist/edge/mcp/remake-tools.js +7 -4
  145. package/dist/edge/mcp/route-tool.js +35 -15
  146. package/dist/edge/mcp/server.js +13 -8
  147. package/dist/edge/mcp/tools-channels.js +11 -9
  148. package/dist/edge/mcp/tools-designs.js +165 -0
  149. package/dist/edge/mcp/tools-library.js +19 -7
  150. package/dist/edge/mcp/tools-planning.js +40 -21
  151. package/dist/edge/mcp/tools-projects.js +38 -11
  152. package/dist/edge/mcp/tools-publishing.js +9 -9
  153. package/dist/edge/mcp/tools-runs.js +23 -10
  154. package/dist/edge/mcp/tools-schedules.js +32 -8
  155. package/dist/edge/mcp/tools-setup.js +75 -11
  156. package/dist/edge/mcp/tools-social.js +47 -0
  157. package/dist/edge/mcp/tools-storage.js +11 -3
  158. package/dist/edge/mcp/tools-template-studio.js +166 -0
  159. package/dist/edge/mcp/tools.js +6 -0
  160. package/dist/edge/remote-access/cli.js +71 -0
  161. package/dist/edge/remote-access/model.js +32 -0
  162. package/dist/edge/remote-access/rules.js +207 -0
  163. package/dist/edge/remote-access/service.js +203 -0
  164. package/dist/edge/remote-access/store.js +44 -0
  165. package/dist/edge/remote-access/views.js +43 -0
  166. package/dist/extension/slopify-studio-chrome.zip +0 -0
  167. package/dist/extension/slopify-studio-firefox.zip +0 -0
  168. package/dist/host-cli/install.js +2 -2
  169. package/dist/host-cli/open-folder.js +2 -2
  170. package/dist/host-cli/paths.js +2 -4
  171. package/dist/host-cli/server.js +4 -4
  172. package/dist/host-cli/service.js +3 -3
  173. package/dist/host-cli/status.js +7 -7
  174. package/dist/kernel/db/migrations/0063-model-checks.sql +20 -0
  175. package/dist/kernel/db/migrations/0064-template-studio.sql +46 -0
  176. package/dist/kernel/db/migrations/0065-promise-and-assistant-reviews.sql +58 -0
  177. package/dist/kernel/db/migrations/0066-assistant.sql +44 -0
  178. package/dist/kernel/db/migrations/0067-autopilot.sql +6 -0
  179. package/dist/kernel/db/migrations/0068-topic-scores.sql +17 -0
  180. package/dist/kernel/db/migrations/0070-designs.sql +53 -0
  181. package/dist/kernel/db/migrations/0071-design-usage.sql +33 -0
  182. package/dist/kernel/db/migrations/0072-design-templates.sql +33 -0
  183. package/dist/kernel/db/migrations/0073-schedule-design-kind.sql +5 -0
  184. package/dist/kernel/db/migrations/0074-template-bakeoffs.sql +44 -0
  185. package/dist/kernel/db/migrations/0075-social-posts.sql +25 -0
  186. package/dist/kernel/db/migrations/0076-channel-social.sql +5 -0
  187. package/dist/kernel/db/migrations/0077-post-prompts.sql +17 -0
  188. package/dist/kernel/db/migrations/0078-uploaded-from-links.sql +7 -0
  189. package/dist/kernel/db/tx.js +2 -4
  190. package/dist/kernel/errors.js +27 -1
  191. package/dist/kernel/json-answer.js +15 -0
  192. package/dist/kernel/lock.js +4 -9
  193. package/dist/kernel/paths.js +8 -0
  194. package/dist/kernel/pipeline.js +12 -0
  195. package/dist/kernel/ports/assistant-cli.js +4 -0
  196. package/dist/kernel/ports/host-cli.js +3 -0
  197. package/dist/kernel/ports/interest.js +4 -0
  198. package/dist/kernel/ports/languages.js +0 -3
  199. package/dist/kernel/ports/model.js +2 -0
  200. package/dist/kernel/ports/settings-places.js +31 -0
  201. package/dist/kernel/ports/system-speech.js +31 -3
  202. package/dist/kernel/runner/attempt.js +14 -4
  203. package/dist/kernel/runner/audition.js +3 -1
  204. package/dist/kernel/runner/checkpoint-authority.js +2 -1
  205. package/dist/kernel/runner/index.js +8 -10
  206. package/dist/kernel/runner/model-access.js +40 -0
  207. package/dist/kernel/runner/model-check.js +153 -0
  208. package/dist/kernel/runner/providers.js +18 -12
  209. package/dist/kernel/runner/standalone.js +11 -7
  210. package/dist/main.js +338 -21
  211. package/dist/model-catalog.js +20 -0
  212. package/dist/model-checks.js +166 -0
  213. package/dist/patch-notes/4.0.0.md +51 -0
  214. package/dist/patch-notes/4.1.0.md +42 -0
  215. package/dist/patch-notes/index.json +12 -0
  216. package/dist/slices/admission/heads.js +2 -1
  217. package/dist/slices/admission/listing-facts.js +95 -0
  218. package/dist/slices/admission/repo.js +11 -7
  219. package/dist/slices/admission/rules.js +50 -15
  220. package/dist/slices/admission/schema.js +8 -0
  221. package/dist/slices/admission/stage-writes.js +0 -3
  222. package/dist/slices/admission/start.js +48 -31
  223. package/dist/slices/admission/state-words.js +34 -5
  224. package/dist/slices/admission/substitute.js +7 -4
  225. package/dist/slices/agents/attention-posts.js +31 -0
  226. package/dist/slices/agents/attention-templates.js +33 -0
  227. package/dist/slices/agents/attention.js +10 -1
  228. package/dist/slices/agents/connection.js +2 -6
  229. package/dist/slices/agents/model.js +6 -3
  230. package/dist/slices/agents/repo.js +15 -7
  231. package/dist/slices/article/continuation.js +0 -63
  232. package/dist/slices/article/plain.js +3 -2
  233. package/dist/slices/article/segments.js +2 -62
  234. package/dist/slices/assistant/access.js +1 -0
  235. package/dist/slices/assistant/asks.js +80 -0
  236. package/dist/slices/assistant/cost.js +54 -0
  237. package/dist/slices/assistant/gate.js +222 -0
  238. package/dist/slices/assistant/model.js +86 -0
  239. package/dist/slices/assistant/names.js +23 -0
  240. package/dist/slices/assistant/prompt.js +74 -0
  241. package/dist/slices/assistant/repo.js +74 -0
  242. package/dist/slices/assistant/tokens.js +18 -0
  243. package/dist/slices/assistant/turn.js +344 -0
  244. package/dist/slices/autopilot/buffer.js +76 -0
  245. package/dist/slices/autopilot/model.js +14 -0
  246. package/dist/slices/autopilot/notify.js +27 -0
  247. package/dist/slices/autopilot/repo.js +34 -0
  248. package/dist/slices/autopilot/signals.js +194 -0
  249. package/dist/slices/autopilot/warnings.js +85 -0
  250. package/dist/slices/autopilot/weekly.js +100 -0
  251. package/dist/slices/backups/files.js +2 -6
  252. package/dist/slices/backups/repo.js +3 -15
  253. package/dist/slices/backups/service.js +8 -7
  254. package/dist/slices/channels/cast-images.js +3 -2
  255. package/dist/slices/channels/model.js +0 -1
  256. package/dist/slices/channels/rebrand.js +0 -3
  257. package/dist/slices/channels/repo.js +4 -0
  258. package/dist/slices/channels/schema.js +7 -1
  259. package/dist/slices/channels/service.js +10 -0
  260. package/dist/slices/channels/videos.js +3 -15
  261. package/dist/slices/checkpoints/index.js +0 -1
  262. package/dist/slices/control/providers.js +2 -28
  263. package/dist/slices/designs/artwork.js +66 -0
  264. package/dist/slices/designs/budget.js +23 -0
  265. package/dist/slices/designs/calls.js +124 -0
  266. package/dist/slices/designs/check-context.js +38 -0
  267. package/dist/slices/designs/check-text.js +43 -0
  268. package/dist/slices/designs/fields.js +88 -0
  269. package/dist/slices/designs/frames.js +34 -0
  270. package/dist/slices/designs/gate.js +130 -0
  271. package/dist/slices/designs/layout.js +293 -0
  272. package/dist/slices/designs/model.js +35 -0
  273. package/dist/slices/designs/pdf-job.js +23 -0
  274. package/dist/slices/designs/pdf-pages.js +30 -0
  275. package/dist/slices/designs/pdf-runner.js +68 -0
  276. package/dist/slices/designs/pdf-worker.js +24 -0
  277. package/dist/slices/designs/picture-size.js +79 -0
  278. package/dist/slices/designs/pictures.js +55 -0
  279. package/dist/slices/designs/quote.js +24 -0
  280. package/dist/slices/designs/render-args.js +36 -0
  281. package/dist/slices/designs/render-input.js +59 -0
  282. package/dist/slices/designs/render.js +215 -0
  283. package/dist/slices/designs/repo.js +101 -0
  284. package/dist/slices/designs/requests.js +80 -0
  285. package/dist/slices/designs/runner.js +239 -0
  286. package/dist/slices/designs/sample-repo.js +91 -0
  287. package/dist/slices/designs/sample.js +223 -0
  288. package/dist/slices/designs/schema.js +87 -0
  289. package/dist/slices/designs/sections.js +44 -0
  290. package/dist/slices/designs/service.js +233 -0
  291. package/dist/slices/designs/steps-repo.js +102 -0
  292. package/dist/slices/designs/studio-kind.js +39 -0
  293. package/dist/slices/designs/template-checks.js +44 -0
  294. package/dist/slices/designs/template-make.js +118 -0
  295. package/dist/slices/designs/templates.js +218 -0
  296. package/dist/slices/document/fonts.js +2 -1
  297. package/dist/slices/document/model.js +7 -4
  298. package/dist/slices/document/render.js +2 -1
  299. package/dist/slices/document/theme-schema.js +1 -1
  300. package/dist/slices/document/theme.js +49 -3
  301. package/dist/slices/episodes/summarize.js +6 -2
  302. package/dist/slices/estimate/index.js +72 -28
  303. package/dist/slices/estimate/requests.js +4 -4
  304. package/dist/slices/fixes/rules.js +3 -1
  305. package/dist/slices/fonts/catalog.js +5 -3
  306. package/dist/slices/fonts/coverage.js +2 -1
  307. package/dist/slices/fonts/discovery.js +3 -2
  308. package/dist/slices/fonts/files.js +2 -6
  309. package/dist/slices/fonts/index.js +0 -1
  310. package/dist/slices/fonts/sfnt.js +45 -10
  311. package/dist/slices/images/appearance.js +8 -16
  312. package/dist/slices/images/scenes.js +102 -28
  313. package/dist/slices/keywords/digest.js +85 -0
  314. package/dist/slices/keywords/facts.js +92 -0
  315. package/dist/slices/keywords/fill.js +80 -0
  316. package/dist/slices/keywords/model.js +75 -0
  317. package/dist/slices/keywords/rules.js +187 -0
  318. package/dist/slices/keywords/schema.js +28 -0
  319. package/dist/slices/keywords/specs.js +260 -0
  320. package/dist/slices/keywords/values.js +74 -0
  321. package/dist/slices/library/lint.js +29 -12
  322. package/dist/slices/library/model.js +2 -0
  323. package/dist/slices/library/photorealistic.js +4 -12
  324. package/dist/slices/library/rename.js +9 -7
  325. package/dist/slices/library/save.js +14 -10
  326. package/dist/slices/library/slots.js +37 -11
  327. package/dist/slices/library/transfer.js +5 -4
  328. package/dist/slices/library/used-by.js +25 -10
  329. package/dist/slices/loudness/loudnorm.js +53 -12
  330. package/dist/slices/loudness/model.js +3 -2
  331. package/dist/slices/loudness/sentence-level.js +2 -1
  332. package/dist/slices/model-checks/cost.js +61 -0
  333. package/dist/slices/model-checks/listing.js +124 -0
  334. package/dist/slices/model-checks/model.js +20 -0
  335. package/dist/slices/model-checks/record.js +32 -0
  336. package/dist/slices/model-checks/repo.js +120 -0
  337. package/dist/slices/model-checks/rules.js +125 -0
  338. package/dist/slices/model-checks/run.js +143 -0
  339. package/dist/slices/model-checks/service.js +198 -0
  340. package/dist/slices/model-upkeep/model.js +1 -1
  341. package/dist/slices/model-upkeep/switch.js +72 -27
  342. package/dist/slices/model-upkeep/usage.js +39 -13
  343. package/dist/slices/narration/breaths.js +3 -1
  344. package/dist/slices/narration/clean-pieces.js +3 -1
  345. package/dist/slices/narration/concat.js +2 -1
  346. package/dist/slices/narration/pauses-model.js +3 -2
  347. package/dist/slices/narration/peaks.js +3 -4
  348. package/dist/slices/narration/pronunciation-chunks.js +4 -3
  349. package/dist/slices/narration/pronunciation.js +6 -4
  350. package/dist/slices/narration/steering.js +4 -3
  351. package/dist/slices/notifications/notifier.js +4 -3
  352. package/dist/slices/notifications/rules.js +5 -2
  353. package/dist/slices/notifications/settings.js +4 -12
  354. package/dist/slices/notifications/subject.js +4 -3
  355. package/dist/slices/onboarding/copy.js +21 -3
  356. package/dist/slices/onboarding/first-run.js +16 -40
  357. package/dist/slices/onboarding/full-video.js +3 -2
  358. package/dist/slices/onboarding/install.js +6 -4
  359. package/dist/slices/onboarding/model.js +26 -37
  360. package/dist/slices/onboarding/packs.js +22 -22
  361. package/dist/slices/onboarding/preferred-model.js +14 -0
  362. package/dist/slices/onboarding/quick-short.js +105 -65
  363. package/dist/slices/onboarding/sample.js +4 -2
  364. package/dist/slices/onboarding/state.js +9 -11
  365. package/dist/slices/onboarding/template.js +8 -6
  366. package/dist/slices/outputs/add.js +3 -2
  367. package/dist/slices/outputs/service.js +2 -2
  368. package/dist/slices/patch-notes/library.js +4 -3
  369. package/dist/slices/play-drafts/convert.js +4 -0
  370. package/dist/slices/play-drafts/readiness.js +3 -3
  371. package/dist/slices/play-drafts/review-inputs.js +9 -3
  372. package/dist/slices/play-drafts/review.js +5 -4
  373. package/dist/slices/play-drafts/schema.js +4 -0
  374. package/dist/slices/play-drafts/service.js +17 -7
  375. package/dist/slices/project-templates/from-project.js +9 -2
  376. package/dist/slices/project-templates/next-chapter.js +2 -1
  377. package/dist/slices/project-templates/provider-defaults.js +22 -0
  378. package/dist/slices/project-templates/repo.js +6 -1
  379. package/dist/slices/project-templates/setup-sections.js +26 -0
  380. package/dist/slices/project-templates/setup.js +14 -1
  381. package/dist/slices/project-templates/stand-ins.js +22 -0
  382. package/dist/slices/rebuild/activity.js +2 -0
  383. package/dist/slices/rebuild/admission-repo.js +3 -2
  384. package/dist/slices/rebuild/narration-history.js +4 -5
  385. package/dist/slices/rebuild/narration-retry.js +2 -1
  386. package/dist/slices/rebuild/preview-details.js +32 -14
  387. package/dist/slices/rebuild/preview-plan.js +3 -8
  388. package/dist/slices/rebuild/recipe-build.js +8 -3
  389. package/dist/slices/rebuild/recipe-describe.js +3 -3
  390. package/dist/slices/rebuild/recipe-digest.js +58 -0
  391. package/dist/slices/rebuild/recipe-input-schema.js +10 -0
  392. package/dist/slices/rebuild/recipe-keywords.js +71 -0
  393. package/dist/slices/rebuild/recipe-legacy.js +5 -1
  394. package/dist/slices/rebuild/recipe-model.js +4 -0
  395. package/dist/slices/rebuild/recipe-narration-text.js +2 -1
  396. package/dist/slices/rebuild/recipe-preparation.js +4 -4
  397. package/dist/slices/rebuild/recipe-reference.js +8 -4
  398. package/dist/slices/rebuild/recipe-reviews.js +27 -7
  399. package/dist/slices/rebuild/recipe-save.js +8 -5
  400. package/dist/slices/rebuild/recipe-scenes.js +40 -11
  401. package/dist/slices/rebuild/recipe-shorts.js +8 -8
  402. package/dist/slices/rebuild/recipe-social.js +46 -0
  403. package/dist/slices/rebuild/recipe-text.js +64 -20
  404. package/dist/slices/rebuild/recipe-validation.js +102 -31
  405. package/dist/slices/rebuild/recipe-visual.js +55 -16
  406. package/dist/slices/rebuild/recipe-voices.js +3 -2
  407. package/dist/slices/rebuild/recipe-work.js +19 -7
  408. package/dist/slices/rebuild/recipe-youtube.js +4 -3
  409. package/dist/slices/rebuild/recovery-selection.js +3 -1
  410. package/dist/slices/rebuild/recovery.js +9 -8
  411. package/dist/slices/rebuild/remake-outdated.js +5 -5
  412. package/dist/slices/rebuild/repo.js +4 -3
  413. package/dist/slices/rebuild/review-promise.js +25 -0
  414. package/dist/slices/rebuild/review-redo.js +3 -2
  415. package/dist/slices/rebuild/runtime-admission.js +4 -3
  416. package/dist/slices/rebuild/runtime-agent-review.js +11 -5
  417. package/dist/slices/rebuild/runtime-animate.js +10 -9
  418. package/dist/slices/rebuild/runtime-article.js +7 -5
  419. package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
  420. package/dist/slices/rebuild/runtime-document.js +11 -13
  421. package/dist/slices/rebuild/runtime-export-bed.js +8 -12
  422. package/dist/slices/rebuild/runtime-export-edit.js +8 -16
  423. package/dist/slices/rebuild/runtime-export-inputs.js +12 -11
  424. package/dist/slices/rebuild/runtime-export-short.js +9 -5
  425. package/dist/slices/rebuild/runtime-export.js +38 -15
  426. package/dist/slices/rebuild/runtime-figure-card.js +2 -1
  427. package/dist/slices/rebuild/runtime-image.js +8 -12
  428. package/dist/slices/rebuild/runtime-instructions.js +2 -0
  429. package/dist/slices/rebuild/runtime-keywords.js +59 -0
  430. package/dist/slices/rebuild/runtime-lines.js +2 -11
  431. package/dist/slices/rebuild/runtime-local.js +17 -22
  432. package/dist/slices/rebuild/runtime-narration-publication.js +2 -5
  433. package/dist/slices/rebuild/runtime-narration-reuse.js +9 -13
  434. package/dist/slices/rebuild/runtime-narration-text.js +13 -17
  435. package/dist/slices/rebuild/runtime-pictures.js +2 -4
  436. package/dist/slices/rebuild/runtime-piece-label.js +10 -6
  437. package/dist/slices/rebuild/runtime-plan.js +31 -5
  438. package/dist/slices/rebuild/runtime-provider.js +67 -25
  439. package/dist/slices/rebuild/runtime-publication.js +10 -9
  440. package/dist/slices/rebuild/runtime-review.js +39 -33
  441. package/dist/slices/rebuild/runtime-run.js +4 -1
  442. package/dist/slices/rebuild/runtime-shorts.js +31 -50
  443. package/dist/slices/rebuild/runtime-store.js +20 -10
  444. package/dist/slices/rebuild/runtime-subtitles.js +28 -25
  445. package/dist/slices/rebuild/runtime-voices.js +10 -16
  446. package/dist/slices/rebuild/runtime-youtube.js +14 -31
  447. package/dist/slices/rebuild/service-readiness.js +11 -3
  448. package/dist/slices/rebuild/transition-repo.js +6 -5
  449. package/dist/slices/rebuild/work-records.js +6 -0
  450. package/dist/slices/reruns/cascade.js +1 -2
  451. package/dist/slices/reruns/index.js +6 -9
  452. package/dist/slices/research/synthesis.js +3 -2
  453. package/dist/slices/reviews/external-model.js +4 -1
  454. package/dist/slices/reviews/external-repo.js +2 -1
  455. package/dist/slices/reviews/external-rows.js +2 -3
  456. package/dist/slices/reviews/holds.js +5 -4
  457. package/dist/slices/reviews/model.js +32 -11
  458. package/dist/slices/reviews/repo.js +2 -1
  459. package/dist/slices/reviews/rules.js +4 -3
  460. package/dist/slices/reviews/verdict.js +2 -1
  461. package/dist/slices/reviews/view.js +5 -2
  462. package/dist/slices/revisions/downloads.js +4 -5
  463. package/dist/slices/revisions/image-plan.js +1 -1
  464. package/dist/slices/revisions/mutation-assets.js +9 -5
  465. package/dist/slices/revisions/mutation-cues.js +2 -5
  466. package/dist/slices/revisions/mutations.js +3 -3
  467. package/dist/slices/revisions/repo.js +3 -1
  468. package/dist/slices/revisions/schema.js +1 -0
  469. package/dist/slices/revisions/view.js +20 -0
  470. package/dist/slices/run-cost/limits.js +5 -2
  471. package/dist/slices/run-cost/meter.js +12 -5
  472. package/dist/slices/run-cost/panel.js +8 -74
  473. package/dist/slices/run-cost/plan-readings.js +127 -0
  474. package/dist/slices/run-cost/plan-use.js +169 -0
  475. package/dist/slices/run-cost/pricing.js +3 -3
  476. package/dist/slices/run-cost/week.js +32 -4
  477. package/dist/slices/schedules/agenda.js +18 -7
  478. package/dist/slices/schedules/by-date.js +42 -0
  479. package/dist/slices/schedules/calendar.js +5 -0
  480. package/dist/slices/schedules/design-agenda.js +36 -0
  481. package/dist/slices/schedules/design-check.js +41 -0
  482. package/dist/slices/schedules/design-run.js +126 -0
  483. package/dist/slices/schedules/next-run.js +62 -0
  484. package/dist/slices/schedules/prepare.js +18 -2
  485. package/dist/slices/schedules/repo.js +30 -21
  486. package/dist/slices/schedules/rescore.js +35 -0
  487. package/dist/slices/schedules/scheduler.js +13 -3
  488. package/dist/slices/schedules/schema.js +57 -3
  489. package/dist/slices/schedules/score.js +107 -0
  490. package/dist/slices/schedules/service.js +57 -24
  491. package/dist/slices/schedules/topic-list.js +15 -0
  492. package/dist/slices/schedules/topic-scores.js +104 -0
  493. package/dist/slices/schedules/topics.js +53 -41
  494. package/dist/slices/settings/cli-paths.js +4 -6
  495. package/dist/slices/settings/first-run.js +8 -43
  496. package/dist/slices/settings/health.js +30 -15
  497. package/dist/slices/settings/key-test.js +1 -1
  498. package/dist/slices/settings/model-reach.js +3 -17
  499. package/dist/slices/settings/model.js +14 -2
  500. package/dist/slices/settings/playback.js +34 -23
  501. package/dist/slices/settings/probe-read.js +16 -0
  502. package/dist/slices/settings/repo.js +33 -0
  503. package/dist/slices/settings/tutorial.js +2 -2
  504. package/dist/slices/settings/voices.js +4 -12
  505. package/dist/slices/shorts/keep.js +2 -1
  506. package/dist/slices/shorts/pick.js +6 -18
  507. package/dist/slices/shorts/prompts.js +4 -14
  508. package/dist/slices/shorts/render.js +6 -2
  509. package/dist/slices/social/channel.js +44 -0
  510. package/dist/slices/social/defaults.js +17 -0
  511. package/dist/slices/social/due.js +36 -0
  512. package/dist/slices/social/extension.js +18 -0
  513. package/dist/slices/social/fit.js +115 -0
  514. package/dist/slices/social/limits.js +186 -0
  515. package/dist/slices/social/links.js +21 -0
  516. package/dist/slices/social/media.js +132 -0
  517. package/dist/slices/social/model.js +75 -0
  518. package/dist/slices/social/notice.js +19 -0
  519. package/dist/slices/social/pack.js +176 -0
  520. package/dist/slices/social/post-prompt.js +56 -0
  521. package/dist/slices/social/queue.js +31 -0
  522. package/dist/slices/social/repo.js +133 -0
  523. package/dist/slices/social/reshape.js +97 -0
  524. package/dist/slices/social/rules.js +5 -0
  525. package/dist/slices/social/schema.js +70 -0
  526. package/dist/slices/social/serve.js +51 -0
  527. package/dist/slices/social/service.js +129 -0
  528. package/dist/slices/social/settings.js +21 -0
  529. package/dist/slices/social/sources.js +114 -0
  530. package/dist/slices/social/state.js +110 -0
  531. package/dist/slices/social/text.js +149 -0
  532. package/dist/slices/storage/arrange.js +3 -1
  533. package/dist/slices/storage/assets.js +8 -10
  534. package/dist/slices/storage/backup-designs.js +74 -0
  535. package/dist/slices/storage/backup-export.js +8 -3
  536. package/dist/slices/storage/backup-format.js +10 -0
  537. package/dist/slices/storage/backup-import.js +22 -4
  538. package/dist/slices/storage/delete-project.js +6 -5
  539. package/dist/slices/storage/files-location.js +6 -8
  540. package/dist/slices/storage/layout.js +13 -2
  541. package/dist/slices/storage/model.js +3 -0
  542. package/dist/slices/storage/portable.js +17 -12
  543. package/dist/slices/storage/schema.js +2 -0
  544. package/dist/slices/storage/staging-refs.js +2 -1
  545. package/dist/slices/storage/staging.js +6 -9
  546. package/dist/slices/storage/trim.js +26 -9
  547. package/dist/slices/studio/calendar.js +7 -2
  548. package/dist/slices/studio/disclosure.js +4 -4
  549. package/dist/slices/studio/model.js +1 -1
  550. package/dist/slices/studio/pack.js +9 -8
  551. package/dist/slices/studio/pick.js +2 -11
  552. package/dist/slices/studio/plan.js +7 -16
  553. package/dist/slices/studio/queue.js +3 -14
  554. package/dist/slices/studio/releases.js +26 -0
  555. package/dist/slices/studio/settings.js +20 -57
  556. package/dist/slices/studio/videos.js +35 -4
  557. package/dist/slices/style-preview/render.js +37 -16
  558. package/dist/slices/style-preview/schema.js +14 -1
  559. package/dist/slices/style-preview/service.js +2 -1
  560. package/dist/slices/style-preview/settings.js +33 -5
  561. package/dist/slices/subtitles/captions.js +1 -1
  562. package/dist/slices/telemetry/collector-client.js +2 -4
  563. package/dist/slices/telemetry/flush.js +2 -4
  564. package/dist/slices/telemetry/model.js +0 -7
  565. package/dist/slices/telemetry/record.js +10 -3
  566. package/dist/slices/template-studio/anatomy-view.js +161 -0
  567. package/dist/slices/template-studio/anatomy.js +73 -0
  568. package/dist/slices/template-studio/bakeoff-make.js +225 -0
  569. package/dist/slices/template-studio/bakeoff-repo.js +83 -0
  570. package/dist/slices/template-studio/bakeoff.js +333 -0
  571. package/dist/slices/template-studio/budget.js +106 -0
  572. package/dist/slices/template-studio/cards.js +78 -0
  573. package/dist/slices/template-studio/checks.js +322 -0
  574. package/dist/slices/template-studio/document-sample.js +56 -0
  575. package/dist/slices/template-studio/edit.js +15 -0
  576. package/dist/slices/template-studio/for-all.js +161 -0
  577. package/dist/slices/template-studio/free-video.js +31 -0
  578. package/dist/slices/template-studio/free.js +176 -0
  579. package/dist/slices/template-studio/gate.js +40 -0
  580. package/dist/slices/template-studio/hidden.js +20 -0
  581. package/dist/slices/template-studio/jobs.js +79 -0
  582. package/dist/slices/template-studio/loudness.js +111 -0
  583. package/dist/slices/template-studio/make.js +40 -0
  584. package/dist/slices/template-studio/model.js +58 -0
  585. package/dist/slices/template-studio/pack-import.js +253 -0
  586. package/dist/slices/template-studio/pack-samples.js +60 -0
  587. package/dist/slices/template-studio/pack.js +207 -0
  588. package/dist/slices/template-studio/paid-view.js +66 -0
  589. package/dist/slices/template-studio/paid.js +355 -0
  590. package/dist/slices/template-studio/quote.js +155 -0
  591. package/dist/slices/template-studio/ready.js +104 -0
  592. package/dist/slices/template-studio/repo.js +134 -0
  593. package/dist/slices/template-studio/reviews-path.js +58 -0
  594. package/dist/slices/template-studio/runner.js +84 -0
  595. package/dist/slices/template-studio/save.js +236 -0
  596. package/dist/slices/template-studio/say-it-as.js +248 -0
  597. package/dist/slices/template-studio/schedule-gate.js +34 -0
  598. package/dist/slices/template-studio/schedule-price.js +32 -0
  599. package/dist/slices/template-studio/schedule-sample.js +91 -0
  600. package/dist/slices/template-studio/schema.js +243 -0
  601. package/dist/slices/template-studio/scope.js +124 -0
  602. package/dist/slices/template-studio/sections.js +176 -0
  603. package/dist/slices/template-studio/service.js +175 -0
  604. package/dist/slices/template-studio/status.js +191 -0
  605. package/dist/slices/template-studio/topic.js +223 -0
  606. package/dist/slices/thumbnail/by-llm.js +1 -1
  607. package/dist/slices/trash/designs.js +76 -0
  608. package/dist/slices/trash/model.js +13 -4
  609. package/dist/slices/trash/service.js +52 -2
  610. package/dist/slices/tutorials/library.js +3 -2
  611. package/dist/slices/uploads/repo.js +37 -1
  612. package/dist/slices/uploads/rules.js +3 -2
  613. package/dist/slices/video/ass-text.js +17 -0
  614. package/dist/slices/video/audio-export.js +2 -64
  615. package/dist/slices/video/edit-list.js +2 -2
  616. package/dist/slices/video/edit-settings.js +1 -1
  617. package/dist/slices/video/encoding.js +169 -0
  618. package/dist/slices/video/ffmpeg.js +23 -21
  619. package/dist/slices/video/figure-card.js +1 -15
  620. package/dist/slices/video/plan.js +2 -1
  621. package/dist/slices/video/slideshow.js +43 -18
  622. package/dist/slices/voices/attribution.js +3 -2
  623. package/dist/slices/voices/model.js +9 -8
  624. package/dist/slices/voices/palette.js +0 -3
  625. package/dist/slices/voices/script.js +2 -1
  626. package/dist/slices/youtube/answer.js +4 -16
  627. package/dist/slices/youtube/edits-repo.js +3 -12
  628. package/dist/tutorials/AI-CLIs.md +25 -25
  629. package/dist/tutorials/Agents-MCP.md +34 -33
  630. package/dist/tutorials/Assistant.md +42 -0
  631. package/dist/tutorials/Backups.md +7 -7
  632. package/dist/tutorials/Calendar.md +17 -25
  633. package/dist/tutorials/Cast-Library.md +19 -19
  634. package/dist/tutorials/Channels.md +42 -35
  635. package/dist/tutorials/Costs-and-Run-Cost.md +38 -40
  636. package/dist/tutorials/Create-Article.md +114 -0
  637. package/dist/tutorials/Create-Extras.md +112 -0
  638. package/dist/tutorials/Create-Images.md +165 -0
  639. package/dist/tutorials/Create-Narration.md +178 -0
  640. package/dist/tutorials/Create-Overview.md +244 -0
  641. package/dist/tutorials/Create-Video-and-Style.md +144 -0
  642. package/dist/tutorials/Docker.md +3 -3
  643. package/dist/tutorials/Document-Themes.md +32 -32
  644. package/dist/tutorials/Editing-a-Project.md +94 -94
  645. package/dist/tutorials/FAQ.md +18 -18
  646. package/dist/tutorials/First-Launch-and-Welcome.md +50 -64
  647. package/dist/tutorials/Home-and-Projects.md +115 -71
  648. package/dist/tutorials/Home.md +33 -33
  649. package/dist/tutorials/Install.md +14 -13
  650. package/dist/tutorials/Intros-and-Outros.md +33 -34
  651. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +62 -58
  652. package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
  653. package/dist/tutorials/Models.md +53 -21
  654. package/dist/tutorials/Multiple-Voices.md +41 -40
  655. package/dist/tutorials/Narration-Aliases-and-Glossary.md +14 -14
  656. package/dist/tutorials/Notifications.md +27 -17
  657. package/dist/tutorials/Other-Languages.md +15 -15
  658. package/dist/tutorials/PDF-Documents.md +38 -29
  659. package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
  660. package/dist/tutorials/Project-Page.md +118 -120
  661. package/dist/tutorials/Prompts-and-Templates.md +84 -0
  662. package/dist/tutorials/Prompts.md +48 -44
  663. package/dist/tutorials/Providers-and-Keys.md +105 -98
  664. package/dist/tutorials/Publishing-to-YouTube.md +31 -31
  665. package/dist/tutorials/Recovery-and-Retries.md +31 -31
  666. package/dist/tutorials/Remote-Access.md +63 -0
  667. package/dist/tutorials/Reviews-and-Checkpoints.md +98 -90
  668. package/dist/tutorials/Schedules.md +46 -27
  669. package/dist/tutorials/Settings-Reference.md +194 -174
  670. package/dist/tutorials/Shorts.md +34 -33
  671. package/dist/tutorials/Start-at-Login.md +3 -3
  672. package/dist/tutorials/Studio-Extension.md +22 -24
  673. package/dist/tutorials/Template-Studio.md +128 -0
  674. package/dist/tutorials/Templates.md +51 -77
  675. package/dist/tutorials/Trash.md +4 -4
  676. package/dist/tutorials/Troubleshooting.md +44 -44
  677. package/dist/tutorials/Uninstalling-and-Moving.md +5 -5
  678. package/dist/tutorials/Updating-and-Patch-Notes.md +18 -18
  679. package/dist/tutorials/Video-Editing.md +34 -34
  680. package/dist/tutorials/Where-Your-Files-Live.md +10 -10
  681. package/dist/tutorials/YouTube-Description.md +26 -26
  682. package/dist/tutorials/Your-First-Short.md +35 -35
  683. package/dist/tutorials/_Sidebar.md +19 -14
  684. package/dist/updater/work-in-progress.js +42 -12
  685. package/dist/web/assets/index-BNftKQrA.css +1 -0
  686. package/dist/web/assets/index-Cpa58woP.js +459 -0
  687. package/dist/web/assets/pdf-BKh8f7R6.js +56 -0
  688. package/dist/web/assets/pdf.worker.min-BmVo14Nb.mjs +28 -0
  689. package/dist/web/assets/pdf.worker.min-D7i8o3wR.js +1 -0
  690. package/dist/web/index.html +2 -2
  691. package/package.json +6 -2
  692. package/dist/slices/article/provided-entries.js +0 -66
  693. package/dist/slices/article/run.js +0 -145
  694. package/dist/slices/images/run.js +0 -234
  695. package/dist/slices/narration/legacy-plan.js +0 -58
  696. package/dist/slices/narration/run.js +0 -270
  697. package/dist/slices/rebuild/runtime-actions.js +0 -90
  698. package/dist/slices/research/run.js +0 -224
  699. package/dist/slices/subtitles/prepare.js +0 -194
  700. package/dist/slices/subtitles/transcript.js +0 -34
  701. package/dist/slices/thumbnail/run.js +0 -219
  702. package/dist/slices/video/audio-inputs.js +0 -33
  703. package/dist/slices/video/reuse-audio.js +0 -62
  704. package/dist/slices/video/run.js +0 -78
  705. package/dist/slices/video/write-export.js +0 -158
  706. package/dist/slices/video/write-subtitles.js +0 -59
  707. package/dist/tutorials/Library-Overview.md +0 -76
  708. package/dist/tutorials/Play-Images.md +0 -161
  709. package/dist/tutorials/Play-Narration.md +0 -167
  710. package/dist/tutorials/Play-Outputs.md +0 -109
  711. package/dist/tutorials/Play-Overview.md +0 -227
  712. package/dist/tutorials/Play-Title-and-Article.md +0 -136
  713. package/dist/tutorials/Play-Video-and-Style.md +0 -143
  714. package/dist/web/assets/index-BadsoORk.css +0 -1
  715. package/dist/web/assets/index-D8LqNsCl.js +0 -326
  716. package/dist/web/assets/pdf-T7MCwjIv.js +0 -54
  717. package/dist/web/assets/pdf.worker.min-Dswkl-cV.mjs +0 -29
  718. package/dist/web/assets/pdf.worker.min-zC6nfbxG.js +0 -1
@@ -38,8 +38,8 @@ export function estimateRequests(requests, catalogue) {
38
38
  catalogueDate: catalogue.updatedAt,
39
39
  assumptions: [
40
40
  "Planning estimate, not a spending limit. Generated lengths use a ±50% range; actual output can exceed it.",
41
- "Assumes roughly 6 characters per word, 4 characters per token, and 150 spoken words per minute.",
42
- "Retries, extra reasoning tokens, web tools, taxes, discounts and included credits are excluded. Unknown charges are not counted as zero.",
41
+ "Assumes about 6 letters per word and 150 spoken words a minute.",
42
+ "Retries, extra thinking, web search, taxes, discounts and included credits are not counted. A charge with no listed price is not counted as zero.",
43
43
  ],
44
44
  };
45
45
  }
@@ -99,8 +99,8 @@ function onPlan(request, catalogue) {
99
99
  high: 0,
100
100
  detail: request.detail ??
101
101
  (amount === null
102
- ? "Runs on your CLI plan, so it adds no charge; no API price is listed for this model."
103
- : `Runs on your CLI plan, so it adds no charge; priced as ${api?.name ?? "the API model"} through the API.`),
102
+ ? "Runs on your subscription at no extra charge; no API price is listed for this model."
103
+ : `Runs on your subscription at no extra charge; through the API it would cost what ${api?.name ?? "the API model"} costs.`),
104
104
  onPlan: true,
105
105
  apiLow: amount === null ? null : amount * 0.5,
106
106
  apiHigh: amount === null ? null : amount * 1.5,
@@ -35,7 +35,9 @@ export function fixFor(step) {
35
35
  const name = nameOf(step.provider);
36
36
  return {
37
37
  kind: "provider-settings",
38
- label: name === undefined ? "Open Settings → Providers" : `Open Settings → Providers → ${name}`,
38
+ label: name === undefined
39
+ ? "Open Settings → Keys & services"
40
+ : `Open Settings → Keys & services → ${name}`,
39
41
  provider: step.provider ?? "",
40
42
  };
41
43
  }
@@ -2,8 +2,10 @@ import { createHash } from "node:crypto";
2
2
  import { lstat, readdir } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
+ import { isUnreachablePath } from "../../kernel/errors.js";
6
+ import { editSettings } from "../../kernel/ports/settings-places.js";
5
7
  import { discoverSystemFonts } from "./discovery.js";
6
- import { readFontFile, unavailable } from "./files.js";
8
+ import { readFontFile } from "./files.js";
7
9
  import { fontSummary } from "./model.js";
8
10
  import { readFontMetadata } from "./sfnt.js";
9
11
  export const fontIdPattern = /^(?:default|(?:system|uploaded)-[a-f0-9]{64})$/;
@@ -54,7 +56,7 @@ export async function resolveBoldFont(paths, id) {
54
56
  };
55
57
  }
56
58
  export function missingFont() {
57
- return new Error("The chosen caption font is no longer available; it may have been uninstalled or deleted. Choose another font in Edit project → Subtitles, then try again.", {
59
+ return new Error(`The chosen caption font is no longer available; it may have been uninstalled or deleted. Choose another font in ${editSettings.captions}, then try again.`, {
58
60
  cause: "font-not-found",
59
61
  });
60
62
  }
@@ -86,7 +88,7 @@ async function uploadedFonts(paths, wanted) {
86
88
  names = wanted === undefined ? await readdir(root) : [`${wanted}.ttf`, `${wanted}.otf`];
87
89
  }
88
90
  catch (error) {
89
- if (unavailable(error))
91
+ if (isUnreachablePath(error))
90
92
  return [];
91
93
  throw error;
92
94
  }
@@ -1,5 +1,6 @@
1
1
  import { fileURLToPath } from "node:url";
2
2
  import { languageInfo, languageLetters } from "../../kernel/ports/languages.js";
3
+ import { editSettings } from "../../kernel/ports/settings-places.js";
3
4
  import { resolveBoldFont, resolveFont } from "./catalog.js";
4
5
  import { discoverSystemFonts } from "./discovery.js";
5
6
  import { readFontFile } from "./files.js";
@@ -45,7 +46,7 @@ export async function captionFont(deps, fontId, language) {
45
46
  for (const font of await deps.system())
46
47
  if (await covers(font, letters))
47
48
  return font;
48
- throw new Error(`The caption font ${chosen.name} has no ${info.name} letters, and Slopify doesn't ship a font for ${info.name} (they are 10-20 MB). In Edit project → Subtitles, use Upload font in the caption font picker to add a font that has them (for example Noto Sans ${info.code === "ja" ? "JP" : info.code === "ko" ? "KR" : "SC"} from fonts.google.com), choose it there, then Try again.`);
49
+ throw new Error(`The caption font ${chosen.name} has no ${info.name} letters, and Slopify doesn't ship a font for ${info.name} (they are 10-20 MB). In ${editSettings.captions}, use Upload font in the caption font picker to add a font that has them (for example Noto Sans ${info.code === "ja" ? "JP" : info.code === "ko" ? "KR" : "SC"} from fonts.google.com), choose it there, then Try again.`);
49
50
  }
50
51
  // The same for text drawn bold (the shorts' captions): the chosen font's bold face when it has
51
52
  // the letters, else the fallback, which the renderer emboldens.
@@ -2,7 +2,8 @@ import { createHash } from "node:crypto";
2
2
  import { opendir } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
4
  import { extname, join, posix, win32 } from "node:path";
5
- import { readFontFile, unavailable } from "./files.js";
5
+ import { isUnreachablePath } from "../../kernel/errors.js";
6
+ import { readFontFile } from "./files.js";
6
7
  import { readFontMetadata } from "./sfnt.js";
7
8
  export function systemFontDirectories(platform, env, home) {
8
9
  if (platform === "win32")
@@ -46,7 +47,7 @@ export async function scanFontDirectories(roots) {
46
47
  }
47
48
  }
48
49
  catch (error) {
49
- if (!unavailable(error))
50
+ if (!isUnreachablePath(error))
50
51
  throw error;
51
52
  }
52
53
  }
@@ -1,11 +1,7 @@
1
1
  import { constants } from "node:fs";
2
2
  import { lstat, open } from "node:fs/promises";
3
+ import { isUnreachablePath } from "../../kernel/errors.js";
3
4
  import { fontMaxBytes } from "./model.js";
4
- export function unavailable(error) {
5
- return (error instanceof Error &&
6
- "code" in error &&
7
- ["ENOENT", "ENOTDIR", "EACCES", "EPERM", "ELOOP"].includes(String(error.code)));
8
- }
9
5
  export async function readFontFile(path) {
10
6
  try {
11
7
  if (!(await lstat(path)).isFile())
@@ -30,7 +26,7 @@ export async function readFontFile(path) {
30
26
  }
31
27
  }
32
28
  catch (error) {
33
- if (unavailable(error))
29
+ if (isUnreachablePath(error))
34
30
  return undefined;
35
31
  throw error;
36
32
  }
@@ -1,5 +1,4 @@
1
1
  export { fontIdPattern, isMissingFont, listFonts, resolveBoldFont, resolveFont, } from "./catalog.js";
2
2
  export { fontMaxBytes } from "./model.js";
3
3
  export { previewFont } from "./preview.js";
4
- export { standaloneFont } from "./sfnt.js";
5
4
  export { uploadFont } from "./upload.js";
@@ -266,8 +266,50 @@ function checksum(bytes) {
266
266
  export function mappedCodepoints(content, index, codepoints) {
267
267
  const bytes = Buffer.from(content.buffer, content.byteOffset, content.byteLength);
268
268
  const face = directories(bytes)?.[index];
269
- const table = face?.tables.get("cmap");
270
- if (face === undefined || table === undefined || !validCmap(bytes, table))
269
+ const cmap = face === undefined ? undefined : unicodeCmap(bytes, face);
270
+ if (cmap === undefined)
271
+ return undefined;
272
+ const found = new Set();
273
+ for (const codepoint of codepoints)
274
+ if (glyphOf(bytes, cmap.at, cmap.format, codepoint, cmap.end) !== 0)
275
+ found.add(codepoint);
276
+ return found;
277
+ }
278
+ // How wide each character is drawn, as a share of the ASS font size: the glyph's advance
279
+ // (hmtx) over the height libass sizes a font by (OS/2 winAscent + winDescent, else the em).
280
+ // Undefined for a character the font has no glyph for, and the whole answer undefined when
281
+ // the font's tables can't be read; the caller then estimates.
282
+ export function fontAdvances(content, index) {
283
+ const bytes = Buffer.from(content.buffer, content.byteOffset, content.byteLength);
284
+ const face = directories(bytes)?.[index];
285
+ const cmap = face === undefined ? undefined : unicodeCmap(bytes, face);
286
+ const head = face?.tables.get("head");
287
+ const hhea = face?.tables.get("hhea");
288
+ const hmtx = face?.tables.get("hmtx");
289
+ if (cmap === undefined || head === undefined || hhea === undefined || hmtx === undefined)
290
+ return undefined;
291
+ const units = bytes.readUInt16BE(head.offset + 18);
292
+ const metrics = bytes.readUInt16BE(hhea.offset + 34);
293
+ const os2 = face?.tables.get("OS/2");
294
+ const win = os2 !== undefined && os2.length >= 78
295
+ ? bytes.readUInt16BE(os2.offset + 74) + bytes.readUInt16BE(os2.offset + 76)
296
+ : 0;
297
+ const height = win >= units / 2 && win <= units * 4 ? win : units;
298
+ const cache = new Map();
299
+ return (codepoint) => {
300
+ if (cache.has(codepoint))
301
+ return cache.get(codepoint);
302
+ const glyph = glyphOf(bytes, cmap.at, cmap.format, codepoint, cmap.end);
303
+ // Glyphs past the last full metric share its advance (validated in `validTables`).
304
+ const advance = glyph === 0 ? undefined : bytes.readUInt16BE(hmtx.offset + Math.min(glyph, metrics - 1) * 4);
305
+ const share = advance === undefined ? undefined : advance / height;
306
+ cache.set(codepoint, share);
307
+ return share;
308
+ };
309
+ }
310
+ function unicodeCmap(bytes, face) {
311
+ const table = face.tables.get("cmap");
312
+ if (table === undefined || !validCmap(bytes, table))
271
313
  return undefined;
272
314
  const count = bytes.readUInt16BE(table.offset + 2);
273
315
  let best;
@@ -283,14 +325,7 @@ export function mappedCodepoints(content, index, codepoints) {
283
325
  if (best === undefined || format > best.format)
284
326
  best = { at, format };
285
327
  }
286
- if (best === undefined)
287
- return undefined;
288
- const end = table.offset + table.length;
289
- const found = new Set();
290
- for (const codepoint of codepoints)
291
- if (glyphOf(bytes, best.at, best.format, codepoint, end) !== 0)
292
- found.add(codepoint);
293
- return found;
328
+ return best === undefined ? undefined : { ...best, end: table.offset + table.length };
294
329
  }
295
330
  function glyphOf(bytes, at, format, codepoint, end) {
296
331
  if (format === 12) {
@@ -1,12 +1,16 @@
1
1
  import { z } from "zod";
2
+ import { jsonBetween } from "../../kernel/json-answer.js";
3
+ import { editSettings } from "../../kernel/ports/settings-places.js";
2
4
  import { appearanceKeyword, detectSlots, render } from "../admission/substitute.js";
3
5
  // How things look, looked up rather than guessed: when an image or thumbnail prompt has
4
6
  // `{{Appearance}}`, one call to the project's AI model searches the web for how the video's
5
7
  // subject and every named character in the article look in their best-known depiction. Each
6
8
  // picture then gets the subject's look and the looks of the characters its scene names, so a
7
9
  // figure looks the same from image to image and like the one people know.
10
+ // `{{Appearance}}` is the old name of `{{Slopify_Appearance}}`; both mean the same.
11
+ const appearanceNames = [appearanceKeyword, "Slopify_Appearance"];
8
12
  export function usesAppearance(body) {
9
- return body != null && detectSlots(body).names.includes(appearanceKeyword);
13
+ return body != null && detectSlots(body).names.some((name) => appearanceNames.includes(name));
10
14
  }
11
15
  const figureSchema = z.object({
12
16
  name: z.string(),
@@ -41,8 +45,8 @@ export function appearanceMessages(brief) {
41
45
  ];
42
46
  }
43
47
  export function checkAppearance(text) {
44
- const fix = "Use Try again; if it keeps happening, choose another model in the Providers section of Edit project.";
45
- const parsed = appearanceSchema.safeParse(jsonObjectOf(text));
48
+ const fix = `Use Try again; if it keeps happening, choose another model in ${editSettings.writer}.`;
49
+ const parsed = appearanceSchema.safeParse(jsonBetween(text, "{", "}"));
46
50
  if (!parsed.success)
47
51
  return { ok: false, reason: `The AI model didn't answer with the looks it looked up. ${fix}` };
48
52
  const tidy = (figure) => ({
@@ -81,7 +85,7 @@ export function appearanceFor(appearance, scene, options = {}) {
81
85
  // The prompt with its looks; a line asking for looks nobody in the picture has is left out.
82
86
  export function withAppearance(body, text) {
83
87
  if (text !== "")
84
- return render(body, { [appearanceKeyword]: text });
88
+ return render(body, { [appearanceKeyword]: text, Slopify_Appearance: text });
85
89
  return body
86
90
  .split("\n")
87
91
  .filter((line) => !usesAppearance(line))
@@ -93,15 +97,3 @@ function names(text, name) {
93
97
  const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
94
98
  return new RegExp(`(^|[^\\p{L}\\p{N}])${escaped}($|[^\\p{L}\\p{N}])`, "iu").test(text);
95
99
  }
96
- function jsonObjectOf(text) {
97
- const start = text.indexOf("{");
98
- const end = text.lastIndexOf("}");
99
- if (start === -1 || end <= start)
100
- return undefined;
101
- try {
102
- return JSON.parse(text.slice(start, end + 1));
103
- }
104
- catch {
105
- return undefined;
106
- }
107
- }
@@ -1,30 +1,80 @@
1
1
  import { z } from "zod";
2
+ import { jsonBetween } from "../../kernel/json-answer.js";
3
+ import { editSettings } from "../../kernel/ports/settings-places.js";
2
4
  import { detectSlots, render, sceneKeyword } from "../admission/substitute.js";
3
5
  // Scenes from the article (Images → Scenes from the article): each image is drawn from its
4
6
  // own scene instead of the same text every time. One call to the project's AI model reads the
5
7
  // article and writes every scene, following the article from its opening to its end in the
6
8
  // order the images are shown.
9
+ // The keywords Slopify fills per picture from the scenes call (`keywords/specs.ts`): any of them
10
+ // turns scenes on for that picture, whatever the Images switch says.
11
+ export const slopifySceneKeywords = ["Slopify_Scene", "Slopify_Setting", "Slopify_Mood"];
12
+ const sceneNames = [sceneKeyword, "Slopify_Scene"];
13
+ const detailNames = ["Slopify_Setting", "Slopify_Mood"];
14
+ // `{{Scene}}` or `{{Slopify_Scene}}`: where the scene goes.
7
15
  export function usesScene(body) {
16
+ return body != null && detectSlots(body).names.some((name) => sceneNames.includes(name));
17
+ }
18
+ // The old name, which follows Images → Scenes from the article as it always did.
19
+ export function usesLegacyScene(body) {
8
20
  return body != null && detectSlots(body).names.includes(sceneKeyword);
9
21
  }
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");
22
+ // `{{Slopify_Scene}}`, `{{Slopify_Setting}}` or `{{Slopify_Mood}}`: the picture gets its scene.
23
+ export function usesSlopifyScene(body) {
24
+ return (body != null &&
25
+ detectSlots(body).names.some((name) => slopifySceneKeywords.includes(name)));
26
+ }
27
+ // `{{Slopify_Setting}}` or `{{Slopify_Mood}}`: the scenes call writes them too.
28
+ export function wantsSceneDetails(body) {
29
+ return body != null && detectSlots(body).names.some((name) => detailNames.includes(name));
30
+ }
31
+ // The prompt with its scene: where the prompt has `{{Scene}}` or `{{Slopify_Scene}}`, there;
32
+ // otherwise, while the Images switch is on (`legacy`), as a line of its own after the prompt's
33
+ // first paragraph, so any prompt takes a scene without editing. With the switch off a picture is
34
+ // here only for its `Slopify_` keywords, and a `{{Scene}}` line is left out as it always was.
35
+ // The setting and mood go where the prompt asks; a line asking for one not written is left out.
36
+ export function withScene(body, scene, options = {}) {
37
+ const legacy = options.legacy !== false;
38
+ const text = legacy ? body : withoutLines(body, [sceneKeyword]);
39
+ const detailed = withDetails(text, options);
40
+ if (usesScene(detailed))
41
+ return render(detailed, { [sceneKeyword]: scene, Slopify_Scene: scene });
42
+ if (!legacy)
43
+ return detailed;
44
+ const at = detailed.indexOf("\n\n");
16
45
  return at === -1
17
- ? `${body}\n\nScene: ${scene}`
18
- : `${body.slice(0, at)}\n\nScene: ${scene}${body.slice(at)}`;
46
+ ? `${detailed}\n\nScene: ${scene}`
47
+ : `${detailed.slice(0, at)}\n\nScene: ${scene}${detailed.slice(at)}`;
19
48
  }
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.
49
+ // A picture that is drawn without scenes (the establishing image): a line holding a `Slopify_`
50
+ // scene keyword is left out, and `{{Scene}}` stays as it always did.
51
+ export function withoutSlopifyScene(body) {
52
+ return withoutLines(body, slopifySceneKeywords);
53
+ }
54
+ // The prompt with no scene: a line that holds a scene keyword is left out, so the image is drawn
55
+ // from the rest of the prompt as it always was.
22
56
  export function withoutScene(body) {
23
- if (!usesScene(body))
57
+ return withoutLines(body, [...sceneNames, ...detailNames]);
58
+ }
59
+ function withDetails(body, details) {
60
+ if (!wantsSceneDetails(body))
61
+ return body;
62
+ const missing = [
63
+ ...(details.setting?.trim() ? [] : ["Slopify_Setting"]),
64
+ ...(details.mood?.trim() ? [] : ["Slopify_Mood"]),
65
+ ];
66
+ return render(withoutLines(body, missing), {
67
+ ...(details.setting?.trim() ? { Slopify_Setting: details.setting.trim() } : {}),
68
+ ...(details.mood?.trim() ? { Slopify_Mood: details.mood.trim() } : {}),
69
+ });
70
+ }
71
+ function withoutLines(body, names) {
72
+ const holds = (line) => detectSlots(line).names.some((name) => names.includes(name));
73
+ if (!body.split("\n").some(holds))
24
74
  return body;
25
75
  return body
26
76
  .split("\n")
27
- .filter((line) => !usesScene(line))
77
+ .filter((line) => !holds(line))
28
78
  .join("\n")
29
79
  .replace(/\n{3,}/g, "\n\n")
30
80
  .trim();
@@ -42,7 +92,25 @@ const countLine = /^Pictures: (\d+)$/m;
42
92
  // prompt asks for one with `{{Scene}}`. They are not tied to a stretch of the article: each is
43
93
  // the moment that would make someone click, a different one for each thumbnail.
44
94
  const thumbnailLine = /^Thumbnails: (\d+)$/m;
95
+ // With `details`, each scene comes with its setting and mood for `{{Slopify_Setting}}` and
96
+ // `{{Slopify_Mood}}`; without, the request is the one it always was.
45
97
  export function sceneMessages(brief) {
98
+ const plain = plainSceneMessages(brief);
99
+ return brief.details === true ? plain.map(askingDetails) : plain;
100
+ }
101
+ const answerLine = /^Answer with a JSON array of exactly (\d+) strings, (.*), and nothing else\.$/m;
102
+ function askingDetails(message) {
103
+ if (message.role !== "system")
104
+ return message;
105
+ return {
106
+ ...message,
107
+ content: message.content.replace(answerLine, (_whole, total, order) => [
108
+ "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.",
109
+ `Answer with a JSON array of exactly ${total} objects, ${order}, each {"scene": "…", "setting": "…", "mood": "…"}, and nothing else.`,
110
+ ].join("\n\n")),
111
+ };
112
+ }
113
+ function plainSceneMessages(brief) {
46
114
  const count = brief.pictures.length;
47
115
  const thumbnails = brief.thumbnails ?? 0;
48
116
  if (thumbnails > 0)
@@ -120,15 +188,33 @@ function withThumbnails(brief, count, thumbnails) {
120
188
  },
121
189
  ];
122
190
  }
191
+ const sceneAnswer = z.array(z.union([
192
+ z.string(),
193
+ z.object({
194
+ scene: z.string(),
195
+ setting: z.string().optional(),
196
+ mood: z.string().optional(),
197
+ }),
198
+ ]));
199
+ // A list of scenes, or of scenes with their setting and mood (`details`, only then present).
123
200
  export function checkScenes(text, count) {
124
- 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 fix = `Use Try again; if it keeps happening, choose another model in ${editSettings.writer}.`;
202
+ const parsed = sceneAnswer.safeParse(jsonBetween(text, "[", "]"));
126
203
  if (!parsed.success)
127
204
  return {
128
205
  ok: false,
129
206
  reason: `The AI model didn't answer with a list of image scenes. ${fix}`,
130
207
  };
131
- const scenes = parsed.data.map((scene) => scene.replace(/\s+/g, " ").trim());
208
+ const tidy = (value) => value.replace(/\s+/g, " ").trim();
209
+ const scenes = parsed.data.map((one) => tidy(typeof one === "string" ? one : one.scene));
210
+ const details = parsed.data.some((one) => typeof one !== "string")
211
+ ? parsed.data.map((one) => typeof one === "string"
212
+ ? {}
213
+ : {
214
+ ...(one.setting === undefined ? {} : { setting: tidy(one.setting) }),
215
+ ...(one.mood === undefined ? {} : { mood: tidy(one.mood) }),
216
+ })
217
+ : undefined;
132
218
  if (scenes.length !== count)
133
219
  return {
134
220
  ok: false,
@@ -136,17 +222,5 @@ export function checkScenes(text, count) {
136
222
  };
137
223
  if (scenes.some((scene) => scene === ""))
138
224
  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
- }
225
+ return { ok: true, scenes, ...(details === undefined ? {} : { details }) };
152
226
  }
@@ -0,0 +1,85 @@
1
+ import { z } from "zod";
2
+ import { editSettings } from "../../kernel/ports/settings-places.js";
3
+ import { keywordSpecs } from "./specs.js";
4
+ // The keyword digest: one call to the project's AI model reads the article once and writes every
5
+ // digest keyword (`{{Slopify_Summary}}`, `{{Slopify_Hook}}`…), so a run that uses several pays for
6
+ // one call. It asks for all six every time, so adding one more digest keyword to a prompt later
7
+ // reuses the same answer.
8
+ export const digestNames = keywordSpecs
9
+ .filter((spec) => spec.filler === "digest")
10
+ .map((spec) => spec.name);
11
+ export function digestMessages(brief) {
12
+ return [
13
+ {
14
+ role: "system",
15
+ content: [
16
+ "You write short texts about a narrated video from its article, for its pictures, thumbnail, description, shorts and reviews.",
17
+ "summary: two or three sentences on what the video covers.",
18
+ "hook: the single most click-worthy idea of the video, in one sentence.",
19
+ "thumbnailText: two to four words to put on the thumbnail, in the video's language.",
20
+ "keyFacts: three to five striking facts the article states, each one sentence.",
21
+ "quote: one quotable line from the article, as it is written there.",
22
+ "tags: five to fifteen search terms someone would type to find this video.",
23
+ "Use only what the article says. Write in the article's language.",
24
+ 'Answer with JSON and nothing else: {"summary": "…", "hook": "…", "thumbnailText": "…", "keyFacts": ["…"], "quote": "…", "tags": ["…"]}.',
25
+ ].join("\n\n"),
26
+ },
27
+ {
28
+ role: "user",
29
+ content: [`Video: ${brief.title}`, `Article:\n\n${brief.article}`].join("\n\n"),
30
+ },
31
+ ];
32
+ }
33
+ const listOrText = z.union([z.array(z.string()), z.string()]);
34
+ const digestSchema = z.object({
35
+ summary: z.string(),
36
+ hook: z.string(),
37
+ thumbnailText: z.string(),
38
+ keyFacts: listOrText,
39
+ quote: z.string(),
40
+ tags: listOrText,
41
+ });
42
+ export function checkDigest(text) {
43
+ const fix = `Use Try again; if it keeps happening, choose another model in ${editSettings.writer}.`;
44
+ const parsed = digestSchema.safeParse(jsonObjectOf(text));
45
+ if (!parsed.success)
46
+ return {
47
+ ok: false,
48
+ reason: `The AI model didn't answer with the keyword digest (summary, hook, thumbnail text, key facts, quote and tags). ${fix}`,
49
+ };
50
+ const tidy = (value) => value.replace(/\s+/g, " ").trim();
51
+ const list = (value) => (typeof value === "string" ? value.split("\n") : value)
52
+ .map((one) => tidy(one).replace(/^[-•*]\s*/, ""))
53
+ .filter((one) => one !== "");
54
+ const { summary, hook, thumbnailText, keyFacts, quote, tags } = parsed.data;
55
+ const digest = {
56
+ Slopify_Summary: tidy(summary),
57
+ Slopify_Hook: tidy(hook),
58
+ Slopify_Thumbnail_Text: tidy(thumbnailText),
59
+ Slopify_Key_Facts: list(keyFacts).join("\n"),
60
+ Slopify_Quote: tidy(quote),
61
+ Slopify_Tags: (typeof tags === "string" ? tags.split(",") : tags)
62
+ .map(tidy)
63
+ .filter((one) => one !== "")
64
+ .join(", "),
65
+ };
66
+ const empty = digestNames.filter((name) => (digest[name] ?? "") === "");
67
+ if (empty.length > 0)
68
+ return {
69
+ ok: false,
70
+ reason: `The AI model left part of the keyword digest empty (${empty.map((name) => `{{${name}}}`).join(", ")}). ${fix}`,
71
+ };
72
+ return { ok: true, digest };
73
+ }
74
+ function jsonObjectOf(text) {
75
+ const start = text.indexOf("{");
76
+ const end = text.lastIndexOf("}");
77
+ if (start === -1 || end <= start)
78
+ return undefined;
79
+ try {
80
+ return JSON.parse(text.slice(start, end + 1));
81
+ }
82
+ catch {
83
+ return undefined;
84
+ }
85
+ }
@@ -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
+ }