@gentbajko/slopify 2.4.0 → 3.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 (469) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +21 -40
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +11 -1
  6. package/dist/adapters/alignment/cache.js +4 -1
  7. package/dist/adapters/alignment/ctc.js +25 -23
  8. package/dist/adapters/alignment/index.js +37 -6
  9. package/dist/adapters/alignment/multilingual.js +267 -0
  10. package/dist/adapters/alignment/protocol.js +11 -0
  11. package/dist/adapters/alignment/quality.js +4 -3
  12. package/dist/adapters/alignment/sentences.js +95 -0
  13. package/dist/adapters/alignment/spec.js +24 -0
  14. package/dist/adapters/alignment/spell.js +769 -0
  15. package/dist/adapters/alignment/text.js +56 -9
  16. package/dist/adapters/alignment/window.js +34 -30
  17. package/dist/adapters/alignment/worker.js +23 -21
  18. package/dist/adapters/explain.js +11 -11
  19. package/dist/adapters/host-cli/index.js +55 -5
  20. package/dist/adapters/host-cli/transport.js +13 -6
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +60 -18
  23. package/dist/adapters/image/codex.js +166 -25
  24. package/dist/adapters/image/fal.js +31 -3
  25. package/dist/adapters/image/google.js +17 -2
  26. package/dist/adapters/image/openai.js +45 -16
  27. package/dist/adapters/image/reference.js +41 -0
  28. package/dist/adapters/image/replicate.js +14 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +36 -0
  31. package/dist/adapters/llm/claude-code.js +168 -32
  32. package/dist/adapters/llm/cli-login-error.js +1 -1
  33. package/dist/adapters/llm/codex-limits.js +157 -0
  34. package/dist/adapters/llm/codex-models.js +2 -1
  35. package/dist/adapters/llm/codex-server-models.js +2 -1
  36. package/dist/adapters/llm/codex.js +99 -12
  37. package/dist/adapters/llm/document-workspace.js +2 -2
  38. package/dist/adapters/llm/gemini-workspace.js +1 -1
  39. package/dist/adapters/llm/gemini.js +16 -3
  40. package/dist/adapters/llm/image-workspace.js +58 -0
  41. package/dist/adapters/llm/openrouter.js +11 -3
  42. package/dist/adapters/llm/run-cli.js +26 -9
  43. package/dist/adapters/tts/cartesia.js +10 -0
  44. package/dist/adapters/tts/elevenlabs.js +31 -9
  45. package/dist/adapters/tts/inworld-async.js +9 -5
  46. package/dist/adapters/tts/inworld.js +17 -3
  47. package/dist/adapters/tts/openai.js +3 -0
  48. package/dist/adapters/tts/voice-languages.js +80 -0
  49. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  50. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/OFL.txt +93 -0
  55. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  56. package/dist/assets/models.yaml +7 -0
  57. package/dist/assets/sample/sample-audiobook.tar +0 -0
  58. package/dist/assets/sample/sample-podcast.tar +0 -0
  59. package/dist/assets/sample/sample-project.tar +0 -0
  60. package/dist/catalog/merge.js +132 -0
  61. package/dist/catalog/registry.js +33 -12
  62. package/dist/catalog/runtime-models.js +1 -1
  63. package/dist/catalog/schema.js +12 -1
  64. package/dist/catalog/store.js +147 -8
  65. package/dist/catalog/validate.js +17 -2
  66. package/dist/compose.yaml +71 -0
  67. package/dist/edge/autostart/docker-record.js +32 -0
  68. package/dist/edge/autostart/docker.js +50 -0
  69. package/dist/edge/autostart/index.js +78 -0
  70. package/dist/edge/autostart/launcher.js +176 -0
  71. package/dist/edge/autostart/model.js +3 -0
  72. package/dist/edge/autostart/native.js +210 -0
  73. package/dist/edge/autostart/prompt.js +66 -0
  74. package/dist/edge/autostart/service.js +81 -0
  75. package/dist/edge/cli.js +57 -51
  76. package/dist/edge/docker-install/activation.js +56 -0
  77. package/dist/edge/docker-install/apply.js +458 -0
  78. package/dist/edge/docker-install/engine.js +362 -0
  79. package/dist/edge/docker-install/run.js +154 -0
  80. package/dist/edge/docker-install/state.js +146 -0
  81. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  82. package/dist/edge/docker.js +14 -22
  83. package/dist/edge/events/hub.js +11 -0
  84. package/dist/edge/host-cli.js +1 -1
  85. package/dist/edge/http/actions.js +32 -10
  86. package/dist/edge/http/app.js +60 -2
  87. package/dist/edge/http/auditions.js +70 -0
  88. package/dist/edge/http/autostart.js +37 -0
  89. package/dist/edge/http/backups.js +40 -0
  90. package/dist/edge/http/channel-memory.js +92 -0
  91. package/dist/edge/http/channels.js +128 -0
  92. package/dist/edge/http/entries.js +22 -3
  93. package/dist/edge/http/files.js +1 -1
  94. package/dist/edge/http/folder-location.js +1 -1
  95. package/dist/edge/http/home.js +39 -0
  96. package/dist/edge/http/host-cli.js +45 -6
  97. package/dist/edge/http/narration-peaks.js +76 -0
  98. package/dist/edge/http/onboarding.js +152 -0
  99. package/dist/edge/http/open-folder.js +1 -1
  100. package/dist/edge/http/patch-notes.js +45 -0
  101. package/dist/edge/http/planning.js +4 -2
  102. package/dist/edge/http/problem.js +1 -0
  103. package/dist/edge/http/project-create.js +87 -0
  104. package/dist/edge/http/projects.js +31 -99
  105. package/dist/edge/http/prompts.js +40 -3
  106. package/dist/edge/http/pronunciations.js +23 -4
  107. package/dist/edge/http/providers.js +86 -0
  108. package/dist/edge/http/reviews.js +121 -0
  109. package/dist/edge/http/revision-files.js +1 -1
  110. package/dist/edge/http/revisions.js +1 -1
  111. package/dist/edge/http/run-cost.js +25 -0
  112. package/dist/edge/http/schedules.js +111 -6
  113. package/dist/edge/http/settings.js +128 -6
  114. package/dist/edge/http/storage-files.js +50 -0
  115. package/dist/edge/http/storage.js +29 -2
  116. package/dist/edge/http/studio.js +207 -0
  117. package/dist/edge/http/style-preview.js +100 -0
  118. package/dist/edge/http/subtitles.js +1 -1
  119. package/dist/edge/http/trash.js +69 -0
  120. package/dist/edge/http/update.js +24 -3
  121. package/dist/edge/http/whats-new.js +15 -0
  122. package/dist/edge/http/youtube-edits.js +83 -0
  123. package/dist/edge/native-update.js +62 -0
  124. package/dist/host-cli/open-folder.js +10 -10
  125. package/dist/host-cli/runtime.js +12 -3
  126. package/dist/kernel/db/migrations/0022-drop-duplicate-held-work.sql +56 -0
  127. package/dist/kernel/db/migrations/0024-reference-attachments.sql +18 -0
  128. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  129. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  130. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  131. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  132. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  133. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  134. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  135. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  136. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  137. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  138. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  139. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  140. package/dist/kernel/paths.js +27 -6
  141. package/dist/kernel/pipeline.js +4 -1
  142. package/dist/kernel/ports/host-cli.js +139 -16
  143. package/dist/kernel/ports/image.js +3 -1
  144. package/dist/kernel/ports/key-probe.js +1 -0
  145. package/dist/kernel/ports/languages.js +157 -0
  146. package/dist/kernel/ports/llm.js +3 -1
  147. package/dist/kernel/ports/model.js +5 -2
  148. package/dist/kernel/ports/narration-aliases.js +60 -0
  149. package/dist/kernel/ports/plan-limits.js +36 -0
  150. package/dist/kernel/runner/attempt.js +18 -9
  151. package/dist/kernel/runner/audition.js +36 -0
  152. package/dist/kernel/runner/graph.js +24 -5
  153. package/dist/kernel/runner/index.js +35 -5
  154. package/dist/kernel/runner/meter.js +1 -0
  155. package/dist/kernel/runner/providers.js +164 -9
  156. package/dist/kernel/runner/retry-policy.js +43 -0
  157. package/dist/kernel/runner/standalone.js +134 -0
  158. package/dist/kernel/runner/work-authority.js +28 -2
  159. package/dist/main.js +321 -20
  160. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  161. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  162. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  163. package/dist/patch-notes/3.0.0.md +70 -0
  164. package/dist/patch-notes/index.json +26 -0
  165. package/dist/slices/admission/model.js +35 -0
  166. package/dist/slices/admission/repo.js +24 -4
  167. package/dist/slices/admission/rules.js +126 -1
  168. package/dist/slices/admission/schema.js +68 -1
  169. package/dist/slices/admission/short-mode.js +47 -0
  170. package/dist/slices/admission/start.js +90 -16
  171. package/dist/slices/article/continuation.js +2 -2
  172. package/dist/slices/article/provided-entries.js +2 -2
  173. package/dist/slices/article/run.js +6 -2
  174. package/dist/slices/article/segments.js +3 -3
  175. package/dist/slices/backups/files.js +135 -0
  176. package/dist/slices/backups/folder.js +43 -0
  177. package/dist/slices/backups/model.js +65 -0
  178. package/dist/slices/backups/repo.js +28 -0
  179. package/dist/slices/backups/schedule.js +60 -0
  180. package/dist/slices/backups/service.js +311 -0
  181. package/dist/slices/batch/index.js +9 -2
  182. package/dist/slices/cancel/index.js +1 -1
  183. package/dist/slices/channels/cast-images.js +110 -0
  184. package/dist/slices/channels/cast-match.js +32 -0
  185. package/dist/slices/channels/images.js +19 -0
  186. package/dist/slices/channels/model.js +5 -0
  187. package/dist/slices/channels/rebrand.js +100 -0
  188. package/dist/slices/channels/repo.js +218 -0
  189. package/dist/slices/channels/runs.js +129 -0
  190. package/dist/slices/channels/schema.js +129 -0
  191. package/dist/slices/channels/service.js +180 -0
  192. package/dist/slices/channels/studio-csv.js +110 -0
  193. package/dist/slices/channels/videos.js +120 -0
  194. package/dist/slices/checkpoints/recovery.js +10 -0
  195. package/dist/slices/control/index.js +2 -1
  196. package/dist/slices/document/fonts.js +1 -1
  197. package/dist/slices/document/render.js +1 -1
  198. package/dist/slices/episodes/related.js +90 -0
  199. package/dist/slices/episodes/repo.js +60 -0
  200. package/dist/slices/episodes/service.js +58 -0
  201. package/dist/slices/episodes/summarize.js +170 -0
  202. package/dist/slices/estimate/index.js +113 -24
  203. package/dist/slices/estimate/requests.js +60 -2
  204. package/dist/slices/fixes/rules.js +73 -0
  205. package/dist/slices/fonts/coverage.js +82 -0
  206. package/dist/slices/fonts/sfnt.js +74 -0
  207. package/dist/slices/images/run.js +8 -5
  208. package/dist/slices/images/scale.js +84 -0
  209. package/dist/slices/library/diff.js +122 -0
  210. package/dist/slices/library/history.js +100 -0
  211. package/dist/slices/library/model.js +3 -0
  212. package/dist/slices/library/photorealistic.js +49 -0
  213. package/dist/slices/library/repo.js +19 -14
  214. package/dist/slices/library/save.js +62 -12
  215. package/dist/slices/library/slots.js +15 -3
  216. package/dist/slices/library/snapshot.js +8 -2
  217. package/dist/slices/library/used-by.js +153 -0
  218. package/dist/slices/loudness/level-pieces.js +41 -0
  219. package/dist/slices/loudness/loudnorm.js +164 -0
  220. package/dist/slices/loudness/model.js +153 -0
  221. package/dist/slices/model-upkeep/model.js +10 -0
  222. package/dist/slices/model-upkeep/switch.js +217 -0
  223. package/dist/slices/model-upkeep/usage.js +183 -0
  224. package/dist/slices/narration/aliases-library.js +31 -0
  225. package/dist/slices/narration/aliases-schema.js +53 -0
  226. package/dist/slices/narration/aliases.js +49 -0
  227. package/dist/slices/narration/blocks.js +352 -0
  228. package/dist/slices/narration/chunk.js +19 -7
  229. package/dist/slices/narration/concat.js +2 -2
  230. package/dist/slices/narration/describe.js +45 -0
  231. package/dist/slices/narration/pauses-model.js +63 -0
  232. package/dist/slices/narration/pauses.js +269 -0
  233. package/dist/slices/narration/peaks-model.js +1 -0
  234. package/dist/slices/narration/peaks.js +94 -0
  235. package/dist/slices/narration/preparation.js +21 -3
  236. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  237. package/dist/slices/narration/pronunciation.js +72 -25
  238. package/dist/slices/narration/run.js +6 -6
  239. package/dist/slices/narration/shared-glossary.js +1 -1
  240. package/dist/slices/narration/steering.js +15 -4
  241. package/dist/slices/notifications/notifier.js +94 -0
  242. package/dist/slices/notifications/rules.js +102 -0
  243. package/dist/slices/notifications/send.js +40 -0
  244. package/dist/slices/notifications/settings.js +34 -0
  245. package/dist/slices/onboarding/copy.js +143 -0
  246. package/dist/slices/onboarding/first-run.js +46 -0
  247. package/dist/slices/onboarding/install.js +119 -0
  248. package/dist/slices/onboarding/model.js +64 -0
  249. package/dist/slices/onboarding/packs.js +491 -0
  250. package/dist/slices/onboarding/quick-short.js +129 -0
  251. package/dist/slices/onboarding/sample.js +132 -0
  252. package/dist/slices/onboarding/state.js +68 -0
  253. package/dist/slices/onboarding/template.js +73 -0
  254. package/dist/slices/patch-notes/library.js +73 -0
  255. package/dist/slices/patch-notes/seen.js +77 -0
  256. package/dist/slices/play-drafts/convert.js +160 -9
  257. package/dist/slices/play-drafts/readiness.js +3 -0
  258. package/dist/slices/play-drafts/repo.js +9 -1
  259. package/dist/slices/play-drafts/review-inputs.js +22 -3
  260. package/dist/slices/play-drafts/schema.js +109 -2
  261. package/dist/slices/play-drafts/service.js +10 -1
  262. package/dist/slices/play-drafts/start.js +43 -18
  263. package/dist/slices/play-drafts/uploads.js +2 -1
  264. package/dist/slices/project-templates/from-project.js +70 -4
  265. package/dist/slices/project-templates/one-off.js +26 -0
  266. package/dist/slices/project-templates/repo.js +2 -1
  267. package/dist/slices/project-templates/service.js +15 -4
  268. package/dist/slices/project-templates/setup.js +29 -2
  269. package/dist/slices/rebuild/admission-repo.js +1 -1
  270. package/dist/slices/rebuild/model.js +6 -0
  271. package/dist/slices/rebuild/preview-details.js +67 -2
  272. package/dist/slices/rebuild/preview-plan.js +13 -6
  273. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  274. package/dist/slices/rebuild/recipe-audio.js +66 -12
  275. package/dist/slices/rebuild/recipe-build.js +15 -5
  276. package/dist/slices/rebuild/recipe-cast.js +18 -0
  277. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  278. package/dist/slices/rebuild/recipe-describe.js +93 -0
  279. package/dist/slices/rebuild/recipe-edit.js +13 -1
  280. package/dist/slices/rebuild/recipe-exports.js +62 -8
  281. package/dist/slices/rebuild/recipe-input-schema.js +43 -1
  282. package/dist/slices/rebuild/recipe-legacy.js +12 -0
  283. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  284. package/dist/slices/rebuild/recipe-model.js +25 -6
  285. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  286. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  287. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  288. package/dist/slices/rebuild/recipe-reference.js +58 -0
  289. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  290. package/dist/slices/rebuild/recipe-save.js +4 -1
  291. package/dist/slices/rebuild/recipe-shorts.js +32 -5
  292. package/dist/slices/rebuild/recipe-text.js +114 -19
  293. package/dist/slices/rebuild/recipe-validation.js +29 -3
  294. package/dist/slices/rebuild/recipe-visual.js +104 -29
  295. package/dist/slices/rebuild/recipe-voices.js +286 -0
  296. package/dist/slices/rebuild/recipe-work.js +77 -6
  297. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  298. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  299. package/dist/slices/rebuild/recovery-model.js +3 -0
  300. package/dist/slices/rebuild/recovery-selection.js +28 -2
  301. package/dist/slices/rebuild/recovery.js +35 -17
  302. package/dist/slices/rebuild/repo.js +3 -3
  303. package/dist/slices/rebuild/retry.js +18 -0
  304. package/dist/slices/rebuild/review-redo.js +110 -0
  305. package/dist/slices/rebuild/runtime-actions.js +14 -2
  306. package/dist/slices/rebuild/runtime-admission.js +4 -1
  307. package/dist/slices/rebuild/runtime-animate.js +4 -4
  308. package/dist/slices/rebuild/runtime-article.js +5 -5
  309. package/dist/slices/rebuild/runtime-document.js +4 -4
  310. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  311. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  312. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  313. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  314. package/dist/slices/rebuild/runtime-export.js +84 -19
  315. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  316. package/dist/slices/rebuild/runtime-image.js +69 -0
  317. package/dist/slices/rebuild/runtime-local.js +154 -27
  318. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  319. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  320. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  321. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  322. package/dist/slices/rebuild/runtime-piece-label.js +29 -0
  323. package/dist/slices/rebuild/runtime-provider.js +134 -19
  324. package/dist/slices/rebuild/runtime-publication.js +21 -5
  325. package/dist/slices/rebuild/runtime-review.js +237 -0
  326. package/dist/slices/rebuild/runtime-run.js +17 -11
  327. package/dist/slices/rebuild/runtime-shorts.js +48 -23
  328. package/dist/slices/rebuild/runtime-store.js +39 -10
  329. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  330. package/dist/slices/rebuild/runtime-voices.js +103 -0
  331. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  332. package/dist/slices/rebuild/service-readiness.js +15 -1
  333. package/dist/slices/rebuild/soften.js +49 -0
  334. package/dist/slices/rebuild/transition-repo.js +10 -3
  335. package/dist/slices/research/run.js +4 -4
  336. package/dist/slices/research/synthesis.js +2 -2
  337. package/dist/slices/reviews/model.js +105 -0
  338. package/dist/slices/reviews/outcome.js +22 -0
  339. package/dist/slices/reviews/repo.js +106 -0
  340. package/dist/slices/reviews/rules.js +63 -0
  341. package/dist/slices/reviews/verdict.js +71 -0
  342. package/dist/slices/reviews/view.js +30 -0
  343. package/dist/slices/revisions/adopt-content.js +12 -2
  344. package/dist/slices/revisions/adopt.js +9 -2
  345. package/dist/slices/revisions/downloads.js +7 -1
  346. package/dist/slices/revisions/image-plan.js +183 -0
  347. package/dist/slices/revisions/mutation-assets.js +43 -9
  348. package/dist/slices/revisions/mutation-cues.js +71 -0
  349. package/dist/slices/revisions/mutation-prepare.js +8 -10
  350. package/dist/slices/revisions/mutation-request.js +2 -1
  351. package/dist/slices/revisions/mutations.js +97 -3
  352. package/dist/slices/revisions/schema.js +16 -2
  353. package/dist/slices/run-cost/limits.js +128 -0
  354. package/dist/slices/run-cost/meter.js +47 -0
  355. package/dist/slices/run-cost/panel.js +234 -0
  356. package/dist/slices/run-cost/pricing.js +92 -0
  357. package/dist/slices/run-cost/week.js +108 -0
  358. package/dist/slices/schedules/agenda.js +158 -0
  359. package/dist/slices/schedules/repo.js +58 -18
  360. package/dist/slices/schedules/scheduler.js +67 -16
  361. package/dist/slices/schedules/schema.js +138 -1
  362. package/dist/slices/schedules/service.js +48 -4
  363. package/dist/slices/schedules/similar.js +90 -0
  364. package/dist/slices/schedules/topic-list.js +163 -0
  365. package/dist/slices/schedules/topics.js +422 -0
  366. package/dist/slices/settings/first-run.js +102 -0
  367. package/dist/slices/settings/health.js +190 -0
  368. package/dist/slices/settings/key-guides.js +128 -0
  369. package/dist/slices/settings/key-test.js +85 -0
  370. package/dist/slices/settings/model.js +11 -1
  371. package/dist/slices/settings/playback.js +15 -0
  372. package/dist/slices/settings/repo.js +17 -3
  373. package/dist/slices/settings/voices.js +64 -4
  374. package/dist/slices/settings/whats-new.js +64 -0
  375. package/dist/slices/shorts/captions.js +9 -0
  376. package/dist/slices/shorts/pick.js +3 -3
  377. package/dist/slices/shorts/prompts.js +1 -1
  378. package/dist/slices/shorts/render.js +54 -5
  379. package/dist/slices/storage/asset-name.js +3 -0
  380. package/dist/slices/storage/backup-export.js +48 -9
  381. package/dist/slices/storage/backup-format.js +38 -1
  382. package/dist/slices/storage/backup-import.js +191 -19
  383. package/dist/slices/storage/delete-project.js +7 -4
  384. package/dist/slices/storage/documents.js +79 -0
  385. package/dist/slices/storage/files-location.js +531 -0
  386. package/dist/slices/storage/layout.js +20 -0
  387. package/dist/slices/storage/model.js +14 -0
  388. package/dist/slices/storage/portable.js +95 -12
  389. package/dist/slices/storage/reconcile.js +19 -2
  390. package/dist/slices/storage/repo.js +4 -1
  391. package/dist/slices/storage/schema.js +11 -0
  392. package/dist/slices/storage/trim.js +146 -0
  393. package/dist/slices/studio/disclosure.js +74 -0
  394. package/dist/slices/studio/model.js +28 -0
  395. package/dist/slices/studio/pack.js +209 -0
  396. package/dist/slices/studio/settings.js +133 -0
  397. package/dist/slices/style-preview/images.js +61 -0
  398. package/dist/slices/style-preview/render.js +161 -0
  399. package/dist/slices/style-preview/schema.js +45 -0
  400. package/dist/slices/style-preview/service.js +93 -0
  401. package/dist/slices/style-preview/settings.js +89 -0
  402. package/dist/slices/subtitles/captions.js +42 -9
  403. package/dist/slices/subtitles/model.js +22 -0
  404. package/dist/slices/subtitles/prepare.js +27 -16
  405. package/dist/slices/subtitles/transcript.js +1 -1
  406. package/dist/slices/thumbnail/run.js +7 -7
  407. package/dist/slices/trash/model.js +25 -0
  408. package/dist/slices/trash/service.js +273 -0
  409. package/dist/slices/uploads/repo.js +20 -0
  410. package/dist/slices/video/ambient-bed-schema.js +15 -0
  411. package/dist/slices/video/ambient-bed.js +95 -0
  412. package/dist/slices/video/ambient-mix.js +72 -0
  413. package/dist/slices/video/audio-inputs.js +2 -2
  414. package/dist/slices/video/cards.js +5 -2
  415. package/dist/slices/video/edit-list.js +23 -2
  416. package/dist/slices/video/edit-settings.js +13 -3
  417. package/dist/slices/video/ffmpeg.js +63 -9
  418. package/dist/slices/video/figure-card.js +492 -0
  419. package/dist/slices/video/figure-spans.js +51 -0
  420. package/dist/slices/video/plan.js +125 -13
  421. package/dist/slices/video/run.js +14 -12
  422. package/dist/slices/video/slideshow.js +39 -4
  423. package/dist/slices/voices/attribution.js +69 -0
  424. package/dist/slices/voices/audio-files.js +82 -0
  425. package/dist/slices/voices/audition.js +25 -0
  426. package/dist/slices/voices/cast.js +56 -0
  427. package/dist/slices/voices/grouping.js +49 -0
  428. package/dist/slices/voices/join.js +35 -0
  429. package/dist/slices/voices/languages.js +25 -0
  430. package/dist/slices/voices/model.js +179 -0
  431. package/dist/slices/voices/palette.js +26 -0
  432. package/dist/slices/voices/panel.js +91 -0
  433. package/dist/slices/voices/portraits.js +55 -0
  434. package/dist/slices/voices/script.js +136 -0
  435. package/dist/slices/voices/timing.js +58 -0
  436. package/dist/slices/youtube/answer.js +1 -1
  437. package/dist/slices/youtube/chapters.js +126 -0
  438. package/dist/slices/youtube/edits-repo.js +69 -0
  439. package/dist/slices/youtube/edits.js +89 -0
  440. package/dist/slices/youtube/placeholders.js +98 -0
  441. package/dist/slices/youtube/transcript.js +11 -4
  442. package/dist/updater/service.js +66 -21
  443. package/dist/web/assets/index-CUA2sUxU.css +1 -0
  444. package/dist/web/assets/index-CX590kE-.js +303 -0
  445. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  446. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  447. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  448. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  449. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  450. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  451. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  452. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  453. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  454. package/dist/web/assets/{pdf-CAJNT254.js → pdf-BG8n_uc5.js} +1 -1
  455. package/dist/web/index.html +2 -2
  456. package/package.json +4 -4
  457. package/dist/edge/docker-launch.js +0 -81
  458. package/dist/edge/docker-projects/activation.js +0 -35
  459. package/dist/edge/docker-projects/claims.js +0 -85
  460. package/dist/edge/docker-projects/committed.js +0 -40
  461. package/dist/edge/docker-projects/engine.js +0 -461
  462. package/dist/edge/docker-projects/install.js +0 -254
  463. package/dist/edge/docker-projects/prune.js +0 -66
  464. package/dist/edge/docker-projects/recover.js +0 -100
  465. package/dist/edge/docker-projects/state.js +0 -225
  466. package/dist/web/assets/index-BGgYMm2u.css +0 -1
  467. package/dist/web/assets/index-BVVgtntL.js +0 -171
  468. package/scripts/docker-run.sh +0 -3
  469. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -0,0 +1,234 @@
1
+ import { z } from "zod";
2
+ import { stageKinds } from "../../kernel/pipeline.js";
3
+ import { planAccountNames, planAccountOf, planAccounts, } from "../../kernel/ports/plan-limits.js";
4
+ const usageRow = z.object({
5
+ stage: z.enum(stageKinds),
6
+ kind: z.enum(["llm", "tts", "image", "video"]),
7
+ provider: z.string(),
8
+ model: z.string(),
9
+ tokens_in: z.number().nullable(),
10
+ tokens_out: z.number().nullable(),
11
+ tokens_cached: z.number().nullable(),
12
+ characters: z.number().nullable(),
13
+ images: z.number().nullable(),
14
+ seconds: z.number().nullable(),
15
+ on_plan: z.number(),
16
+ cost: z.number().nullable(),
17
+ api_model: z.string().nullable(),
18
+ api_cost: z.number().nullable(),
19
+ price_json: z.string().nullable(),
20
+ });
21
+ const windowSchema = z.object({
22
+ kind: z.enum(["five_hour", "weekly", "other"]),
23
+ usedPercent: z.number(),
24
+ resetsAt: z.string().nullable(),
25
+ });
26
+ const readingSchema = z.object({
27
+ before: z.array(windowSchema).optional(),
28
+ after: z.array(windowSchema).optional(),
29
+ });
30
+ const emptyTotals = {
31
+ tokensIn: 0,
32
+ tokensOut: 0,
33
+ cachedTokens: 0,
34
+ characters: 0,
35
+ images: 0,
36
+ seconds: 0,
37
+ };
38
+ const emptyLine = {
39
+ calls: 0,
40
+ cost: 0,
41
+ unpriced: 0,
42
+ apiEquivalent: null,
43
+ apiUnpriced: 0,
44
+ };
45
+ function add(into, row) {
46
+ const onPlan = row.on_plan === 1;
47
+ return {
48
+ ...into,
49
+ calls: into.calls + 1,
50
+ cost: into.cost + (row.cost ?? 0),
51
+ unpriced: into.unpriced + (row.cost === null ? 1 : 0),
52
+ apiEquivalent: onPlan ? (into.apiEquivalent ?? 0) + (row.api_cost ?? 0) : into.apiEquivalent,
53
+ apiUnpriced: into.apiUnpriced + (onPlan && row.api_cost === null ? 1 : 0),
54
+ tokensIn: into.tokensIn + (row.tokens_in ?? 0),
55
+ tokensOut: into.tokensOut + (row.tokens_out ?? 0),
56
+ cachedTokens: into.cachedTokens + (row.tokens_cached ?? 0),
57
+ characters: into.characters + (row.characters ?? 0),
58
+ images: into.images + (row.images ?? 0),
59
+ seconds: into.seconds + (row.seconds ?? 0),
60
+ };
61
+ }
62
+ export function runCostOf(db, projectId) {
63
+ const rows = db
64
+ .prepare("SELECT stage, kind, provider, model, tokens_in, tokens_out, tokens_cached, characters, images, seconds, on_plan, cost, api_model, api_cost, price_json FROM provider_usage WHERE project_id = ? ORDER BY created_at, rowid")
65
+ .all(projectId)
66
+ .map((row) => usageRow.parse(row));
67
+ const walls = stageWalls(db, projectId);
68
+ const stages = new Map();
69
+ const models = new Map();
70
+ let total = { ...emptyLine, ...emptyTotals };
71
+ let catalogueDate = null;
72
+ for (const row of rows) {
73
+ total = add(total, row);
74
+ stages.set(row.stage, add(stages.get(row.stage) ?? {
75
+ stage: row.stage,
76
+ wallMs: walls.get(row.stage) ?? null,
77
+ ...emptyLine,
78
+ ...emptyTotals,
79
+ }, row));
80
+ const key = JSON.stringify([row.provider, row.model, row.kind]);
81
+ models.set(key, add(models.get(key) ?? {
82
+ provider: row.provider,
83
+ model: row.model,
84
+ kind: row.kind,
85
+ onPlan: row.on_plan === 1,
86
+ apiModel: row.api_model,
87
+ ...emptyLine,
88
+ ...emptyTotals,
89
+ }, row));
90
+ catalogueDate = catalogueOf(row.price_json) ?? catalogueDate;
91
+ }
92
+ // A stage that ran with no provider call (a render, a document) still has its time.
93
+ for (const [stage, wallMs] of walls)
94
+ if (!stages.has(stage))
95
+ stages.set(stage, { stage, wallMs, ...emptyLine, ...emptyTotals });
96
+ const plans = planUses(db, projectId, rows);
97
+ return {
98
+ currency: "USD",
99
+ ...total,
100
+ totals: {
101
+ tokensIn: total.tokensIn,
102
+ tokensOut: total.tokensOut,
103
+ cachedTokens: total.cachedTokens,
104
+ characters: total.characters,
105
+ images: total.images,
106
+ seconds: total.seconds,
107
+ wallMs: [...walls.values()].reduce((sum, ms) => sum + ms, 0),
108
+ },
109
+ byStage: stageKinds.flatMap((kind) => {
110
+ const found = stages.get(kind);
111
+ return found === undefined ? [] : [found];
112
+ }),
113
+ byModel: [...models.values()].toSorted((left, right) => right.cost - left.cost ||
114
+ (right.apiEquivalent ?? 0) - (left.apiEquivalent ?? 0) ||
115
+ left.provider.localeCompare(right.provider) ||
116
+ left.model.localeCompare(right.model)),
117
+ plans,
118
+ waits: limitWaitsOf(db, projectId),
119
+ catalogueDate,
120
+ };
121
+ }
122
+ function catalogueOf(json) {
123
+ if (json === null)
124
+ return null;
125
+ const parsed = z.object({ catalogue: z.string() }).safeParse(JSON.parse(json));
126
+ return parsed.success ? parsed.data.catalogue : null;
127
+ }
128
+ function stageWalls(db, projectId) {
129
+ const walls = new Map();
130
+ for (const raw of db
131
+ .prepare("SELECT kind, started_at, finished_at FROM stages WHERE project_id = ?")
132
+ .all(projectId)) {
133
+ const row = z
134
+ .object({
135
+ kind: z.enum(stageKinds),
136
+ started_at: z.string().nullable(),
137
+ finished_at: z.string().nullable(),
138
+ })
139
+ .parse(raw);
140
+ if (row.started_at === null || row.finished_at === null)
141
+ continue;
142
+ const ms = Date.parse(row.finished_at) - Date.parse(row.started_at);
143
+ if (Number.isFinite(ms) && ms >= 0)
144
+ walls.set(row.kind, ms);
145
+ }
146
+ return walls;
147
+ }
148
+ // Two readings belong to the same window when their resets agree to within a few minutes;
149
+ // a reset moving on means the window rolled over between them.
150
+ const sameWindowMs = 5 * 60_000;
151
+ export function windowUse(readings) {
152
+ const byKind = new Map();
153
+ for (const reading of readings)
154
+ for (const window of reading)
155
+ byKind.set(window.kind, [...(byKind.get(window.kind) ?? []), window]);
156
+ const uses = [];
157
+ for (const [kind, seen] of byKind) {
158
+ let used = 0;
159
+ for (let at = 1; at < seen.length; at += 1) {
160
+ const previous = seen[at - 1];
161
+ const current = seen[at];
162
+ if (previous === undefined || current === undefined)
163
+ continue;
164
+ const same = previous.resetsAt === current.resetsAt ||
165
+ (previous.resetsAt !== null &&
166
+ current.resetsAt !== null &&
167
+ Math.abs(Date.parse(previous.resetsAt) - Date.parse(current.resetsAt)) < sameWindowMs);
168
+ used += same ? Math.max(0, current.usedPercent - previous.usedPercent) : current.usedPercent;
169
+ }
170
+ const last = seen.at(-1);
171
+ if (last !== undefined)
172
+ uses.push({ kind, usedPercent: used, nowPercent: last.usedPercent, resetsAt: last.resetsAt });
173
+ }
174
+ const order = ["five_hour", "weekly", "other"];
175
+ return uses.toSorted((left, right) => order.indexOf(left.kind) - order.indexOf(right.kind));
176
+ }
177
+ function planUses(db, projectId, rows) {
178
+ const readings = db
179
+ .prepare("SELECT account, reading_json FROM plan_limit_readings WHERE project_id = ? ORDER BY created_at, rowid")
180
+ .all(projectId)
181
+ .map((row) => z.object({ account: z.string(), reading_json: z.string() }).parse(row));
182
+ return planAccounts.flatMap((account) => {
183
+ const calls = rows.filter((row) => planAccountOf(row.provider) === account).length;
184
+ if (calls === 0)
185
+ return [];
186
+ const sequence = [];
187
+ for (const row of readings) {
188
+ if (row.account !== account)
189
+ continue;
190
+ const parsed = readingSchema.safeParse(JSON.parse(row.reading_json));
191
+ if (!parsed.success)
192
+ continue;
193
+ if (parsed.data.before?.length)
194
+ sequence.push(parsed.data.before);
195
+ if (parsed.data.after?.length)
196
+ sequence.push(parsed.data.after);
197
+ }
198
+ return [
199
+ {
200
+ account,
201
+ name: planAccountNames[account],
202
+ calls,
203
+ reported: sequence.length > 0,
204
+ windows: windowUse(sequence),
205
+ },
206
+ ];
207
+ });
208
+ }
209
+ export function limitWaitsOf(db, projectId) {
210
+ // Only a stage still running is waiting: a row left by a cancel or pause is not shown.
211
+ return db
212
+ .prepare(`SELECT w.account, w.stage, l.resets_at, l.retry_at FROM plan_limit_waiters w
213
+ JOIN plan_limit_waits l ON l.account = w.account
214
+ JOIN stages s ON s.project_id = w.project_id AND s.kind = w.stage AND s.state = 'running'
215
+ WHERE w.project_id = ? ORDER BY w.since`)
216
+ .all(projectId)
217
+ .map((raw) => {
218
+ const row = z
219
+ .object({
220
+ account: z.enum(planAccounts),
221
+ stage: z.enum(stageKinds),
222
+ resets_at: z.string().nullable(),
223
+ retry_at: z.string(),
224
+ })
225
+ .parse(raw);
226
+ return {
227
+ account: row.account,
228
+ name: planAccountNames[row.account],
229
+ stage: row.stage,
230
+ resetsAt: row.resets_at,
231
+ retryAt: row.retry_at,
232
+ };
233
+ });
234
+ }
@@ -0,0 +1,92 @@
1
+ import { isLocalCliProvider } from "../settings/model.js";
2
+ // Per million tokens; cached input at its own rate when the catalogue gives one.
3
+ export function tokenCost(rates, tokensIn, tokensOut, cachedTokens = 0) {
4
+ if (rates.inputPerMillionTokens === undefined || rates.outputPerMillionTokens === undefined)
5
+ return null;
6
+ const cached = Math.min(Math.max(0, cachedTokens), tokensIn);
7
+ return (((tokensIn - cached) * rates.inputPerMillionTokens +
8
+ cached * (rates.cachedInputPerMillionTokens ?? rates.inputPerMillionTokens) +
9
+ tokensOut * rates.outputPerMillionTokens) /
10
+ 1_000_000);
11
+ }
12
+ // The API model a CLI's model is billed as when it is called through a key: Claude Code's
13
+ // models are Anthropic's, Codex's are OpenAI's and Gemini's are Google's, all listed in the
14
+ // catalogue under OpenRouter. A version the catalogue does not list has no API price.
15
+ export function apiEquivalentOf(provider, model, catalogue) {
16
+ const listed = (id) => catalogue.llm.find((entry) => entry.provider === "openrouter" && entry.id === id);
17
+ // Context sizes ("[1m]") and dated snapshots ("-20251001") price as the model itself.
18
+ const bare = model
19
+ .replace(/\[[^\]]*\]/g, "")
20
+ .replace(/-\d{8}$/, "")
21
+ .trim();
22
+ if (provider === "claude-code") {
23
+ const family = /^(?:claude-)?(opus|sonnet|haiku|fable)(?:-(\d+)(?:-(\d{1,2}))?)?$/i.exec(bare);
24
+ if (family === null)
25
+ return undefined;
26
+ const name = (family[1] ?? "").toLowerCase();
27
+ if (family[2] !== undefined)
28
+ return listed(`anthropic/claude-${name}-${family[2]}${family[3] ? `.${family[3]}` : ""}`);
29
+ // An alias ("sonnet") is the newest of that family the catalogue lists.
30
+ return catalogue.llm
31
+ .filter((entry) => entry.provider === "openrouter" && entry.id.startsWith(`anthropic/claude-${name}-`))
32
+ .toSorted((left, right) => right.id.localeCompare(left.id, "en", { numeric: true }))[0];
33
+ }
34
+ if (bare === "" || bare === "codex-imagegen")
35
+ return undefined;
36
+ if (provider === "codex" || provider === "codex-image")
37
+ return listed(`openai/${bare}`);
38
+ if (provider === "gemini")
39
+ return listed(`google/${bare}`);
40
+ return undefined;
41
+ }
42
+ export function priceCall(call, catalogue) {
43
+ const date = catalogue.updatedAt;
44
+ if (isLocalCliProvider(call.provider)) {
45
+ // Only text is priced through the API: what a Codex image would cost there depends on
46
+ // how many drafts the agent drew, which the CLI does not say.
47
+ const api = call.kind === "llm" ? apiEquivalentOf(call.provider, call.model, catalogue) : undefined;
48
+ const apiCost = api === undefined || call.tokensIn === undefined || call.tokensOut === undefined
49
+ ? null
50
+ : tokenCost(api.pricing, call.tokensIn, call.tokensOut, call.cachedTokens);
51
+ return {
52
+ onPlan: true,
53
+ cost: 0,
54
+ apiModel: api?.id ?? null,
55
+ apiCost,
56
+ price: api === undefined ? null : { catalogue: date, model: api.id, ...api.pricing },
57
+ };
58
+ }
59
+ const family = call.kind === "video" ? "image" : call.kind;
60
+ const entry = catalogue[family].find((model) => model.provider === call.provider && model.id === call.model);
61
+ const rates = entry?.pricing;
62
+ let cost = null;
63
+ if (rates !== undefined)
64
+ switch (call.kind) {
65
+ case "llm":
66
+ cost =
67
+ call.tokensIn === undefined || call.tokensOut === undefined
68
+ ? null
69
+ : tokenCost(rates, call.tokensIn, call.tokensOut, call.cachedTokens);
70
+ break;
71
+ case "tts":
72
+ // ceiling: a per-minute voice is left unpriced rather than guessed from characters;
73
+ // the narration's measured length is known only after the stage writes the file.
74
+ cost =
75
+ rates.perMillionCharacters === undefined || call.characters === undefined
76
+ ? null
77
+ : (call.characters / 1_000_000) * rates.perMillionCharacters;
78
+ break;
79
+ case "image":
80
+ case "video":
81
+ // An image-to-video model's perImage is the price of one clip.
82
+ cost = rates.perImage === undefined ? null : rates.perImage * (call.images ?? 1);
83
+ break;
84
+ }
85
+ return {
86
+ onPlan: false,
87
+ cost,
88
+ apiModel: null,
89
+ apiCost: null,
90
+ price: rates === undefined ? null : { catalogue: date, model: call.model, ...rates },
91
+ };
92
+ }
@@ -0,0 +1,108 @@
1
+ import { z } from "zod";
2
+ import { planAccountNames, planAccounts, } from "../../kernel/ports/plan-limits.js";
3
+ import { defaultChannelId } from "../channels/model.js";
4
+ import { projectChannels } from "../channels/repo.js";
5
+ const usageRow = z.object({
6
+ project_id: z.string(),
7
+ on_plan: z.number(),
8
+ cost: z.number().nullable(),
9
+ api_cost: z.number().nullable(),
10
+ });
11
+ const standaloneRow = z.object({
12
+ channel_id: z.string(),
13
+ on_plan: z.number(),
14
+ cost: z.number().nullable(),
15
+ api_cost: z.number().nullable(),
16
+ });
17
+ const windowSchema = z.object({
18
+ kind: z.enum(["five_hour", "weekly", "other"]),
19
+ usedPercent: z.number(),
20
+ resetsAt: z.string().nullable(),
21
+ });
22
+ const readingSchema = z.object({
23
+ before: z.array(windowSchema).optional(),
24
+ after: z.array(windowSchema).optional(),
25
+ });
26
+ // `channelId` narrows the counts to one channel: its projects, and the calls made for its
27
+ // schedules and itself. Plan standings are the account's whole allowance, which every channel
28
+ // shares, so they are never narrowed.
29
+ export function weekSummary(db, since, channelId) {
30
+ const known = channelId === undefined ? undefined : knownChannels(db);
31
+ const channels = channelId === undefined ? undefined : projectChannels(db);
32
+ const projectInScope = (projectId) => channels === undefined || (channels.get(projectId) ?? defaultChannelId) === channelId;
33
+ let calls = 0;
34
+ let cost = 0;
35
+ let unpriced = 0;
36
+ let apiEquivalent = null;
37
+ const count = (row) => {
38
+ calls += 1;
39
+ cost += row.cost ?? 0;
40
+ if (row.cost === null)
41
+ unpriced += 1;
42
+ if (row.on_plan === 1)
43
+ apiEquivalent = (apiEquivalent ?? 0) + (row.api_cost ?? 0);
44
+ };
45
+ for (const raw of db
46
+ .prepare("SELECT project_id, on_plan, cost, api_cost FROM provider_usage WHERE created_at >= ?")
47
+ .all(since)) {
48
+ const row = usageRow.parse(raw);
49
+ if (projectInScope(row.project_id))
50
+ count(row);
51
+ }
52
+ for (const raw of db
53
+ .prepare("SELECT channel_id, on_plan, cost, api_cost FROM standalone_usage WHERE created_at >= ?")
54
+ .all(since)) {
55
+ const row = standaloneRow.parse(raw);
56
+ // A channel deleted since counts under the default channel, as its projects do.
57
+ const channel = known === undefined || known.has(row.channel_id) ? row.channel_id : defaultChannelId;
58
+ if (channelId === undefined || channel === channelId)
59
+ count(row);
60
+ }
61
+ // A video is made when its file lands; a remade video counts again, as it was made again.
62
+ const videos = db
63
+ .prepare("SELECT project_id FROM outputs WHERE role = 'video' AND created_at >= ?")
64
+ .all(since)
65
+ .filter((row) => projectInScope(String(row.project_id))).length;
66
+ return { since, videos, calls, cost, unpriced, apiEquivalent, plans: planStandings(db) };
67
+ }
68
+ function knownChannels(db) {
69
+ return new Set(db
70
+ .prepare("SELECT id FROM channels")
71
+ .all()
72
+ .map((row) => String(row.id)));
73
+ }
74
+ function planStandings(db) {
75
+ return planAccounts.flatMap((account) => {
76
+ const rows = db
77
+ .prepare(`SELECT reading_json, created_at FROM (
78
+ SELECT reading_json, created_at, rowid AS n, 0 AS source FROM plan_limit_readings WHERE account = ?
79
+ UNION ALL
80
+ SELECT reading_json, created_at, rowid AS n, 1 AS source FROM standalone_usage WHERE account = ? AND reading_json IS NOT NULL
81
+ ) ORDER BY created_at DESC, source DESC, n DESC LIMIT 20`)
82
+ .all(account, account);
83
+ for (const raw of rows) {
84
+ const row = z.object({ reading_json: z.string(), created_at: z.string() }).parse(raw);
85
+ const parsed = readingSchema.safeParse(JSON.parse(row.reading_json));
86
+ if (!parsed.success)
87
+ continue;
88
+ const windows = parsed.data.after?.length
89
+ ? parsed.data.after
90
+ : (parsed.data.before ?? []);
91
+ if (windows.length === 0)
92
+ continue;
93
+ const weekly = windows.find((window) => window.kind === "weekly");
94
+ const fiveHour = windows.find((window) => window.kind === "five_hour");
95
+ return [
96
+ {
97
+ account,
98
+ name: planAccountNames[account],
99
+ weeklyPercent: weekly?.usedPercent ?? null,
100
+ fiveHourPercent: fiveHour?.usedPercent ?? null,
101
+ weeklyResetsAt: weekly?.resetsAt ?? null,
102
+ readAt: row.created_at,
103
+ },
104
+ ];
105
+ }
106
+ return [];
107
+ });
108
+ }
@@ -0,0 +1,158 @@
1
+ import { z } from "zod";
2
+ import { derive } from "../../kernel/runner/graph.js";
3
+ import { liveProject, projectPaused, stageStandingsByProject } from "../admission/repo.js";
4
+ import { queueEntries } from "../batch/index.js";
5
+ import { nextOccurrence } from "./calendar.js";
6
+ import { scheduleRows } from "./repo.js";
7
+ import { calendarMaxDays, } from "./schema.js";
8
+ import { renderedTitle } from "./topic-list.js";
9
+ // Enough for a run every day of the longest range, with room for a queue.
10
+ const occurrencesMax = 1000;
11
+ const dayMs = 24 * 60 * 60_000;
12
+ // Everything the coming weeks hold between `from` and `to`: every run a live schedule will
13
+ // make, with the topic it will use; every project running in the range or finished in it; and
14
+ // every batch item still waiting its turn.
15
+ export function calendarRange(deps, from, to) {
16
+ if (Number.isNaN(from.valueOf()) ||
17
+ Number.isNaN(to.valueOf()) ||
18
+ to <= from ||
19
+ to.valueOf() - from.valueOf() > calendarMaxDays * dayMs)
20
+ return { ok: false, reason: "invalid-input" };
21
+ const now = deps.clock.now();
22
+ const runs = scheduleRows(deps.db)
23
+ .flatMap((schedule) => upcomingRuns(deps, schedule, now, from, to))
24
+ .sort((a, b) => a.at.localeCompare(b.at) || a.scheduleName.localeCompare(b.scheduleName));
25
+ return {
26
+ ok: true,
27
+ value: {
28
+ from: from.toISOString(),
29
+ to: to.toISOString(),
30
+ runs,
31
+ projects: projectsIn(deps, from, to),
32
+ queued: queued(deps),
33
+ },
34
+ };
35
+ }
36
+ // Run k from now uses the k-th queued topic. A schedule that does not find its own topics
37
+ // completes when its queue empties, so its runs stop there; one with no queue at all runs the
38
+ // template as saved every time.
39
+ function upcomingRuns(deps, schedule, now, from, to) {
40
+ if (schedule.deletedAt !== null)
41
+ return [];
42
+ if (schedule.status !== "active" && schedule.status !== "paused")
43
+ return [];
44
+ const paused = schedule.status === "paused";
45
+ // A paused schedule resumes from the next occurrence after the moment Resume is pressed.
46
+ let at = schedule.nextRunAt === null
47
+ ? null
48
+ : paused
49
+ ? nextOccurrence(schedule.cadence, schedule.timezone, now)
50
+ : new Date(schedule.nextRunAt);
51
+ const template = deps.template(schedule.templateId, schedule.templateVersion);
52
+ const templateName = template?.name ?? null;
53
+ const form = template?.document.form;
54
+ const { items } = schedule;
55
+ const generation = schedule.topicGeneration.mode;
56
+ const held = generation === "hold" ? schedule.topics.held : 0;
57
+ const runs = [];
58
+ for (let k = 0; at !== null && at < to && k < occurrencesMax; k++) {
59
+ if (generation === "off" && items.length > 0 && k >= items.length)
60
+ break;
61
+ const queuedTopic = items[k];
62
+ // Known for a queued topic, and for a run of the template as saved; a held or generated
63
+ // topic has no text yet.
64
+ const known = queuedTopic !== undefined || generation === "off";
65
+ if (at >= from)
66
+ runs.push({
67
+ at: at.toISOString(),
68
+ scheduleId: schedule.id,
69
+ scheduleName: schedule.name,
70
+ scheduleVersion: schedule.version,
71
+ paused,
72
+ templateId: schedule.templateId,
73
+ templateVersion: schedule.templateVersion,
74
+ templateName,
75
+ index: queuedTopic === undefined ? null : k,
76
+ topic: queuedTopic?.title ?? null,
77
+ topicSource: queuedTopic !== undefined
78
+ ? "queued"
79
+ : generation === "off"
80
+ ? "template"
81
+ : k < items.length + held
82
+ ? "held"
83
+ : "generated",
84
+ renderedTitle: form !== undefined && known ? renderedTitle(form, schedule, queuedTopic) : null,
85
+ });
86
+ if (schedule.cadence.kind === "once")
87
+ break;
88
+ at = nextOccurrence(schedule.cadence, schedule.timezone, new Date(at.valueOf() + 1000));
89
+ }
90
+ return runs;
91
+ }
92
+ const projectRow = z.object({
93
+ id: z.string(),
94
+ title: z.string(),
95
+ created_at: z.string(),
96
+ finished_at: z.string().nullable(),
97
+ });
98
+ // Running (or waiting) projects made before `to`, and finished ones whose last stage ended in
99
+ // the range.
100
+ function projectsIn(deps, from, to) {
101
+ const standings = stageStandingsByProject(deps.db);
102
+ const fromSchedule = new Map();
103
+ for (const row of deps.db
104
+ .prepare(`SELECT DISTINCT admitted.value AS project_id, schedule_runs.schedule_id
105
+ FROM schedule_runs, json_each(schedule_runs.project_ids_json) AS admitted`)
106
+ .all()) {
107
+ const parsed = z.object({ project_id: z.string(), schedule_id: z.string() }).parse(row);
108
+ fromSchedule.set(parsed.project_id, parsed.schedule_id);
109
+ }
110
+ return deps.db
111
+ .prepare(`SELECT projects.id,projects.title,projects.created_at,max(stages.finished_at) AS finished_at
112
+ FROM projects LEFT JOIN stages ON stages.project_id=projects.id
113
+ WHERE projects.created_at<? AND ${liveProject()} GROUP BY projects.id ORDER BY projects.created_at,projects.id`)
114
+ .all(to.toISOString())
115
+ .flatMap((row) => {
116
+ const project = projectRow.parse(row);
117
+ const state = derive(standings.get(project.id) ?? [], projectPaused(deps.db, project.id));
118
+ const terminal = state === "done" || state === "partial" || state === "failed" || state === "canceled";
119
+ if (terminal) {
120
+ const ended = project.finished_at ?? project.created_at;
121
+ if (ended < from.toISOString() || ended >= to.toISOString())
122
+ return [];
123
+ }
124
+ return [
125
+ {
126
+ id: project.id,
127
+ title: project.title,
128
+ state,
129
+ createdAt: project.created_at,
130
+ finishedAt: terminal ? (project.finished_at ?? project.created_at) : null,
131
+ scheduleId: fromSchedule.get(project.id) ?? null,
132
+ },
133
+ ];
134
+ });
135
+ }
136
+ function queued(deps) {
137
+ return queueEntries(deps.db).flatMap((entry) => {
138
+ if (entry.state === "finished")
139
+ return [];
140
+ const row = deps.db
141
+ .prepare(`SELECT projects.title, batches.created_at FROM projects, batches
142
+ WHERE projects.id=? AND batches.id=? AND ${liveProject()}`)
143
+ .get(entry.projectId, entry.batchId);
144
+ if (row === undefined)
145
+ return [];
146
+ const parsed = z.object({ title: z.string(), created_at: z.string() }).parse(row);
147
+ return [
148
+ {
149
+ projectId: entry.projectId,
150
+ title: parsed.title,
151
+ batchId: entry.batchId,
152
+ position: entry.position,
153
+ state: entry.state,
154
+ queuedAt: parsed.created_at,
155
+ },
156
+ ];
157
+ });
158
+ }