@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
@@ -5,10 +5,12 @@ import { crc32 } from "node:zlib";
5
5
  import { strFromU8, strToU8, Unzip, UnzipInflate, zipSync } from "fflate";
6
6
  import { z } from "zod";
7
7
  import { transact } from "../../kernel/db/tx.js";
8
+ import { isUnreachablePath } from "../../kernel/errors.js";
8
9
  import { liveProject } from "../admission/repo.js";
9
10
  import { silenceGapSecondsMax } from "../admission/rules.js";
10
11
  import { detectSlots } from "../admission/substitute.js";
11
12
  import { agentConnectionKey } from "../agents/connection.js";
13
+ import { dailySampleBudgetKey, dailySampleBudgetSchema } from "../designs/budget.js";
12
14
  import { fontMaxBytes } from "../fonts/model.js";
13
15
  import { readFontMetadata } from "../fonts/sfnt.js";
14
16
  import { lintEntry, lintPrompt } from "../library/lint.js";
@@ -29,6 +31,8 @@ import { postingPlanKey, postingPlanSchema } from "../studio/plan.js";
29
31
  import { leadHoursMax } from "../studio/plan-model.js";
30
32
  import { leadHoursKey } from "../studio/releases.js";
31
33
  import { autoCommentKey, storedPlaylistsSchema, storedProjectPlaylistsSchema, studioChannelPlaylistPrefix, studioFillQueuePrefix, studioPairingKey, studioPlaylistKey, studioProjectPlaylistsKey, studioRealFootageKey, } from "../studio/settings.js";
34
+ import { dailyBudgetKey, dailyBudgetMax, samplesUsePlansKey } from "../template-studio/budget.js";
35
+ import { notTemplateSample } from "../template-studio/repo.js";
32
36
  import { channelLinksKey } from "../youtube/edits-repo.js";
33
37
  import { defaultBackupsDir, projectDir, stagingPath } from "./layout.js";
34
38
  import { stageKinds } from "./model.js";
@@ -299,7 +303,7 @@ function planStagedExport(deps) {
299
303
  stat = lstatSync(path);
300
304
  }
301
305
  catch (error) {
302
- if (unavailable(error))
306
+ if (isUnreachablePath(error))
303
307
  continue;
304
308
  throw error;
305
309
  }
@@ -358,7 +362,7 @@ const storedAppearance = z.enum(appearances);
358
362
  const storedLoudness = z
359
363
  .object({ enabled: z.boolean(), videoLufs: z.number(), audioFilesLufs: z.number() })
360
364
  .strict();
361
- // Settings → Channel links, and the providers a fresh Play form starts with.
365
+ // Settings → YouTube Studio and posting, and the providers a fresh Play form starts with.
362
366
  // The AI use marks: ticked voices, Image prompts and projects, by id.
363
367
  const storedIds = z.array(z.string().max(200)).max(100_000);
364
368
  // The channel page's "Keep only titles containing…" text, one per channel.
@@ -410,6 +414,12 @@ function portableSettings(settings) {
410
414
  storedProjectPlaylistsSchema.parse(parsed);
411
415
  else if (key === channelLinksKey)
412
416
  storedChannelLinks.parse(parsed);
417
+ else if (key === dailySampleBudgetKey)
418
+ dailySampleBudgetSchema.parse(parsed);
419
+ else if (key === dailyBudgetKey)
420
+ z.number().finite().min(0).max(dailyBudgetMax).parse(parsed);
421
+ else if (key === samplesUsePlansKey)
422
+ z.boolean().parse(parsed);
413
423
  else if (key === realPersonVoicesKey ||
414
424
  key === photorealisticPromptsKey ||
415
425
  key === studioRealFootageKey)
@@ -462,7 +472,7 @@ function validateLibraryRows(manifest) {
462
472
  const promptIds = new Set();
463
473
  const promptNames = new Set();
464
474
  for (const row of manifest.prompts) {
465
- if (row.name !== row.name.trim() || lintPrompt(row).length > 0)
475
+ if (row.name !== row.name.trim() || lintPrompt(row, detectSlots(row.body).names).length > 0)
466
476
  throw new Error("The backup contains an invalid prompt.");
467
477
  uniquePortableRow(promptIds, row.id, "prompt id");
468
478
  uniquePortableRow(promptNames, `${row.kind}\0${row.name.toLowerCase()}`, "prompt name");
@@ -471,7 +481,7 @@ function validateLibraryRows(manifest) {
471
481
  const entryIds = new Set();
472
482
  const entryNames = new Set();
473
483
  for (const row of manifest.entries) {
474
- if (row.name !== row.name.trim() || lintEntry(row).length > 0)
484
+ if (row.name !== row.name.trim() || lintEntry(row, detectSlots(row.body).names).length > 0)
475
485
  throw new Error("The backup contains an invalid entry.");
476
486
  uniquePortableRow(entryIds, row.id, "entry id");
477
487
  uniquePortableRow(entryNames, `${row.category}\0${row.name.toLowerCase()}`, "entry name");
@@ -825,7 +835,7 @@ function installedUploadedFonts(paths) {
825
835
  names = readdirSync(root);
826
836
  }
827
837
  catch (error) {
828
- if (unavailable(error))
838
+ if (isUnreachablePath(error))
829
839
  return [];
830
840
  throw error;
831
841
  }
@@ -884,7 +894,7 @@ function readStoredFont(path) {
884
894
  return readFileSync(descriptor);
885
895
  }
886
896
  catch (error) {
887
- if (unavailable(error))
897
+ if (isUnreachablePath(error))
888
898
  return undefined;
889
899
  throw error;
890
900
  }
@@ -903,11 +913,6 @@ function writeImportedFile(deps, path, content, created) {
903
913
  closeSync(descriptor);
904
914
  }
905
915
  }
906
- function unavailable(error) {
907
- return (error instanceof Error &&
908
- "code" in error &&
909
- ["ENOENT", "ENOTDIR", "EACCES", "EPERM", "ELOOP"].includes(String(error.code)));
910
- }
911
916
  function planStagedImport(deps, archive, files) {
912
917
  const sourceIds = new Set();
913
918
  const archivePaths = new Set();
@@ -963,7 +968,7 @@ export function storageBytes(paths) {
963
968
  export function storageUsage(deps) {
964
969
  const totals = storageBytes(deps.paths);
965
970
  const byProject = deps.db
966
- .prepare(`SELECT id,title FROM projects WHERE ${liveProject()} ORDER BY created_at DESC, id DESC`)
971
+ .prepare(`SELECT id,title FROM projects WHERE ${liveProject()} AND ${notTemplateSample()} ORDER BY created_at DESC, id DESC`)
967
972
  .all()
968
973
  .flatMap((row) => {
969
974
  if (typeof row.id !== "string" || typeof row.title !== "string")
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { socialPlatforms } from "../social/model.js";
2
3
  import { outputRoles, stagedFileStates, stageKinds } from "./model.js";
3
4
  export const metaSchema = z.object({
4
5
  segment: z.enum(["body", "intro", "outro"]).optional(),
@@ -26,6 +27,7 @@ export const metaSchema = z.object({
26
27
  })
27
28
  .optional(),
28
29
  master: z.object({ target: z.number(), integrated: z.number(), truePeak: z.number() }).optional(),
30
+ platform: z.enum(socialPlatforms).optional(),
29
31
  });
30
32
  export const outputSchema = z.object({
31
33
  id: z.string(),
@@ -1,4 +1,5 @@
1
1
  import { rmSync } from "node:fs";
2
+ import { errorText } from "../../kernel/errors.js";
2
3
  import { stagingPath } from "./layout.js";
3
4
  import { deleteStagedFile, stagedFileById } from "./repo.js";
4
5
  export function stagedFileReferenced(db, id) {
@@ -18,7 +19,7 @@ export function releaseStagedFile(deps, id) {
18
19
  }
19
20
  catch (error) {
20
21
  deps.log.write("warn", "staging.release", {
21
- detail: `${id}: ${error instanceof Error ? error.message : String(error)}`,
22
+ detail: `${id}: ${errorText(error)}`,
22
23
  });
23
24
  return;
24
25
  }
@@ -3,10 +3,10 @@ import { basename, dirname } from "node:path";
3
3
  import { pipeline } from "node:stream/promises";
4
4
  import { z } from "zod";
5
5
  import { transact } from "../../kernel/db/tx.js";
6
+ import { errorText } from "../../kernel/errors.js";
6
7
  import { extensionOf, outputFileName, outputPath, stagingPath } from "./layout.js";
7
8
  import { deleteStagedFile, insertOutput, insertStagedFile, markStagedFileCopied, stagedFileById, } from "./repo.js";
8
9
  import { releaseStagedFile, stagedFileReferenced } from "./staging-refs.js";
9
- export { prepareStagedFile, prepareText } from "./prepare.js";
10
10
  // The upload's own name is data, never a path component: the file is stored under a
11
11
  // generated id, and anything with a separator, a traversal, or a control character is
12
12
  // refused rather than sanitised, so nothing silently lands under a name nobody chose.
@@ -53,7 +53,7 @@ export async function stageUpload(deps, input) {
53
53
  deps.emit(event);
54
54
  }
55
55
  catch (error) {
56
- deps.log.write("warn", "staging.emit", { detail: messageOf(error) });
56
+ deps.log.write("warn", "staging.emit", { detail: errorText(error) });
57
57
  }
58
58
  };
59
59
  const progress = (state) => {
@@ -92,10 +92,10 @@ export async function stageUpload(deps, input) {
92
92
  stagedFileId: id,
93
93
  stageKind: input.stageKind,
94
94
  originalFilename,
95
- detail: messageOf(error),
95
+ detail: errorText(error),
96
96
  });
97
97
  deps.log.write("error", "staging.failed", {
98
- detail: `${id} ${originalFilename}: ${messageOf(error)}`,
98
+ detail: `${id} ${originalFilename}: ${errorText(error)}`,
99
99
  });
100
100
  throw error;
101
101
  }
@@ -152,7 +152,7 @@ export function attachStagedFile(deps, input) {
152
152
  catch (error) {
153
153
  deps.log.write("error", "storage.attach", {
154
154
  projectId: input.projectId,
155
- detail: `${staged.id} → ${name}: ${messageOf(error)}`,
155
+ detail: `${staged.id} → ${name}: ${errorText(error)}`,
156
156
  });
157
157
  throw error;
158
158
  }
@@ -210,7 +210,7 @@ function discard(deps, id, target, event) {
210
210
  }
211
211
  catch (error) {
212
212
  // The row stays `copying`, so the next boot's reconcile removes the file instead.
213
- deps.log.write("warn", event, { detail: `${id}: ${messageOf(error)}` });
213
+ deps.log.write("warn", event, { detail: `${id}: ${errorText(error)}` });
214
214
  return;
215
215
  }
216
216
  deleteStagedFile(deps.db, id);
@@ -222,6 +222,3 @@ function closed(sink) {
222
222
  sink.once("close", resolve);
223
223
  });
224
224
  }
225
- function messageOf(error) {
226
- return error instanceof Error ? error.message : String(error);
227
- }
@@ -2,6 +2,8 @@ import { existsSync, readdirSync, rmdirSync, statSync, unlinkSync } from "node:f
2
2
  import { join, relative, sep } from "node:path";
3
3
  import { derive } from "../../kernel/runner/graph.js";
4
4
  import { liveProject, projectExists, projectPaused, stagesOf } from "../admission/repo.js";
5
+ import { bundledSampleProjects } from "../onboarding/state.js";
6
+ import { notTemplateSample } from "../template-studio/repo.js";
5
7
  import { projectSettled } from "./arrange.js";
6
8
  import { backupsFolderName, outputPath, projectDir } from "./layout.js";
7
9
  import { pieceFile } from "./reconcile.js";
@@ -10,7 +12,9 @@ import { pieceFile } from "./reconcile.js";
10
12
  // video and shorts show included: making them again costs money and never gives the same ones. Two
11
13
  // rules keep it safe. A file the user supplied is never removed, because nothing could make
12
14
  // it again. And only files a project record names are removed: anything else in the folder
13
- // is reconcile's business (`reconcile.ts`), which leaves the Backups folder alone.
15
+ // is reconcile's business (`reconcile.ts`), which leaves the Backups folder alone. A bundled
16
+ // sample is read-only and keeps every file: nothing on it counts as removable, and no clean-up
17
+ // touches it.
14
18
  // What a finished project is for: the files that get uploaded or read, from every revision,
15
19
  // so an older video in History stays too.
16
20
  export const outputRoles = [
@@ -32,17 +36,20 @@ export const outputRoles = [
32
36
  "image",
33
37
  "animated_image",
34
38
  "short_image",
39
+ "social_post",
35
40
  ];
36
41
  export function projectStorage(deps, projectId) {
37
42
  const files = filesOf(deps.paths, projectId);
38
- const kept = keptPaths(deps.db, projectId);
39
43
  const outputs = outputPaths(deps.db, projectId);
40
- const removable = removablePaths(deps.db, projectId, kept);
44
+ const sample = bundledSampleProjects(deps.db).has(projectId);
45
+ const removable = sample
46
+ ? new Set()
47
+ : removablePaths(deps.db, projectId, keptPaths(deps.db, projectId));
41
48
  let outputsBytes = 0;
42
49
  let workingBytes = 0;
43
50
  let removableFiles = 0;
44
51
  let removableBytes = 0;
45
- const history = historyPaths(deps, projectId);
52
+ const history = sample ? new Set() : historyPaths(deps, projectId);
46
53
  let historyFiles = 0;
47
54
  let historyBytes = 0;
48
55
  for (const [path, bytes] of files) {
@@ -105,15 +112,16 @@ export function deleteOldVersions(deps, projectId) {
105
112
  removeEmptyUnder(join(projectDir(deps.paths, projectId), "History"));
106
113
  return { ok: true, files: count, bytesFreed };
107
114
  }
108
- // Every project's old versions, skipping any that runs or waits to run.
115
+ // Every project's old versions, skipping any that runs or waits to run. A template studio
116
+ // sample keeps its files (its bake-offs and resamples reuse them), and is no project to count;
117
+ // nor is a bundled sample, which is read-only.
109
118
  export function deleteAllOldVersions(deps) {
110
119
  const busyIds = [];
111
120
  let projects = 0;
112
121
  let files = 0;
113
122
  let bytesFreed = 0;
114
123
  let busy = 0;
115
- for (const row of deps.db.prepare(`SELECT id FROM projects WHERE ${liveProject()}`).all()) {
116
- const id = String(row.id);
124
+ for (const id of cleanable(deps.db)) {
117
125
  if (placedFiles(deps.paths, id).size === 0)
118
126
  continue;
119
127
  const result = deleteOldVersions(deps, id);
@@ -138,8 +146,7 @@ export function cleanUpAll(deps) {
138
146
  let files = old.files;
139
147
  let bytesFreed = old.bytesFreed;
140
148
  let skipped = old.busy;
141
- for (const row of deps.db.prepare(`SELECT id FROM projects WHERE ${liveProject()}`).all()) {
142
- const id = String(row.id);
149
+ for (const id of cleanable(deps.db)) {
143
150
  const result = keepOutputsOnly(deps, id);
144
151
  if (result.ok) {
145
152
  files += result.files;
@@ -150,6 +157,16 @@ export function cleanUpAll(deps) {
150
157
  }
151
158
  return { files, bytesFreed, skipped };
152
159
  }
160
+ // The projects the clean-ups for every project go through: neither a template studio sample nor
161
+ // a bundled one.
162
+ function cleanable(db) {
163
+ const samples = bundledSampleProjects(db);
164
+ return db
165
+ .prepare(`SELECT id FROM projects WHERE ${liveProject()} AND ${notTemplateSample()}`)
166
+ .all()
167
+ .map((row) => String(row.id))
168
+ .filter((id) => !samples.has(id));
169
+ }
153
170
  // The folders under `root` left empty, deepest first, and `root` itself if it is empty then.
154
171
  function removeEmptyUnder(root) {
155
172
  if (!existsSync(root))
@@ -1,8 +1,9 @@
1
1
  import { listProjects } from "../admission/repo.js";
2
+ import { uploadedProjects } from "../uploads/repo.js";
2
3
  import { uploadPack } from "./pack.js";
3
4
  import { readPlan } from "./plan.js";
4
5
  import { seriesOf } from "./plan-model.js";
5
- import { allReleases, lineSlots, readLeadHours, releasesOf } from "./releases.js";
6
+ import { allReleases, lineShortTimes, lineSlots, lineZone, readLeadHours, releasesOf, } from "./releases.js";
6
7
  import { videoOf } from "./videos.js";
7
8
  const hourMs = 60 * 60 * 1000;
8
9
  export function releaseCalendar(deps, options) {
@@ -16,6 +17,7 @@ export function releaseCalendar(deps, options) {
16
17
  return at >= from.getTime() && at < until.getTime();
17
18
  };
18
19
  const projects = new Map(listProjects(deps.db).map((project) => [project.id, project]));
20
+ const uploaded = uploadedProjects(deps.db);
19
21
  const longs = allReleases(deps.db).filter((release) => release.short === 0 && projects.has(release.projectId) && inWindow(release.at));
20
22
  // A schedule's line shows the schedule's name; a stored line its series.
21
23
  const seriesOfLine = (line) => {
@@ -30,6 +32,7 @@ export function releaseCalendar(deps, options) {
30
32
  return {
31
33
  at: long.at,
32
34
  line: long.line,
35
+ timeZone: lineZone(plan, long.line),
33
36
  series: project === undefined ? "" : seriesOf(project.config),
34
37
  project: { id: long.projectId, title: project?.title ?? "" },
35
38
  items: items.map((item) => {
@@ -38,7 +41,7 @@ export function releaseCalendar(deps, options) {
38
41
  const release = at === undefined || at === "" ? null : at;
39
42
  const uploadBy = release === null ? null : new Date(Date.parse(release) - leadMs);
40
43
  const video = videoOf(deps.db, long.projectId, item.short ?? null);
41
- const state = video?.uploadState === "done"
44
+ const state = video?.uploadState === "done" || (short === 0 && uploaded.has(long.projectId))
42
45
  ? "scheduled"
43
46
  : video?.uploadState === "filled"
44
47
  ? "filled"
@@ -66,6 +69,8 @@ export function releaseCalendar(deps, options) {
66
69
  entries.push({
67
70
  at: slot.longAt,
68
71
  line: slot.row,
72
+ timeZone: lineZone(plan, slot.row),
73
+ shortsAt: lineShortTimes(plan, slot.row, slot.longAt),
69
74
  series: seriesOfLine(slot.row),
70
75
  project: null,
71
76
  items: [],
@@ -16,7 +16,7 @@
16
16
  // So the automatic answer is Yes only when one of the three cases applies, and No otherwise:
17
17
  // 1. a saved voice marked "Imitates a real person" (Settings → Voices) narrates it: an AI
18
18
  // voice cloned from, or made to sound like, a real person, so that person seems to say it;
19
- // 2. uploaded clips the person marked as real footage (the project's Prepare upload) get the
19
+ // 2. uploaded clips the person marked as real footage (the project's Copy for YouTube Studio) get the
20
20
  // Look's atmosphere overlay (embers, dust or fog), which adds to a real scene what wasn't
21
21
  // there. A colour grade, vignette or grain alone is one of YouTube's minor edits;
22
22
  // 3. an AI image model draws its pictures from an Image prompt marked "Draws photorealistic
@@ -49,18 +49,18 @@ export function aiDisclosureOf(input) {
49
49
  if (input.realFootage && input.footageOverlay !== undefined)
50
50
  reasons.push(`the Look's ${input.footageOverlay} overlay is laid over uploaded clips marked as real footage (YouTube's case 2: "${aiUseCases[1]}")`);
51
51
  if (input.photorealistic)
52
- reasons.push(`an AI image model draws its pictures from an Image prompt marked photorealistic in Library → Prompts (YouTube's case 3: "${aiUseCases[2]}")`);
52
+ reasons.push(`an AI image model draws its pictures from an Image prompt marked photorealistic in Prompts (YouTube's case 3: "${aiUseCases[2]}")`);
53
53
  if (reasons.length > 0)
54
54
  return {
55
55
  altered: true,
56
56
  why: `Yes because ${list(reasons)}. Selecting Yes adds YouTube's AI label.`,
57
57
  };
58
58
  const footage = input.realFootage && input.kind === "video"
59
- ? " The uploaded real footage only gets colour, vignette or grain, which YouTube counts as minor edits."
59
+ ? " The uploaded real footage only gets color, vignette or grain, which YouTube counts as minor edits."
60
60
  : "";
61
61
  return {
62
62
  altered: false,
63
- why: `No because none of YouTube's three AI use cases applies: no narrating voice is marked as imitating a real person (Settings → Voices), no real footage is altered, and no AI pictures come from an Image prompt marked photorealistic (Library → Prompts). Stylised AI pictures and an AI narrator that doesn't pose as a real person don't need the label.${footage}`,
63
+ why: `No because none of YouTube's three AI use cases applies: no narrating voice is marked as imitating a real person (Settings → Voices), no real footage is altered, and no AI pictures come from an Image prompt marked photorealistic (Prompts). Stylized AI pictures and an AI narrator that doesn't pose as a real person don't need the label.${footage}`,
64
64
  };
65
65
  }
66
66
  function quotedList(names) {
@@ -13,7 +13,7 @@ export const studioAudience = "not_made_for_kids";
13
13
  export const studioUploadUrl = "https://www.youtube.com/upload";
14
14
  // What a playlist takes: every upload, only long videos, or only shorts.
15
15
  export const playlistUses = ["all", "long", "shorts"];
16
- // The steps in Studio's order, as the Prepare upload view lists them.
16
+ // The steps in Studio's order, as Copy for YouTube Studio lists them.
17
17
  export const studioSteps = [
18
18
  "video",
19
19
  "title",
@@ -1,5 +1,6 @@
1
1
  import { readFileSync, statSync } from "node:fs";
2
2
  import { z } from "zod";
3
+ import { editSettings } from "../../kernel/ports/settings-places.js";
3
4
  import { thumbnailCountOf } from "../admission/model.js";
4
5
  import { projectById } from "../admission/repo.js";
5
6
  import { channelById, projectChannelId } from "../channels/repo.js";
@@ -55,7 +56,7 @@ export function uploadPack(deps, projectId) {
55
56
  ? undefined
56
57
  : readFileSync(outputPath(deps.paths, projectId, output.path), "utf8").trim();
57
58
  const channelId = projectChannelId(deps.db, projectId);
58
- // The channel's playlists (Settings → YouTube Studio), ticked as this project chose.
59
+ // The channel's playlists (Settings → YouTube Studio and posting), ticked as this project chose.
59
60
  const playlistChoices = projectPlaylists(deps.db, projectId, channelId);
60
61
  // The long video goes into the chosen playlists that take long videos, each short into those
61
62
  // that take shorts.
@@ -88,7 +89,7 @@ export function uploadPack(deps, projectId) {
88
89
  });
89
90
  const video = outputs.find((output) => output.role === "video");
90
91
  if (video === undefined)
91
- missing.push("The video isn't made yet. Let the Video stage finish (or use Continue the run on the project page), then open Prepare upload again.");
92
+ missing.push("The video isn't made yet. Let the Video stage finish (or use Continue the run on the project page), then open Copy for YouTube Studio again.");
92
93
  const written = text(outputs.find((output) => output.role === "youtube_description"));
93
94
  const writtenTags = text(outputs.find((output) => output.role === "youtube_tags"));
94
95
  // As the project page shows it: the user's edits, and channel links filled in.
@@ -109,7 +110,7 @@ export function uploadPack(deps, projectId) {
109
110
  if (description === undefined)
110
111
  missing.push(config.youtubeDescription === true
111
112
  ? "The YouTube description isn't written yet. It is written after the video's subtitle timing; wait for the Video stage to finish."
112
- : "No YouTube description is written for this project. Turn on YouTube description in Edit project → Prompts to have one written with chapters and tags.");
113
+ : `No YouTube description is written for this project. Turn on YouTube description in ${editSettings.extras} to have one written with chapters and tags.`);
113
114
  const count = thumbnailCountOf(config);
114
115
  // The first thumbnail, then the second and third, and never one left from when the project
115
116
  // made three and now makes one.
@@ -118,12 +119,12 @@ export function uploadPack(deps, projectId) {
118
119
  .sort((a, b) => (a.meta.index ?? 1) - (b.meta.index ?? 1));
119
120
  if (thumbnails.length === 0)
120
121
  missing.push(config.sources.thumbnail === "off"
121
- ? "This project makes no thumbnail, so YouTube will pick a frame. Set Thumbnail in Edit project to make or upload one."
122
- : "The thumbnail isn't made yet. Let the Thumbnail stage finish, then open Prepare upload again.");
122
+ ? `This project makes no thumbnail, so YouTube will pick a frame. Set Thumbnail source in ${editSettings.extras} to make or upload one.`
123
+ : "The thumbnail isn't made yet. Let the Thumbnail stage finish, then open Copy for YouTube Studio again.");
123
124
  else if (thumbnails.length < count)
124
- missing.push(`Only ${String(thumbnails.length)} of the ${String(count)} thumbnails are made. Regenerate the missing ones in the project's Images section.`);
125
+ missing.push(`Only ${String(thumbnails.length)} of the ${String(count)} thumbnails are made. Make the missing ones in the project's Images section.`);
125
126
  if (playlistChoices.length === 0)
126
- missing.push("No playlist is set for this project's channel, so the playlist step is left to you. Set one in Settings → YouTube Studio → Playlists.");
127
+ missing.push("No playlist is set for this project's channel, so the playlist step is left to you. Set one in Settings → YouTube Studio and posting → Playlists.");
127
128
  // The video's titles and thumbnails in the project's order, and the ones the person picked
128
129
  // for the upload first (`pick.ts`); the A/B test later tries the rest beside them.
129
130
  const ownTitle = project.title.slice(0, studioTitleMax);
@@ -189,7 +190,7 @@ export function uploadPack(deps, projectId) {
189
190
  output.meta.sentences?.[1] === clip.last);
190
191
  const render = exact ?? mine.at(-1);
191
192
  if (render === undefined)
192
- missing.push(`Short ${String(clip.number)} isn't rendered yet. Let the Video stage finish, then open Prepare upload again.`);
193
+ missing.push(`Short ${String(clip.number)} isn't rendered yet. Let the Video stage finish, then open Copy for YouTube Studio again.`);
193
194
  items.push({
194
195
  kind: "short",
195
196
  short: clip.number,
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { readSetting, writeSetting } from "../settings/repo.js";
2
+ import { readJsonSetting, writeSetting } from "../settings/repo.js";
3
3
  export const uploadPickSchema = z.object({
4
4
  title: z.number().int().min(0).max(9),
5
5
  thumbnail: z.number().int().min(0).max(9),
@@ -7,16 +7,7 @@ export const uploadPickSchema = z.object({
7
7
  export const uploadPickPrefix = "studio.uploadPick.";
8
8
  const keyOf = (projectId) => `${uploadPickPrefix}${projectId}`;
9
9
  export function readUploadPick(db, projectId) {
10
- const stored = readSetting(db, keyOf(projectId));
11
- if (stored === undefined)
12
- return { title: 0, thumbnail: 0 };
13
- try {
14
- const parsed = uploadPickSchema.safeParse(JSON.parse(stored));
15
- return parsed.success ? parsed.data : { title: 0, thumbnail: 0 };
16
- }
17
- catch {
18
- return { title: 0, thumbnail: 0 };
19
- }
10
+ return readJsonSetting(db, keyOf(projectId), uploadPickSchema, { title: 0, thumbnail: 0 });
20
11
  }
21
12
  export function writeUploadPick(db, projectId, pick) {
22
13
  writeSetting(db, keyOf(projectId), JSON.stringify(uploadPickSchema.parse(pick)));
@@ -1,6 +1,6 @@
1
1
  import { templateById } from "../project-templates/repo.js";
2
2
  import { scheduleRows } from "../schedules/repo.js";
3
- import { readSetting, writeSetting } from "../settings/repo.js";
3
+ import { readJsonSetting, writeSetting } from "../settings/repo.js";
4
4
  // The posting plan: a week of long-video slots, each with its shorts. Each schedule sets its
5
5
  // own (its release times, one line per day it runs, in its time zone); lines saved in Settings
6
6
  // before that are kept in the plan's time zone. A finished project takes the next free slot when its
@@ -11,7 +11,7 @@ export { postingPlanSchema, weekdays, } from "./plan-model.js";
11
11
  import { postingPlanSchema, seriesOf } from "./plan-model.js";
12
12
  export const postingPlanKey = "studio.postingPlan";
13
13
  const key = postingPlanKey;
14
- // No plan until one is made: nothing is scheduled, and Prepare upload says so.
14
+ // No plan until one is made: nothing is scheduled, and Copy for YouTube Studio says so.
15
15
  export function emptyPlan(timeZone) {
16
16
  return { timeZone, rows: [] };
17
17
  }
@@ -20,20 +20,11 @@ export function localTimeZone() {
20
20
  }
21
21
  // The plan as stored in Settings: the lines made before schedules had release times.
22
22
  export function readStoredPlan(db) {
23
- const stored = readSetting(db, key);
24
- if (stored !== undefined)
25
- try {
26
- const parsed = postingPlanSchema.safeParse(JSON.parse(stored));
27
- if (parsed.success)
28
- return {
29
- ...parsed.data,
30
- rows: parsed.data.rows.filter((row) => row.schedule === undefined),
31
- };
32
- }
33
- catch {
34
- // A broken setting reads as no plan.
35
- }
36
- return emptyPlan(localTimeZone());
23
+ // A broken setting reads as no plan.
24
+ const stored = readJsonSetting(db, key, postingPlanSchema, undefined);
25
+ if (stored === undefined)
26
+ return emptyPlan(localTimeZone());
27
+ return { ...stored, rows: stored.rows.filter((row) => row.schedule === undefined) };
37
28
  }
38
29
  // The series of a schedule's template, from its project title ("{{Topic}} | Stories" →
39
30
  // "Stories"); "" when the template is gone or its title names none.
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { z } from "zod";
3
- import { readSetting, writeSetting } from "../settings/repo.js";
3
+ import { deleteSetting, readJsonSetting, writeSetting } from "../settings/repo.js";
4
4
  import { studioFillQueuePrefix, studioPairing } from "./settings.js";
5
5
  // What Fill in YouTube Studio chose, waiting for the extension: one entry per video or short,
6
6
  // oldest first. Each new upload dialog in Studio is filled from the first entry, which then
@@ -22,24 +22,13 @@ function queueKey(db) {
22
22
  }
23
23
  function write(db, key, entries) {
24
24
  if (entries.length === 0)
25
- db.prepare("DELETE FROM settings WHERE key = ?").run(key);
25
+ deleteSetting(db, key);
26
26
  else
27
27
  writeSetting(db, key, JSON.stringify(entries));
28
28
  }
29
29
  // The waiting entries, oldest first, without the stale ones.
30
30
  export function readFillQueue(db, now) {
31
- const stored = readSetting(db, queueKey(db));
32
- if (stored === undefined)
33
- return [];
34
- let raw;
35
- try {
36
- raw = JSON.parse(stored);
37
- }
38
- catch {
39
- return [];
40
- }
41
- if (!Array.isArray(raw))
42
- return [];
31
+ const raw = readJsonSetting(db, queueKey(db), z.array(z.unknown()), []);
43
32
  return raw.flatMap((one) => {
44
33
  const parsed = entrySchema.safeParse(one);
45
34
  if (!parsed.success)
@@ -63,6 +63,32 @@ export function lineSlots(plan, from, until) {
63
63
  })))
64
64
  .toSorted((left, right) => Date.parse(left.longAt) - Date.parse(right.longAt));
65
65
  }
66
+ // The short times a line gives the video released at `longAt`: each short the line's first
67
+ // time after the one before it, the way a project's shorts are planned (other releases aside).
68
+ export function lineShortTimes(plan, row, longAt, shorts) {
69
+ const line = plan.rows.find((one) => one.name === row);
70
+ if (line === undefined)
71
+ return [];
72
+ const start = Date.parse(longAt);
73
+ const times = line.shorts
74
+ .flatMap((slot) => zonedTimes(zoneOf(plan, line), slot, new Date(start), new Date(start + 5 * 7 * 24 * hourMs)))
75
+ .toSorted((left, right) => left.getTime() - right.getTime());
76
+ const out = [];
77
+ let after = start;
78
+ for (let short = 1; short <= (shorts ?? line.shorts.length); short += 1) {
79
+ const at = times.find((time) => time.getTime() > after);
80
+ if (at === undefined)
81
+ break;
82
+ after = at.getTime();
83
+ out.push(at.toISOString());
84
+ }
85
+ return out;
86
+ }
87
+ // The time zone a line's times are read in: its schedule's, else the plan's.
88
+ export function lineZone(plan, row) {
89
+ const line = plan.rows.find((one) => one.name === row);
90
+ return line === undefined ? plan.timeZone : zoneOf(plan, line);
91
+ }
66
92
  // The coming long-video times a project may take: far enough ahead to upload in time, of a
67
93
  // line that takes it, in an hour no other release has.
68
94
  export function freeSlots(db, plan, who, now, count, exceptProject) {