@gentbajko/slopify 3.25.8 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (604) hide show
  1. package/dist/adapters/account-limits.js +30 -0
  2. package/dist/adapters/alignment/cache.js +3 -5
  3. package/dist/adapters/alignment/ctc.js +1 -2
  4. package/dist/adapters/alignment/lock.js +4 -3
  5. package/dist/adapters/alignment/multilingual.js +0 -3
  6. package/dist/adapters/alignment/prefetch.js +2 -1
  7. package/dist/adapters/alignment/worker.js +2 -1
  8. package/dist/adapters/explain.js +0 -3
  9. package/dist/adapters/ffmpeg.js +19 -1
  10. package/dist/adapters/host-cli/index.js +19 -3
  11. package/dist/adapters/http-failure.js +53 -0
  12. package/dist/adapters/image/codex.js +36 -4
  13. package/dist/adapters/image/fal.js +17 -50
  14. package/dist/adapters/image/google.js +31 -55
  15. package/dist/adapters/image/openai.js +20 -47
  16. package/dist/adapters/image/reference.js +36 -0
  17. package/dist/adapters/image/replicate.js +18 -47
  18. package/dist/adapters/interest/search-interest.js +113 -0
  19. package/dist/adapters/key-probes.js +53 -2
  20. package/dist/adapters/llm/antigravity.js +4 -0
  21. package/dist/adapters/llm/assistant-cli.js +388 -0
  22. package/dist/adapters/llm/child-session.js +83 -0
  23. package/dist/adapters/llm/claude-code-models.js +51 -98
  24. package/dist/adapters/llm/claude-code.js +13 -2
  25. package/dist/adapters/llm/codex-limits.js +27 -68
  26. package/dist/adapters/llm/codex-server-models.js +61 -109
  27. package/dist/adapters/llm/codex.js +12 -1
  28. package/dist/adapters/llm/openrouter.js +10 -47
  29. package/dist/adapters/llm/run-cli.js +6 -22
  30. package/dist/adapters/llm/sse-lines.js +5 -1
  31. package/dist/adapters/model-access.js +37 -0
  32. package/dist/adapters/tts/cartesia.js +15 -46
  33. package/dist/adapters/tts/elevenlabs.js +20 -52
  34. package/dist/adapters/tts/gemini.js +19 -40
  35. package/dist/adapters/tts/inworld-async.js +4 -1
  36. package/dist/adapters/tts/inworld.js +3 -0
  37. package/dist/adapters/tts/openai.js +20 -50
  38. package/dist/assets/models-lifecycle.yaml +6 -0
  39. package/dist/assistant-wiring.js +60 -0
  40. package/dist/autopilot-timers.js +59 -0
  41. package/dist/catalog/lifecycle.js +52 -0
  42. package/dist/catalog/propose.js +204 -0
  43. package/dist/catalog/registry.js +3 -1
  44. package/dist/catalog/schema.js +2 -0
  45. package/dist/catalog/store.js +39 -0
  46. package/dist/edge/autostart/native.js +3 -5
  47. package/dist/edge/autostart/prompt.js +2 -1
  48. package/dist/edge/autostart/service.js +3 -4
  49. package/dist/edge/cli-args.js +3 -4
  50. package/dist/edge/cli.js +2 -1
  51. package/dist/edge/docker-install/apply.js +11 -11
  52. package/dist/edge/docker-install/run.js +2 -1
  53. package/dist/edge/docker-install/state.js +4 -3
  54. package/dist/edge/docker-install/tree.js +2 -4
  55. package/dist/edge/docker-install/volume.js +3 -2
  56. package/dist/edge/docker.js +5 -4
  57. package/dist/edge/events/hub.js +5 -5
  58. package/dist/edge/http/actions.js +4 -25
  59. package/dist/edge/http/actor.js +5 -3
  60. package/dist/edge/http/app.js +85 -10
  61. package/dist/edge/http/assistant.js +127 -0
  62. package/dist/edge/http/audio-preview.js +4 -11
  63. package/dist/edge/http/auditions.js +1 -1
  64. package/dist/edge/http/autostart.js +5 -5
  65. package/dist/edge/http/backups.js +1 -3
  66. package/dist/edge/http/channel-memory.js +7 -20
  67. package/dist/edge/http/channels.js +13 -26
  68. package/dist/edge/http/checkpoints.js +33 -17
  69. package/dist/edge/http/design-templates.js +167 -0
  70. package/dist/edge/http/designs.js +178 -0
  71. package/dist/edge/http/diagnostics.js +5 -1
  72. package/dist/edge/http/document-themes.js +2 -11
  73. package/dist/edge/http/draft-files.js +1 -3
  74. package/dist/edge/http/draft-problem.js +4 -9
  75. package/dist/edge/http/drafts.js +3 -3
  76. package/dist/edge/http/entries.js +6 -9
  77. package/dist/edge/http/extension-auth.js +27 -0
  78. package/dist/edge/http/external-reviews.js +12 -11
  79. package/dist/edge/http/files.js +5 -11
  80. package/dist/edge/http/folder-location.js +1 -4
  81. package/dist/edge/http/fonts.js +0 -7
  82. package/dist/edge/http/home.js +38 -13
  83. package/dist/edge/http/host-cli.js +18 -8
  84. package/dist/edge/http/ids.js +8 -0
  85. package/dist/edge/http/keywords.js +35 -0
  86. package/dist/edge/http/model-checks.js +132 -0
  87. package/dist/edge/http/narration-peaks.js +5 -13
  88. package/dist/edge/http/onboarding.js +1 -14
  89. package/dist/edge/http/open-folder.js +5 -10
  90. package/dist/edge/http/outputs.js +4 -12
  91. package/dist/edge/http/patch-notes.js +4 -10
  92. package/dist/edge/http/planning.js +2 -6
  93. package/dist/edge/http/problem.js +2 -4
  94. package/dist/edge/http/project-create.js +1 -0
  95. package/dist/edge/http/project-templates.js +29 -17
  96. package/dist/edge/http/projects.js +2 -12
  97. package/dist/edge/http/prompts.js +7 -14
  98. package/dist/edge/http/pronunciations.js +1 -2
  99. package/dist/edge/http/providers.js +39 -24
  100. package/dist/edge/http/refusal.js +29 -0
  101. package/dist/edge/http/remote-access.js +40 -0
  102. package/dist/edge/http/remote-guard.js +71 -0
  103. package/dist/edge/http/request-guard.js +91 -0
  104. package/dist/edge/http/reviews.js +11 -24
  105. package/dist/edge/http/revision-files.js +5 -10
  106. package/dist/edge/http/revisions.js +10 -15
  107. package/dist/edge/http/run-cost.js +1 -2
  108. package/dist/edge/http/same-page.js +80 -0
  109. package/dist/edge/http/schedules.js +59 -32
  110. package/dist/edge/http/settings.js +22 -13
  111. package/dist/edge/http/social.js +282 -0
  112. package/dist/edge/http/staging.js +2 -4
  113. package/dist/edge/http/storage-files.js +5 -7
  114. package/dist/edge/http/storage.js +23 -23
  115. package/dist/edge/http/studio.js +23 -50
  116. package/dist/edge/http/style-preview.js +1 -4
  117. package/dist/edge/http/subtitles.js +3 -17
  118. package/dist/edge/http/template-studio.js +433 -0
  119. package/dist/edge/http/trash.js +10 -11
  120. package/dist/edge/http/tutorial.js +1 -2
  121. package/dist/edge/http/tutorials.js +5 -11
  122. package/dist/edge/http/update.js +7 -13
  123. package/dist/edge/http/waveform.js +3 -6
  124. package/dist/edge/http/youtube-edits.js +2 -3
  125. package/dist/edge/mcp/assistant.js +279 -0
  126. package/dist/edge/mcp/attribution.js +8 -2
  127. package/dist/edge/mcp/call-route.js +11 -2
  128. package/dist/edge/mcp/excluded-routes.js +25 -0
  129. package/dist/edge/mcp/http.js +11 -2
  130. package/dist/edge/mcp/instructions.js +5 -3
  131. package/dist/edge/mcp/library-tools.js +1 -0
  132. package/dist/edge/mcp/route-tool.js +35 -15
  133. package/dist/edge/mcp/server.js +13 -8
  134. package/dist/edge/mcp/tools-channels.js +2 -0
  135. package/dist/edge/mcp/tools-designs.js +165 -0
  136. package/dist/edge/mcp/tools-library.js +14 -2
  137. package/dist/edge/mcp/tools-planning.js +23 -4
  138. package/dist/edge/mcp/tools-projects.js +28 -1
  139. package/dist/edge/mcp/tools-runs.js +14 -1
  140. package/dist/edge/mcp/tools-schedules.js +28 -4
  141. package/dist/edge/mcp/tools-setup.js +61 -3
  142. package/dist/edge/mcp/tools-social.js +46 -0
  143. package/dist/edge/mcp/tools-storage.js +8 -0
  144. package/dist/edge/mcp/tools-template-studio.js +166 -0
  145. package/dist/edge/mcp/tools.js +6 -0
  146. package/dist/edge/remote-access/cli.js +71 -0
  147. package/dist/edge/remote-access/model.js +32 -0
  148. package/dist/edge/remote-access/rules.js +207 -0
  149. package/dist/edge/remote-access/service.js +203 -0
  150. package/dist/edge/remote-access/store.js +44 -0
  151. package/dist/edge/remote-access/views.js +43 -0
  152. package/dist/extension/slopify-studio-chrome.zip +0 -0
  153. package/dist/extension/slopify-studio-firefox.zip +0 -0
  154. package/dist/host-cli/install.js +2 -2
  155. package/dist/host-cli/open-folder.js +2 -2
  156. package/dist/host-cli/paths.js +2 -4
  157. package/dist/host-cli/server.js +4 -4
  158. package/dist/host-cli/service.js +3 -3
  159. package/dist/host-cli/status.js +7 -7
  160. package/dist/kernel/db/migrations/0063-model-checks.sql +20 -0
  161. package/dist/kernel/db/migrations/0064-template-studio.sql +46 -0
  162. package/dist/kernel/db/migrations/0065-promise-and-assistant-reviews.sql +58 -0
  163. package/dist/kernel/db/migrations/0066-assistant.sql +44 -0
  164. package/dist/kernel/db/migrations/0067-autopilot.sql +6 -0
  165. package/dist/kernel/db/migrations/0068-topic-scores.sql +17 -0
  166. package/dist/kernel/db/migrations/0070-designs.sql +53 -0
  167. package/dist/kernel/db/migrations/0071-design-usage.sql +33 -0
  168. package/dist/kernel/db/migrations/0072-design-templates.sql +33 -0
  169. package/dist/kernel/db/migrations/0073-schedule-design-kind.sql +5 -0
  170. package/dist/kernel/db/migrations/0074-template-bakeoffs.sql +44 -0
  171. package/dist/kernel/db/migrations/0075-social-posts.sql +25 -0
  172. package/dist/kernel/db/migrations/0076-channel-social.sql +5 -0
  173. package/dist/kernel/db/migrations/0077-post-prompts.sql +17 -0
  174. package/dist/kernel/db/tx.js +2 -4
  175. package/dist/kernel/errors.js +27 -1
  176. package/dist/kernel/json-answer.js +15 -0
  177. package/dist/kernel/lock.js +4 -9
  178. package/dist/kernel/paths.js +8 -0
  179. package/dist/kernel/ports/assistant-cli.js +4 -0
  180. package/dist/kernel/ports/host-cli.js +3 -0
  181. package/dist/kernel/ports/interest.js +4 -0
  182. package/dist/kernel/ports/languages.js +0 -3
  183. package/dist/kernel/ports/model.js +2 -0
  184. package/dist/kernel/runner/attempt.js +3 -1
  185. package/dist/kernel/runner/audition.js +3 -1
  186. package/dist/kernel/runner/checkpoint-authority.js +2 -1
  187. package/dist/kernel/runner/index.js +8 -10
  188. package/dist/kernel/runner/model-access.js +40 -0
  189. package/dist/kernel/runner/model-check.js +153 -0
  190. package/dist/kernel/runner/providers.js +15 -10
  191. package/dist/kernel/runner/standalone.js +9 -5
  192. package/dist/main.js +334 -19
  193. package/dist/model-checks.js +166 -0
  194. package/dist/patch-notes/3.25.9.md +5 -0
  195. package/dist/patch-notes/4.0.0.md +51 -0
  196. package/dist/patch-notes/index.json +12 -0
  197. package/dist/slices/admission/heads.js +2 -1
  198. package/dist/slices/admission/repo.js +2 -1
  199. package/dist/slices/admission/rules.js +43 -8
  200. package/dist/slices/admission/schema.js +8 -0
  201. package/dist/slices/admission/stage-writes.js +0 -3
  202. package/dist/slices/admission/start.js +48 -31
  203. package/dist/slices/admission/substitute.js +7 -4
  204. package/dist/slices/agents/attention-posts.js +31 -0
  205. package/dist/slices/agents/attention-templates.js +33 -0
  206. package/dist/slices/agents/attention.js +10 -1
  207. package/dist/slices/agents/connection.js +2 -6
  208. package/dist/slices/agents/model.js +6 -3
  209. package/dist/slices/agents/repo.js +15 -7
  210. package/dist/slices/article/continuation.js +0 -63
  211. package/dist/slices/article/plain.js +3 -2
  212. package/dist/slices/article/segments.js +2 -62
  213. package/dist/slices/assistant/access.js +1 -0
  214. package/dist/slices/assistant/asks.js +80 -0
  215. package/dist/slices/assistant/cost.js +54 -0
  216. package/dist/slices/assistant/gate.js +222 -0
  217. package/dist/slices/assistant/model.js +79 -0
  218. package/dist/slices/assistant/names.js +23 -0
  219. package/dist/slices/assistant/prompt.js +74 -0
  220. package/dist/slices/assistant/repo.js +74 -0
  221. package/dist/slices/assistant/tokens.js +18 -0
  222. package/dist/slices/assistant/turn.js +342 -0
  223. package/dist/slices/autopilot/buffer.js +76 -0
  224. package/dist/slices/autopilot/model.js +14 -0
  225. package/dist/slices/autopilot/notify.js +27 -0
  226. package/dist/slices/autopilot/repo.js +34 -0
  227. package/dist/slices/autopilot/signals.js +194 -0
  228. package/dist/slices/autopilot/warnings.js +85 -0
  229. package/dist/slices/autopilot/weekly.js +100 -0
  230. package/dist/slices/backups/files.js +2 -6
  231. package/dist/slices/backups/repo.js +3 -15
  232. package/dist/slices/backups/service.js +4 -3
  233. package/dist/slices/channels/cast-images.js +2 -1
  234. package/dist/slices/channels/model.js +0 -1
  235. package/dist/slices/channels/rebrand.js +0 -3
  236. package/dist/slices/channels/repo.js +4 -0
  237. package/dist/slices/channels/schema.js +6 -0
  238. package/dist/slices/channels/service.js +10 -0
  239. package/dist/slices/channels/videos.js +2 -14
  240. package/dist/slices/checkpoints/index.js +0 -1
  241. package/dist/slices/control/providers.js +0 -26
  242. package/dist/slices/designs/artwork.js +66 -0
  243. package/dist/slices/designs/budget.js +23 -0
  244. package/dist/slices/designs/calls.js +124 -0
  245. package/dist/slices/designs/check-context.js +38 -0
  246. package/dist/slices/designs/check-text.js +43 -0
  247. package/dist/slices/designs/fields.js +88 -0
  248. package/dist/slices/designs/frames.js +34 -0
  249. package/dist/slices/designs/gate.js +128 -0
  250. package/dist/slices/designs/layout.js +293 -0
  251. package/dist/slices/designs/model.js +35 -0
  252. package/dist/slices/designs/pdf-job.js +23 -0
  253. package/dist/slices/designs/pdf-pages.js +30 -0
  254. package/dist/slices/designs/pdf-runner.js +68 -0
  255. package/dist/slices/designs/pdf-worker.js +24 -0
  256. package/dist/slices/designs/picture-size.js +79 -0
  257. package/dist/slices/designs/pictures.js +55 -0
  258. package/dist/slices/designs/quote.js +24 -0
  259. package/dist/slices/designs/render-args.js +36 -0
  260. package/dist/slices/designs/render-input.js +59 -0
  261. package/dist/slices/designs/render.js +215 -0
  262. package/dist/slices/designs/repo.js +101 -0
  263. package/dist/slices/designs/requests.js +80 -0
  264. package/dist/slices/designs/runner.js +239 -0
  265. package/dist/slices/designs/sample-repo.js +91 -0
  266. package/dist/slices/designs/sample.js +222 -0
  267. package/dist/slices/designs/schema.js +87 -0
  268. package/dist/slices/designs/sections.js +44 -0
  269. package/dist/slices/designs/service.js +233 -0
  270. package/dist/slices/designs/steps-repo.js +102 -0
  271. package/dist/slices/designs/studio-kind.js +39 -0
  272. package/dist/slices/designs/template-checks.js +44 -0
  273. package/dist/slices/designs/template-make.js +118 -0
  274. package/dist/slices/designs/templates.js +218 -0
  275. package/dist/slices/document/fonts.js +2 -1
  276. package/dist/slices/episodes/summarize.js +5 -1
  277. package/dist/slices/estimate/index.js +45 -7
  278. package/dist/slices/fonts/catalog.js +3 -2
  279. package/dist/slices/fonts/discovery.js +3 -2
  280. package/dist/slices/fonts/files.js +2 -6
  281. package/dist/slices/fonts/index.js +0 -1
  282. package/dist/slices/fonts/sfnt.js +45 -10
  283. package/dist/slices/images/appearance.js +6 -15
  284. package/dist/slices/images/scenes.js +100 -27
  285. package/dist/slices/keywords/digest.js +84 -0
  286. package/dist/slices/keywords/facts.js +92 -0
  287. package/dist/slices/keywords/fill.js +80 -0
  288. package/dist/slices/keywords/model.js +75 -0
  289. package/dist/slices/keywords/rules.js +187 -0
  290. package/dist/slices/keywords/schema.js +28 -0
  291. package/dist/slices/keywords/specs.js +260 -0
  292. package/dist/slices/keywords/values.js +74 -0
  293. package/dist/slices/library/lint.js +29 -12
  294. package/dist/slices/library/model.js +2 -0
  295. package/dist/slices/library/photorealistic.js +4 -12
  296. package/dist/slices/library/rename.js +9 -7
  297. package/dist/slices/library/save.js +14 -10
  298. package/dist/slices/library/slots.js +37 -11
  299. package/dist/slices/library/transfer.js +2 -1
  300. package/dist/slices/library/used-by.js +25 -10
  301. package/dist/slices/loudness/loudnorm.js +51 -11
  302. package/dist/slices/model-checks/cost.js +61 -0
  303. package/dist/slices/model-checks/listing.js +124 -0
  304. package/dist/slices/model-checks/model.js +20 -0
  305. package/dist/slices/model-checks/record.js +32 -0
  306. package/dist/slices/model-checks/repo.js +120 -0
  307. package/dist/slices/model-checks/rules.js +112 -0
  308. package/dist/slices/model-checks/run.js +143 -0
  309. package/dist/slices/model-checks/service.js +198 -0
  310. package/dist/slices/model-upkeep/switch.js +70 -25
  311. package/dist/slices/model-upkeep/usage.js +26 -11
  312. package/dist/slices/narration/breaths.js +2 -1
  313. package/dist/slices/narration/clean-pieces.js +2 -1
  314. package/dist/slices/narration/concat.js +2 -1
  315. package/dist/slices/narration/peaks.js +3 -4
  316. package/dist/slices/narration/pronunciation-chunks.js +4 -3
  317. package/dist/slices/narration/pronunciation.js +3 -2
  318. package/dist/slices/narration/steering.js +4 -3
  319. package/dist/slices/notifications/notifier.js +4 -3
  320. package/dist/slices/notifications/rules.js +5 -2
  321. package/dist/slices/notifications/settings.js +4 -12
  322. package/dist/slices/notifications/subject.js +2 -1
  323. package/dist/slices/onboarding/copy.js +2 -1
  324. package/dist/slices/onboarding/first-run.js +3 -2
  325. package/dist/slices/onboarding/full-video.js +3 -2
  326. package/dist/slices/onboarding/install.js +4 -3
  327. package/dist/slices/onboarding/model.js +0 -16
  328. package/dist/slices/onboarding/packs.js +22 -22
  329. package/dist/slices/onboarding/quick-short.js +2 -1
  330. package/dist/slices/onboarding/sample.js +0 -1
  331. package/dist/slices/onboarding/state.js +2 -11
  332. package/dist/slices/onboarding/template.js +2 -1
  333. package/dist/slices/patch-notes/library.js +3 -2
  334. package/dist/slices/play-drafts/convert.js +4 -0
  335. package/dist/slices/play-drafts/review-inputs.js +8 -2
  336. package/dist/slices/play-drafts/review.js +5 -4
  337. package/dist/slices/play-drafts/schema.js +4 -0
  338. package/dist/slices/project-templates/from-project.js +9 -2
  339. package/dist/slices/project-templates/repo.js +6 -1
  340. package/dist/slices/project-templates/setup.js +8 -0
  341. package/dist/slices/project-templates/stand-ins.js +22 -0
  342. package/dist/slices/rebuild/activity.js +2 -0
  343. package/dist/slices/rebuild/admission-repo.js +3 -2
  344. package/dist/slices/rebuild/narration-history.js +4 -5
  345. package/dist/slices/rebuild/narration-retry.js +2 -1
  346. package/dist/slices/rebuild/preview-details.js +27 -9
  347. package/dist/slices/rebuild/preview-plan.js +3 -8
  348. package/dist/slices/rebuild/recipe-build.js +8 -3
  349. package/dist/slices/rebuild/recipe-describe.js +3 -3
  350. package/dist/slices/rebuild/recipe-digest.js +58 -0
  351. package/dist/slices/rebuild/recipe-input-schema.js +10 -0
  352. package/dist/slices/rebuild/recipe-keywords.js +71 -0
  353. package/dist/slices/rebuild/recipe-legacy.js +5 -1
  354. package/dist/slices/rebuild/recipe-model.js +4 -0
  355. package/dist/slices/rebuild/recipe-narration-text.js +2 -1
  356. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  357. package/dist/slices/rebuild/recipe-reference.js +8 -4
  358. package/dist/slices/rebuild/recipe-reviews.js +27 -7
  359. package/dist/slices/rebuild/recipe-save.js +6 -3
  360. package/dist/slices/rebuild/recipe-scenes.js +40 -11
  361. package/dist/slices/rebuild/recipe-shorts.js +8 -8
  362. package/dist/slices/rebuild/recipe-social.js +46 -0
  363. package/dist/slices/rebuild/recipe-text.js +64 -20
  364. package/dist/slices/rebuild/recipe-validation.js +98 -28
  365. package/dist/slices/rebuild/recipe-visual.js +55 -16
  366. package/dist/slices/rebuild/recipe-work.js +15 -3
  367. package/dist/slices/rebuild/recipe-youtube.js +4 -3
  368. package/dist/slices/rebuild/recovery-selection.js +3 -1
  369. package/dist/slices/rebuild/recovery.js +3 -2
  370. package/dist/slices/rebuild/repo.js +4 -3
  371. package/dist/slices/rebuild/review-promise.js +25 -0
  372. package/dist/slices/rebuild/review-redo.js +2 -1
  373. package/dist/slices/rebuild/runtime-admission.js +4 -3
  374. package/dist/slices/rebuild/runtime-agent-review.js +11 -5
  375. package/dist/slices/rebuild/runtime-animate.js +9 -8
  376. package/dist/slices/rebuild/runtime-article.js +3 -2
  377. package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
  378. package/dist/slices/rebuild/runtime-document.js +8 -11
  379. package/dist/slices/rebuild/runtime-export-bed.js +6 -11
  380. package/dist/slices/rebuild/runtime-export-edit.js +6 -15
  381. package/dist/slices/rebuild/runtime-export-inputs.js +7 -7
  382. package/dist/slices/rebuild/runtime-export-short.js +8 -4
  383. package/dist/slices/rebuild/runtime-export.js +34 -12
  384. package/dist/slices/rebuild/runtime-figure-card.js +2 -1
  385. package/dist/slices/rebuild/runtime-image.js +4 -9
  386. package/dist/slices/rebuild/runtime-instructions.js +2 -0
  387. package/dist/slices/rebuild/runtime-keywords.js +59 -0
  388. package/dist/slices/rebuild/runtime-lines.js +2 -11
  389. package/dist/slices/rebuild/runtime-local.js +14 -20
  390. package/dist/slices/rebuild/runtime-narration-publication.js +2 -5
  391. package/dist/slices/rebuild/runtime-narration-reuse.js +9 -13
  392. package/dist/slices/rebuild/runtime-narration-text.js +10 -15
  393. package/dist/slices/rebuild/runtime-pictures.js +2 -4
  394. package/dist/slices/rebuild/runtime-piece-label.js +10 -6
  395. package/dist/slices/rebuild/runtime-plan.js +31 -5
  396. package/dist/slices/rebuild/runtime-provider.js +60 -19
  397. package/dist/slices/rebuild/runtime-publication.js +10 -9
  398. package/dist/slices/rebuild/runtime-review.js +33 -28
  399. package/dist/slices/rebuild/runtime-run.js +4 -1
  400. package/dist/slices/rebuild/runtime-shorts.js +34 -50
  401. package/dist/slices/rebuild/runtime-store.js +3 -8
  402. package/dist/slices/rebuild/runtime-subtitles.js +16 -14
  403. package/dist/slices/rebuild/runtime-voices.js +8 -15
  404. package/dist/slices/rebuild/runtime-youtube.js +11 -29
  405. package/dist/slices/rebuild/transition-repo.js +6 -5
  406. package/dist/slices/rebuild/work-records.js +6 -0
  407. package/dist/slices/reruns/cascade.js +1 -2
  408. package/dist/slices/reruns/index.js +6 -9
  409. package/dist/slices/reviews/external-model.js +3 -0
  410. package/dist/slices/reviews/external-repo.js +2 -1
  411. package/dist/slices/reviews/external-rows.js +2 -3
  412. package/dist/slices/reviews/model.js +26 -8
  413. package/dist/slices/reviews/repo.js +2 -1
  414. package/dist/slices/reviews/rules.js +1 -0
  415. package/dist/slices/reviews/view.js +5 -2
  416. package/dist/slices/revisions/downloads.js +4 -5
  417. package/dist/slices/revisions/mutation-assets.js +7 -4
  418. package/dist/slices/revisions/mutation-cues.js +2 -5
  419. package/dist/slices/revisions/mutations.js +0 -1
  420. package/dist/slices/revisions/repo.js +3 -1
  421. package/dist/slices/revisions/schema.js +1 -0
  422. package/dist/slices/revisions/view.js +20 -0
  423. package/dist/slices/run-cost/limits.js +5 -2
  424. package/dist/slices/run-cost/meter.js +12 -5
  425. package/dist/slices/run-cost/panel.js +8 -74
  426. package/dist/slices/run-cost/plan-readings.js +127 -0
  427. package/dist/slices/run-cost/plan-use.js +169 -0
  428. package/dist/slices/run-cost/pricing.js +3 -3
  429. package/dist/slices/run-cost/week.js +32 -4
  430. package/dist/slices/schedules/agenda.js +11 -2
  431. package/dist/slices/schedules/by-date.js +42 -0
  432. package/dist/slices/schedules/calendar.js +5 -0
  433. package/dist/slices/schedules/design-agenda.js +36 -0
  434. package/dist/slices/schedules/design-check.js +41 -0
  435. package/dist/slices/schedules/design-run.js +126 -0
  436. package/dist/slices/schedules/next-run.js +62 -0
  437. package/dist/slices/schedules/prepare.js +18 -2
  438. package/dist/slices/schedules/repo.js +24 -16
  439. package/dist/slices/schedules/rescore.js +35 -0
  440. package/dist/slices/schedules/scheduler.js +13 -3
  441. package/dist/slices/schedules/schema.js +54 -3
  442. package/dist/slices/schedules/score.js +107 -0
  443. package/dist/slices/schedules/service.js +57 -24
  444. package/dist/slices/schedules/topic-list.js +15 -0
  445. package/dist/slices/schedules/topic-scores.js +104 -0
  446. package/dist/slices/schedules/topics.js +48 -36
  447. package/dist/slices/settings/cli-paths.js +4 -6
  448. package/dist/slices/settings/first-run.js +2 -11
  449. package/dist/slices/settings/model-reach.js +3 -17
  450. package/dist/slices/settings/model.js +10 -0
  451. package/dist/slices/settings/playback.js +33 -22
  452. package/dist/slices/settings/probe-read.js +16 -0
  453. package/dist/slices/settings/repo.js +33 -0
  454. package/dist/slices/settings/tutorial.js +2 -2
  455. package/dist/slices/settings/voices.js +4 -12
  456. package/dist/slices/shorts/pick.js +2 -15
  457. package/dist/slices/shorts/prompts.js +2 -13
  458. package/dist/slices/shorts/render.js +3 -0
  459. package/dist/slices/social/channel.js +44 -0
  460. package/dist/slices/social/defaults.js +17 -0
  461. package/dist/slices/social/due.js +36 -0
  462. package/dist/slices/social/extension.js +18 -0
  463. package/dist/slices/social/fit.js +115 -0
  464. package/dist/slices/social/limits.js +186 -0
  465. package/dist/slices/social/links.js +21 -0
  466. package/dist/slices/social/media.js +131 -0
  467. package/dist/slices/social/model.js +75 -0
  468. package/dist/slices/social/notice.js +19 -0
  469. package/dist/slices/social/pack.js +176 -0
  470. package/dist/slices/social/post-prompt.js +55 -0
  471. package/dist/slices/social/queue.js +31 -0
  472. package/dist/slices/social/repo.js +133 -0
  473. package/dist/slices/social/reshape.js +97 -0
  474. package/dist/slices/social/rules.js +5 -0
  475. package/dist/slices/social/schema.js +70 -0
  476. package/dist/slices/social/serve.js +51 -0
  477. package/dist/slices/social/service.js +128 -0
  478. package/dist/slices/social/settings.js +21 -0
  479. package/dist/slices/social/sources.js +114 -0
  480. package/dist/slices/social/state.js +110 -0
  481. package/dist/slices/social/text.js +149 -0
  482. package/dist/slices/storage/arrange.js +3 -1
  483. package/dist/slices/storage/assets.js +8 -10
  484. package/dist/slices/storage/backup-designs.js +74 -0
  485. package/dist/slices/storage/backup-export.js +8 -3
  486. package/dist/slices/storage/backup-format.js +10 -0
  487. package/dist/slices/storage/backup-import.js +20 -2
  488. package/dist/slices/storage/delete-project.js +6 -5
  489. package/dist/slices/storage/files-location.js +6 -8
  490. package/dist/slices/storage/layout.js +13 -2
  491. package/dist/slices/storage/model.js +3 -0
  492. package/dist/slices/storage/portable.js +16 -11
  493. package/dist/slices/storage/schema.js +2 -0
  494. package/dist/slices/storage/staging-refs.js +2 -1
  495. package/dist/slices/storage/staging.js +6 -9
  496. package/dist/slices/storage/trim.js +10 -3
  497. package/dist/slices/studio/calendar.js +4 -1
  498. package/dist/slices/studio/pick.js +2 -11
  499. package/dist/slices/studio/plan.js +6 -15
  500. package/dist/slices/studio/queue.js +3 -14
  501. package/dist/slices/studio/releases.js +26 -0
  502. package/dist/slices/studio/settings.js +14 -51
  503. package/dist/slices/studio/videos.js +12 -0
  504. package/dist/slices/style-preview/render.js +34 -13
  505. package/dist/slices/style-preview/schema.js +13 -0
  506. package/dist/slices/style-preview/service.js +2 -1
  507. package/dist/slices/style-preview/settings.js +33 -5
  508. package/dist/slices/telemetry/collector-client.js +2 -4
  509. package/dist/slices/telemetry/flush.js +2 -4
  510. package/dist/slices/telemetry/model.js +0 -7
  511. package/dist/slices/telemetry/record.js +10 -3
  512. package/dist/slices/template-studio/anatomy-view.js +118 -0
  513. package/dist/slices/template-studio/anatomy.js +73 -0
  514. package/dist/slices/template-studio/bakeoff-make.js +225 -0
  515. package/dist/slices/template-studio/bakeoff-repo.js +83 -0
  516. package/dist/slices/template-studio/bakeoff.js +333 -0
  517. package/dist/slices/template-studio/budget.js +103 -0
  518. package/dist/slices/template-studio/checks.js +273 -0
  519. package/dist/slices/template-studio/document-sample.js +56 -0
  520. package/dist/slices/template-studio/edit.js +15 -0
  521. package/dist/slices/template-studio/for-all.js +161 -0
  522. package/dist/slices/template-studio/free-video.js +31 -0
  523. package/dist/slices/template-studio/free.js +176 -0
  524. package/dist/slices/template-studio/gate.js +40 -0
  525. package/dist/slices/template-studio/hidden.js +20 -0
  526. package/dist/slices/template-studio/jobs.js +79 -0
  527. package/dist/slices/template-studio/loudness.js +111 -0
  528. package/dist/slices/template-studio/make.js +40 -0
  529. package/dist/slices/template-studio/model.js +54 -0
  530. package/dist/slices/template-studio/pack-import.js +253 -0
  531. package/dist/slices/template-studio/pack-samples.js +60 -0
  532. package/dist/slices/template-studio/pack.js +205 -0
  533. package/dist/slices/template-studio/paid-view.js +66 -0
  534. package/dist/slices/template-studio/paid.js +348 -0
  535. package/dist/slices/template-studio/quote.js +148 -0
  536. package/dist/slices/template-studio/ready.js +104 -0
  537. package/dist/slices/template-studio/repo.js +134 -0
  538. package/dist/slices/template-studio/reviews-path.js +58 -0
  539. package/dist/slices/template-studio/runner.js +84 -0
  540. package/dist/slices/template-studio/save.js +236 -0
  541. package/dist/slices/template-studio/say-it-as.js +248 -0
  542. package/dist/slices/template-studio/schedule-gate.js +34 -0
  543. package/dist/slices/template-studio/schedule-price.js +30 -0
  544. package/dist/slices/template-studio/schedule-sample.js +87 -0
  545. package/dist/slices/template-studio/schema.js +218 -0
  546. package/dist/slices/template-studio/scope.js +124 -0
  547. package/dist/slices/template-studio/sections.js +176 -0
  548. package/dist/slices/template-studio/service.js +173 -0
  549. package/dist/slices/template-studio/status.js +174 -0
  550. package/dist/slices/template-studio/topic.js +223 -0
  551. package/dist/slices/trash/designs.js +76 -0
  552. package/dist/slices/trash/model.js +13 -4
  553. package/dist/slices/trash/service.js +52 -2
  554. package/dist/slices/tutorials/library.js +3 -2
  555. package/dist/slices/video/ass-text.js +17 -0
  556. package/dist/slices/video/audio-export.js +2 -64
  557. package/dist/slices/video/encoding.js +160 -0
  558. package/dist/slices/video/ffmpeg.js +23 -21
  559. package/dist/slices/video/figure-card.js +1 -15
  560. package/dist/slices/video/slideshow.js +43 -18
  561. package/dist/slices/voices/palette.js +0 -3
  562. package/dist/slices/youtube/answer.js +2 -15
  563. package/dist/slices/youtube/edits-repo.js +2 -11
  564. package/dist/tutorials/Agents-MCP.md +2 -1
  565. package/dist/tutorials/Assistant.md +42 -0
  566. package/dist/tutorials/Channels.md +2 -0
  567. package/dist/tutorials/Home-and-Projects.md +19 -0
  568. package/dist/tutorials/Install.md +1 -0
  569. package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
  570. package/dist/tutorials/Models.md +33 -1
  571. package/dist/tutorials/Notifications.md +6 -0
  572. package/dist/tutorials/Play-Images.md +4 -0
  573. package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
  574. package/dist/tutorials/Prompts.md +10 -6
  575. package/dist/tutorials/Remote-Access.md +63 -0
  576. package/dist/tutorials/Reviews-and-Checkpoints.md +13 -2
  577. package/dist/tutorials/Schedules.md +21 -1
  578. package/dist/tutorials/Settings-Reference.md +50 -8
  579. package/dist/tutorials/Template-Studio.md +125 -0
  580. package/dist/tutorials/Templates.md +3 -0
  581. package/dist/tutorials/_Sidebar.md +5 -0
  582. package/dist/updater/work-in-progress.js +42 -12
  583. package/dist/web/assets/index-CkAI_2E7.css +1 -0
  584. package/dist/web/assets/index-DcD0cdfo.js +334 -0
  585. package/dist/web/assets/{pdf-T7MCwjIv.js → pdf-TODRwF31.js} +1 -1
  586. package/dist/web/index.html +2 -2
  587. package/package.json +5 -1
  588. package/dist/slices/article/provided-entries.js +0 -66
  589. package/dist/slices/article/run.js +0 -145
  590. package/dist/slices/images/run.js +0 -234
  591. package/dist/slices/narration/legacy-plan.js +0 -58
  592. package/dist/slices/narration/run.js +0 -270
  593. package/dist/slices/rebuild/runtime-actions.js +0 -90
  594. package/dist/slices/research/run.js +0 -224
  595. package/dist/slices/subtitles/prepare.js +0 -194
  596. package/dist/slices/subtitles/transcript.js +0 -34
  597. package/dist/slices/thumbnail/run.js +0 -219
  598. package/dist/slices/video/audio-inputs.js +0 -33
  599. package/dist/slices/video/reuse-audio.js +0 -62
  600. package/dist/slices/video/run.js +0 -78
  601. package/dist/slices/video/write-export.js +0 -158
  602. package/dist/slices/video/write-subtitles.js +0 -59
  603. package/dist/web/assets/index-BadsoORk.css +0 -1
  604. package/dist/web/assets/index-D8LqNsCl.js +0 -326
@@ -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
@@ -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) {
@@ -1,6 +1,6 @@
1
1
  import { randomBytes, timingSafeEqual } from "node:crypto";
2
2
  import { z } from "zod";
3
- import { readSetting, writeSetting } from "../settings/repo.js";
3
+ import { deleteSetting, readJsonSetting, stringListSchema, writeSetting, } from "../settings/repo.js";
4
4
  import { playlistUses, studioPlaylistMax, } from "./model.js";
5
5
  // Rows of the key/value `settings` table. The playlists travel with a backup; the
6
6
  // pairing stays on this machine (`slices/storage/portable.ts`): its token is what lets the
@@ -58,20 +58,12 @@ export function readChannelPlaylists(db) {
58
58
  return out;
59
59
  }
60
60
  function storedPlaylists(db, key) {
61
- const stored = readSetting(db, key);
62
- if (stored === undefined)
61
+ const stored = readJsonSetting(db, key, storedPlaylistsSchema, null);
62
+ if (stored === null)
63
63
  return null;
64
- try {
65
- const parsed = storedPlaylistsSchema.safeParse(JSON.parse(stored));
66
- if (!parsed.success)
67
- return null;
68
- if (typeof parsed.data === "string")
69
- return parsed.data.trim() === "" ? null : [{ name: parsed.data.trim(), byDefault: true }];
70
- return parsed.data.length === 0 ? null : parsed.data;
71
- }
72
- catch {
73
- return null;
74
- }
64
+ if (typeof stored === "string")
65
+ return stored.trim() === "" ? null : [{ name: stored.trim(), byDefault: true }];
66
+ return stored.length === 0 ? null : stored;
75
67
  }
76
68
  // Why a list can't be saved, in words for Settings → YouTube Studio; undefined when it can.
77
69
  export function studioPlaylistsProblem(list) {
@@ -100,7 +92,7 @@ export function saveStudioPlaylists(db, list, channelId) {
100
92
  .filter((one) => one.name !== "");
101
93
  const key = playlistKey(channelId);
102
94
  if (value.length === 0) {
103
- db.prepare("DELETE FROM settings WHERE key = ?").run(key);
95
+ deleteSetting(db, key);
104
96
  return [];
105
97
  }
106
98
  writeSetting(db, key, JSON.stringify(value));
@@ -111,16 +103,7 @@ export function saveStudioPlaylists(db, list, channelId) {
111
103
  export const studioProjectPlaylistsKey = "studio.projectPlaylists";
112
104
  export const storedProjectPlaylistsSchema = z.record(z.string().max(64), z.array(z.string().max(studioPlaylistMax)).max(studioPlaylistsMax));
113
105
  function projectChoices(db) {
114
- const stored = readSetting(db, studioProjectPlaylistsKey);
115
- if (stored === undefined)
116
- return {};
117
- try {
118
- const parsed = storedProjectPlaylistsSchema.safeParse(JSON.parse(stored));
119
- return parsed.success ? parsed.data : {};
120
- }
121
- catch {
122
- return {};
123
- }
106
+ return readJsonSetting(db, studioProjectPlaylistsKey, storedProjectPlaylistsSchema, {});
124
107
  }
125
108
  // The channel's playlists, each ticked as this project goes into it: the project's own choice
126
109
  // when it made one (a playlist the channel no longer lists is dropped), else the defaults.
@@ -139,7 +122,7 @@ export function saveProjectPlaylists(db, projectId, names) {
139
122
  const { [projectId]: _previous, ...others } = projectChoices(db);
140
123
  const next = names === null ? others : { ...others, [projectId]: [...names] };
141
124
  if (Object.keys(next).length === 0)
142
- db.prepare("DELETE FROM settings WHERE key = ?").run(studioProjectPlaylistsKey);
125
+ deleteSetting(db, studioProjectPlaylistsKey);
143
126
  else
144
127
  writeSetting(db, studioProjectPlaylistsKey, JSON.stringify(next));
145
128
  }
@@ -150,18 +133,8 @@ export const studioRealFootageKey = "studio.realFootage";
150
133
  // "on" or "off": whether the extension posts and pins each video's comment once it is public.
151
134
  export const autoCommentKey = "studio.autoComment";
152
135
  function realFootageIds(db) {
153
- const stored = readSetting(db, studioRealFootageKey);
154
- if (stored === undefined)
155
- return [];
156
- try {
157
- const value = JSON.parse(stored);
158
- return Array.isArray(value)
159
- ? value.filter((one) => typeof one === "string")
160
- : [];
161
- }
162
- catch {
163
- return [];
164
- }
136
+ const value = readJsonSetting(db, studioRealFootageKey, stringListSchema, []);
137
+ return value;
165
138
  }
166
139
  export function readRealFootage(db, projectId) {
167
140
  return realFootageIds(db).includes(projectId);
@@ -170,25 +143,15 @@ export function saveRealFootage(db, projectId, on) {
170
143
  const others = realFootageIds(db).filter((one) => one !== projectId);
171
144
  const next = on ? [...others, projectId].sort() : others;
172
145
  if (next.length === 0)
173
- db.prepare("DELETE FROM settings WHERE key = ?").run(studioRealFootageKey);
146
+ deleteSetting(db, studioRealFootageKey);
174
147
  else
175
148
  writeSetting(db, studioRealFootageKey, JSON.stringify(next));
176
149
  }
177
150
  // The pairing token, made on first read. One per install: a new one (Settings → YouTube
178
151
  // Studio → New pairing token) forgets the extension that used the old one.
179
152
  export function studioPairing(db) {
180
- const stored = readSetting(db, studioPairingKey);
181
- if (stored !== undefined) {
182
- try {
183
- const parsed = pairingSchema.safeParse(JSON.parse(stored));
184
- if (parsed.success)
185
- return parsed.data;
186
- }
187
- catch {
188
- // A damaged row is replaced below.
189
- }
190
- }
191
- return resetStudioPairing(db);
153
+ // A damaged row is replaced.
154
+ return readJsonSetting(db, studioPairingKey, pairingSchema, undefined) ?? resetStudioPairing(db);
192
155
  }
193
156
  // What is waiting to be filled in Studio is kept per pairing (`queue.ts`): one row,
194
157
  // `studio.fillQueue.<token hash>`, so a new token starts with nothing waiting.
@@ -63,6 +63,18 @@ export function videoOf(db, projectId, short) {
63
63
  .get(projectId, slot(short));
64
64
  return row === undefined ? undefined : rowOf(row);
65
65
  }
66
+ // What the extension reported after setting up Studio's A/B Testing on the video's Details page,
67
+ // asked for from On YouTube or its popup: "started" once filled (the person presses Set test),
68
+ // "failed" with its reason. A failed later attempt keeps a test already set up ("started"), so
69
+ // its result is still read. False when the project has no video recorded for that upload.
70
+ export function setAbState(db, projectId, short, state, message, at) {
71
+ return (db
72
+ .prepare(`UPDATE youtube_videos SET
73
+ ab_state=CASE WHEN ?='failed' AND ab_state='started' THEN 'started' ELSE ? END,
74
+ ab_message=?, ab_at=?
75
+ WHERE project_id=? AND short=?`)
76
+ .run(state, state, message, at, projectId, slot(short)).changes > 0);
77
+ }
66
78
  export function setTaskState(db, task, projectId, short, state, message) {
67
79
  const column = task === "finish" ? "finish" : "comment";
68
80
  db.prepare(`UPDATE youtube_videos SET ${column}_state=?, ${column}_message=? WHERE project_id=? AND short=?`).run(state, message, projectId, slot(short));
@@ -1,5 +1,6 @@
1
1
  import { copyFileSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
+ import { errorText } from "../../kernel/errors.js";
3
4
  import { defaultMotionStyle, defaultZoomPercent } from "../admission/rules.js";
4
5
  import { isMissingFont, resolveBoldFont, resolveFont } from "../fonts/index.js";
5
6
  import { renderShort } from "../shorts/render.js";
@@ -9,7 +10,7 @@ import { planRender } from "../video/plan.js";
9
10
  import { renderSlideshow } from "../video/slideshow.js";
10
11
  import { bundledSampleAssets, sampleNarrationSeconds } from "./narration.js";
11
12
  import { stylePreviewSeconds } from "./schema.js";
12
- import { previewCues, previewWords } from "./settings.js";
13
+ import { previewCues, previewShots, previewWords } from "./settings.js";
13
14
  export class StylePreviewError extends Error {
14
15
  reason;
15
16
  constructor(reason, message) {
@@ -25,7 +26,6 @@ const previewFrame = {
25
26
  "9:16": { width: 270, height: 480 },
26
27
  "1:1": { width: 360, height: 360 },
27
28
  };
28
- const shotSeconds = stylePreviewSeconds / 3;
29
29
  const chapterTitle = "Chapter one";
30
30
  export function ffmpegStylePreview(deps) {
31
31
  const assets = deps.assets ?? bundledSampleAssets();
@@ -33,25 +33,32 @@ export function ffmpegStylePreview(deps) {
33
33
  try {
34
34
  // A real picture is shown for all three shots: the motion, the Look and the captions read
35
35
  // on it, and the transition and chapter card still show at the cuts.
36
- const images = picture !== undefined
36
+ const stills = picture !== undefined
37
37
  ? [picture, picture, picture]
38
38
  : settings.format === "9:16"
39
39
  ? assets.tall
40
40
  : assets.wide;
41
+ // A longer preview shows the stills again in turn.
42
+ const { shots } = previewShots(settings);
43
+ const images = settings.short !== undefined
44
+ ? stills
45
+ : Array.from({ length: shots }, (_, index) => stills[index % stills.length] ?? "");
46
+ const machine = await deps.machine?.();
41
47
  if (settings.short !== undefined)
42
- await renderShortPreview(deps, settings, settings.short, assets, images, output, signal);
48
+ await renderShortPreview(deps, settings, settings.short, assets, images, output, signal, machine);
43
49
  else
44
- await renderPreview(deps, settings, assets, images, output, signal);
50
+ await renderPreview(deps, settings, assets, images, output, signal, machine);
45
51
  }
46
52
  catch (error) {
47
53
  throw explained(deps.log, error);
48
54
  }
49
55
  };
50
56
  }
51
- async function renderPreview(deps, settings, assets, images, output, signal) {
57
+ async function renderPreview(deps, settings, assets, images, output, signal, machine) {
52
58
  mkdirSync(deps.dir, { recursive: true, mode: 0o700 });
53
59
  const workspace = mkdtempSync(join(deps.dir, "work-"));
54
60
  try {
61
+ const { shotSeconds } = previewShots(settings);
55
62
  const cardFont = settings.chapterCard === null
56
63
  ? undefined
57
64
  : await resolveFont(deps.paths, settings.chapterCard.fontId);
@@ -60,8 +67,8 @@ async function renderPreview(deps, settings, assets, images, output, signal) {
60
67
  gapSeconds: 0,
61
68
  edgeSeconds: 0,
62
69
  imageSeconds: shotSeconds,
63
- zoomPercent: defaultZoomPercent,
64
- motionStyle: defaultMotionStyle,
70
+ zoomPercent: settings.timing?.zoomPercent ?? defaultZoomPercent,
71
+ motionStyle: settings.timing?.motionStyle ?? defaultMotionStyle,
65
72
  images,
66
73
  output,
67
74
  edit: {
@@ -81,14 +88,14 @@ async function renderPreview(deps, settings, assets, images, output, signal) {
81
88
  const edit = {
82
89
  ...plan.editList,
83
90
  ...previewFrame[settings.format],
84
- audio: [{ kind: "body", path: assets.narration, seconds: plan.totalSeconds }],
91
+ audio: narrationFor(assets.narration, plan.totalSeconds),
85
92
  };
86
93
  const captions = settings.captions;
87
94
  if (captions !== null) {
88
95
  const font = await resolveFont(deps.paths, captions.fontId);
89
96
  mkdirSync(join(workspace, "fonts"), { mode: 0o700 });
90
97
  copyFileSync(font.path, join(workspace, "fonts", `selected${font.extension}`));
91
- writeFileSync(join(workspace, "subtitles.ass"), serializeAss(previewCues(captions.text), {
98
+ writeFileSync(join(workspace, "subtitles.ass"), serializeAss(previewCues(captions.text, plan.totalSeconds), {
92
99
  ...subtitleFrame(settings.format),
93
100
  fontName: font.assName,
94
101
  fontSize: captions.fontSize,
@@ -105,6 +112,7 @@ async function renderPreview(deps, settings, assets, images, output, signal) {
105
112
  signal,
106
113
  log: deps.log,
107
114
  onProgress: () => { },
115
+ machine,
108
116
  });
109
117
  }
110
118
  finally {
@@ -113,7 +121,7 @@ async function renderPreview(deps, settings, assets, images, output, signal) {
113
121
  }
114
122
  // The Shorts layout, drawn at the preview's size: the same audio cut, image timing, captions
115
123
  // and headline a short gets, from the sample narration.
116
- async function renderShortPreview(deps, settings, short, assets, images, output, signal) {
124
+ async function renderShortPreview(deps, settings, short, assets, images, output, signal, machine) {
117
125
  mkdirSync(deps.dir, { recursive: true, mode: 0o700 });
118
126
  await renderShort({
119
127
  bin: deps.ffmpeg,
@@ -121,7 +129,7 @@ async function renderShortPreview(deps, settings, short, assets, images, output,
121
129
  start: 0,
122
130
  end: sampleNarrationSeconds,
123
131
  images,
124
- imageSeconds: shotSeconds,
132
+ imageSeconds: stylePreviewSeconds / 3,
125
133
  motionStyle: defaultMotionStyle,
126
134
  zoomPercent: defaultZoomPercent,
127
135
  words: previewWords(short.text),
@@ -134,12 +142,25 @@ async function renderShortPreview(deps, settings, short, assets, images, output,
134
142
  signal,
135
143
  log: deps.log,
136
144
  onProgress: () => { },
145
+ machine,
137
146
  });
138
147
  }
148
+ // The six seconds of sample narration back to back under a longer preview, the last cut to
149
+ // what is left.
150
+ function narrationFor(path, seconds) {
151
+ const pieces = [];
152
+ for (let at = 0; at < seconds - 0.001; at += sampleNarrationSeconds)
153
+ pieces.push({
154
+ kind: "body",
155
+ path,
156
+ seconds: Math.round(Math.min(sampleNarrationSeconds, seconds - at) * 1000) / 1000,
157
+ });
158
+ return pieces;
159
+ }
139
160
  function explained(log, error) {
140
161
  if (error instanceof StylePreviewError)
141
162
  return error;
142
- const message = error instanceof Error ? error.message : String(error);
163
+ const message = errorText(error);
143
164
  if (isMissingFont(error))
144
165
  return new StylePreviewError("font-missing", "The style preview couldn't render because the chosen caption font is no longer available; it may have been uninstalled or deleted. Choose another font under Subtitles, then press Render again.");
145
166
  if (message.startsWith("Slopify could not start ffmpeg") || /\bENOENT\b/.test(message))
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { formats } from "../../kernel/pipeline.js";
3
+ import { motionStyles } from "../admission/model.js";
3
4
  import { videoEditSchema } from "../admission/schema.js";
4
5
  import { subtitleModes, subtitlePositions } from "../subtitles/model.js";
5
6
  // "See it before you make it": a few seconds rendered by the real renderer with a project's
@@ -8,6 +9,8 @@ import { subtitleModes, subtitlePositions } from "../subtitles/model.js";
8
9
  // How long the preview runs: three images of two seconds, so a transition and a chapter
9
10
  // card both show, under six seconds of the sample narration.
10
11
  export const stylePreviewSeconds = 6;
12
+ // The longest preview: the template studio's 30-second video sample.
13
+ export const stylePreviewSecondsMax = 30;
11
14
  export const stylePreviewTextMax = 200;
12
15
  // The sample text Play starts with. It, or no text, captions the sample narration with its own
13
16
  // words; any other text is spread over the stretches the narration speaks.
@@ -47,6 +50,16 @@ export const stylePreviewRequestSchema = z.object({
47
50
  title: z.string().max(shortTitlePreviewMax).optional(),
48
51
  })
49
52
  .optional(),
53
+ // How long it runs, in whole seconds; absent is six. The Shorts layout ignores it.
54
+ seconds: z.number().int().min(stylePreviewSeconds).max(stylePreviewSecondsMax).optional(),
55
+ // The run's own picture timing and motion; absent is two-second shots with the defaults.
56
+ timing: z
57
+ .object({
58
+ imageSeconds: z.number().finite().min(1).max(120),
59
+ zoomPercent: z.number().finite().min(0).max(100),
60
+ motionStyle: z.enum(motionStyles),
61
+ })
62
+ .optional(),
50
63
  // Render again even when this exact preview is already saved.
51
64
  force: z.boolean().optional(),
52
65
  });
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { mkdirSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ import { errorText } from "../../kernel/errors.js";
4
5
  import { normalizeStylePreview, previewSeconds, stylePreviewHash } from "./settings.js";
5
6
  // Rendered previews are kept in the data folder by the hash of their settings, so a setting
6
7
  // changed back, or the same style in another project, plays at once. `force` renders again
@@ -86,7 +87,7 @@ function prune(deps) {
86
87
  }
87
88
  catch (error) {
88
89
  deps.log.write("warn", "style-preview.prune", {
89
- detail: error instanceof Error ? error.message : String(error),
90
+ detail: errorText(error),
90
91
  });
91
92
  }
92
93
  }
@@ -61,8 +61,27 @@ image) {
61
61
  },
62
62
  chapterCard: edit.chapterCards ? { fontId: subtitles.fontId } : null,
63
63
  ...(image === undefined ? {} : { image }),
64
+ ...(request.seconds === undefined || request.seconds === stylePreviewSeconds
65
+ ? {}
66
+ : { seconds: request.seconds }),
67
+ ...(request.timing === undefined
68
+ ? {}
69
+ : {
70
+ timing: {
71
+ imageSeconds: Math.round(request.timing.imageSeconds * 10) / 10,
72
+ zoomPercent: Math.round(request.timing.zoomPercent * 10) / 10,
73
+ motionStyle: request.timing.motionStyle,
74
+ },
75
+ }),
64
76
  };
65
77
  }
78
+ // The shots a preview is cut into: two seconds each by default, else the run's own picture
79
+ // length, as many as fill its seconds (at least one).
80
+ export function previewShots(settings) {
81
+ const seconds = settings.seconds ?? stylePreviewSeconds;
82
+ const shotSeconds = settings.timing?.imageSeconds ?? stylePreviewSeconds / 3;
83
+ return { shotSeconds, shots: Math.max(1, Math.round(seconds / shotSeconds)) };
84
+ }
66
85
  // JSON with every object's keys in order, so equal settings always hash alike.
67
86
  export function stableJson(value) {
68
87
  if (Array.isArray(value))
@@ -80,9 +99,10 @@ export function stylePreviewHash(settings) {
80
99
  }
81
100
  // How long the rendered preview runs: a Shorts preview at a higher speed is shorter.
82
101
  export function previewSeconds(settings) {
83
- return settings.short === undefined
84
- ? stylePreviewSeconds
85
- : round(stylePreviewSeconds / settings.short.speed);
102
+ if (settings.short !== undefined)
103
+ return round(stylePreviewSeconds / settings.short.speed);
104
+ const { shotSeconds, shots } = previewShots(settings);
105
+ return round(shotSeconds * shots);
86
106
  }
87
107
  // ceiling: a pause this long between two words of the sample narration starts a new phrase,
88
108
  // the same pause `captionCues` starts a new cue at.
@@ -131,8 +151,16 @@ export function previewWords(text, narration = sampleNarrationWords) {
131
151
  }
132
152
  return timed;
133
153
  }
134
- export function previewCues(text) {
135
- return captionCues(previewWords(text));
154
+ // The captions of `seconds` of preview: the six seconds' words again every six seconds, as the
155
+ // sample narration repeats under a longer preview.
156
+ export function previewCues(text, seconds = stylePreviewSeconds) {
157
+ const once = previewWords(text);
158
+ const words = [];
159
+ for (let start = 0; start < seconds; start += stylePreviewSeconds)
160
+ for (const word of once)
161
+ if (start + word.end <= seconds)
162
+ words.push({ ...word, start: round(start + word.start), end: round(start + word.end) });
163
+ return captionCues(words);
136
164
  }
137
165
  function round(value) {
138
166
  return Math.round(value * 1000) / 1000;
@@ -1,3 +1,4 @@
1
+ import { errorText } from "../../kernel/errors.js";
1
2
  // The collector URL is built into the release and is not user
2
3
  // configurable. The environment variable below is a test seam, not a documented knob:
3
4
  // packages/app/test/telemetry-flush.test.ts points it at a fake collector.
@@ -42,10 +43,7 @@ export function httpPostEvents(endpoint, timeoutMs) {
42
43
  catch (error) {
43
44
  // Offline is the ordinary case, not a fault: the events stay queued and the user is
44
45
  // told nothing.
45
- return { ok: false, retriable: true, reason: messageOf(error) };
46
+ return { ok: false, retriable: true, reason: errorText(error) };
46
47
  }
47
48
  };
48
49
  }
49
- function messageOf(error) {
50
- return error instanceof Error ? error.message : String(error);
51
- }
@@ -1,3 +1,4 @@
1
+ import { errorText } from "../../kernel/errors.js";
1
2
  import { machineOf, markDelivered, undeliveredEvents } from "./repo.js";
2
3
  // ceiling: 200 events per request against the collector's cap of 500, and at most 25
3
4
  // requests per flush. A queue longer than 5000 undelivered events drains over several
@@ -75,7 +76,7 @@ export function createFlusher(deps, delayMs) {
75
76
  .catch((error) => {
76
77
  // The timer has no caller to fail: a broken flush is logged and the next one
77
78
  // tries again.
78
- deps.log.write("warn", "telemetry.flush", { detail: messageOf(error) });
79
+ deps.log.write("warn", "telemetry.flush", { detail: errorText(error) });
79
80
  })
80
81
  .finally(() => {
81
82
  running = false;
@@ -106,6 +107,3 @@ function envelope(event, machineId) {
106
107
  createdAt: event.createdAt,
107
108
  };
108
109
  }
109
- function messageOf(error) {
110
- return error instanceof Error ? error.message : String(error);
111
- }
@@ -8,13 +8,6 @@ export const telemetryEventTypes = ["install", "project.created", "stage.complet
8
8
  // writes the intro and outro texts, the audio stage narrates all three - so the event type
9
9
  // stays `stage.completed` and the pair (stage, segment) says which unit it is.
10
10
  export const audioSegments = ["body", "intro", "outro"];
11
- export const noTokens = { tokensIn: 0, tokensOut: 0 };
12
- export function plusUsage(tokens, usage) {
13
- return {
14
- tokensIn: tokens.tokensIn + (usage?.inputTokens ?? 0),
15
- tokensOut: tokens.tokensOut + (usage?.outputTokens ?? 0),
16
- };
17
- }
18
11
  // The whole privacy surface. API keys, prompt bodies, keyword values, titles, article and
19
12
  // research text, files, filenames, OS, locale and hardware are all barred from a payload,
20
13
  // which is exactly what the first-run notice promises. The schema below is strict, so a
@@ -1,3 +1,4 @@
1
+ import { errorText } from "../../kernel/errors.js";
1
2
  import { payloadSchema } from "./model.js";
2
3
  import { insertTelemetryEvent, machineOf } from "./repo.js";
3
4
  // Appends one row to the local queue and hands it back. Throws: the notice dismissal
@@ -32,10 +33,16 @@ export function record(deps, type, counters) {
32
33
  }
33
34
  catch (error) {
34
35
  deps.log.write("warn", "telemetry.record", {
35
- detail: `${type}: ${messageOf(error)}`,
36
+ detail: `${type}: ${errorText(error)}`,
36
37
  });
37
38
  }
38
39
  }
39
- function messageOf(error) {
40
- return error instanceof Error ? error.message : String(error);
40
+ // A template studio sample (or bake-off copy) is the studio's, not a video of the person's:
41
+ // Settings → Usage and the public counters leave its stages out.
42
+ export function skippingProjects(count, skipped) {
43
+ return (type, counters, projectId) => {
44
+ if (projectId !== undefined && skipped(projectId))
45
+ return;
46
+ count(type, counters, projectId);
47
+ };
41
48
  }