@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
@@ -2,18 +2,26 @@ import { zValidator } from "@hono/zod-validator";
2
2
  import { Hono } from "hono";
3
3
  import { HTTPException } from "hono/http-exception";
4
4
  import { z } from "zod";
5
+ import { setupSectionNames } from "../../slices/project-templates/setup-sections.js";
5
6
  import { calendarRange } from "../../slices/schedules/agenda.js";
6
7
  import { rejectHeldTopics, restoreRejectedTopics } from "../../slices/schedules/held-bulk.js";
7
8
  import { preparedTopics, prepareTopic } from "../../slices/schedules/prepare.js";
8
- import { calendarMaxDays, calendarSchema, heldTopicEditSchema, heldTopicSchema, queueMax, scheduleCreateSchema, scheduleRunSchema, scheduleSummarySchema, scheduleUpdateSchema, topicMoveSchema, topicQueueSchema, topicTransferSchema, } from "../../slices/schedules/schema.js";
9
+ import { rescoreTopics } from "../../slices/schedules/rescore.js";
10
+ import { calendarMaxDays, calendarSchema, heldTopicEditSchema, heldTopicSchema, queueMax, scheduleCreateSchema, scheduleRunSchema, scheduleSaveBodySchema, scheduleSummarySchema, scheduleVersionBodySchema, topicMoveSchema, topicQueueSchema, topicTransferSchema, } from "../../slices/schedules/schema.js";
9
11
  import { cancelSchedule, createSchedule, deleteSchedule, listScheduleRuns, listSchedules, pauseSchedule, resumeSchedule, updateSchedule, } from "../../slices/schedules/service.js";
12
+ import { scoresFor } from "../../slices/schedules/topic-scores.js";
10
13
  import { approveHeldTopics, editHeldTopic, heldTopics, moveTopic, rejectHeldTopic, replaceTopics, transferTopic, } from "../../slices/schedules/topics.js";
11
- import { onInvalid, problem, titleOf } from "./problem.js";
14
+ import { onInvalid, problem } from "./problem.js";
15
+ import { refusal } from "./refusal.js";
12
16
  const id = z.object({ id: z.uuid() });
13
17
  const topicParam = z.object({ id: z.uuid(), topicId: z.string().min(1).max(100) });
14
18
  const topicIds = z.array(z.string().min(1).max(100)).min(1).max(queueMax);
15
- const heldIds = z.object({ ids: topicIds }).strict();
16
- const heldRestore = z
19
+ // Prepare one queued topic: its title as the queue lists it.
20
+ export const prepareTopicBody = z.object({ topic: z.string().min(1).max(500) }).strict();
21
+ // Approve one held topic, optionally under another title.
22
+ export const approveHeldTopicBody = z.object({ title: z.string().optional() }).strict();
23
+ export const heldIds = z.object({ ids: topicIds }).strict();
24
+ export const heldRestore = z
17
25
  .object({
18
26
  topics: z
19
27
  .array(z
@@ -27,38 +35,36 @@ const heldRestore = z
27
35
  })
28
36
  .strict();
29
37
  function refused(c, result) {
30
- const status = ["not-found", "missing-template", "topic-not-found"].includes(result.reason)
31
- ? 404
32
- : [
33
- "conflict",
34
- "cancel-required",
35
- "spend-limit",
36
- "unsupported-media",
37
- "queue-full",
38
- "busy",
39
- ].includes(result.reason)
40
- ? 409
41
- : 400;
42
38
  const detail = {
43
39
  "invalid-input": "Some schedule settings are not valid. Check each field and save again.",
44
40
  "not-found": "This schedule no longer exists; it may have been deleted. Reload the page to see your schedules.",
45
41
  conflict: "This schedule changed while you were editing, possibly because a run just started. Reload the page to see the latest, then make your change again.",
46
42
  "cancel-required": "This schedule is still active. Cancel it first, then delete it. Completed schedules can be deleted straight away.",
47
43
  "missing-template": "The template you picked was deleted or changed. Choose a template again, then save.",
48
- "unsupported-media": "This template uses audio, images, a thumbnail, shorts background music or an ambient sound file you supplied, so it cannot run on a schedule: a scheduled run has no one to attach the file again. Pick a template that generates these instead, or on Play remove the music under Outputs → More shorts options (or pick Rain, Fireplace or Wind under Video and style → Ambient sound) and save the template again.",
44
+ "unsupported-media": `This template uses audio, images, a thumbnail, shorts background music or an ambient sound file you supplied, so it cannot run on a schedule: a scheduled run has no one to attach the file again. Pick a template that generates these instead, or open this one in Edit template, remove the music under ${setupSectionNames.shorts} (or pick Rain, Fireplace or Wind as the Ambient sound under ${setupSectionNames.video}) and save it again.`,
49
45
  "not-due": "This one-off time has already passed. Choose a time in the future.",
50
46
  "spend-limit": "The estimated cost is above this schedule's spend limit, or some prices are unknown. Raise the spend limit or choose models with known prices.",
51
- readiness: "A provider this template needs is not ready, for example a missing API key, model or voice. Check Settings → Providers, then try again.",
47
+ readiness: "A provider this template needs is not ready, for example a missing API key, model or voice. Check Settings → Keys & services, then try again.",
52
48
  "queue-full": `A schedule's topic queue holds at most ${String(queueMax)} topics. Remove some under Schedules → Edit, then try again.`,
53
49
  "topic-not-found": "This topic was already approved, turned down or used. Reload the page to see the topics waiting now.",
54
50
  busy: "Slopify is already generating topics for this schedule. Wait for it to finish, then try again.",
51
+ "not-ready": "This schedule's template has no version approved for schedules. Open it from Templates with Edit template, then choose More → Approve for schedules.",
55
52
  "invalid-topics": "Some topics name keywords this template does not use. Check each topic under Schedules → Edit and save again.",
56
53
  };
57
- return problem(c, {
58
- status,
59
- title: titleOf(status),
54
+ return refusal(c, result.reason, {
55
+ statuses: {
56
+ "not-found": 404,
57
+ "missing-template": 404,
58
+ "topic-not-found": 404,
59
+ conflict: 409,
60
+ "cancel-required": 409,
61
+ "spend-limit": 409,
62
+ "unsupported-media": 409,
63
+ "queue-full": 409,
64
+ busy: 409,
65
+ },
66
+ otherwise: 400,
60
67
  detail: result.message ?? detail[result.reason],
61
- extensions: { reason: result.reason },
62
68
  });
63
69
  }
64
70
  export function scheduleRoutes(deps) {
@@ -90,38 +96,44 @@ function routes(deps) {
90
96
  ? c.json({
91
97
  schedule: scheduleSummarySchema.parse(schedule),
92
98
  runs: runs.value.map((run) => scheduleRunSchema.parse(run)),
99
+ // Each queued and held topic's score and reasons, by `topicKey`, while ranking
100
+ // is on.
101
+ scores: scoresFor(service().db, schedule.id, [
102
+ ...schedule.items.map((item) => item.title),
103
+ ...heldTopics(service(), schedule.id).map((topic) => topic.title),
104
+ ]),
93
105
  })
94
106
  : refused(c, runs);
95
107
  })
96
- .put("/:id", zValidator("param", id, onInvalid), zValidator("json", scheduleUpdateSchema.unwrap().omit({ id: true }), onInvalid), (c) => {
108
+ .put("/:id", zValidator("param", id, onInvalid), zValidator("json", scheduleSaveBodySchema, onInvalid), (c) => {
97
109
  const result = updateSchedule(service(), {
98
110
  ...c.req.valid("json"),
99
111
  id: c.req.valid("param").id,
100
112
  });
101
113
  return result.ok ? c.json(scheduleSummarySchema.parse(result.value)) : refused(c, result);
102
114
  })
103
- .post("/:id/pause", zValidator("param", id, onInvalid), zValidator("json", z.object({ baseVersion: z.number().int().positive() }).strict(), onInvalid), (c) => {
115
+ .post("/:id/pause", zValidator("param", id, onInvalid), zValidator("json", scheduleVersionBodySchema, onInvalid), (c) => {
104
116
  const result = pauseSchedule(service(), {
105
117
  ...c.req.valid("json"),
106
118
  id: c.req.valid("param").id,
107
119
  });
108
120
  return result.ok ? c.json(scheduleSummarySchema.parse(result.value)) : refused(c, result);
109
121
  })
110
- .post("/:id/resume", zValidator("param", id, onInvalid), zValidator("json", z.object({ baseVersion: z.number().int().positive() }).strict(), onInvalid), (c) => {
122
+ .post("/:id/resume", zValidator("param", id, onInvalid), zValidator("json", scheduleVersionBodySchema, onInvalid), (c) => {
111
123
  const result = resumeSchedule(service(), {
112
124
  ...c.req.valid("json"),
113
125
  id: c.req.valid("param").id,
114
126
  });
115
127
  return result.ok ? c.json(scheduleSummarySchema.parse(result.value)) : refused(c, result);
116
128
  })
117
- .post("/:id/cancel", zValidator("param", id, onInvalid), zValidator("json", z.object({ baseVersion: z.number().int().positive() }).strict(), onInvalid), (c) => {
129
+ .post("/:id/cancel", zValidator("param", id, onInvalid), zValidator("json", scheduleVersionBodySchema, onInvalid), (c) => {
118
130
  const result = cancelSchedule(service(), {
119
131
  ...c.req.valid("json"),
120
132
  id: c.req.valid("param").id,
121
133
  });
122
134
  return result.ok ? c.json(scheduleSummarySchema.parse(result.value)) : refused(c, result);
123
135
  })
124
- .delete("/:id", zValidator("param", id, onInvalid), zValidator("json", z.object({ baseVersion: z.number().int().positive() }).strict(), onInvalid), (c) => {
136
+ .delete("/:id", zValidator("param", id, onInvalid), zValidator("json", scheduleVersionBodySchema, onInvalid), (c) => {
125
137
  const result = deleteSchedule(service(), {
126
138
  ...c.req.valid("json"),
127
139
  id: c.req.valid("param").id,
@@ -132,14 +144,31 @@ function routes(deps) {
132
144
  const scheduleId = c.req.valid("param").id;
133
145
  if (listSchedules(service()).every((item) => item.id !== scheduleId))
134
146
  return refused(c, { ok: false, reason: "not-found" });
147
+ const topics = heldTopics(service(), scheduleId).map((topic) => heldTopicSchema.parse(topic));
135
148
  return c.json({
136
- topics: heldTopics(service(), scheduleId).map((topic) => heldTopicSchema.parse(topic)),
149
+ topics,
150
+ scores: scoresFor(service().db, scheduleId, topics.map((topic) => topic.title)),
137
151
  });
152
+ })
153
+ // Rescore: every queued and held topic scored again, then put in score order. With search
154
+ // ranking it asks Google, DuckDuckGo and Wikipedia, up to 15 seconds.
155
+ .post("/:id/topics/score", zValidator("param", id, onInvalid), async (c) => {
156
+ const result = await rescoreTopics(service(), c.req.valid("param").id);
157
+ if (result.ok)
158
+ return c.json(scheduleSummarySchema.parse(result.value));
159
+ if (result.reason === "scoring-off")
160
+ return problem(c, {
161
+ status: 409,
162
+ detail: result.message,
163
+ extensions: { reason: result.reason },
164
+ });
165
+ return refused(c, result);
138
166
  })
139
167
  // Which queued topics are prepared ahead, and their projects.
140
168
  .get("/:id/prepared", zValidator("param", id, onInvalid), (c) => c.json({ prepared: preparedTopics(service(), c.req.valid("param").id) }))
141
- // Prepare one queued topic now: everything but the video, which its scheduled day renders.
142
- .post("/:id/topics/prepare", zValidator("param", id, onInvalid), zValidator("json", z.object({ topic: z.string().min(1).max(500) }).strict(), onInvalid), async (c) => {
169
+ // Make ahead one queued topic: everything but the video now, which its scheduled day puts
170
+ // together.
171
+ .post("/:id/topics/prepare", zValidator("param", id, onInvalid), zValidator("json", prepareTopicBody, onInvalid), async (c) => {
143
172
  const result = await prepareTopic(service(), {
144
173
  scheduleId: c.req.valid("param").id,
145
174
  title: c.req.valid("json").topic,
@@ -148,17 +177,19 @@ function routes(deps) {
148
177
  return c.json({ projectId: result.projectId, title: result.title }, 201);
149
178
  const detail = {
150
179
  "not-found": "This topic is no longer in the schedule's queue. Reload the page to see the queue as it is now.",
151
- "already-prepared": "This topic is already prepared: its project is in Projects under the same title.",
152
- "missing-template": "This schedule's template was deleted, so there is nothing to prepare from. Choose another template under Edit.",
153
- "spend-limit": "Preparing it would go over this schedule's spend limit. Raise the limit under Edit, or prepare it from Play instead.",
180
+ "already-prepared": "This topic is already made ahead: its project is in Projects under the same title.",
181
+ paused: "Its schedule is paused, so it makes nothing, ahead or on its day. Resume it under Calendar → Schedules, then press Make ahead again.",
182
+ "missing-template": "This schedule's template was deleted, so there is nothing to make it from. Choose another template under Edit.",
183
+ "spend-limit": "Making it ahead would go over this schedule's spend limit. Raise the limit under Edit, or make it from Create instead.",
184
+ design: "A poster schedule makes each poster on its day, so there is nothing to make ahead. To have one now, open its poster template (Templates → Poster templates) and press Make a poster; to have it sooner on a schedule, give its row an earlier date in the Schedule section.",
154
185
  };
155
186
  return problem(c, {
156
187
  status: result.reason === "not-found" ? 404 : 409,
157
- title: titleOf(result.reason === "not-found" ? 404 : 409),
158
- detail: detail[result.reason] ??
188
+ detail: (result.reason === "not-ready" ? result.detail : undefined) ??
189
+ detail[result.reason] ??
159
190
  (result.detail === undefined
160
- ? `This topic couldn't be prepared (${result.reason}). Open Play with the schedule's template to see what the setup needs, then try again.`
161
- : `This topic couldn't be prepared: ${result.detail} Fix it in the schedule's template (Library → Templates), then try again.`),
191
+ ? `This topic couldn't be made ahead (${result.reason}). Open Create with the schedule's template to see what the setup needs, then try again.`
192
+ : `This topic couldn't be made ahead: ${result.detail} Fix it in the schedule's template (Templates → Edit template), then try again.`),
162
193
  extensions: { reason: result.reason },
163
194
  });
164
195
  })
@@ -181,7 +212,6 @@ function routes(deps) {
181
212
  if (refusal !== undefined)
182
213
  return problem(c, {
183
214
  status: 409,
184
- title: titleOf(409),
185
215
  detail: refusal,
186
216
  extensions: { reason: "busy" },
187
217
  });
@@ -206,7 +236,7 @@ function routes(deps) {
206
236
  const result = restoreRejectedTopics(service(), c.req.valid("param").id, c.req.valid("json").topics);
207
237
  return result.ok ? c.json(scheduleSummarySchema.parse(result.value)) : refused(c, result);
208
238
  })
209
- .post("/:id/topics/held/:topicId/approve", zValidator("param", topicParam, onInvalid), zValidator("json", z.object({ title: z.string().optional() }).strict(), onInvalid), (c) => {
239
+ .post("/:id/topics/held/:topicId/approve", zValidator("param", topicParam, onInvalid), zValidator("json", approveHeldTopicBody, onInvalid), (c) => {
210
240
  const { id: scheduleId, topicId } = c.req.valid("param");
211
241
  const { title } = c.req.valid("json");
212
242
  const result = approveHeldTopics(service(), scheduleId, [topicId], title === undefined ? {} : { [topicId]: title });
@@ -257,7 +287,6 @@ export function calendarRoutes(deps) {
257
287
  if (!result.ok)
258
288
  return problem(c, {
259
289
  status: 400,
260
- title: titleOf(400),
261
290
  detail: `Choose a range whose end is after its start and at most ${String(calendarMaxDays)} days long.`,
262
291
  extensions: { reason: result.reason },
263
292
  });
@@ -10,9 +10,10 @@ import { readNotificationUrl, saveNotificationUrl } from "../../slices/notificat
10
10
  import { appearances, providerIds } from "../../slices/settings/model.js";
11
11
  import { readSettings, saveSettings } from "../../slices/settings/playback.js";
12
12
  import { addVoice, removeVoice, setVoiceImitatesRealPerson, setVoiceLanguages, voiceIdMax, voiceNameMax, voices, } from "../../slices/settings/voices.js";
13
+ import { clipEncodeSpeeds, noNvenc, renderPriorities, videoEncoders, } from "../../slices/video/encoding.js";
13
14
  import { readChannelLinks, writeChannelLinks } from "../../slices/youtube/edits-repo.js";
14
15
  import { channelLinkNameMax, channelLinksMax, channelLinksProblem, channelLinkUrlMax, } from "../../slices/youtube/placeholders.js";
15
- import { onInvalid, problem, titleOf } from "./problem.js";
16
+ import { onInvalid, problem } from "./problem.js";
16
17
  const idParam = z.object({ id: z.string().min(1).max(64) });
17
18
  // Shape only. The rules - trimming, emptiness, length, the provider speaking at all -
18
19
  // are the slice's, so one set of messages reaches the form.
@@ -26,7 +27,7 @@ const voiceLanguagesBody = z.object({ languages: z.array(z.string().max(20)).max
26
27
  const voiceRealPersonBody = z.object({ imitatesRealPerson: z.boolean() });
27
28
  // Length and scheme are the slice's rules, so their sentences reach the field; this bound only
28
29
  // keeps a pasted novel off the parser.
29
- const notificationBody = z.object({ url: z.string().max(notificationUrlMax * 2) });
30
+ export const notificationBody = z.object({ url: z.string().max(notificationUrlMax * 2) });
30
31
  // Shape and a generous bound only; `channelLinksProblem` says what is wrong in Settings' words.
31
32
  export const channelLinksBody = z.object({
32
33
  links: z
@@ -45,6 +46,10 @@ const playbackBody = z.object({
45
46
  .optional(),
46
47
  // Absent keeps what is saved, as with loudness.
47
48
  subtitleModel: z.enum(englishSubtitleModels).optional(),
49
+ // Absent keeps what is saved, as with loudness.
50
+ videoEncoder: z.enum(videoEncoders).optional(),
51
+ clipEncodeSpeed: z.enum(clipEncodeSpeeds).optional(),
52
+ renderPriority: z.enum(renderPriorities).optional(),
48
53
  });
49
54
  // The return type is inferred so Hono keeps the route types the SPA's client is
50
55
  // generated from; see stagingRoutes.
@@ -61,16 +66,34 @@ export function settingsRoutes(deps) {
61
66
  ...body,
62
67
  loudness: body.loudness ?? saved.loudness,
63
68
  subtitleModel: body.subtitleModel ?? saved.subtitleModel,
69
+ videoEncoder: body.videoEncoder ?? saved.videoEncoder,
70
+ clipEncodeSpeed: body.clipEncodeSpeed ?? saved.clipEncodeSpeed,
71
+ renderPriority: body.renderPriority ?? saved.renderPriority,
64
72
  });
65
73
  if (!result.ok) {
66
74
  return problem(c, {
67
75
  status: 400,
68
- title: titleOf(400),
69
76
  detail: "These settings cannot be saved yet. Fix the highlighted fields and try again.",
70
77
  extensions: { fields: result.fields },
71
78
  });
72
79
  }
73
80
  return c.json(result.settings);
81
+ })
82
+ // Settings → Production defaults → Video encoder offers the NVIDIA card only where its
83
+ // encoder works; the first ask runs a test encode, later ones answer from memory.
84
+ .get("/encoders", async (c) => {
85
+ const status = deps.nvenc === undefined
86
+ ? { available: false, reason: noNvenc }
87
+ : await deps.nvenc();
88
+ return c.json({
89
+ nvidia: status.available
90
+ ? { available: true }
91
+ : {
92
+ available: false,
93
+ reason: status.reason,
94
+ ...(status.detail === undefined ? {} : { detail: status.detail }),
95
+ },
96
+ });
74
97
  })
75
98
  // The named links `{{Name}}` placeholders in YouTube descriptions fill from
76
99
  // (`slices/youtube/placeholders.ts`).
@@ -81,8 +104,7 @@ export function settingsRoutes(deps) {
81
104
  if (refused !== undefined)
82
105
  return problem(c, {
83
106
  status: 400,
84
- title: titleOf(400),
85
- detail: `The channel links weren't saved: ${refused} Then press Save in Settings → Channel links.`,
107
+ detail: `The channel links weren't saved: ${refused} Then press Save in Settings → YouTube Studio and posting.`,
86
108
  });
87
109
  writeChannelLinks(deps.db, links);
88
110
  return c.json({ links: readChannelLinks(deps.db) });
@@ -93,7 +115,6 @@ export function settingsRoutes(deps) {
93
115
  if (!result.ok) {
94
116
  return problem(c, {
95
117
  status: 400,
96
- title: titleOf(400),
97
118
  detail: `The Notification URL wasn't saved: ${result.message} Fix it in Settings → Notifications → Notification URL, then press Save.`,
98
119
  extensions: { fields: [{ field: "url", message: result.message }] },
99
120
  });
@@ -108,7 +129,6 @@ export function settingsRoutes(deps) {
108
129
  if (invalid !== undefined) {
109
130
  return problem(c, {
110
131
  status: 400,
111
- title: titleOf(400),
112
132
  detail: `The test notification wasn't sent: ${invalid}`,
113
133
  extensions: { fields: [{ field: "url", message: invalid }] },
114
134
  });
@@ -118,7 +138,6 @@ export function settingsRoutes(deps) {
118
138
  deps.log.write("warn", "notification.test", { detail: sendFailureText(result) });
119
139
  return problem(c, {
120
140
  status: 502,
121
- title: titleOf(502),
122
141
  detail: `The test notification wasn't delivered: ${sendFailureText(result)}. Check the address in Settings → Notifications → Notification URL (for ntfy, https://ntfy.sh/your-topic), then press Send test notification again.`,
123
142
  });
124
143
  }
@@ -147,12 +166,10 @@ export function settingsRoutes(deps) {
147
166
  return result.reason === "duplicate-voice-id"
148
167
  ? problem(c, {
149
168
  status: 409,
150
- title: titleOf(409),
151
169
  detail: "This voice ID is already saved for this provider. Use the existing voice, or enter a different voice ID.",
152
170
  })
153
171
  : problem(c, {
154
172
  status: 400,
155
- title: titleOf(400),
156
173
  detail: "This voice cannot be added yet. Fix the highlighted field and try again.",
157
174
  extensions: { fields: [fieldOf(result.reason)] },
158
175
  });
@@ -166,12 +183,10 @@ export function settingsRoutes(deps) {
166
183
  return result.reason === "missing"
167
184
  ? problem(c, {
168
185
  status: 404,
169
- title: titleOf(404),
170
186
  detail: "This voice no longer exists; it may have been deleted. Reload Settings → Voices to see your voices.",
171
187
  })
172
188
  : problem(c, {
173
189
  status: 400,
174
- title: titleOf(400),
175
190
  detail: "The languages weren't saved. Fix the highlighted field and try again.",
176
191
  extensions: { fields: [fieldOf("unknown-language")] },
177
192
  });
@@ -183,7 +198,6 @@ export function settingsRoutes(deps) {
183
198
  ? c.body(null, 204)
184
199
  : problem(c, {
185
200
  status: 404,
186
- title: titleOf(404),
187
201
  detail: "This voice no longer exists; it may have been deleted. Reload Settings → Voices to see your voices.",
188
202
  });
189
203
  })
@@ -191,7 +205,6 @@ export function settingsRoutes(deps) {
191
205
  if (!removeVoice(voiceDeps, c.req.valid("param").id).ok) {
192
206
  return problem(c, {
193
207
  status: 404,
194
- title: titleOf(404),
195
208
  detail: "This voice no longer exists; it may have been deleted already. Reload the page to see your voices.",
196
209
  });
197
210
  }
@@ -0,0 +1,283 @@
1
+ import { zValidator } from "@hono/zod-validator";
2
+ import { Hono } from "hono";
3
+ import { z } from "zod";
4
+ import { projectById } from "../../slices/admission/repo.js";
5
+ import { webhookBody } from "../../slices/notifications/address.js";
6
+ import { sendFailureText } from "../../slices/notifications/send.js";
7
+ import { readNotificationUrl } from "../../slices/notifications/settings.js";
8
+ import { setupSectionNames } from "../../slices/project-templates/setup-sections.js";
9
+ import { waitsForRelease } from "../../slices/social/due.js";
10
+ import { postingExtension, writeExtensionSites } from "../../slices/social/extension.js";
11
+ import { platformLabels } from "../../slices/social/model.js";
12
+ import { postReadyNotice } from "../../slices/social/notice.js";
13
+ import { packFor, postPacks } from "../../slices/social/pack.js";
14
+ import { readPost, writeChoice, writeReddit, writeTextEdit } from "../../slices/social/repo.js";
15
+ import { choiceBody, linkBody, platformSchema, redditBody, resultBody, textBody, } from "../../slices/social/schema.js";
16
+ import { serveMedia } from "../../slices/social/serve.js";
17
+ import { duePosts, fillPost, linksToFind, nextPost, notPostingSentence, reportPost, setPostLink, } from "../../slices/social/service.js";
18
+ import { fileResponse } from "./byte-range.js";
19
+ import { extensionAuth } from "./extension-auth.js";
20
+ import { idString } from "./ids.js";
21
+ import { onInvalid, problem } from "./problem.js";
22
+ import { fromSlopifyPage } from "./same-page.js";
23
+ const projectParam = z.object({ projectId: idString });
24
+ const postParam = projectParam.extend({ platform: platformSchema });
25
+ const fileParam = projectParam.extend({
26
+ file: z
27
+ .string()
28
+ .min(1)
29
+ .max(200)
30
+ .regex(/^[A-Za-z0-9_-]+$/),
31
+ });
32
+ const nextQuery = z.object({
33
+ platform: platformSchema,
34
+ projectId: z.string().min(1).max(200).optional(),
35
+ });
36
+ const dueQuery = z.object({ hours: z.coerce.number().int().min(1).max(48).optional() });
37
+ const sitesBody = z.object({ sites: z.array(platformSchema).max(6) });
38
+ export function socialRoutes(deps) {
39
+ const { allowOrigin, allowed, refused } = extensionAuth(deps.db);
40
+ const media = {
41
+ db: deps.db,
42
+ paths: deps.paths,
43
+ reshaper: deps.social?.reshaper,
44
+ ...(deps.designs === undefined
45
+ ? {}
46
+ : {
47
+ renderDesign: async (designId, format, signal) => {
48
+ const drawn = await deps.designs?.renderer.render(designId, format, "png", signal);
49
+ return drawn?.ok === true
50
+ ? { ok: true, path: drawn.value.path }
51
+ : {
52
+ ok: false,
53
+ sentence: drawn?.message ??
54
+ "The design couldn't be drawn. Open it in Designs to check it, then close Copy for YouTube Studio and open it again.",
55
+ };
56
+ },
57
+ }),
58
+ };
59
+ const samePage = (c) => fromSlopifyPage(c, deps.remoteAccess?.remoteHost)
60
+ ? undefined
61
+ : problem(c, {
62
+ status: 403,
63
+ detail: "For your safety, a project's posts can only be changed from the Slopify page itself. Open Slopify and try again there.",
64
+ });
65
+ const answer = (c, projectId) => {
66
+ const result = postPacks(deps, projectId, deps.clock.now());
67
+ if (!result.ok)
68
+ return unknownProject(c);
69
+ const body = {
70
+ ...result.packs,
71
+ extension: postingExtension(deps.db),
72
+ };
73
+ return c.json(body);
74
+ };
75
+ const refusal = (c, refused) => problem(c, { status: refused.status, detail: refused.sentence });
76
+ const posting = (c, projectId, platform) => packFor(deps, projectId, platform, deps.clock.now()) === undefined
77
+ ? problem(c, {
78
+ status: 404,
79
+ detail: notPostingSentence(platform),
80
+ })
81
+ : undefined;
82
+ // A post filled at its release time tells the Notification URL, when one is set. Never
83
+ // awaited: the extension's report doesn't wait on it.
84
+ const notify = (text, projectId) => {
85
+ const url = readNotificationUrl(deps.db);
86
+ const send = deps.sendNotification;
87
+ if (url === null || send === undefined)
88
+ return;
89
+ void send(url, webhookBody(text)).then((sent) => {
90
+ if (!sent.ok)
91
+ deps.log.write("warn", "social.notify", { projectId, detail: sendFailureText(sent) });
92
+ });
93
+ };
94
+ const changed = (projectId) => deps.hub.emit(projectId, { type: "project.updated", projectId });
95
+ return (new Hono()
96
+ .get("/packs/:projectId", zValidator("param", projectParam, onInvalid), (c) => answer(c, c.req.valid("param").projectId))
97
+ // The project's own pick for one site: other settings, not posting there ({off: true}),
98
+ // or back to the template's (null).
99
+ .put("/packs/:projectId/:platform/choice", zValidator("param", postParam, onInvalid), zValidator("json", choiceBody, onInvalid), (c) => {
100
+ const denied = samePage(c);
101
+ if (denied !== undefined)
102
+ return denied;
103
+ const { projectId, platform } = c.req.valid("param");
104
+ if (postPacks(deps, projectId, deps.clock.now()).ok === false)
105
+ return unknownProject(c);
106
+ const { choice } = c.req.valid("json");
107
+ const config = projectById(deps.db, projectId)?.config.social?.[platform];
108
+ if (choice !== null &&
109
+ !("off" in choice) &&
110
+ choice.text === "prompt" &&
111
+ config?.text !== "prompt")
112
+ return problem(c, {
113
+ status: 409,
114
+ detail: `A post written by a post prompt is a paid step, so it is picked in the template, in Edit template → ${setupSectionNames.youtube}, where Save shows its cost. For this project, pick a free text source in Copy for YouTube Studio → ${platformLabels[platform]} → Settings.`,
115
+ });
116
+ writeChoice(deps.db, { projectId, platform }, choice, deps.clock.now());
117
+ changed(projectId);
118
+ return answer(c, projectId);
119
+ })
120
+ .put("/packs/:projectId/:platform/text", zValidator("param", postParam, onInvalid), zValidator("json", textBody, onInvalid), (c) => {
121
+ const denied = samePage(c);
122
+ if (denied !== undefined)
123
+ return denied;
124
+ const { projectId, platform } = c.req.valid("param");
125
+ const missing = posting(c, projectId, platform);
126
+ if (missing !== undefined)
127
+ return missing;
128
+ writeTextEdit(deps.db, { projectId, platform }, c.req.valid("json"), deps.clock.now());
129
+ changed(projectId);
130
+ return answer(c, projectId);
131
+ })
132
+ .delete("/packs/:projectId/:platform/text", zValidator("param", postParam, onInvalid), (c) => {
133
+ const denied = samePage(c);
134
+ if (denied !== undefined)
135
+ return denied;
136
+ const { projectId, platform } = c.req.valid("param");
137
+ const missing = posting(c, projectId, platform);
138
+ if (missing !== undefined)
139
+ return missing;
140
+ writeTextEdit(deps.db, { projectId, platform }, null, deps.clock.now());
141
+ changed(projectId);
142
+ return answer(c, projectId);
143
+ })
144
+ .put("/packs/:projectId/:platform/reddit", zValidator("param", postParam, onInvalid), zValidator("json", redditBody, onInvalid), (c) => {
145
+ const denied = samePage(c);
146
+ if (denied !== undefined)
147
+ return denied;
148
+ const { projectId, platform } = c.req.valid("param");
149
+ if (platform !== "reddit")
150
+ return problem(c, {
151
+ status: 400,
152
+ detail: "Only a Reddit post has a subreddit, title and flair. Open the Reddit post instead.",
153
+ });
154
+ const missing = posting(c, projectId, platform);
155
+ if (missing !== undefined)
156
+ return missing;
157
+ writeReddit(deps.db, { projectId, platform }, c.req.valid("json").reddit, deps.clock.now());
158
+ changed(projectId);
159
+ return answer(c, projectId);
160
+ })
161
+ // The post's link, pasted by the person (posted by hand, or the extension missed it); null
162
+ // removes it, so the post can be filled again.
163
+ .put("/packs/:projectId/:platform/link", zValidator("param", postParam, onInvalid), zValidator("json", linkBody, onInvalid), (c) => {
164
+ const denied = samePage(c);
165
+ if (denied !== undefined)
166
+ return denied;
167
+ const { projectId, platform } = c.req.valid("param");
168
+ const result = setPostLink(deps, projectId, platform, c.req.valid("json").url, deps.clock.now());
169
+ if (!result.ok)
170
+ return refusal(c, result);
171
+ changed(projectId);
172
+ return answer(c, projectId);
173
+ })
174
+ // Fill in <site>: never offered to agents or the assistant (`excluded-routes.ts`).
175
+ .post("/packs/:projectId/:platform/fill", zValidator("param", postParam, onInvalid), (c) => {
176
+ const denied = samePage(c);
177
+ if (denied !== undefined)
178
+ return denied;
179
+ const { projectId, platform } = c.req.valid("param");
180
+ const result = fillPost(deps, projectId, platform, deps.clock.now());
181
+ if (!result.ok)
182
+ return refusal(c, result);
183
+ changed(projectId);
184
+ return c.json({
185
+ pack: result.pack,
186
+ waitsForRelease: result.waitsForRelease,
187
+ url: result.pack.composeUrl,
188
+ });
189
+ })
190
+ .get("/media/:projectId/:file", zValidator("param", fileParam, onInvalid), async (c) => {
191
+ const { projectId, file } = c.req.valid("param");
192
+ return served(c, await serveMedia(media, projectId, file, deps.clock.now(), c.req.raw.signal));
193
+ })
194
+ // The extension's routes.
195
+ .options("/ext/*", (c) => {
196
+ allowOrigin(c, false);
197
+ return c.body(null, 204);
198
+ })
199
+ .get("/ext/next", zValidator("query", nextQuery, onInvalid), (c) => {
200
+ allowOrigin(c, false);
201
+ if (!allowed(c))
202
+ return refused(c);
203
+ const { platform, projectId } = c.req.valid("query");
204
+ const next = nextPost(deps, platform, deps.clock.now(), projectId);
205
+ if (next === undefined)
206
+ return problem(c, {
207
+ status: 404,
208
+ detail: "Nothing is waiting to be filled on this site. In Slopify, open the project, press Copy for YouTube Studio, then Fill in the site's post.",
209
+ });
210
+ changed(next.pack.projectId);
211
+ return c.json(next);
212
+ })
213
+ .get("/ext/packs/:projectId/:platform", zValidator("param", postParam, onInvalid), (c) => {
214
+ allowOrigin(c, false);
215
+ if (!allowed(c))
216
+ return refused(c);
217
+ const { projectId, platform } = c.req.valid("param");
218
+ const pack = packFor(deps, projectId, platform, deps.clock.now());
219
+ return pack === undefined ? unknownPost(c) : c.json(pack);
220
+ })
221
+ .get("/ext/files/:projectId/:file", zValidator("param", fileParam, onInvalid), async (c) => {
222
+ allowOrigin(c, false);
223
+ if (!allowed(c))
224
+ return refused(c);
225
+ const { projectId, file } = c.req.valid("param");
226
+ return served(c, await serveMedia(media, projectId, file, deps.clock.now(), c.req.raw.signal));
227
+ })
228
+ .get("/ext/due", zValidator("query", dueQuery, onInvalid), (c) => {
229
+ allowOrigin(c, false);
230
+ if (!allowed(c))
231
+ return refused(c);
232
+ const hours = c.req.valid("query").hours ?? 24;
233
+ return c.json({ posts: duePosts(deps, deps.clock.now(), hours * 3_600_000) });
234
+ })
235
+ .post("/ext/result", zValidator("json", resultBody, onInvalid), (c) => {
236
+ allowOrigin(c, false);
237
+ if (!allowed(c))
238
+ return refused(c);
239
+ const body = c.req.valid("json");
240
+ const result = reportPost(deps, body, deps.clock.now());
241
+ if (!result.ok)
242
+ return refusal(c, result);
243
+ changed(body.projectId);
244
+ if (body.state === "filled" && waitsForRelease(body.platform) && result.pack !== undefined)
245
+ notify(postReadyNotice(result.pack), body.projectId);
246
+ return c.json({ status: readPost(deps.db, body).state });
247
+ })
248
+ .get("/ext/find-links", (c) => {
249
+ allowOrigin(c, false);
250
+ if (!allowed(c))
251
+ return refused(c);
252
+ return c.json({ posts: linksToFind(deps, deps.clock.now()) });
253
+ })
254
+ // Which sites the extension may post on, as its options last granted them.
255
+ .post("/ext/sites", zValidator("json", sitesBody, onInvalid), (c) => {
256
+ allowOrigin(c, false);
257
+ if (!allowed(c))
258
+ return refused(c);
259
+ writeExtensionSites(deps.db, c.req.valid("json").sites, deps.clock.now());
260
+ return c.json(postingExtension(deps.db));
261
+ }));
262
+ }
263
+ function served(c, result) {
264
+ if (!result.ok)
265
+ return problem(c, { status: result.status, detail: result.sentence });
266
+ return fileResponse(c, result.path, result.bytes, {
267
+ "content-type": result.contentType,
268
+ "content-disposition": `inline; filename="${result.filename.replace(/["\\]/g, "")}"`,
269
+ "x-content-type-options": "nosniff",
270
+ });
271
+ }
272
+ function unknownProject(c) {
273
+ return problem(c, {
274
+ status: 404,
275
+ detail: "This project no longer exists. Go back to Projects and open it again.",
276
+ });
277
+ }
278
+ function unknownPost(c) {
279
+ return problem(c, {
280
+ status: 404,
281
+ detail: "That project no longer posts to this site. In Slopify, open the project's Copy for YouTube Studio and press Fill again.",
282
+ });
283
+ }