@gentbajko/slopify 2.5.0 → 3.0.1

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 (542) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +24 -41
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +31 -0
  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 +44 -5
  20. package/dist/adapters/host-cli/transport.js +7 -5
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +1 -1
  23. package/dist/adapters/image/codex.js +87 -22
  24. package/dist/adapters/image/fal.js +13 -5
  25. package/dist/adapters/image/google.js +11 -8
  26. package/dist/adapters/image/openai.js +14 -7
  27. package/dist/adapters/image/reference.js +34 -0
  28. package/dist/adapters/image/replicate.js +7 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +85 -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.js +99 -12
  35. package/dist/adapters/llm/document-workspace.js +2 -2
  36. package/dist/adapters/llm/gemini-workspace.js +1 -1
  37. package/dist/adapters/llm/gemini.js +16 -3
  38. package/dist/adapters/llm/image-workspace.js +58 -0
  39. package/dist/adapters/llm/openrouter.js +11 -3
  40. package/dist/adapters/llm/run-cli.js +12 -7
  41. package/dist/adapters/tts/cartesia.js +10 -0
  42. package/dist/adapters/tts/elevenlabs.js +31 -9
  43. package/dist/adapters/tts/gemini.js +206 -0
  44. package/dist/adapters/tts/inworld-async.js +9 -5
  45. package/dist/adapters/tts/inworld.js +17 -3
  46. package/dist/adapters/tts/openai.js +3 -0
  47. package/dist/adapters/tts/pcm-mp3.js +62 -0
  48. package/dist/adapters/tts/system.js +147 -0
  49. package/dist/adapters/tts/voice-languages.js +80 -0
  50. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  55. package/dist/assets/fonts/noto/OFL.txt +93 -0
  56. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  57. package/dist/assets/models.yaml +38 -0
  58. package/dist/assets/sample/sample-audiobook.tar +0 -0
  59. package/dist/assets/sample/sample-podcast.tar +0 -0
  60. package/dist/assets/sample/sample-project.tar +0 -0
  61. package/dist/assets/style-preview/narration.mp3 +0 -0
  62. package/dist/assets/style-preview/tall-1.jpg +0 -0
  63. package/dist/assets/style-preview/tall-2.jpg +0 -0
  64. package/dist/assets/style-preview/tall-3.jpg +0 -0
  65. package/dist/assets/style-preview/wide-1.jpg +0 -0
  66. package/dist/assets/style-preview/wide-2.jpg +0 -0
  67. package/dist/assets/style-preview/wide-3.jpg +0 -0
  68. package/dist/catalog/merge.js +132 -0
  69. package/dist/catalog/registry.js +17 -14
  70. package/dist/catalog/schema.js +2 -0
  71. package/dist/catalog/store.js +147 -8
  72. package/dist/catalog/validate.js +2 -2
  73. package/dist/compose.yaml +71 -0
  74. package/dist/edge/autostart/docker-record.js +32 -0
  75. package/dist/edge/autostart/docker.js +118 -0
  76. package/dist/edge/autostart/index.js +78 -0
  77. package/dist/edge/autostart/launcher.js +176 -0
  78. package/dist/edge/autostart/model.js +3 -0
  79. package/dist/edge/autostart/native.js +210 -0
  80. package/dist/edge/autostart/prompt.js +72 -0
  81. package/dist/edge/autostart/service.js +90 -0
  82. package/dist/edge/cli-args.js +71 -0
  83. package/dist/edge/cli.js +64 -63
  84. package/dist/edge/docker-install/activation.js +56 -0
  85. package/dist/edge/docker-install/apply.js +458 -0
  86. package/dist/edge/docker-install/engine.js +362 -0
  87. package/dist/edge/docker-install/run.js +160 -0
  88. package/dist/edge/docker-install/state.js +146 -0
  89. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  90. package/dist/edge/docker.js +14 -22
  91. package/dist/edge/events/hub.js +3 -1
  92. package/dist/edge/host-cli.js +1 -1
  93. package/dist/edge/http/actions.js +38 -11
  94. package/dist/edge/http/app.js +60 -2
  95. package/dist/edge/http/auditions.js +70 -0
  96. package/dist/edge/http/autostart.js +37 -0
  97. package/dist/edge/http/byte-range.js +42 -0
  98. package/dist/edge/http/channel-memory.js +92 -0
  99. package/dist/edge/http/channels.js +128 -0
  100. package/dist/edge/http/diagnostics.js +1 -0
  101. package/dist/edge/http/draft-files.js +2 -4
  102. package/dist/edge/http/entries.js +22 -3
  103. package/dist/edge/http/files.js +11 -7
  104. package/dist/edge/http/folder-location.js +1 -1
  105. package/dist/edge/http/home.js +39 -0
  106. package/dist/edge/http/host-cli.js +29 -5
  107. package/dist/edge/http/narration-peaks.js +76 -0
  108. package/dist/edge/http/onboarding.js +212 -0
  109. package/dist/edge/http/open-folder.js +1 -1
  110. package/dist/edge/http/patch-notes.js +45 -0
  111. package/dist/edge/http/planning.js +4 -2
  112. package/dist/edge/http/project-create.js +87 -0
  113. package/dist/edge/http/project-templates.js +19 -2
  114. package/dist/edge/http/projects.js +37 -100
  115. package/dist/edge/http/prompts.js +40 -3
  116. package/dist/edge/http/pronunciations.js +23 -4
  117. package/dist/edge/http/providers.js +119 -3
  118. package/dist/edge/http/reviews.js +131 -0
  119. package/dist/edge/http/revision-files.js +3 -5
  120. package/dist/edge/http/revisions.js +1 -1
  121. package/dist/edge/http/run-cost.js +25 -0
  122. package/dist/edge/http/schedules.js +115 -6
  123. package/dist/edge/http/settings.js +84 -6
  124. package/dist/edge/http/storage-files.js +50 -0
  125. package/dist/edge/http/storage.js +43 -2
  126. package/dist/edge/http/studio.js +287 -0
  127. package/dist/edge/http/style-preview.js +61 -0
  128. package/dist/edge/http/subtitles.js +1 -1
  129. package/dist/edge/http/trash.js +69 -0
  130. package/dist/edge/http/tutorials.js +59 -0
  131. package/dist/edge/http/update.js +24 -3
  132. package/dist/edge/http/whats-new.js +15 -0
  133. package/dist/edge/http/youtube-edits.js +92 -0
  134. package/dist/edge/native-update.js +62 -0
  135. package/dist/extension/slopify-studio-chrome.zip +0 -0
  136. package/dist/extension/slopify-studio-firefox.zip +0 -0
  137. package/dist/host-cli/gemini-login.js +83 -0
  138. package/dist/host-cli/open-folder.js +10 -10
  139. package/dist/host-cli/runtime.js +9 -1
  140. package/dist/host-cli/status.js +4 -2
  141. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  142. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  143. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  144. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  145. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  146. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  147. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  148. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  149. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  150. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  151. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  152. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  153. package/dist/kernel/db/migrations/0041-cast-hosts.sql +3 -0
  154. package/dist/kernel/paths.js +27 -6
  155. package/dist/kernel/pipeline.js +4 -1
  156. package/dist/kernel/ports/host-cli.js +122 -14
  157. package/dist/kernel/ports/key-probe.js +1 -0
  158. package/dist/kernel/ports/languages.js +157 -0
  159. package/dist/kernel/ports/model.js +10 -5
  160. package/dist/kernel/ports/narration-aliases.js +60 -0
  161. package/dist/kernel/ports/plan-limits.js +36 -0
  162. package/dist/kernel/ports/system-speech.js +203 -0
  163. package/dist/kernel/runner/attempt.js +17 -8
  164. package/dist/kernel/runner/audition.js +36 -0
  165. package/dist/kernel/runner/graph.js +40 -5
  166. package/dist/kernel/runner/index.js +39 -9
  167. package/dist/kernel/runner/meter.js +1 -0
  168. package/dist/kernel/runner/providers.js +142 -7
  169. package/dist/kernel/runner/retry-policy.js +43 -0
  170. package/dist/kernel/runner/standalone.js +134 -0
  171. package/dist/kernel/runner/work-authority.js +28 -2
  172. package/dist/main.js +277 -16
  173. package/dist/model-catalog.js +2 -0
  174. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  175. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  176. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  177. package/dist/patch-notes/3.0.0.md +70 -0
  178. package/dist/patch-notes/3.0.1.md +105 -0
  179. package/dist/patch-notes/index.json +32 -0
  180. package/dist/slices/admission/model.js +27 -0
  181. package/dist/slices/admission/repo.js +24 -4
  182. package/dist/slices/admission/rules.js +80 -2
  183. package/dist/slices/admission/schema.js +58 -1
  184. package/dist/slices/admission/short-mode.js +47 -0
  185. package/dist/slices/admission/start.js +42 -6
  186. package/dist/slices/article/continuation.js +2 -2
  187. package/dist/slices/article/provided-entries.js +2 -2
  188. package/dist/slices/article/run.js +6 -2
  189. package/dist/slices/article/segments.js +3 -3
  190. package/dist/slices/backups/folder.js +14 -6
  191. package/dist/slices/batch/index.js +7 -2
  192. package/dist/slices/cancel/index.js +1 -1
  193. package/dist/slices/channels/cast-images.js +110 -0
  194. package/dist/slices/channels/cast-match.js +32 -0
  195. package/dist/slices/channels/images.js +19 -0
  196. package/dist/slices/channels/model.js +5 -0
  197. package/dist/slices/channels/rebrand.js +100 -0
  198. package/dist/slices/channels/repo.js +221 -0
  199. package/dist/slices/channels/runs.js +129 -0
  200. package/dist/slices/channels/schema.js +152 -0
  201. package/dist/slices/channels/service.js +180 -0
  202. package/dist/slices/channels/studio-csv.js +110 -0
  203. package/dist/slices/channels/videos.js +120 -0
  204. package/dist/slices/checkpoints/recovery.js +10 -0
  205. package/dist/slices/control/index.js +5 -2
  206. package/dist/slices/control/providers.js +4 -1
  207. package/dist/slices/document/fonts.js +1 -1
  208. package/dist/slices/document/render.js +1 -1
  209. package/dist/slices/episodes/related.js +90 -0
  210. package/dist/slices/episodes/repo.js +60 -0
  211. package/dist/slices/episodes/service.js +58 -0
  212. package/dist/slices/episodes/summarize.js +170 -0
  213. package/dist/slices/estimate/index.js +109 -28
  214. package/dist/slices/estimate/requests.js +68 -2
  215. package/dist/slices/eta/history.js +56 -0
  216. package/dist/slices/eta/model.js +63 -0
  217. package/dist/slices/eta/view.js +25 -0
  218. package/dist/slices/fixes/rules.js +73 -0
  219. package/dist/slices/fonts/coverage.js +82 -0
  220. package/dist/slices/fonts/sfnt.js +74 -0
  221. package/dist/slices/images/run.js +8 -5
  222. package/dist/slices/images/scale.js +84 -0
  223. package/dist/slices/library/diff.js +122 -0
  224. package/dist/slices/library/history.js +100 -0
  225. package/dist/slices/library/model.js +3 -0
  226. package/dist/slices/library/photorealistic.js +49 -0
  227. package/dist/slices/library/repo.js +19 -14
  228. package/dist/slices/library/save.js +62 -12
  229. package/dist/slices/library/slots.js +7 -1
  230. package/dist/slices/library/used-by.js +153 -0
  231. package/dist/slices/loudness/level-pieces.js +41 -0
  232. package/dist/slices/loudness/loudnorm.js +164 -0
  233. package/dist/slices/loudness/model.js +153 -0
  234. package/dist/slices/model-upkeep/model.js +10 -0
  235. package/dist/slices/model-upkeep/switch.js +217 -0
  236. package/dist/slices/model-upkeep/usage.js +185 -0
  237. package/dist/slices/narration/aliases-library.js +31 -0
  238. package/dist/slices/narration/aliases-schema.js +53 -0
  239. package/dist/slices/narration/aliases.js +49 -0
  240. package/dist/slices/narration/blocks.js +352 -0
  241. package/dist/slices/narration/chunk.js +19 -7
  242. package/dist/slices/narration/concat.js +2 -2
  243. package/dist/slices/narration/describe.js +45 -0
  244. package/dist/slices/narration/pauses-model.js +63 -0
  245. package/dist/slices/narration/pauses.js +269 -0
  246. package/dist/slices/narration/peaks-model.js +1 -0
  247. package/dist/slices/narration/peaks.js +94 -0
  248. package/dist/slices/narration/preparation.js +21 -3
  249. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  250. package/dist/slices/narration/pronunciation.js +72 -25
  251. package/dist/slices/narration/run.js +6 -6
  252. package/dist/slices/narration/shared-glossary.js +1 -1
  253. package/dist/slices/narration/steering.js +15 -4
  254. package/dist/slices/notifications/notifier.js +47 -3
  255. package/dist/slices/notifications/rules.js +48 -3
  256. package/dist/slices/onboarding/copy.js +143 -0
  257. package/dist/slices/onboarding/first-run.js +72 -0
  258. package/dist/slices/onboarding/full-video.js +81 -0
  259. package/dist/slices/onboarding/install.js +119 -0
  260. package/dist/slices/onboarding/model.js +85 -0
  261. package/dist/slices/onboarding/packs.js +491 -0
  262. package/dist/slices/onboarding/quick-short.js +144 -0
  263. package/dist/slices/onboarding/sample.js +132 -0
  264. package/dist/slices/onboarding/state.js +68 -0
  265. package/dist/slices/onboarding/template.js +73 -0
  266. package/dist/slices/patch-notes/library.js +73 -0
  267. package/dist/slices/patch-notes/seen.js +77 -0
  268. package/dist/slices/play-drafts/convert.js +127 -8
  269. package/dist/slices/play-drafts/readiness.js +4 -1
  270. package/dist/slices/play-drafts/repo.js +2 -0
  271. package/dist/slices/play-drafts/review-inputs.js +20 -3
  272. package/dist/slices/play-drafts/schema.js +89 -0
  273. package/dist/slices/play-drafts/service.js +4 -1
  274. package/dist/slices/play-drafts/start.js +43 -18
  275. package/dist/slices/project-templates/from-project.js +59 -6
  276. package/dist/slices/project-templates/next-chapter.js +94 -0
  277. package/dist/slices/project-templates/one-off.js +26 -0
  278. package/dist/slices/project-templates/repo.js +2 -1
  279. package/dist/slices/project-templates/schema.js +7 -1
  280. package/dist/slices/project-templates/service.js +15 -4
  281. package/dist/slices/project-templates/setup.js +18 -2
  282. package/dist/slices/rebuild/admission-repo.js +1 -1
  283. package/dist/slices/rebuild/model.js +6 -0
  284. package/dist/slices/rebuild/preview-details.js +46 -3
  285. package/dist/slices/rebuild/preview-plan.js +13 -6
  286. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  287. package/dist/slices/rebuild/recipe-audio.js +66 -12
  288. package/dist/slices/rebuild/recipe-build.js +8 -4
  289. package/dist/slices/rebuild/recipe-cast.js +18 -0
  290. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  291. package/dist/slices/rebuild/recipe-describe.js +93 -0
  292. package/dist/slices/rebuild/recipe-edit.js +13 -1
  293. package/dist/slices/rebuild/recipe-exports.js +67 -8
  294. package/dist/slices/rebuild/recipe-input-schema.js +35 -0
  295. package/dist/slices/rebuild/recipe-legacy.js +9 -0
  296. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  297. package/dist/slices/rebuild/recipe-model.js +25 -6
  298. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  299. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  300. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  301. package/dist/slices/rebuild/recipe-reference.js +15 -1
  302. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  303. package/dist/slices/rebuild/recipe-save.js +4 -1
  304. package/dist/slices/rebuild/recipe-shorts.js +23 -3
  305. package/dist/slices/rebuild/recipe-text.js +114 -19
  306. package/dist/slices/rebuild/recipe-validation.js +14 -2
  307. package/dist/slices/rebuild/recipe-visual.js +96 -33
  308. package/dist/slices/rebuild/recipe-voices.js +286 -0
  309. package/dist/slices/rebuild/recipe-work.js +77 -6
  310. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  311. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  312. package/dist/slices/rebuild/recovery-model.js +3 -0
  313. package/dist/slices/rebuild/recovery-selection.js +28 -2
  314. package/dist/slices/rebuild/recovery.js +35 -17
  315. package/dist/slices/rebuild/repo.js +3 -3
  316. package/dist/slices/rebuild/retry.js +18 -0
  317. package/dist/slices/rebuild/review-redo.js +125 -0
  318. package/dist/slices/rebuild/runtime-actions.js +5 -0
  319. package/dist/slices/rebuild/runtime-admission.js +4 -1
  320. package/dist/slices/rebuild/runtime-animate.js +4 -4
  321. package/dist/slices/rebuild/runtime-article.js +5 -5
  322. package/dist/slices/rebuild/runtime-document.js +4 -4
  323. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  324. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  325. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  326. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  327. package/dist/slices/rebuild/runtime-export.js +84 -19
  328. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  329. package/dist/slices/rebuild/runtime-image.js +21 -2
  330. package/dist/slices/rebuild/runtime-local.js +148 -24
  331. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  332. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  333. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  334. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  335. package/dist/slices/rebuild/runtime-piece-label.js +27 -0
  336. package/dist/slices/rebuild/runtime-provider.js +126 -18
  337. package/dist/slices/rebuild/runtime-publication.js +21 -5
  338. package/dist/slices/rebuild/runtime-review.js +267 -0
  339. package/dist/slices/rebuild/runtime-run.js +17 -11
  340. package/dist/slices/rebuild/runtime-shorts.js +44 -17
  341. package/dist/slices/rebuild/runtime-store.js +62 -11
  342. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  343. package/dist/slices/rebuild/runtime-voices.js +103 -0
  344. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  345. package/dist/slices/rebuild/service-readiness.js +3 -3
  346. package/dist/slices/rebuild/soften.js +49 -0
  347. package/dist/slices/rebuild/transition-repo.js +9 -2
  348. package/dist/slices/research/run.js +4 -4
  349. package/dist/slices/research/synthesis.js +2 -2
  350. package/dist/slices/reviews/model.js +105 -0
  351. package/dist/slices/reviews/outcome.js +22 -0
  352. package/dist/slices/reviews/repo.js +118 -0
  353. package/dist/slices/reviews/rules.js +63 -0
  354. package/dist/slices/reviews/verdict.js +71 -0
  355. package/dist/slices/reviews/view.js +30 -0
  356. package/dist/slices/revisions/adopt-content.js +5 -2
  357. package/dist/slices/revisions/adopt.js +3 -1
  358. package/dist/slices/revisions/downloads.js +7 -1
  359. package/dist/slices/revisions/image-plan.js +183 -0
  360. package/dist/slices/revisions/mutation-assets.js +10 -0
  361. package/dist/slices/revisions/mutation-cues.js +71 -0
  362. package/dist/slices/revisions/mutation-request.js +2 -1
  363. package/dist/slices/revisions/mutations.js +97 -3
  364. package/dist/slices/revisions/schema.js +11 -1
  365. package/dist/slices/run-cost/limits.js +128 -0
  366. package/dist/slices/run-cost/meter.js +47 -0
  367. package/dist/slices/run-cost/panel.js +256 -0
  368. package/dist/slices/run-cost/pricing.js +95 -0
  369. package/dist/slices/run-cost/week.js +108 -0
  370. package/dist/slices/schedules/agenda.js +222 -0
  371. package/dist/slices/schedules/repo.js +58 -18
  372. package/dist/slices/schedules/scheduler.js +68 -17
  373. package/dist/slices/schedules/schema.js +179 -1
  374. package/dist/slices/schedules/service.js +43 -4
  375. package/dist/slices/schedules/similar.js +128 -0
  376. package/dist/slices/schedules/topic-list.js +163 -0
  377. package/dist/slices/schedules/topics.js +523 -0
  378. package/dist/slices/settings/first-run.js +106 -0
  379. package/dist/slices/settings/health.js +247 -0
  380. package/dist/slices/settings/key-guides.js +138 -0
  381. package/dist/slices/settings/key-test.js +93 -0
  382. package/dist/slices/settings/keys.js +10 -5
  383. package/dist/slices/settings/model-reach.js +52 -0
  384. package/dist/slices/settings/model.js +55 -1
  385. package/dist/slices/settings/models.js +5 -1
  386. package/dist/slices/settings/playback.js +15 -0
  387. package/dist/slices/settings/readiness.js +14 -2
  388. package/dist/slices/settings/repo.js +17 -3
  389. package/dist/slices/settings/tutorial-schema.js +5 -0
  390. package/dist/slices/settings/voices.js +64 -4
  391. package/dist/slices/settings/whats-new.js +64 -0
  392. package/dist/slices/shorts/captions.js +9 -0
  393. package/dist/slices/shorts/pick.js +3 -3
  394. package/dist/slices/shorts/prompts.js +1 -1
  395. package/dist/slices/shorts/render.js +63 -13
  396. package/dist/slices/storage/asset-name.js +3 -0
  397. package/dist/slices/storage/backup-export.js +48 -9
  398. package/dist/slices/storage/backup-format.js +38 -1
  399. package/dist/slices/storage/backup-import.js +191 -19
  400. package/dist/slices/storage/delete-project.js +7 -4
  401. package/dist/slices/storage/documents.js +87 -0
  402. package/dist/slices/storage/downloads.js +33 -1
  403. package/dist/slices/storage/files-location.js +531 -0
  404. package/dist/slices/storage/layout.js +14 -4
  405. package/dist/slices/storage/model.js +11 -0
  406. package/dist/slices/storage/portable.js +94 -12
  407. package/dist/slices/storage/reconcile.js +14 -1
  408. package/dist/slices/storage/repo.js +4 -1
  409. package/dist/slices/storage/schema.js +11 -0
  410. package/dist/slices/storage/trim.js +146 -0
  411. package/dist/slices/studio/disclosure.js +74 -0
  412. package/dist/slices/studio/model.js +30 -0
  413. package/dist/slices/studio/pack.js +211 -0
  414. package/dist/slices/studio/queue.js +69 -0
  415. package/dist/slices/studio/settings.js +170 -0
  416. package/dist/slices/style-preview/images.js +61 -0
  417. package/dist/slices/style-preview/narration.js +33 -0
  418. package/dist/slices/style-preview/render.js +148 -0
  419. package/dist/slices/style-preview/schema.js +59 -0
  420. package/dist/slices/style-preview/service.js +92 -0
  421. package/dist/slices/style-preview/settings.js +139 -0
  422. package/dist/slices/subtitles/captions.js +42 -9
  423. package/dist/slices/subtitles/model.js +22 -0
  424. package/dist/slices/subtitles/prepare.js +27 -16
  425. package/dist/slices/subtitles/transcript.js +1 -1
  426. package/dist/slices/thumbnail/run.js +7 -7
  427. package/dist/slices/trash/model.js +25 -0
  428. package/dist/slices/trash/service.js +273 -0
  429. package/dist/slices/tutorials/anchors.js +57 -0
  430. package/dist/slices/tutorials/library.js +185 -0
  431. package/dist/slices/uploads/repo.js +20 -0
  432. package/dist/slices/video/ambient-bed-schema.js +15 -0
  433. package/dist/slices/video/ambient-bed.js +95 -0
  434. package/dist/slices/video/ambient-mix.js +72 -0
  435. package/dist/slices/video/audio-inputs.js +2 -2
  436. package/dist/slices/video/cards.js +5 -2
  437. package/dist/slices/video/edit-list.js +23 -2
  438. package/dist/slices/video/edit-settings.js +13 -3
  439. package/dist/slices/video/ffmpeg.js +63 -9
  440. package/dist/slices/video/figure-card.js +492 -0
  441. package/dist/slices/video/figure-spans.js +51 -0
  442. package/dist/slices/video/plan.js +125 -13
  443. package/dist/slices/video/run.js +14 -12
  444. package/dist/slices/video/slideshow.js +39 -4
  445. package/dist/slices/voices/attribution.js +69 -0
  446. package/dist/slices/voices/audio-files.js +87 -0
  447. package/dist/slices/voices/audition.js +25 -0
  448. package/dist/slices/voices/cast.js +74 -0
  449. package/dist/slices/voices/grouping.js +59 -0
  450. package/dist/slices/voices/join.js +35 -0
  451. package/dist/slices/voices/languages.js +25 -0
  452. package/dist/slices/voices/model.js +218 -0
  453. package/dist/slices/voices/palette.js +26 -0
  454. package/dist/slices/voices/panel.js +91 -0
  455. package/dist/slices/voices/portraits.js +55 -0
  456. package/dist/slices/voices/script.js +136 -0
  457. package/dist/slices/voices/timing.js +58 -0
  458. package/dist/slices/youtube/answer.js +1 -1
  459. package/dist/slices/youtube/chapters.js +126 -0
  460. package/dist/slices/youtube/edits-repo.js +84 -0
  461. package/dist/slices/youtube/edits.js +89 -0
  462. package/dist/slices/youtube/placeholders.js +98 -0
  463. package/dist/slices/youtube/transcript.js +11 -4
  464. package/dist/tutorials/AI-CLIs.md +146 -0
  465. package/dist/tutorials/Backups.md +121 -0
  466. package/dist/tutorials/Calendar.md +103 -0
  467. package/dist/tutorials/Cast-Library.md +112 -0
  468. package/dist/tutorials/Channels.md +148 -0
  469. package/dist/tutorials/Costs-and-Run-Cost.md +136 -0
  470. package/dist/tutorials/Docker.md +171 -0
  471. package/dist/tutorials/Document-Themes.md +207 -0
  472. package/dist/tutorials/Editing-a-Project.md +204 -0
  473. package/dist/tutorials/FAQ.md +134 -0
  474. package/dist/tutorials/First-Launch-and-Welcome.md +153 -0
  475. package/dist/tutorials/Home-and-Projects.md +174 -0
  476. package/dist/tutorials/Home.md +119 -0
  477. package/dist/tutorials/Install.md +154 -0
  478. package/dist/tutorials/Intros-and-Outros.md +86 -0
  479. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +215 -0
  480. package/dist/tutorials/Library-Overview.md +75 -0
  481. package/dist/tutorials/Models.md +133 -0
  482. package/dist/tutorials/Multiple-Voices.md +161 -0
  483. package/dist/tutorials/Narration-Aliases-and-Glossary.md +129 -0
  484. package/dist/tutorials/Notifications.md +86 -0
  485. package/dist/tutorials/Other-Languages.md +110 -0
  486. package/dist/tutorials/PDF-Documents.md +100 -0
  487. package/dist/tutorials/Play-Images.md +135 -0
  488. package/dist/tutorials/Play-Narration.md +167 -0
  489. package/dist/tutorials/Play-Outputs.md +109 -0
  490. package/dist/tutorials/Play-Overview.md +216 -0
  491. package/dist/tutorials/Play-Title-and-Article.md +135 -0
  492. package/dist/tutorials/Play-Video-and-Style.md +143 -0
  493. package/dist/tutorials/Project-Page.md +228 -0
  494. package/dist/tutorials/Prompts.md +136 -0
  495. package/dist/tutorials/Providers-and-Keys.md +290 -0
  496. package/dist/tutorials/Publishing-to-YouTube.md +135 -0
  497. package/dist/tutorials/Recovery-and-Retries.md +141 -0
  498. package/dist/tutorials/Reviews-and-Checkpoints.md +143 -0
  499. package/dist/tutorials/Schedules.md +154 -0
  500. package/dist/tutorials/Settings-Reference.md +258 -0
  501. package/dist/tutorials/Shorts.md +116 -0
  502. package/dist/tutorials/Start-at-Login.md +126 -0
  503. package/dist/tutorials/Studio-Extension.md +146 -0
  504. package/dist/tutorials/Templates.md +146 -0
  505. package/dist/tutorials/Trash.md +87 -0
  506. package/dist/tutorials/Troubleshooting.md +149 -0
  507. package/dist/tutorials/Uninstalling-and-Moving.md +113 -0
  508. package/dist/tutorials/Updating-and-Patch-Notes.md +120 -0
  509. package/dist/tutorials/Video-Editing.md +135 -0
  510. package/dist/tutorials/Where-Your-Files-Live.md +149 -0
  511. package/dist/tutorials/YouTube-Description.md +132 -0
  512. package/dist/tutorials/Your-First-Short.md +125 -0
  513. package/dist/tutorials/_Footer.md +1 -0
  514. package/dist/tutorials/_Sidebar.md +69 -0
  515. package/dist/updater/service.js +66 -21
  516. package/dist/web/assets/index-DBLSfqA-.css +1 -0
  517. package/dist/web/assets/index-DFdIhmm0.js +307 -0
  518. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  519. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  520. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  521. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  522. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  523. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  524. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  525. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  526. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  527. package/dist/web/assets/{pdf-BLF2i8c2.js → pdf-B9OY1ioX.js} +1 -1
  528. package/dist/web/index.html +2 -2
  529. package/package.json +5 -5
  530. package/dist/edge/docker-launch.js +0 -81
  531. package/dist/edge/docker-projects/activation.js +0 -35
  532. package/dist/edge/docker-projects/claims.js +0 -85
  533. package/dist/edge/docker-projects/committed.js +0 -40
  534. package/dist/edge/docker-projects/engine.js +0 -461
  535. package/dist/edge/docker-projects/install.js +0 -254
  536. package/dist/edge/docker-projects/prune.js +0 -66
  537. package/dist/edge/docker-projects/recover.js +0 -100
  538. package/dist/edge/docker-projects/state.js +0 -225
  539. package/dist/web/assets/index-CktpxlJd.js +0 -171
  540. package/dist/web/assets/index-DB-vOnGU.css +0 -1
  541. package/scripts/docker-run.sh +0 -3
  542. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -0,0 +1,47 @@
1
+ import { planAccountOf } from "../../kernel/ports/plan-limits.js";
2
+ import { resolveChannelId, scheduleChannel } from "../channels/repo.js";
3
+ import { priceCall } from "./pricing.js";
4
+ // Prices each call as it lands and keeps it with the rates it used; a CLI's plan windows go
5
+ // beside it. Separate from the anonymous telemetry log: this is per project, never leaves the
6
+ // machine, and is kept whether or not the usage-stats notice was dismissed.
7
+ export function createUsageMeter(deps) {
8
+ return {
9
+ record: (call) => {
10
+ const priced = priceCall(call, deps.catalogue());
11
+ const at = deps.clock.now().toISOString();
12
+ deps.db
13
+ .prepare(`INSERT INTO provider_usage (id, project_id, stage, kind, provider, model, tokens_in, tokens_out, tokens_cached, characters, images, seconds, size, quality, wall_ms, on_plan, cost, api_model, api_cost, price_json, created_at)
14
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
15
+ .run(deps.ids.next(), call.projectId, call.stage, call.kind, call.provider, call.model, call.tokensIn ?? null, call.tokensOut ?? null, call.cachedTokens ?? null, call.characters ?? null, call.images ?? null, call.seconds ?? null, call.size ?? null, call.quality ?? null, Math.round(call.wallMs), priced.onPlan ? 1 : 0, priced.cost, priced.apiModel, priced.apiCost, priced.price === null ? null : JSON.stringify(priced.price), at);
16
+ const account = planAccountOf(call.provider);
17
+ if (account !== undefined && call.limits !== undefined)
18
+ deps.db
19
+ .prepare("INSERT INTO plan_limit_readings (id, project_id, account, reading_json, created_at) VALUES (?, ?, ?, ?, ?)")
20
+ .run(deps.ids.next(), call.projectId, account, JSON.stringify(call.limits), at);
21
+ },
22
+ };
23
+ }
24
+ // The same pricing for a call made outside a project (`kernel/runner/standalone.ts`), kept
25
+ // against its schedule or channel with the channel it counts under on Home.
26
+ export function createStandaloneMeter(deps) {
27
+ return {
28
+ record: (call) => {
29
+ const priced = priceCall(call, deps.catalogue());
30
+ const account = planAccountOf(call.provider);
31
+ const reading = account !== undefined && call.limits !== undefined
32
+ ? { account, json: JSON.stringify(call.limits) }
33
+ : undefined;
34
+ deps.db
35
+ .prepare(`INSERT INTO standalone_usage (id, owner_kind, owner_id, channel_id, purpose, kind, provider, model, tokens_in, tokens_out, tokens_cached, characters, images, seconds, size, quality, wall_ms, on_plan, cost, api_model, api_cost, price_json, account, reading_json, created_at)
36
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
37
+ .run(deps.ids.next(), call.owner.kind, call.owner.id, channelOf(deps.db, call.owner), call.purpose, call.kind, call.provider, call.model, call.tokensIn ?? null, call.tokensOut ?? null, call.cachedTokens ?? null, call.characters ?? null, call.images ?? null, call.seconds ?? null, call.size ?? null, call.quality ?? null, Math.round(call.wallMs), priced.onPlan ? 1 : 0, priced.cost, priced.apiModel, priced.apiCost, priced.price === null ? null : JSON.stringify(priced.price), reading?.account ?? null, reading?.json ?? null, deps.clock.now().toISOString());
38
+ },
39
+ };
40
+ }
41
+ // A schedule counts under its template's channel; one whose template is gone, like a channel
42
+ // this install doesn't have, under the default channel.
43
+ function channelOf(db, owner) {
44
+ return owner.kind === "schedule"
45
+ ? resolveChannelId(db, scheduleChannel(db, owner.id)?.id)
46
+ : resolveChannelId(db, owner.id);
47
+ }
@@ -0,0 +1,256 @@
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
+ return limitWaitRows(db, projectId).map((row) => row.wait);
211
+ }
212
+ // Every project's waits at once, for the lists that show "Waiting for Codex limits" on a row
213
+ // (Projects, Home's Running now, the calendar) without asking once per project.
214
+ export function limitWaitsByProject(db) {
215
+ const found = new Map();
216
+ for (const row of limitWaitRows(db)) {
217
+ const list = found.get(row.projectId) ?? [];
218
+ list.push(row.wait);
219
+ found.set(row.projectId, list);
220
+ }
221
+ return found;
222
+ }
223
+ // A wait as a list row carries it: the plan's name and times, not its account key.
224
+ export function listingWait(wait) {
225
+ return { name: wait.name, stage: wait.stage, resetsAt: wait.resetsAt, retryAt: wait.retryAt };
226
+ }
227
+ function limitWaitRows(db, projectId) {
228
+ // Only a stage still running is waiting: a row left by a cancel or pause is not shown.
229
+ return db
230
+ .prepare(`SELECT w.project_id, w.account, w.stage, l.resets_at, l.retry_at FROM plan_limit_waiters w
231
+ JOIN plan_limit_waits l ON l.account = w.account
232
+ JOIN stages s ON s.project_id = w.project_id AND s.kind = w.stage AND s.state = 'running'
233
+ ${projectId === undefined ? "" : "WHERE w.project_id = ?"} ORDER BY w.since`)
234
+ .all(...(projectId === undefined ? [] : [projectId]))
235
+ .map((raw) => {
236
+ const row = z
237
+ .object({
238
+ project_id: z.string(),
239
+ account: z.enum(planAccounts),
240
+ stage: z.enum(stageKinds),
241
+ resets_at: z.string().nullable(),
242
+ retry_at: z.string(),
243
+ })
244
+ .parse(raw);
245
+ return {
246
+ projectId: row.project_id,
247
+ wait: {
248
+ account: row.account,
249
+ name: planAccountNames[row.account],
250
+ stage: row.stage,
251
+ resetsAt: row.resets_at,
252
+ retryAt: row.retry_at,
253
+ },
254
+ };
255
+ });
256
+ }
@@ -0,0 +1,95 @@
1
+ import { isLocalCliProvider, systemVoiceProvider } 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
+ // The computer's own voice costs nothing.
60
+ if (call.provider === systemVoiceProvider)
61
+ return { onPlan: false, cost: 0, apiModel: null, apiCost: null, price: null };
62
+ const family = call.kind === "video" ? "image" : call.kind;
63
+ const entry = catalogue[family].find((model) => model.provider === call.provider && model.id === call.model);
64
+ const rates = entry?.pricing;
65
+ let cost = null;
66
+ if (rates !== undefined)
67
+ switch (call.kind) {
68
+ case "llm":
69
+ cost =
70
+ call.tokensIn === undefined || call.tokensOut === undefined
71
+ ? null
72
+ : tokenCost(rates, call.tokensIn, call.tokensOut, call.cachedTokens);
73
+ break;
74
+ case "tts":
75
+ // ceiling: a per-minute voice is left unpriced rather than guessed from characters;
76
+ // the narration's measured length is known only after the stage writes the file.
77
+ cost =
78
+ rates.perMillionCharacters === undefined || call.characters === undefined
79
+ ? null
80
+ : (call.characters / 1_000_000) * rates.perMillionCharacters;
81
+ break;
82
+ case "image":
83
+ case "video":
84
+ // An image-to-video model's perImage is the price of one clip.
85
+ cost = rates.perImage === undefined ? null : rates.perImage * (call.images ?? 1);
86
+ break;
87
+ }
88
+ return {
89
+ onPlan: false,
90
+ cost,
91
+ apiModel: null,
92
+ apiCost: null,
93
+ price: rates === undefined ? null : { catalogue: date, model: call.model, ...rates },
94
+ };
95
+ }
@@ -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
+ }