@gentbajko/slopify 3.25.9 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (603) hide show
  1. package/dist/adapters/account-limits.js +30 -0
  2. package/dist/adapters/alignment/cache.js +3 -5
  3. package/dist/adapters/alignment/ctc.js +1 -2
  4. package/dist/adapters/alignment/lock.js +4 -3
  5. package/dist/adapters/alignment/multilingual.js +0 -3
  6. package/dist/adapters/alignment/prefetch.js +2 -1
  7. package/dist/adapters/alignment/worker.js +2 -1
  8. package/dist/adapters/explain.js +0 -3
  9. package/dist/adapters/ffmpeg.js +19 -1
  10. package/dist/adapters/host-cli/index.js +19 -3
  11. package/dist/adapters/http-failure.js +53 -0
  12. package/dist/adapters/image/codex.js +36 -4
  13. package/dist/adapters/image/fal.js +17 -50
  14. package/dist/adapters/image/google.js +31 -55
  15. package/dist/adapters/image/openai.js +20 -47
  16. package/dist/adapters/image/reference.js +36 -0
  17. package/dist/adapters/image/replicate.js +18 -47
  18. package/dist/adapters/interest/search-interest.js +113 -0
  19. package/dist/adapters/key-probes.js +53 -2
  20. package/dist/adapters/llm/antigravity.js +4 -0
  21. package/dist/adapters/llm/assistant-cli.js +388 -0
  22. package/dist/adapters/llm/child-session.js +83 -0
  23. package/dist/adapters/llm/claude-code-models.js +51 -98
  24. package/dist/adapters/llm/claude-code.js +13 -2
  25. package/dist/adapters/llm/codex-limits.js +27 -68
  26. package/dist/adapters/llm/codex-server-models.js +61 -109
  27. package/dist/adapters/llm/codex.js +12 -1
  28. package/dist/adapters/llm/openrouter.js +10 -47
  29. package/dist/adapters/llm/run-cli.js +6 -22
  30. package/dist/adapters/llm/sse-lines.js +5 -1
  31. package/dist/adapters/model-access.js +37 -0
  32. package/dist/adapters/tts/cartesia.js +15 -46
  33. package/dist/adapters/tts/elevenlabs.js +20 -52
  34. package/dist/adapters/tts/gemini.js +19 -40
  35. package/dist/adapters/tts/inworld-async.js +4 -1
  36. package/dist/adapters/tts/inworld.js +3 -0
  37. package/dist/adapters/tts/openai.js +20 -50
  38. package/dist/assets/models-lifecycle.yaml +6 -0
  39. package/dist/assistant-wiring.js +60 -0
  40. package/dist/autopilot-timers.js +59 -0
  41. package/dist/catalog/lifecycle.js +52 -0
  42. package/dist/catalog/propose.js +204 -0
  43. package/dist/catalog/registry.js +3 -1
  44. package/dist/catalog/schema.js +2 -0
  45. package/dist/catalog/store.js +39 -0
  46. package/dist/edge/autostart/native.js +3 -5
  47. package/dist/edge/autostart/prompt.js +2 -1
  48. package/dist/edge/autostart/service.js +3 -4
  49. package/dist/edge/cli-args.js +3 -4
  50. package/dist/edge/cli.js +2 -1
  51. package/dist/edge/docker-install/apply.js +11 -11
  52. package/dist/edge/docker-install/run.js +2 -1
  53. package/dist/edge/docker-install/state.js +4 -3
  54. package/dist/edge/docker-install/tree.js +2 -4
  55. package/dist/edge/docker-install/volume.js +3 -2
  56. package/dist/edge/docker.js +5 -4
  57. package/dist/edge/events/hub.js +5 -5
  58. package/dist/edge/http/actions.js +4 -25
  59. package/dist/edge/http/actor.js +5 -3
  60. package/dist/edge/http/app.js +85 -10
  61. package/dist/edge/http/assistant.js +127 -0
  62. package/dist/edge/http/audio-preview.js +4 -11
  63. package/dist/edge/http/auditions.js +1 -1
  64. package/dist/edge/http/autostart.js +5 -5
  65. package/dist/edge/http/backups.js +1 -3
  66. package/dist/edge/http/channel-memory.js +7 -20
  67. package/dist/edge/http/channels.js +13 -26
  68. package/dist/edge/http/checkpoints.js +33 -17
  69. package/dist/edge/http/design-templates.js +167 -0
  70. package/dist/edge/http/designs.js +178 -0
  71. package/dist/edge/http/diagnostics.js +5 -1
  72. package/dist/edge/http/document-themes.js +2 -11
  73. package/dist/edge/http/draft-files.js +1 -3
  74. package/dist/edge/http/draft-problem.js +4 -9
  75. package/dist/edge/http/drafts.js +3 -3
  76. package/dist/edge/http/entries.js +6 -9
  77. package/dist/edge/http/extension-auth.js +27 -0
  78. package/dist/edge/http/external-reviews.js +12 -11
  79. package/dist/edge/http/files.js +5 -11
  80. package/dist/edge/http/folder-location.js +1 -4
  81. package/dist/edge/http/fonts.js +0 -7
  82. package/dist/edge/http/home.js +38 -13
  83. package/dist/edge/http/host-cli.js +18 -8
  84. package/dist/edge/http/ids.js +8 -0
  85. package/dist/edge/http/keywords.js +35 -0
  86. package/dist/edge/http/model-checks.js +132 -0
  87. package/dist/edge/http/narration-peaks.js +5 -13
  88. package/dist/edge/http/onboarding.js +1 -14
  89. package/dist/edge/http/open-folder.js +5 -10
  90. package/dist/edge/http/outputs.js +4 -12
  91. package/dist/edge/http/patch-notes.js +4 -10
  92. package/dist/edge/http/planning.js +2 -6
  93. package/dist/edge/http/problem.js +2 -4
  94. package/dist/edge/http/project-create.js +1 -0
  95. package/dist/edge/http/project-templates.js +29 -17
  96. package/dist/edge/http/projects.js +2 -12
  97. package/dist/edge/http/prompts.js +7 -14
  98. package/dist/edge/http/pronunciations.js +1 -2
  99. package/dist/edge/http/providers.js +39 -24
  100. package/dist/edge/http/refusal.js +29 -0
  101. package/dist/edge/http/remote-access.js +40 -0
  102. package/dist/edge/http/remote-guard.js +71 -0
  103. package/dist/edge/http/request-guard.js +91 -0
  104. package/dist/edge/http/reviews.js +11 -24
  105. package/dist/edge/http/revision-files.js +5 -10
  106. package/dist/edge/http/revisions.js +10 -15
  107. package/dist/edge/http/run-cost.js +1 -2
  108. package/dist/edge/http/same-page.js +80 -0
  109. package/dist/edge/http/schedules.js +59 -32
  110. package/dist/edge/http/settings.js +22 -13
  111. package/dist/edge/http/social.js +282 -0
  112. package/dist/edge/http/staging.js +2 -4
  113. package/dist/edge/http/storage-files.js +5 -7
  114. package/dist/edge/http/storage.js +23 -23
  115. package/dist/edge/http/studio.js +23 -50
  116. package/dist/edge/http/style-preview.js +1 -4
  117. package/dist/edge/http/subtitles.js +3 -17
  118. package/dist/edge/http/template-studio.js +433 -0
  119. package/dist/edge/http/trash.js +10 -11
  120. package/dist/edge/http/tutorial.js +1 -2
  121. package/dist/edge/http/tutorials.js +5 -11
  122. package/dist/edge/http/update.js +7 -13
  123. package/dist/edge/http/waveform.js +3 -6
  124. package/dist/edge/http/youtube-edits.js +2 -3
  125. package/dist/edge/mcp/assistant.js +279 -0
  126. package/dist/edge/mcp/attribution.js +8 -2
  127. package/dist/edge/mcp/call-route.js +11 -2
  128. package/dist/edge/mcp/excluded-routes.js +25 -0
  129. package/dist/edge/mcp/http.js +11 -2
  130. package/dist/edge/mcp/instructions.js +5 -3
  131. package/dist/edge/mcp/library-tools.js +1 -0
  132. package/dist/edge/mcp/route-tool.js +35 -15
  133. package/dist/edge/mcp/server.js +13 -8
  134. package/dist/edge/mcp/tools-channels.js +2 -0
  135. package/dist/edge/mcp/tools-designs.js +165 -0
  136. package/dist/edge/mcp/tools-library.js +14 -2
  137. package/dist/edge/mcp/tools-planning.js +23 -4
  138. package/dist/edge/mcp/tools-projects.js +28 -1
  139. package/dist/edge/mcp/tools-runs.js +14 -1
  140. package/dist/edge/mcp/tools-schedules.js +28 -4
  141. package/dist/edge/mcp/tools-setup.js +61 -3
  142. package/dist/edge/mcp/tools-social.js +46 -0
  143. package/dist/edge/mcp/tools-storage.js +8 -0
  144. package/dist/edge/mcp/tools-template-studio.js +166 -0
  145. package/dist/edge/mcp/tools.js +6 -0
  146. package/dist/edge/remote-access/cli.js +71 -0
  147. package/dist/edge/remote-access/model.js +32 -0
  148. package/dist/edge/remote-access/rules.js +207 -0
  149. package/dist/edge/remote-access/service.js +203 -0
  150. package/dist/edge/remote-access/store.js +44 -0
  151. package/dist/edge/remote-access/views.js +43 -0
  152. package/dist/extension/slopify-studio-chrome.zip +0 -0
  153. package/dist/extension/slopify-studio-firefox.zip +0 -0
  154. package/dist/host-cli/install.js +2 -2
  155. package/dist/host-cli/open-folder.js +2 -2
  156. package/dist/host-cli/paths.js +2 -4
  157. package/dist/host-cli/server.js +4 -4
  158. package/dist/host-cli/service.js +3 -3
  159. package/dist/host-cli/status.js +7 -7
  160. package/dist/kernel/db/migrations/0063-model-checks.sql +20 -0
  161. package/dist/kernel/db/migrations/0064-template-studio.sql +46 -0
  162. package/dist/kernel/db/migrations/0065-promise-and-assistant-reviews.sql +58 -0
  163. package/dist/kernel/db/migrations/0066-assistant.sql +44 -0
  164. package/dist/kernel/db/migrations/0067-autopilot.sql +6 -0
  165. package/dist/kernel/db/migrations/0068-topic-scores.sql +17 -0
  166. package/dist/kernel/db/migrations/0070-designs.sql +53 -0
  167. package/dist/kernel/db/migrations/0071-design-usage.sql +33 -0
  168. package/dist/kernel/db/migrations/0072-design-templates.sql +33 -0
  169. package/dist/kernel/db/migrations/0073-schedule-design-kind.sql +5 -0
  170. package/dist/kernel/db/migrations/0074-template-bakeoffs.sql +44 -0
  171. package/dist/kernel/db/migrations/0075-social-posts.sql +25 -0
  172. package/dist/kernel/db/migrations/0076-channel-social.sql +5 -0
  173. package/dist/kernel/db/migrations/0077-post-prompts.sql +17 -0
  174. package/dist/kernel/db/tx.js +2 -4
  175. package/dist/kernel/errors.js +27 -1
  176. package/dist/kernel/json-answer.js +15 -0
  177. package/dist/kernel/lock.js +4 -9
  178. package/dist/kernel/paths.js +8 -0
  179. package/dist/kernel/ports/assistant-cli.js +4 -0
  180. package/dist/kernel/ports/host-cli.js +3 -0
  181. package/dist/kernel/ports/interest.js +4 -0
  182. package/dist/kernel/ports/languages.js +0 -3
  183. package/dist/kernel/ports/model.js +2 -0
  184. package/dist/kernel/runner/attempt.js +3 -1
  185. package/dist/kernel/runner/audition.js +3 -1
  186. package/dist/kernel/runner/checkpoint-authority.js +2 -1
  187. package/dist/kernel/runner/index.js +8 -10
  188. package/dist/kernel/runner/model-access.js +40 -0
  189. package/dist/kernel/runner/model-check.js +153 -0
  190. package/dist/kernel/runner/providers.js +15 -10
  191. package/dist/kernel/runner/standalone.js +9 -5
  192. package/dist/main.js +334 -19
  193. package/dist/model-checks.js +166 -0
  194. package/dist/patch-notes/4.0.0.md +51 -0
  195. package/dist/patch-notes/index.json +6 -0
  196. package/dist/slices/admission/heads.js +2 -1
  197. package/dist/slices/admission/repo.js +2 -1
  198. package/dist/slices/admission/rules.js +43 -8
  199. package/dist/slices/admission/schema.js +8 -0
  200. package/dist/slices/admission/stage-writes.js +0 -3
  201. package/dist/slices/admission/start.js +48 -31
  202. package/dist/slices/admission/substitute.js +7 -4
  203. package/dist/slices/agents/attention-posts.js +31 -0
  204. package/dist/slices/agents/attention-templates.js +33 -0
  205. package/dist/slices/agents/attention.js +10 -1
  206. package/dist/slices/agents/connection.js +2 -6
  207. package/dist/slices/agents/model.js +6 -3
  208. package/dist/slices/agents/repo.js +15 -7
  209. package/dist/slices/article/continuation.js +0 -63
  210. package/dist/slices/article/plain.js +3 -2
  211. package/dist/slices/article/segments.js +2 -62
  212. package/dist/slices/assistant/access.js +1 -0
  213. package/dist/slices/assistant/asks.js +80 -0
  214. package/dist/slices/assistant/cost.js +54 -0
  215. package/dist/slices/assistant/gate.js +222 -0
  216. package/dist/slices/assistant/model.js +79 -0
  217. package/dist/slices/assistant/names.js +23 -0
  218. package/dist/slices/assistant/prompt.js +74 -0
  219. package/dist/slices/assistant/repo.js +74 -0
  220. package/dist/slices/assistant/tokens.js +18 -0
  221. package/dist/slices/assistant/turn.js +342 -0
  222. package/dist/slices/autopilot/buffer.js +76 -0
  223. package/dist/slices/autopilot/model.js +14 -0
  224. package/dist/slices/autopilot/notify.js +27 -0
  225. package/dist/slices/autopilot/repo.js +34 -0
  226. package/dist/slices/autopilot/signals.js +194 -0
  227. package/dist/slices/autopilot/warnings.js +85 -0
  228. package/dist/slices/autopilot/weekly.js +100 -0
  229. package/dist/slices/backups/files.js +2 -6
  230. package/dist/slices/backups/repo.js +3 -15
  231. package/dist/slices/backups/service.js +4 -3
  232. package/dist/slices/channels/cast-images.js +2 -1
  233. package/dist/slices/channels/model.js +0 -1
  234. package/dist/slices/channels/rebrand.js +0 -3
  235. package/dist/slices/channels/repo.js +4 -0
  236. package/dist/slices/channels/schema.js +6 -0
  237. package/dist/slices/channels/service.js +10 -0
  238. package/dist/slices/channels/videos.js +2 -14
  239. package/dist/slices/checkpoints/index.js +0 -1
  240. package/dist/slices/control/providers.js +0 -26
  241. package/dist/slices/designs/artwork.js +66 -0
  242. package/dist/slices/designs/budget.js +23 -0
  243. package/dist/slices/designs/calls.js +124 -0
  244. package/dist/slices/designs/check-context.js +38 -0
  245. package/dist/slices/designs/check-text.js +43 -0
  246. package/dist/slices/designs/fields.js +88 -0
  247. package/dist/slices/designs/frames.js +34 -0
  248. package/dist/slices/designs/gate.js +128 -0
  249. package/dist/slices/designs/layout.js +293 -0
  250. package/dist/slices/designs/model.js +35 -0
  251. package/dist/slices/designs/pdf-job.js +23 -0
  252. package/dist/slices/designs/pdf-pages.js +30 -0
  253. package/dist/slices/designs/pdf-runner.js +68 -0
  254. package/dist/slices/designs/pdf-worker.js +24 -0
  255. package/dist/slices/designs/picture-size.js +79 -0
  256. package/dist/slices/designs/pictures.js +55 -0
  257. package/dist/slices/designs/quote.js +24 -0
  258. package/dist/slices/designs/render-args.js +36 -0
  259. package/dist/slices/designs/render-input.js +59 -0
  260. package/dist/slices/designs/render.js +215 -0
  261. package/dist/slices/designs/repo.js +101 -0
  262. package/dist/slices/designs/requests.js +80 -0
  263. package/dist/slices/designs/runner.js +239 -0
  264. package/dist/slices/designs/sample-repo.js +91 -0
  265. package/dist/slices/designs/sample.js +222 -0
  266. package/dist/slices/designs/schema.js +87 -0
  267. package/dist/slices/designs/sections.js +44 -0
  268. package/dist/slices/designs/service.js +233 -0
  269. package/dist/slices/designs/steps-repo.js +102 -0
  270. package/dist/slices/designs/studio-kind.js +39 -0
  271. package/dist/slices/designs/template-checks.js +44 -0
  272. package/dist/slices/designs/template-make.js +118 -0
  273. package/dist/slices/designs/templates.js +218 -0
  274. package/dist/slices/document/fonts.js +2 -1
  275. package/dist/slices/episodes/summarize.js +5 -1
  276. package/dist/slices/estimate/index.js +45 -7
  277. package/dist/slices/fonts/catalog.js +3 -2
  278. package/dist/slices/fonts/discovery.js +3 -2
  279. package/dist/slices/fonts/files.js +2 -6
  280. package/dist/slices/fonts/index.js +0 -1
  281. package/dist/slices/fonts/sfnt.js +45 -10
  282. package/dist/slices/images/appearance.js +6 -15
  283. package/dist/slices/images/scenes.js +100 -27
  284. package/dist/slices/keywords/digest.js +84 -0
  285. package/dist/slices/keywords/facts.js +92 -0
  286. package/dist/slices/keywords/fill.js +80 -0
  287. package/dist/slices/keywords/model.js +75 -0
  288. package/dist/slices/keywords/rules.js +187 -0
  289. package/dist/slices/keywords/schema.js +28 -0
  290. package/dist/slices/keywords/specs.js +260 -0
  291. package/dist/slices/keywords/values.js +74 -0
  292. package/dist/slices/library/lint.js +29 -12
  293. package/dist/slices/library/model.js +2 -0
  294. package/dist/slices/library/photorealistic.js +4 -12
  295. package/dist/slices/library/rename.js +9 -7
  296. package/dist/slices/library/save.js +14 -10
  297. package/dist/slices/library/slots.js +37 -11
  298. package/dist/slices/library/transfer.js +2 -1
  299. package/dist/slices/library/used-by.js +25 -10
  300. package/dist/slices/loudness/loudnorm.js +51 -11
  301. package/dist/slices/model-checks/cost.js +61 -0
  302. package/dist/slices/model-checks/listing.js +124 -0
  303. package/dist/slices/model-checks/model.js +20 -0
  304. package/dist/slices/model-checks/record.js +32 -0
  305. package/dist/slices/model-checks/repo.js +120 -0
  306. package/dist/slices/model-checks/rules.js +112 -0
  307. package/dist/slices/model-checks/run.js +143 -0
  308. package/dist/slices/model-checks/service.js +198 -0
  309. package/dist/slices/model-upkeep/switch.js +70 -25
  310. package/dist/slices/model-upkeep/usage.js +26 -11
  311. package/dist/slices/narration/breaths.js +2 -1
  312. package/dist/slices/narration/clean-pieces.js +2 -1
  313. package/dist/slices/narration/concat.js +2 -1
  314. package/dist/slices/narration/peaks.js +3 -4
  315. package/dist/slices/narration/pronunciation-chunks.js +4 -3
  316. package/dist/slices/narration/pronunciation.js +3 -2
  317. package/dist/slices/narration/steering.js +4 -3
  318. package/dist/slices/notifications/notifier.js +4 -3
  319. package/dist/slices/notifications/rules.js +5 -2
  320. package/dist/slices/notifications/settings.js +4 -12
  321. package/dist/slices/notifications/subject.js +2 -1
  322. package/dist/slices/onboarding/copy.js +2 -1
  323. package/dist/slices/onboarding/first-run.js +3 -2
  324. package/dist/slices/onboarding/full-video.js +3 -2
  325. package/dist/slices/onboarding/install.js +4 -3
  326. package/dist/slices/onboarding/model.js +0 -16
  327. package/dist/slices/onboarding/packs.js +22 -22
  328. package/dist/slices/onboarding/quick-short.js +2 -1
  329. package/dist/slices/onboarding/sample.js +0 -1
  330. package/dist/slices/onboarding/state.js +2 -11
  331. package/dist/slices/onboarding/template.js +2 -1
  332. package/dist/slices/patch-notes/library.js +3 -2
  333. package/dist/slices/play-drafts/convert.js +4 -0
  334. package/dist/slices/play-drafts/review-inputs.js +8 -2
  335. package/dist/slices/play-drafts/review.js +5 -4
  336. package/dist/slices/play-drafts/schema.js +4 -0
  337. package/dist/slices/project-templates/from-project.js +9 -2
  338. package/dist/slices/project-templates/repo.js +6 -1
  339. package/dist/slices/project-templates/setup.js +8 -0
  340. package/dist/slices/project-templates/stand-ins.js +22 -0
  341. package/dist/slices/rebuild/activity.js +2 -0
  342. package/dist/slices/rebuild/admission-repo.js +3 -2
  343. package/dist/slices/rebuild/narration-history.js +4 -5
  344. package/dist/slices/rebuild/narration-retry.js +2 -1
  345. package/dist/slices/rebuild/preview-details.js +27 -9
  346. package/dist/slices/rebuild/preview-plan.js +3 -8
  347. package/dist/slices/rebuild/recipe-build.js +8 -3
  348. package/dist/slices/rebuild/recipe-describe.js +3 -3
  349. package/dist/slices/rebuild/recipe-digest.js +58 -0
  350. package/dist/slices/rebuild/recipe-input-schema.js +10 -0
  351. package/dist/slices/rebuild/recipe-keywords.js +71 -0
  352. package/dist/slices/rebuild/recipe-legacy.js +5 -1
  353. package/dist/slices/rebuild/recipe-model.js +4 -0
  354. package/dist/slices/rebuild/recipe-narration-text.js +2 -1
  355. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  356. package/dist/slices/rebuild/recipe-reference.js +8 -4
  357. package/dist/slices/rebuild/recipe-reviews.js +27 -7
  358. package/dist/slices/rebuild/recipe-save.js +6 -3
  359. package/dist/slices/rebuild/recipe-scenes.js +40 -11
  360. package/dist/slices/rebuild/recipe-shorts.js +8 -8
  361. package/dist/slices/rebuild/recipe-social.js +46 -0
  362. package/dist/slices/rebuild/recipe-text.js +64 -20
  363. package/dist/slices/rebuild/recipe-validation.js +98 -28
  364. package/dist/slices/rebuild/recipe-visual.js +55 -16
  365. package/dist/slices/rebuild/recipe-work.js +15 -3
  366. package/dist/slices/rebuild/recipe-youtube.js +4 -3
  367. package/dist/slices/rebuild/recovery-selection.js +3 -1
  368. package/dist/slices/rebuild/recovery.js +3 -2
  369. package/dist/slices/rebuild/repo.js +4 -3
  370. package/dist/slices/rebuild/review-promise.js +25 -0
  371. package/dist/slices/rebuild/review-redo.js +2 -1
  372. package/dist/slices/rebuild/runtime-admission.js +4 -3
  373. package/dist/slices/rebuild/runtime-agent-review.js +11 -5
  374. package/dist/slices/rebuild/runtime-animate.js +9 -8
  375. package/dist/slices/rebuild/runtime-article.js +3 -2
  376. package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
  377. package/dist/slices/rebuild/runtime-document.js +8 -11
  378. package/dist/slices/rebuild/runtime-export-bed.js +6 -11
  379. package/dist/slices/rebuild/runtime-export-edit.js +6 -15
  380. package/dist/slices/rebuild/runtime-export-inputs.js +7 -7
  381. package/dist/slices/rebuild/runtime-export-short.js +8 -4
  382. package/dist/slices/rebuild/runtime-export.js +34 -12
  383. package/dist/slices/rebuild/runtime-figure-card.js +2 -1
  384. package/dist/slices/rebuild/runtime-image.js +4 -9
  385. package/dist/slices/rebuild/runtime-instructions.js +2 -0
  386. package/dist/slices/rebuild/runtime-keywords.js +59 -0
  387. package/dist/slices/rebuild/runtime-lines.js +2 -11
  388. package/dist/slices/rebuild/runtime-local.js +14 -20
  389. package/dist/slices/rebuild/runtime-narration-publication.js +2 -5
  390. package/dist/slices/rebuild/runtime-narration-reuse.js +9 -13
  391. package/dist/slices/rebuild/runtime-narration-text.js +10 -15
  392. package/dist/slices/rebuild/runtime-pictures.js +2 -4
  393. package/dist/slices/rebuild/runtime-piece-label.js +10 -6
  394. package/dist/slices/rebuild/runtime-plan.js +31 -5
  395. package/dist/slices/rebuild/runtime-provider.js +60 -19
  396. package/dist/slices/rebuild/runtime-publication.js +10 -9
  397. package/dist/slices/rebuild/runtime-review.js +33 -28
  398. package/dist/slices/rebuild/runtime-run.js +4 -1
  399. package/dist/slices/rebuild/runtime-shorts.js +25 -45
  400. package/dist/slices/rebuild/runtime-store.js +3 -8
  401. package/dist/slices/rebuild/runtime-subtitles.js +16 -14
  402. package/dist/slices/rebuild/runtime-voices.js +8 -15
  403. package/dist/slices/rebuild/runtime-youtube.js +11 -29
  404. package/dist/slices/rebuild/transition-repo.js +6 -5
  405. package/dist/slices/rebuild/work-records.js +6 -0
  406. package/dist/slices/reruns/cascade.js +1 -2
  407. package/dist/slices/reruns/index.js +6 -9
  408. package/dist/slices/reviews/external-model.js +3 -0
  409. package/dist/slices/reviews/external-repo.js +2 -1
  410. package/dist/slices/reviews/external-rows.js +2 -3
  411. package/dist/slices/reviews/model.js +26 -8
  412. package/dist/slices/reviews/repo.js +2 -1
  413. package/dist/slices/reviews/rules.js +1 -0
  414. package/dist/slices/reviews/view.js +5 -2
  415. package/dist/slices/revisions/downloads.js +4 -5
  416. package/dist/slices/revisions/mutation-assets.js +7 -4
  417. package/dist/slices/revisions/mutation-cues.js +2 -5
  418. package/dist/slices/revisions/mutations.js +0 -1
  419. package/dist/slices/revisions/repo.js +3 -1
  420. package/dist/slices/revisions/schema.js +1 -0
  421. package/dist/slices/revisions/view.js +20 -0
  422. package/dist/slices/run-cost/limits.js +5 -2
  423. package/dist/slices/run-cost/meter.js +12 -5
  424. package/dist/slices/run-cost/panel.js +8 -74
  425. package/dist/slices/run-cost/plan-readings.js +127 -0
  426. package/dist/slices/run-cost/plan-use.js +169 -0
  427. package/dist/slices/run-cost/pricing.js +3 -3
  428. package/dist/slices/run-cost/week.js +32 -4
  429. package/dist/slices/schedules/agenda.js +11 -2
  430. package/dist/slices/schedules/by-date.js +42 -0
  431. package/dist/slices/schedules/calendar.js +5 -0
  432. package/dist/slices/schedules/design-agenda.js +36 -0
  433. package/dist/slices/schedules/design-check.js +41 -0
  434. package/dist/slices/schedules/design-run.js +126 -0
  435. package/dist/slices/schedules/next-run.js +62 -0
  436. package/dist/slices/schedules/prepare.js +18 -2
  437. package/dist/slices/schedules/repo.js +24 -16
  438. package/dist/slices/schedules/rescore.js +35 -0
  439. package/dist/slices/schedules/scheduler.js +13 -3
  440. package/dist/slices/schedules/schema.js +54 -3
  441. package/dist/slices/schedules/score.js +107 -0
  442. package/dist/slices/schedules/service.js +57 -24
  443. package/dist/slices/schedules/topic-list.js +15 -0
  444. package/dist/slices/schedules/topic-scores.js +104 -0
  445. package/dist/slices/schedules/topics.js +48 -36
  446. package/dist/slices/settings/cli-paths.js +4 -6
  447. package/dist/slices/settings/first-run.js +2 -11
  448. package/dist/slices/settings/model-reach.js +3 -17
  449. package/dist/slices/settings/model.js +10 -0
  450. package/dist/slices/settings/playback.js +33 -22
  451. package/dist/slices/settings/probe-read.js +16 -0
  452. package/dist/slices/settings/repo.js +33 -0
  453. package/dist/slices/settings/tutorial.js +2 -2
  454. package/dist/slices/settings/voices.js +4 -12
  455. package/dist/slices/shorts/pick.js +2 -15
  456. package/dist/slices/shorts/prompts.js +2 -13
  457. package/dist/slices/shorts/render.js +3 -0
  458. package/dist/slices/social/channel.js +44 -0
  459. package/dist/slices/social/defaults.js +17 -0
  460. package/dist/slices/social/due.js +36 -0
  461. package/dist/slices/social/extension.js +18 -0
  462. package/dist/slices/social/fit.js +115 -0
  463. package/dist/slices/social/limits.js +186 -0
  464. package/dist/slices/social/links.js +21 -0
  465. package/dist/slices/social/media.js +131 -0
  466. package/dist/slices/social/model.js +75 -0
  467. package/dist/slices/social/notice.js +19 -0
  468. package/dist/slices/social/pack.js +176 -0
  469. package/dist/slices/social/post-prompt.js +55 -0
  470. package/dist/slices/social/queue.js +31 -0
  471. package/dist/slices/social/repo.js +133 -0
  472. package/dist/slices/social/reshape.js +97 -0
  473. package/dist/slices/social/rules.js +5 -0
  474. package/dist/slices/social/schema.js +70 -0
  475. package/dist/slices/social/serve.js +51 -0
  476. package/dist/slices/social/service.js +128 -0
  477. package/dist/slices/social/settings.js +21 -0
  478. package/dist/slices/social/sources.js +114 -0
  479. package/dist/slices/social/state.js +110 -0
  480. package/dist/slices/social/text.js +149 -0
  481. package/dist/slices/storage/arrange.js +3 -1
  482. package/dist/slices/storage/assets.js +8 -10
  483. package/dist/slices/storage/backup-designs.js +74 -0
  484. package/dist/slices/storage/backup-export.js +8 -3
  485. package/dist/slices/storage/backup-format.js +10 -0
  486. package/dist/slices/storage/backup-import.js +20 -2
  487. package/dist/slices/storage/delete-project.js +6 -5
  488. package/dist/slices/storage/files-location.js +6 -8
  489. package/dist/slices/storage/layout.js +13 -2
  490. package/dist/slices/storage/model.js +3 -0
  491. package/dist/slices/storage/portable.js +16 -11
  492. package/dist/slices/storage/schema.js +2 -0
  493. package/dist/slices/storage/staging-refs.js +2 -1
  494. package/dist/slices/storage/staging.js +6 -9
  495. package/dist/slices/storage/trim.js +10 -3
  496. package/dist/slices/studio/calendar.js +4 -1
  497. package/dist/slices/studio/pick.js +2 -11
  498. package/dist/slices/studio/plan.js +6 -15
  499. package/dist/slices/studio/queue.js +3 -14
  500. package/dist/slices/studio/releases.js +26 -0
  501. package/dist/slices/studio/settings.js +14 -51
  502. package/dist/slices/studio/videos.js +12 -0
  503. package/dist/slices/style-preview/render.js +34 -13
  504. package/dist/slices/style-preview/schema.js +13 -0
  505. package/dist/slices/style-preview/service.js +2 -1
  506. package/dist/slices/style-preview/settings.js +33 -5
  507. package/dist/slices/telemetry/collector-client.js +2 -4
  508. package/dist/slices/telemetry/flush.js +2 -4
  509. package/dist/slices/telemetry/model.js +0 -7
  510. package/dist/slices/telemetry/record.js +10 -3
  511. package/dist/slices/template-studio/anatomy-view.js +118 -0
  512. package/dist/slices/template-studio/anatomy.js +73 -0
  513. package/dist/slices/template-studio/bakeoff-make.js +225 -0
  514. package/dist/slices/template-studio/bakeoff-repo.js +83 -0
  515. package/dist/slices/template-studio/bakeoff.js +333 -0
  516. package/dist/slices/template-studio/budget.js +103 -0
  517. package/dist/slices/template-studio/checks.js +273 -0
  518. package/dist/slices/template-studio/document-sample.js +56 -0
  519. package/dist/slices/template-studio/edit.js +15 -0
  520. package/dist/slices/template-studio/for-all.js +161 -0
  521. package/dist/slices/template-studio/free-video.js +31 -0
  522. package/dist/slices/template-studio/free.js +176 -0
  523. package/dist/slices/template-studio/gate.js +40 -0
  524. package/dist/slices/template-studio/hidden.js +20 -0
  525. package/dist/slices/template-studio/jobs.js +79 -0
  526. package/dist/slices/template-studio/loudness.js +111 -0
  527. package/dist/slices/template-studio/make.js +40 -0
  528. package/dist/slices/template-studio/model.js +54 -0
  529. package/dist/slices/template-studio/pack-import.js +253 -0
  530. package/dist/slices/template-studio/pack-samples.js +60 -0
  531. package/dist/slices/template-studio/pack.js +205 -0
  532. package/dist/slices/template-studio/paid-view.js +66 -0
  533. package/dist/slices/template-studio/paid.js +348 -0
  534. package/dist/slices/template-studio/quote.js +148 -0
  535. package/dist/slices/template-studio/ready.js +104 -0
  536. package/dist/slices/template-studio/repo.js +134 -0
  537. package/dist/slices/template-studio/reviews-path.js +58 -0
  538. package/dist/slices/template-studio/runner.js +84 -0
  539. package/dist/slices/template-studio/save.js +236 -0
  540. package/dist/slices/template-studio/say-it-as.js +248 -0
  541. package/dist/slices/template-studio/schedule-gate.js +34 -0
  542. package/dist/slices/template-studio/schedule-price.js +30 -0
  543. package/dist/slices/template-studio/schedule-sample.js +87 -0
  544. package/dist/slices/template-studio/schema.js +218 -0
  545. package/dist/slices/template-studio/scope.js +124 -0
  546. package/dist/slices/template-studio/sections.js +176 -0
  547. package/dist/slices/template-studio/service.js +173 -0
  548. package/dist/slices/template-studio/status.js +174 -0
  549. package/dist/slices/template-studio/topic.js +223 -0
  550. package/dist/slices/trash/designs.js +76 -0
  551. package/dist/slices/trash/model.js +13 -4
  552. package/dist/slices/trash/service.js +52 -2
  553. package/dist/slices/tutorials/library.js +3 -2
  554. package/dist/slices/video/ass-text.js +17 -0
  555. package/dist/slices/video/audio-export.js +2 -64
  556. package/dist/slices/video/encoding.js +160 -0
  557. package/dist/slices/video/ffmpeg.js +23 -21
  558. package/dist/slices/video/figure-card.js +1 -15
  559. package/dist/slices/video/slideshow.js +43 -18
  560. package/dist/slices/voices/palette.js +0 -3
  561. package/dist/slices/youtube/answer.js +2 -15
  562. package/dist/slices/youtube/edits-repo.js +2 -11
  563. package/dist/tutorials/Agents-MCP.md +2 -1
  564. package/dist/tutorials/Assistant.md +42 -0
  565. package/dist/tutorials/Channels.md +2 -0
  566. package/dist/tutorials/Home-and-Projects.md +19 -0
  567. package/dist/tutorials/Install.md +1 -0
  568. package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
  569. package/dist/tutorials/Models.md +33 -1
  570. package/dist/tutorials/Notifications.md +6 -0
  571. package/dist/tutorials/Play-Images.md +4 -0
  572. package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
  573. package/dist/tutorials/Prompts.md +10 -6
  574. package/dist/tutorials/Remote-Access.md +63 -0
  575. package/dist/tutorials/Reviews-and-Checkpoints.md +13 -2
  576. package/dist/tutorials/Schedules.md +21 -1
  577. package/dist/tutorials/Settings-Reference.md +50 -8
  578. package/dist/tutorials/Template-Studio.md +125 -0
  579. package/dist/tutorials/Templates.md +3 -0
  580. package/dist/tutorials/_Sidebar.md +5 -0
  581. package/dist/updater/work-in-progress.js +42 -12
  582. package/dist/web/assets/index-CkAI_2E7.css +1 -0
  583. package/dist/web/assets/index-DcD0cdfo.js +334 -0
  584. package/dist/web/assets/{pdf-T7MCwjIv.js → pdf-TODRwF31.js} +1 -1
  585. package/dist/web/index.html +2 -2
  586. package/package.json +5 -1
  587. package/dist/slices/article/provided-entries.js +0 -66
  588. package/dist/slices/article/run.js +0 -145
  589. package/dist/slices/images/run.js +0 -234
  590. package/dist/slices/narration/legacy-plan.js +0 -58
  591. package/dist/slices/narration/run.js +0 -270
  592. package/dist/slices/rebuild/runtime-actions.js +0 -90
  593. package/dist/slices/research/run.js +0 -224
  594. package/dist/slices/subtitles/prepare.js +0 -194
  595. package/dist/slices/subtitles/transcript.js +0 -34
  596. package/dist/slices/thumbnail/run.js +0 -219
  597. package/dist/slices/video/audio-inputs.js +0 -33
  598. package/dist/slices/video/reuse-audio.js +0 -62
  599. package/dist/slices/video/run.js +0 -78
  600. package/dist/slices/video/write-export.js +0 -158
  601. package/dist/slices/video/write-subtitles.js +0 -59
  602. package/dist/web/assets/index-BadsoORk.css +0 -1
  603. package/dist/web/assets/index-D8LqNsCl.js +0 -326
@@ -0,0 +1,76 @@
1
+ import { nextOccurrence } from "../schedules/calendar.js";
2
+ const dayMs = 24 * 60 * 60_000;
3
+ // How far ahead the content is: each run the schedules will make is a slot, filled by a run
4
+ // made ahead or else by the next finished video not uploaded yet, in time order. A paused
5
+ // schedule's runs fill nothing and run dry never: they wait for Resume.
6
+ export function bufferForecast(input) {
7
+ const slots = input.runs
8
+ .filter((run) => !run.paused)
9
+ .toSorted((a, b) => a.at.localeCompare(b.at));
10
+ const prepared = slots.filter((run) => run.prepared !== null).length;
11
+ let spare = input.ready;
12
+ let coveredUntil = null;
13
+ let firstGap = null;
14
+ for (const slot of slots) {
15
+ if (slot.prepared === null) {
16
+ if (spare === 0) {
17
+ firstGap = slot.at;
18
+ break;
19
+ }
20
+ spare -= 1;
21
+ }
22
+ coveredUntil = slot.at;
23
+ }
24
+ const daysTo = (at) => Math.max(0, Math.floor((at.valueOf() - input.now.valueOf()) / dayMs));
25
+ const perSchedule = input.schedules.map((schedule) => scheduleForecast(schedule, input.runs.filter((run) => run.scheduleId === schedule.id)));
26
+ const dry = perSchedule
27
+ .flatMap((one) => (one.runsDryAt === null ? [] : [one.runsDryAt]))
28
+ .toSorted();
29
+ return {
30
+ readyCount: input.ready + prepared,
31
+ daysAhead: slots.length === 0
32
+ ? null
33
+ : firstGap === null
34
+ ? daysTo(input.until)
35
+ : daysTo(new Date(firstGap)),
36
+ coveredUntil,
37
+ runsDryAt: dry[0] ?? null,
38
+ coveredBeyondWindow: slots.length > 0 && firstGap === null,
39
+ perSchedule,
40
+ };
41
+ }
42
+ function scheduleForecast(schedule, runs) {
43
+ const base = {
44
+ scheduleId: schedule.id,
45
+ name: schedule.name,
46
+ paused: schedule.paused,
47
+ queued: schedule.queued,
48
+ generating: schedule.generation,
49
+ generationFailing: schedule.generationFailing,
50
+ };
51
+ if (schedule.paused)
52
+ return { ...base, runsDryAt: null };
53
+ const sorted = runs.toSorted((a, b) => a.at.localeCompare(b.at));
54
+ const firstUnqueued = sorted.find((run) => run.topicSource !== "queued")?.at ?? null;
55
+ switch (schedule.generation) {
56
+ // It completes when its queue empties: the occurrence after the last queued run is the
57
+ // first with nothing to make. With no queue it runs its template as saved, every time.
58
+ case "off": {
59
+ if (schedule.queued === 0)
60
+ return { ...base, runsDryAt: null };
61
+ const queued = sorted.filter((run) => run.topicSource === "queued");
62
+ const last = queued.at(-1);
63
+ // The queue reaches past the window: it doesn't run dry within it.
64
+ if (last === undefined || queued.length < schedule.queued)
65
+ return { ...base, runsDryAt: null };
66
+ const next = nextOccurrence(schedule.cadence, schedule.timezone, new Date(new Date(last.at).valueOf() + 1000));
67
+ return { ...base, runsDryAt: next === null ? null : next.toISOString() };
68
+ }
69
+ // New topics wait for approval, so a run past the approved queue has nothing yet.
70
+ case "hold":
71
+ return { ...base, runsDryAt: firstUnqueued };
72
+ // It refills its queue by itself, unless finding topics keeps failing.
73
+ case "queue":
74
+ return { ...base, runsDryAt: schedule.generationFailing ? firstUnqueued : null };
75
+ }
76
+ }
@@ -0,0 +1,14 @@
1
+ // Autopilot health: how far ahead the schedules' content is, and what is about to stop them
2
+ // (a CLI signed out, a plan or key running low, the disk filling, a queue of topics running
3
+ // out, topic generation failing, a model retiring). Browser-safe: Home reads these shapes.
4
+ // ceiling: the forecast looks two months ahead; the calendar can read 92 days.
5
+ export const forecastDays = 60;
6
+ // ceiling: a hidden warning comes back after a week if it is still true.
7
+ export const snoozeDays = 7;
8
+ // ceiling: a queue with fewer runs than this left, and nothing to refill it, is a warning.
9
+ export const fewTopicsLeft = 3;
10
+ // ceiling: the plan or key share at which work may soon stop.
11
+ export const lowPercent = 80;
12
+ // ceiling: free disk below either of these is a warning.
13
+ export const diskLowBytes = 5 * 1024 ** 3;
14
+ export const diskLowShare = 0.1;
@@ -0,0 +1,27 @@
1
+ import { webhookBody } from "../notifications/address.js";
2
+ import { sendFailureText } from "../notifications/send.js";
3
+ import { readNotificationUrl } from "../notifications/settings.js";
4
+ import { markWeeklySent, weeklyNotifyOf, weeklySentFor } from "./repo.js";
5
+ import { weeklyDue, weeklyNoticeText, weeklyReport } from "./weekly.js";
6
+ const weekMs = 7 * 24 * 60 * 60_000;
7
+ // The Monday report, sent to the Notification URL once a week while Weekly report is on. A
8
+ // send that fails is tried again at the next check; one that went out is not sent again.
9
+ export async function sendWeeklyReport(deps) {
10
+ if (!weeklyNotifyOf(deps.db))
11
+ return "off";
12
+ const now = deps.clock.now();
13
+ const { due, week } = weeklyDue(now, deps.timeZone, weeklySentFor(deps.db));
14
+ if (!due)
15
+ return "not-due";
16
+ const url = readNotificationUrl(deps.db);
17
+ if (url === null)
18
+ return "no-url";
19
+ const report = weeklyReport(deps.db, new Date(now.valueOf() - weekMs).toISOString(), now.toISOString());
20
+ const result = await deps.send(url, webhookBody(weeklyNoticeText(report)));
21
+ if (!result.ok) {
22
+ deps.log.write("warn", "autopilot.weekly", { detail: sendFailureText(result) });
23
+ return "failed";
24
+ }
25
+ markWeeklySent(deps.db, week);
26
+ return "sent";
27
+ }
@@ -0,0 +1,34 @@
1
+ import { z } from "zod";
2
+ import { readJsonSetting, writeSetting } from "../settings/repo.js";
3
+ const weeklyNotifyKey = "autopilot.weekly-notify";
4
+ const weeklySentKey = "autopilot.weekly-sent-for";
5
+ // The hidden warnings still hidden at `now`; ended ones are removed as they are read.
6
+ export function readSnoozes(db, now) {
7
+ const at = now.toISOString();
8
+ db.prepare("DELETE FROM autopilot_snoozes WHERE until<=?").run(at);
9
+ return new Map(db
10
+ .prepare("SELECT warning_key, until FROM autopilot_snoozes")
11
+ .all()
12
+ .map((row) => {
13
+ const parsed = z.object({ warning_key: z.string(), until: z.string() }).parse(row);
14
+ return [parsed.warning_key, parsed.until];
15
+ }));
16
+ }
17
+ export function snoozeWarning(db, key, until) {
18
+ db.prepare("INSERT INTO autopilot_snoozes(warning_key, until) VALUES (?,?) ON CONFLICT(warning_key) DO UPDATE SET until=excluded.until").run(key, until.toISOString());
19
+ }
20
+ // Settings → Notifications' Weekly report. Off unless the person turned it on.
21
+ export function weeklyNotifyOf(db) {
22
+ return readJsonSetting(db, weeklyNotifyKey, z.boolean(), false);
23
+ }
24
+ export function setWeeklyNotify(db, enabled) {
25
+ writeSetting(db, weeklyNotifyKey, JSON.stringify(enabled));
26
+ }
27
+ // The ISO week the last report was sent for ("2026-W42").
28
+ export function weeklySentFor(db) {
29
+ const week = readJsonSetting(db, weeklySentKey, z.string(), null);
30
+ return week ?? undefined;
31
+ }
32
+ export function markWeeklySent(db, week) {
33
+ writeSetting(db, weeklySentKey, JSON.stringify(week));
34
+ }
@@ -0,0 +1,194 @@
1
+ import { errorText } from "../../kernel/errors.js";
2
+ import { defaultChannelId } from "../channels/model.js";
3
+ import { projectChannels, templateChannelId } from "../channels/repo.js";
4
+ import { choiceSites, retiredModelUsage, slotLabels } from "../model-upkeep/usage.js";
5
+ import { weekSummary } from "../run-cost/week.js";
6
+ import { calendarRange } from "../schedules/agenda.js";
7
+ import { scheduleRows } from "../schedules/repo.js";
8
+ import { bufferForecast } from "./buffer.js";
9
+ import { forecastDays } from "./model.js";
10
+ import { readSnoozes, weeklyNotifyOf } from "./repo.js";
11
+ import { warningsOf } from "./warnings.js";
12
+ import { weeklyReport } from "./weekly.js";
13
+ const dayMs = 24 * 60 * 60_000;
14
+ // ceiling: one probe that hangs is skipped after this, so the hourly check always ends.
15
+ export const probeTimeoutMs = 15_000;
16
+ const clis = {
17
+ "claude-code": { name: "Claude Code", signIn: "claude auth login" },
18
+ codex: { name: "Codex", signIn: "codex login" },
19
+ gemini: { name: "Antigravity CLI", signIn: "agy" },
20
+ };
21
+ const cliOfProvider = (provider) => provider === "codex-image"
22
+ ? "codex"
23
+ : provider === "claude-code" || provider === "codex" || provider === "gemini"
24
+ ? provider
25
+ : undefined;
26
+ const live = (schedule) => schedule.deletedAt === null && (schedule.status === "active" || schedule.status === "paused");
27
+ // A design schedule makes designed pieces from dated rows: no video fills a slot, no topic runs
28
+ // out and no CLI writes it, so the forecast and its warnings leave it out entirely.
29
+ const makesVideos = (schedule) => schedule.templateKind !== "design";
30
+ // The CLIs an active schedule's template or topic finding chooses: the only ones worth asking.
31
+ export function clisInUse(db) {
32
+ const schedules = scheduleRows(db).filter((one) => one.deletedAt === null && one.status === "active" && makesVideos(one));
33
+ const templates = new Set(schedules.map((one) => one.templateId));
34
+ const ids = new Set(schedules.map((one) => one.id));
35
+ const found = new Set();
36
+ for (const site of choiceSites(db)) {
37
+ const used = (site.kind === "template" && templates.has(site.id)) ||
38
+ (site.kind === "schedule" && ids.has(site.id));
39
+ if (!used)
40
+ continue;
41
+ for (const choice of Object.values(site.choices)) {
42
+ const cli = choice === undefined ? undefined : cliOfProvider(choice.provider);
43
+ if (cli !== undefined)
44
+ found.add(cli);
45
+ }
46
+ }
47
+ for (const schedule of schedules) {
48
+ const cli = cliOfProvider(schedule.topicGeneration.llm?.provider ?? "");
49
+ if (schedule.topicGeneration.mode !== "off" && cli !== undefined)
50
+ found.add(cli);
51
+ }
52
+ return [...found].sort();
53
+ }
54
+ async function within(deps, what, probe) {
55
+ const signal = AbortSignal.timeout(probeTimeoutMs);
56
+ try {
57
+ return await Promise.race([
58
+ probe(signal),
59
+ new Promise((_resolve, reject) => {
60
+ signal.addEventListener("abort", () => reject(new Error(`${what} took too long`)), {
61
+ once: true,
62
+ });
63
+ }),
64
+ ]);
65
+ }
66
+ catch (error) {
67
+ deps.log.write("warn", "autopilot.probe", { detail: `${what}: ${errorText(error)}` });
68
+ return undefined;
69
+ }
70
+ }
71
+ export async function collectProbes(deps) {
72
+ const asked = await Promise.all(clisInUse(deps.db).map(async (id) => {
73
+ const login = deps.cliLogin;
74
+ if (login === undefined)
75
+ return undefined;
76
+ const answer = await within(deps, `${clis[id].name} sign-in`, (signal) => login(id, signal));
77
+ return answer === undefined ? undefined : [id, answer];
78
+ }));
79
+ const key = deps.openRouterKey;
80
+ const disk = deps.disk;
81
+ const [openRouter, space] = await Promise.all([
82
+ key === undefined ? undefined : within(deps, "OpenRouter key", key),
83
+ disk === undefined ? undefined : within(deps, "free disk space", () => disk()),
84
+ ]);
85
+ return {
86
+ checkedAt: deps.clock.now().toISOString(),
87
+ clis: new Map(asked.flatMap((one) => (one === undefined ? [] : [one]))),
88
+ openRouter: openRouter ?? null,
89
+ disk: space ?? null,
90
+ };
91
+ }
92
+ // The hourly check's last answers, held in memory: Home reads them and never waits for a CLI.
93
+ export function createAutopilotHealth(deps) {
94
+ let latest;
95
+ let running;
96
+ return {
97
+ latest: () => latest,
98
+ refresh: () => {
99
+ running ??= collectProbes(deps)
100
+ .then((probes) => {
101
+ latest = probes;
102
+ return probes;
103
+ })
104
+ .finally(() => {
105
+ running = undefined;
106
+ });
107
+ return running;
108
+ },
109
+ };
110
+ }
111
+ // Home's autopilot block for every channel, or one: the forecast and warnings from the
112
+ // database now and the probes' last answers, and the last seven days.
113
+ export function autopilotView(deps, probes, channelId) {
114
+ const now = deps.clock.now();
115
+ const until = new Date(now.valueOf() + forecastDays * dayMs);
116
+ const inChannel = (templateId) => channelId === undefined || templateChannelId(deps.db, templateId) === channelId;
117
+ const schedules = scheduleRows(deps.db).filter((one) => live(one) && makesVideos(one) && inChannel(one.templateId));
118
+ const ahead = calendarRange(deps, now, until);
119
+ // Finished videos not uploaded: the calendar's longest look back.
120
+ const behind = calendarRange(deps, new Date(now.valueOf() - 91 * dayMs), now);
121
+ const channels = projectChannels(deps.db);
122
+ const ready = behind.ok
123
+ ? behind.value.projects.filter((one) => one.readyToUpload === true &&
124
+ one.setAside !== true &&
125
+ (channelId === undefined || (channels.get(one.id) ?? defaultChannelId) === channelId)).length
126
+ : 0;
127
+ // A run its template has no version for is skipped: it fills no slot.
128
+ const runs = ahead.ok
129
+ ? ahead.value.runs.filter((run) => makesVideos(run) && inChannel(run.templateId) && run.notReady !== true)
130
+ : [];
131
+ const forecast = bufferForecast({
132
+ now,
133
+ until,
134
+ runs,
135
+ ready,
136
+ schedules: schedules.map((one) => ({
137
+ id: one.id,
138
+ name: one.name,
139
+ paused: one.status === "paused",
140
+ queued: one.items.length,
141
+ generation: one.topicGeneration.mode,
142
+ generationFailing: one.topics.error !== null,
143
+ cadence: one.cadence,
144
+ timezone: one.timezone,
145
+ })),
146
+ });
147
+ const active = new Set(schedules.filter((one) => one.status === "active").map((one) => one.id));
148
+ const templates = new Set(schedules.filter((one) => active.has(one.id)).map((one) => one.templateId));
149
+ const catalogue = deps.catalogue?.();
150
+ const signals = {
151
+ clis: [...(probes?.clis ?? new Map())].map(([id, login]) => ({
152
+ id,
153
+ ...clis[id],
154
+ login,
155
+ })),
156
+ plans: weekSummary(deps.db, now.toISOString()).plans,
157
+ openRouter: probes?.openRouter ?? null,
158
+ disk: probes?.disk ?? null,
159
+ schedules: schedules.map((one) => ({
160
+ id: one.id,
161
+ name: one.name,
162
+ paused: one.status === "paused",
163
+ queued: one.items.length,
164
+ generation: one.topicGeneration.mode,
165
+ topicsError: one.topics.error,
166
+ notReadyTemplate: deps.notReady?.(one.templateId) === undefined
167
+ ? null
168
+ : (deps.template(one.templateId)?.name ?? "this schedule's template"),
169
+ })),
170
+ retired: catalogue === undefined
171
+ ? []
172
+ : retiredModelUsage(deps.db, catalogue).flatMap((use) => (use.kind === "template" && templates.has(use.id)) ||
173
+ (use.kind === "schedule" && active.has(use.id))
174
+ ? [
175
+ {
176
+ kind: use.kind,
177
+ id: use.id,
178
+ name: use.name,
179
+ slot: slotLabels[use.slot],
180
+ model: use.model,
181
+ why: use.why,
182
+ replacement: use.replacement?.name ?? null,
183
+ },
184
+ ]
185
+ : []),
186
+ };
187
+ return {
188
+ forecast,
189
+ warnings: warningsOf(signals, readSnoozes(deps.db, now), now),
190
+ checkedAt: probes?.checkedAt ?? null,
191
+ weekly: weeklyReport(deps.db, new Date(now.valueOf() - 7 * dayMs).toISOString(), now.toISOString(), channelId),
192
+ weeklyNotify: weeklyNotifyOf(deps.db),
193
+ };
194
+ }
@@ -0,0 +1,85 @@
1
+ import { diskLowBytes, diskLowShare, fewTopicsLeft, lowPercent, } from "./model.js";
2
+ const gb = (bytes) => `${(bytes / 1024 ** 3).toFixed(1)} GB`;
3
+ const dollars = (value) => `$${value.toFixed(2)}`;
4
+ // Each warning says what is wrong, why it stops the autopilot, and where the fix is. A
5
+ // warning hidden with Hide stays hidden until its snooze ends.
6
+ export function warningsOf(signals, snoozes, now) {
7
+ const found = [];
8
+ for (const cli of signals.clis)
9
+ if (cli.login === "signed-out")
10
+ found.push({
11
+ key: `cli-signed-out:${cli.id}`,
12
+ severity: "problem",
13
+ text: `${cli.name} is signed out, so scheduled runs that use it will fail. Open a terminal on this computer, run "${cli.signIn}" and sign in.`,
14
+ fix: { kind: "settings", section: "providers", label: "Open Providers" },
15
+ });
16
+ for (const plan of signals.plans) {
17
+ const weekly = plan.weeklyPercent ?? 0;
18
+ const fiveHour = plan.fiveHourPercent ?? 0;
19
+ if (weekly < lowPercent && fiveHour < lowPercent)
20
+ continue;
21
+ const window = weekly >= fiveHour ? "weekly" : "5-hour";
22
+ found.push({
23
+ key: `plan-low:${plan.name}`,
24
+ severity: "warning",
25
+ text: `${plan.name} has used ${String(Math.round(Math.max(weekly, fiveHour)))}% of its ${window} limit, so scheduled runs on it may wait until the limit resets. Check when it resets, or move a template to another provider.`,
26
+ fix: { kind: "settings", section: "usage", label: "See plan usage" },
27
+ });
28
+ }
29
+ const key = signals.openRouter;
30
+ if (key !== null && key.limit > 0 && key.remaining / key.limit < 1 - lowPercent / 100)
31
+ found.push({
32
+ key: "openrouter-low",
33
+ severity: key.remaining <= 0 ? "problem" : "warning",
34
+ text: `Your OpenRouter key has ${dollars(Math.max(0, key.remaining))} left of its ${dollars(key.limit)} limit, so runs that use OpenRouter stop when it runs out. Raise the key's limit or add credit on openrouter.ai.`,
35
+ fix: { kind: "settings", section: "providers", label: "Open Providers" },
36
+ });
37
+ const disk = signals.disk;
38
+ if (disk !== null &&
39
+ (disk.freeBytes < diskLowBytes ||
40
+ (disk.totalBytes > 0 && disk.freeBytes / disk.totalBytes < diskLowShare)))
41
+ found.push({
42
+ key: "disk-low",
43
+ severity: disk.freeBytes < diskLowBytes ? "problem" : "warning",
44
+ text: `Only ${gb(disk.freeBytes)} is free on the disk Slopify saves to, so renders and downloads may fail. Free some space, or delete old versions and empty the Trash in Settings → Backup & storage.`,
45
+ fix: { kind: "settings", section: "storage", label: "Open Backup & storage" },
46
+ });
47
+ for (const schedule of signals.schedules) {
48
+ if (schedule.paused)
49
+ continue;
50
+ if (schedule.notReadyTemplate !== null)
51
+ found.push({
52
+ key: `template-not-ready:${schedule.id}`,
53
+ severity: "problem",
54
+ text: `“${schedule.name}” skips its runs because its template “${schedule.notReadyTemplate}” has no version marked ready. Open it in Library → Templates → Open studio and press Mark ready.`,
55
+ fix: { kind: "templates", label: "Open Templates" },
56
+ });
57
+ if (schedule.topicsError !== null && schedule.generation !== "off")
58
+ found.push({
59
+ key: `topics-failing:${schedule.id}`,
60
+ severity: "problem",
61
+ text: `“${schedule.name}” couldn't find new topics: ${schedule.topicsError} Its queue won't refill until that is fixed. Open the schedule, fix what the error says, then find topics again.`,
62
+ fix: { kind: "schedule", scheduleId: schedule.id, label: "Open the schedule" },
63
+ });
64
+ if (schedule.generation === "off" && schedule.queued > 0 && schedule.queued < fewTopicsLeft)
65
+ found.push({
66
+ key: `few-topics:${schedule.id}:${String(schedule.queued)}`,
67
+ severity: "warning",
68
+ text: `“${schedule.name}” has ${schedule.queued === 1 ? "1 topic" : `${String(schedule.queued)} topics`} left and doesn't find new ones, so it stops after ${schedule.queued === 1 ? "its next run" : `its next ${String(schedule.queued)} runs`}. Add topics to its queue, or turn on topic generation.`,
69
+ fix: { kind: "schedule", scheduleId: schedule.id, label: "Open the schedule" },
70
+ });
71
+ }
72
+ for (const use of signals.retired)
73
+ found.push({
74
+ key: `retired:${use.kind}:${use.id}:${use.model}`,
75
+ severity: "warning",
76
+ text: `The ${use.slot.toLowerCase()} “${use.model}” in ${use.kind} “${use.name}” is ${use.why === "retired" ? "being retired" : "no longer offered"}, so its runs will fail once it is gone. Switch it to ${use.replacement === null ? "another model" : `“${use.replacement}”`}.`,
77
+ fix: use.kind === "schedule"
78
+ ? { kind: "schedule", scheduleId: use.id, label: "Open the schedule" }
79
+ : { kind: "templates", label: "Open Templates" },
80
+ });
81
+ const at = now.toISOString();
82
+ return found
83
+ .filter((warning) => (snoozes.get(warning.key) ?? "") <= at)
84
+ .toSorted((a, b) => Number(b.severity === "problem") - Number(a.severity === "problem"));
85
+ }
@@ -0,0 +1,100 @@
1
+ /// <reference lib="esnext.temporal" />
2
+ import { z } from "zod";
3
+ import { liveProject } from "../admission/repo.js";
4
+ import { defaultChannelId } from "../channels/model.js";
5
+ import { projectChannels } from "../channels/repo.js";
6
+ import { weekSummary } from "../run-cost/week.js";
7
+ import { validTimeZone } from "../schedules/calendar.js";
8
+ import { notTemplateSample } from "../template-studio/repo.js";
9
+ // ceiling: the report names this many failed steps; the rest are counted.
10
+ const failedShown = 10;
11
+ // The hour on Monday, in the person's time zone, from which the week's report is sent.
12
+ const sendHour = 9;
13
+ const stageNames = {
14
+ research: "Research",
15
+ article: "Article",
16
+ audio: "Narration",
17
+ images: "Images",
18
+ thumbnail: "Thumbnail",
19
+ video: "Video",
20
+ document: "Document",
21
+ };
22
+ const failedRow = z.object({
23
+ project_id: z.string(),
24
+ title: z.string(),
25
+ kind: z.string(),
26
+ failure_reason: z.string().nullable(),
27
+ });
28
+ // The last week in one read: what finished and what failed, what it cost and where the plans
29
+ // stand. The same stored calls Home's This week reads; nothing is estimated.
30
+ export function weeklyReport(db, since, until, channelId) {
31
+ const summary = weekSummary(db, since, channelId);
32
+ const channels = channelId === undefined ? undefined : projectChannels(db);
33
+ const failed = db
34
+ .prepare(`SELECT stages.project_id, projects.title, stages.kind, stages.failure_reason
35
+ FROM stages JOIN projects ON projects.id=stages.project_id
36
+ WHERE stages.state='failed' AND stages.finished_at>=? AND ${liveProject()}
37
+ AND ${notTemplateSample()}
38
+ ORDER BY stages.finished_at DESC, stages.id`)
39
+ .all(since)
40
+ .map((raw) => failedRow.parse(raw))
41
+ .filter((row) => channels === undefined || (channels.get(row.project_id) ?? defaultChannelId) === channelId)
42
+ .map((row) => ({
43
+ projectId: row.project_id,
44
+ title: row.title.trim() === "" ? "Untitled project" : row.title,
45
+ stage: stageNames[row.kind] ?? row.kind,
46
+ reason: row.failure_reason ?? "It stopped without a reason.",
47
+ }));
48
+ return {
49
+ since,
50
+ until,
51
+ finished: summary.videos,
52
+ failed: failed.slice(0, failedShown),
53
+ failedMore: Math.max(0, failed.length - failedShown),
54
+ cost: summary.cost,
55
+ unpriced: summary.unpriced,
56
+ apiEquivalent: summary.apiEquivalent,
57
+ plans: summary.plans.map((plan) => ({
58
+ name: plan.name,
59
+ weeklyPercent: plan.weeklyPercent,
60
+ fiveHourPercent: plan.fiveHourPercent,
61
+ })),
62
+ };
63
+ }
64
+ // The ISO week a moment falls in, in the person's time zone: "2026-W42".
65
+ export function isoWeekOf(now, timeZone) {
66
+ const zone = validTimeZone(timeZone) ? timeZone : "UTC";
67
+ const date = Temporal.Instant.fromEpochMilliseconds(now.valueOf()).toZonedDateTimeISO(zone);
68
+ return `${String(date.yearOfWeek)}-W${String(date.weekOfYear).padStart(2, "0")}`;
69
+ }
70
+ // Whether this week's report is still to be sent: from Monday 09:00 in the person's time zone,
71
+ // once a week. A Slopify that was off on Monday sends it when it next runs that week.
72
+ export function weeklyDue(now, timeZone, sentFor) {
73
+ const zone = validTimeZone(timeZone) ? timeZone : "UTC";
74
+ const date = Temporal.Instant.fromEpochMilliseconds(now.valueOf()).toZonedDateTimeISO(zone);
75
+ const week = isoWeekOf(now, zone);
76
+ const started = date.dayOfWeek > 1 || date.hour >= sendHour;
77
+ return { due: started && sentFor !== week, week };
78
+ }
79
+ const money = (value) => `$${value.toFixed(2)}`;
80
+ export function weeklyNoticeText(report) {
81
+ const failures = report.failed.length + report.failedMore;
82
+ const plans = report.plans
83
+ .filter((plan) => plan.weeklyPercent !== null)
84
+ .map((plan) => `${plan.name} ${String(Math.round(plan.weeklyPercent ?? 0))}% of its week`);
85
+ const named = report.failed
86
+ .slice(0, 3)
87
+ .map((one) => `“${one.title}” (${one.stage}): ${one.reason.replace(/[.!?\s]+$/, "")}`);
88
+ return {
89
+ headline: `Slopify last week: ${report.finished === 1 ? "1 video" : `${String(report.finished)} videos`} made, ${failures === 1 ? "1 step" : `${String(failures)} steps`} failed`,
90
+ detail: [
91
+ `Cost ${money(report.cost)}${report.unpriced > 0 ? ` plus ${String(report.unpriced)} calls with no known price` : ""}.`,
92
+ plans.length === 0 ? "" : `Plans: ${plans.join(", ")}.`,
93
+ named.length === 0
94
+ ? ""
95
+ : `Failed: ${named.join("; ")}${failures > named.length ? `; and ${String(failures - named.length)} more` : ""}. Open each project and use the button beside the failed step.`,
96
+ ]
97
+ .filter((line) => line !== "")
98
+ .join(" "),
99
+ };
100
+ }
@@ -1,6 +1,7 @@
1
1
  import { constants } from "node:fs";
2
2
  import { lstat, mkdir, open, readdir, rename, rm } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
+ import { errnoCode } from "../../kernel/errors.js";
4
5
  // Only files whose whole name matches these are this feature's. Pruning and the leftover sweep
5
6
  // touch nothing else in the folder: not an Export everything download
6
7
  // (slopify-backup-2026-09-27.tar), not a file the user renamed, not a symlink.
@@ -92,7 +93,7 @@ export async function listBackups(folder) {
92
93
  names = await readdir(folder);
93
94
  }
94
95
  catch (error) {
95
- if (codeOf(error) === "ENOENT")
96
+ if (errnoCode(error) === "ENOENT")
96
97
  return [];
97
98
  throw error;
98
99
  }
@@ -128,8 +129,3 @@ export async function removeLeftovers(folder) {
128
129
  }
129
130
  return removed;
130
131
  }
131
- export function codeOf(error) {
132
- return error instanceof Error && "code" in error && typeof error.code === "string"
133
- ? error.code
134
- : undefined;
135
- }
@@ -1,28 +1,16 @@
1
- import { readSetting, writeSetting } from "../settings/repo.js";
1
+ import { readJsonSetting, writeSetting } from "../settings/repo.js";
2
2
  import { backupStatusSchema, configKey, defaultConfig, emptyStatus, statusKey, storedConfigSchema, } from "./model.js";
3
3
  // A value this build cannot read (hand-edited, or written by a newer Slopify) falls back to
4
4
  // the default rather than stopping the app; the next save writes a good one.
5
5
  export function readConfig(db, fallbackTimeZone) {
6
- const parsed = storedConfigSchema.safeParse(parseJson(readSetting(db, configKey)));
7
- return parsed.success ? parsed.data : defaultConfig(fallbackTimeZone);
6
+ return readJsonSetting(db, configKey, storedConfigSchema, defaultConfig(fallbackTimeZone));
8
7
  }
9
8
  export function writeConfig(db, config) {
10
9
  writeSetting(db, configKey, JSON.stringify(storedConfigSchema.parse(config)));
11
10
  }
12
11
  export function readStatus(db) {
13
- const parsed = backupStatusSchema.safeParse(parseJson(readSetting(db, statusKey)));
14
- return parsed.success ? parsed.data : emptyStatus;
12
+ return readJsonSetting(db, statusKey, backupStatusSchema, emptyStatus);
15
13
  }
16
14
  export function writeStatus(db, status) {
17
15
  writeSetting(db, statusKey, JSON.stringify(backupStatusSchema.parse(status)));
18
16
  }
19
- function parseJson(value) {
20
- if (value === undefined)
21
- return undefined;
22
- try {
23
- return JSON.parse(value);
24
- }
25
- catch {
26
- return undefined;
27
- }
28
- }
@@ -1,7 +1,8 @@
1
1
  import { statfs } from "node:fs/promises";
2
+ import { errnoCode } from "../../kernel/errors.js";
2
3
  import { BackupBusyError, planBackup, streamBackup, } from "../storage/backup-export.js";
3
4
  import { defaultBackupsDir } from "../storage/layout.js";
4
- import { backupFileName, codeOf, ensureFolder, listBackups, pruneBackups, removeLeftovers, writeAtomically, } from "./files.js";
5
+ import { backupFileName, ensureFolder, listBackups, pruneBackups, removeLeftovers, writeAtomically, } from "./files.js";
5
6
  import { effectiveFolder, folderProblem, hostFolder } from "./folder.js";
6
7
  import { readConfig, readStatus, writeConfig, writeStatus } from "./repo.js";
7
8
  import { decideBackup, nextSlot } from "./schedule.js";
@@ -59,7 +60,7 @@ export function createBackupService(deps) {
59
60
  }
60
61
  catch (error) {
61
62
  deps.log.write("warn", "backups.prune", { detail: String(error) });
62
- detail = `The backup was saved, but older backups in ${folder} could not be removed (${codeOf(error) ?? "unknown error"}), so more than ${settings.keep} are kept. Check Slopify can delete files in that folder, or pick another Folder in Settings → Backups.`;
63
+ detail = `The backup was saved, but older backups in ${folder} could not be removed (${errnoCode(error) ?? "unknown error"}), so more than ${settings.keep} are kept. Check Slopify can delete files in that folder, or pick another Folder in Settings → Backups.`;
63
64
  }
64
65
  record({
65
66
  ...attempt,
@@ -279,7 +280,7 @@ export function explain(error, folder, retries, signal) {
279
280
  return "The backup was not saved: Slopify stopped while writing it, and the unfinished file was removed. It runs again after Slopify starts.";
280
281
  if (error instanceof NoSpaceError)
281
282
  return `The backup was not saved: it needs about ${bytes(error.needed)}, but the disk holding ${folder} has ${bytes(error.free)} free. Free up space there, lower "Keep last" in Settings → Backups, or pick another Folder.`;
282
- switch (codeOf(error)) {
283
+ switch (errnoCode(error)) {
283
284
  case "ENOSPC":
284
285
  case "EDQUOT":
285
286
  return `The backup was not saved: the disk holding ${folder} ran out of space. Free up space there, lower "Keep last" in Settings → Backups, or pick another Folder.`;
@@ -1,3 +1,4 @@
1
+ import { errorText } from "../../kernel/errors.js";
1
2
  import { castImageMaxBytes, castImagesPerMember, sniffImage, storeImageBlob } from "./images.js";
2
3
  import { castMemberById } from "./repo.js";
3
4
  import { castGenerateSchema } from "./schema.js";
@@ -58,7 +59,7 @@ export function generateCastImage(deps, memberId, input) {
58
59
  .prepare("UPDATE cast_images SET state='ready',sha256=? WHERE id=? AND state='generating'")
59
60
  .run(sha256, id);
60
61
  }, (error) => {
61
- const reason = error instanceof Error ? error.message : String(error);
62
+ const reason = errorText(error);
62
63
  deps.log?.write("warn", "channels.cast-image.failed", { detail: reason.slice(0, 500) });
63
64
  fail(deps, id, `The picture couldn't be made: ${reason.slice(0, 500)} Check the image provider in Settings → Providers, then press Generate again.`);
64
65
  });
@@ -1,5 +1,4 @@
1
1
  // Every install has this channel (migration 0032). A template or project whose channel is
2
2
  // NULL, or names a channel this install does not have, belongs to it.
3
3
  export const defaultChannelId = "00000000-0000-4000-8000-000000000001";
4
- export const defaultChannelName = "My channel";
5
4
  export const castKinds = ["character", "creature", "place", "object"];
@@ -8,9 +8,6 @@ import { defaultDocumentTheme } from "../document/model.js";
8
8
  import { entryByName } from "../library/repo.js";
9
9
  import { channelById, resolveChannelId } from "./repo.js";
10
10
  import { brandDocument, castSnapshot } from "./runs.js";
11
- export function channelChanged(db, before, after) {
12
- return resolveChannelId(db, before.channelId) !== resolveChannelId(db, after.channelId);
13
- }
14
11
  export function rebrandedEdit(db, base, edit) {
15
12
  const oldId = resolveChannelId(db, base.channelId);
16
13
  const newId = resolveChannelId(db, edit.config.channelId);