@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.
- package/LICENSE +201 -21
- package/NOTICE +12 -0
- package/README.md +21 -40
- package/SUBTITLES.md +1 -1
- package/dist/adapter-registry.js +11 -1
- package/dist/adapters/alignment/cache.js +4 -1
- package/dist/adapters/alignment/ctc.js +25 -23
- package/dist/adapters/alignment/index.js +37 -6
- package/dist/adapters/alignment/multilingual.js +267 -0
- package/dist/adapters/alignment/protocol.js +11 -0
- package/dist/adapters/alignment/quality.js +4 -3
- package/dist/adapters/alignment/sentences.js +95 -0
- package/dist/adapters/alignment/spec.js +24 -0
- package/dist/adapters/alignment/spell.js +769 -0
- package/dist/adapters/alignment/text.js +56 -9
- package/dist/adapters/alignment/window.js +34 -30
- package/dist/adapters/alignment/worker.js +23 -21
- package/dist/adapters/explain.js +11 -11
- package/dist/adapters/host-cli/index.js +55 -5
- package/dist/adapters/host-cli/transport.js +13 -6
- package/dist/adapters/image/bytes.js +2 -2
- package/dist/adapters/image/codex-output.js +60 -18
- package/dist/adapters/image/codex.js +166 -25
- package/dist/adapters/image/fal.js +31 -3
- package/dist/adapters/image/google.js +17 -2
- package/dist/adapters/image/openai.js +45 -16
- package/dist/adapters/image/reference.js +41 -0
- package/dist/adapters/image/replicate.js +14 -2
- package/dist/adapters/image/video.js +2 -2
- package/dist/adapters/key-probes.js +36 -0
- package/dist/adapters/llm/claude-code.js +168 -32
- package/dist/adapters/llm/cli-login-error.js +1 -1
- package/dist/adapters/llm/codex-limits.js +157 -0
- package/dist/adapters/llm/codex-models.js +2 -1
- package/dist/adapters/llm/codex-server-models.js +2 -1
- package/dist/adapters/llm/codex.js +99 -12
- package/dist/adapters/llm/document-workspace.js +2 -2
- package/dist/adapters/llm/gemini-workspace.js +1 -1
- package/dist/adapters/llm/gemini.js +16 -3
- package/dist/adapters/llm/image-workspace.js +58 -0
- package/dist/adapters/llm/openrouter.js +11 -3
- package/dist/adapters/llm/run-cli.js +26 -9
- package/dist/adapters/tts/cartesia.js +10 -0
- package/dist/adapters/tts/elevenlabs.js +31 -9
- package/dist/adapters/tts/inworld-async.js +9 -5
- package/dist/adapters/tts/inworld.js +17 -3
- package/dist/adapters/tts/openai.js +3 -0
- package/dist/adapters/tts/voice-languages.js +80 -0
- package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/OFL.txt +93 -0
- package/dist/assets/fonts/noto/SOURCE.txt +18 -0
- package/dist/assets/models.yaml +7 -0
- package/dist/assets/sample/sample-audiobook.tar +0 -0
- package/dist/assets/sample/sample-podcast.tar +0 -0
- package/dist/assets/sample/sample-project.tar +0 -0
- package/dist/catalog/merge.js +132 -0
- package/dist/catalog/registry.js +33 -12
- package/dist/catalog/runtime-models.js +1 -1
- package/dist/catalog/schema.js +12 -1
- package/dist/catalog/store.js +147 -8
- package/dist/catalog/validate.js +17 -2
- package/dist/compose.yaml +71 -0
- package/dist/edge/autostart/docker-record.js +32 -0
- package/dist/edge/autostart/docker.js +50 -0
- package/dist/edge/autostart/index.js +78 -0
- package/dist/edge/autostart/launcher.js +176 -0
- package/dist/edge/autostart/model.js +3 -0
- package/dist/edge/autostart/native.js +210 -0
- package/dist/edge/autostart/prompt.js +66 -0
- package/dist/edge/autostart/service.js +81 -0
- package/dist/edge/cli.js +57 -51
- package/dist/edge/docker-install/activation.js +56 -0
- package/dist/edge/docker-install/apply.js +458 -0
- package/dist/edge/docker-install/engine.js +362 -0
- package/dist/edge/docker-install/run.js +154 -0
- package/dist/edge/docker-install/state.js +146 -0
- package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
- package/dist/edge/docker.js +14 -22
- package/dist/edge/events/hub.js +11 -0
- package/dist/edge/host-cli.js +1 -1
- package/dist/edge/http/actions.js +32 -10
- package/dist/edge/http/app.js +60 -2
- package/dist/edge/http/auditions.js +70 -0
- package/dist/edge/http/autostart.js +37 -0
- package/dist/edge/http/backups.js +40 -0
- package/dist/edge/http/channel-memory.js +92 -0
- package/dist/edge/http/channels.js +128 -0
- package/dist/edge/http/entries.js +22 -3
- package/dist/edge/http/files.js +1 -1
- package/dist/edge/http/folder-location.js +1 -1
- package/dist/edge/http/home.js +39 -0
- package/dist/edge/http/host-cli.js +45 -6
- package/dist/edge/http/narration-peaks.js +76 -0
- package/dist/edge/http/onboarding.js +152 -0
- package/dist/edge/http/open-folder.js +1 -1
- package/dist/edge/http/patch-notes.js +45 -0
- package/dist/edge/http/planning.js +4 -2
- package/dist/edge/http/problem.js +1 -0
- package/dist/edge/http/project-create.js +87 -0
- package/dist/edge/http/projects.js +31 -99
- package/dist/edge/http/prompts.js +40 -3
- package/dist/edge/http/pronunciations.js +23 -4
- package/dist/edge/http/providers.js +86 -0
- package/dist/edge/http/reviews.js +121 -0
- package/dist/edge/http/revision-files.js +1 -1
- package/dist/edge/http/revisions.js +1 -1
- package/dist/edge/http/run-cost.js +25 -0
- package/dist/edge/http/schedules.js +111 -6
- package/dist/edge/http/settings.js +128 -6
- package/dist/edge/http/storage-files.js +50 -0
- package/dist/edge/http/storage.js +29 -2
- package/dist/edge/http/studio.js +207 -0
- package/dist/edge/http/style-preview.js +100 -0
- package/dist/edge/http/subtitles.js +1 -1
- package/dist/edge/http/trash.js +69 -0
- package/dist/edge/http/update.js +24 -3
- package/dist/edge/http/whats-new.js +15 -0
- package/dist/edge/http/youtube-edits.js +83 -0
- package/dist/edge/native-update.js +62 -0
- package/dist/host-cli/open-folder.js +10 -10
- package/dist/host-cli/runtime.js +12 -3
- package/dist/kernel/db/migrations/0022-drop-duplicate-held-work.sql +56 -0
- package/dist/kernel/db/migrations/0024-reference-attachments.sql +18 -0
- package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
- package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
- package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
- package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
- package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
- package/dist/kernel/db/migrations/0032-channels.sql +65 -0
- package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
- package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
- package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
- package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
- package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
- package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
- package/dist/kernel/paths.js +27 -6
- package/dist/kernel/pipeline.js +4 -1
- package/dist/kernel/ports/host-cli.js +139 -16
- package/dist/kernel/ports/image.js +3 -1
- package/dist/kernel/ports/key-probe.js +1 -0
- package/dist/kernel/ports/languages.js +157 -0
- package/dist/kernel/ports/llm.js +3 -1
- package/dist/kernel/ports/model.js +5 -2
- package/dist/kernel/ports/narration-aliases.js +60 -0
- package/dist/kernel/ports/plan-limits.js +36 -0
- package/dist/kernel/runner/attempt.js +18 -9
- package/dist/kernel/runner/audition.js +36 -0
- package/dist/kernel/runner/graph.js +24 -5
- package/dist/kernel/runner/index.js +35 -5
- package/dist/kernel/runner/meter.js +1 -0
- package/dist/kernel/runner/providers.js +164 -9
- package/dist/kernel/runner/retry-policy.js +43 -0
- package/dist/kernel/runner/standalone.js +134 -0
- package/dist/kernel/runner/work-authority.js +28 -2
- package/dist/main.js +321 -20
- package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
- package/dist/patch-notes/1.0-to-2.0.md +74 -0
- package/dist/patch-notes/2.0-to-3.0.md +257 -0
- package/dist/patch-notes/3.0.0.md +70 -0
- package/dist/patch-notes/index.json +26 -0
- package/dist/slices/admission/model.js +35 -0
- package/dist/slices/admission/repo.js +24 -4
- package/dist/slices/admission/rules.js +126 -1
- package/dist/slices/admission/schema.js +68 -1
- package/dist/slices/admission/short-mode.js +47 -0
- package/dist/slices/admission/start.js +90 -16
- package/dist/slices/article/continuation.js +2 -2
- package/dist/slices/article/provided-entries.js +2 -2
- package/dist/slices/article/run.js +6 -2
- package/dist/slices/article/segments.js +3 -3
- package/dist/slices/backups/files.js +135 -0
- package/dist/slices/backups/folder.js +43 -0
- package/dist/slices/backups/model.js +65 -0
- package/dist/slices/backups/repo.js +28 -0
- package/dist/slices/backups/schedule.js +60 -0
- package/dist/slices/backups/service.js +311 -0
- package/dist/slices/batch/index.js +9 -2
- package/dist/slices/cancel/index.js +1 -1
- package/dist/slices/channels/cast-images.js +110 -0
- package/dist/slices/channels/cast-match.js +32 -0
- package/dist/slices/channels/images.js +19 -0
- package/dist/slices/channels/model.js +5 -0
- package/dist/slices/channels/rebrand.js +100 -0
- package/dist/slices/channels/repo.js +218 -0
- package/dist/slices/channels/runs.js +129 -0
- package/dist/slices/channels/schema.js +129 -0
- package/dist/slices/channels/service.js +180 -0
- package/dist/slices/channels/studio-csv.js +110 -0
- package/dist/slices/channels/videos.js +120 -0
- package/dist/slices/checkpoints/recovery.js +10 -0
- package/dist/slices/control/index.js +2 -1
- package/dist/slices/document/fonts.js +1 -1
- package/dist/slices/document/render.js +1 -1
- package/dist/slices/episodes/related.js +90 -0
- package/dist/slices/episodes/repo.js +60 -0
- package/dist/slices/episodes/service.js +58 -0
- package/dist/slices/episodes/summarize.js +170 -0
- package/dist/slices/estimate/index.js +113 -24
- package/dist/slices/estimate/requests.js +60 -2
- package/dist/slices/fixes/rules.js +73 -0
- package/dist/slices/fonts/coverage.js +82 -0
- package/dist/slices/fonts/sfnt.js +74 -0
- package/dist/slices/images/run.js +8 -5
- package/dist/slices/images/scale.js +84 -0
- package/dist/slices/library/diff.js +122 -0
- package/dist/slices/library/history.js +100 -0
- package/dist/slices/library/model.js +3 -0
- package/dist/slices/library/photorealistic.js +49 -0
- package/dist/slices/library/repo.js +19 -14
- package/dist/slices/library/save.js +62 -12
- package/dist/slices/library/slots.js +15 -3
- package/dist/slices/library/snapshot.js +8 -2
- package/dist/slices/library/used-by.js +153 -0
- package/dist/slices/loudness/level-pieces.js +41 -0
- package/dist/slices/loudness/loudnorm.js +164 -0
- package/dist/slices/loudness/model.js +153 -0
- package/dist/slices/model-upkeep/model.js +10 -0
- package/dist/slices/model-upkeep/switch.js +217 -0
- package/dist/slices/model-upkeep/usage.js +183 -0
- package/dist/slices/narration/aliases-library.js +31 -0
- package/dist/slices/narration/aliases-schema.js +53 -0
- package/dist/slices/narration/aliases.js +49 -0
- package/dist/slices/narration/blocks.js +352 -0
- package/dist/slices/narration/chunk.js +19 -7
- package/dist/slices/narration/concat.js +2 -2
- package/dist/slices/narration/describe.js +45 -0
- package/dist/slices/narration/pauses-model.js +63 -0
- package/dist/slices/narration/pauses.js +269 -0
- package/dist/slices/narration/peaks-model.js +1 -0
- package/dist/slices/narration/peaks.js +94 -0
- package/dist/slices/narration/preparation.js +21 -3
- package/dist/slices/narration/pronunciation-chunks.js +3 -3
- package/dist/slices/narration/pronunciation.js +72 -25
- package/dist/slices/narration/run.js +6 -6
- package/dist/slices/narration/shared-glossary.js +1 -1
- package/dist/slices/narration/steering.js +15 -4
- package/dist/slices/notifications/notifier.js +94 -0
- package/dist/slices/notifications/rules.js +102 -0
- package/dist/slices/notifications/send.js +40 -0
- package/dist/slices/notifications/settings.js +34 -0
- package/dist/slices/onboarding/copy.js +143 -0
- package/dist/slices/onboarding/first-run.js +46 -0
- package/dist/slices/onboarding/install.js +119 -0
- package/dist/slices/onboarding/model.js +64 -0
- package/dist/slices/onboarding/packs.js +491 -0
- package/dist/slices/onboarding/quick-short.js +129 -0
- package/dist/slices/onboarding/sample.js +132 -0
- package/dist/slices/onboarding/state.js +68 -0
- package/dist/slices/onboarding/template.js +73 -0
- package/dist/slices/patch-notes/library.js +73 -0
- package/dist/slices/patch-notes/seen.js +77 -0
- package/dist/slices/play-drafts/convert.js +160 -9
- package/dist/slices/play-drafts/readiness.js +3 -0
- package/dist/slices/play-drafts/repo.js +9 -1
- package/dist/slices/play-drafts/review-inputs.js +22 -3
- package/dist/slices/play-drafts/schema.js +109 -2
- package/dist/slices/play-drafts/service.js +10 -1
- package/dist/slices/play-drafts/start.js +43 -18
- package/dist/slices/play-drafts/uploads.js +2 -1
- package/dist/slices/project-templates/from-project.js +70 -4
- package/dist/slices/project-templates/one-off.js +26 -0
- package/dist/slices/project-templates/repo.js +2 -1
- package/dist/slices/project-templates/service.js +15 -4
- package/dist/slices/project-templates/setup.js +29 -2
- package/dist/slices/rebuild/admission-repo.js +1 -1
- package/dist/slices/rebuild/model.js +6 -0
- package/dist/slices/rebuild/preview-details.js +67 -2
- package/dist/slices/rebuild/preview-plan.js +13 -6
- package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
- package/dist/slices/rebuild/recipe-audio.js +66 -12
- package/dist/slices/rebuild/recipe-build.js +15 -5
- package/dist/slices/rebuild/recipe-cast.js +18 -0
- package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
- package/dist/slices/rebuild/recipe-describe.js +93 -0
- package/dist/slices/rebuild/recipe-edit.js +13 -1
- package/dist/slices/rebuild/recipe-exports.js +62 -8
- package/dist/slices/rebuild/recipe-input-schema.js +43 -1
- package/dist/slices/rebuild/recipe-legacy.js +12 -0
- package/dist/slices/rebuild/recipe-loudness.js +45 -0
- package/dist/slices/rebuild/recipe-model.js +25 -6
- package/dist/slices/rebuild/recipe-pauses.js +7 -0
- package/dist/slices/rebuild/recipe-preparation.js +3 -3
- package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
- package/dist/slices/rebuild/recipe-reference.js +58 -0
- package/dist/slices/rebuild/recipe-reviews.js +117 -0
- package/dist/slices/rebuild/recipe-save.js +4 -1
- package/dist/slices/rebuild/recipe-shorts.js +32 -5
- package/dist/slices/rebuild/recipe-text.js +114 -19
- package/dist/slices/rebuild/recipe-validation.js +29 -3
- package/dist/slices/rebuild/recipe-visual.js +104 -29
- package/dist/slices/rebuild/recipe-voices.js +286 -0
- package/dist/slices/rebuild/recipe-work.js +77 -6
- package/dist/slices/rebuild/recipe-youtube.js +2 -0
- package/dist/slices/rebuild/recovery-conflict.js +7 -3
- package/dist/slices/rebuild/recovery-model.js +3 -0
- package/dist/slices/rebuild/recovery-selection.js +28 -2
- package/dist/slices/rebuild/recovery.js +35 -17
- package/dist/slices/rebuild/repo.js +3 -3
- package/dist/slices/rebuild/retry.js +18 -0
- package/dist/slices/rebuild/review-redo.js +110 -0
- package/dist/slices/rebuild/runtime-actions.js +14 -2
- package/dist/slices/rebuild/runtime-admission.js +4 -1
- package/dist/slices/rebuild/runtime-animate.js +4 -4
- package/dist/slices/rebuild/runtime-article.js +5 -5
- package/dist/slices/rebuild/runtime-document.js +4 -4
- package/dist/slices/rebuild/runtime-export-bed.js +43 -0
- package/dist/slices/rebuild/runtime-export-edit.js +70 -11
- package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
- package/dist/slices/rebuild/runtime-export-short.js +85 -0
- package/dist/slices/rebuild/runtime-export.js +84 -19
- package/dist/slices/rebuild/runtime-figure-card.js +87 -0
- package/dist/slices/rebuild/runtime-image.js +69 -0
- package/dist/slices/rebuild/runtime-local.js +154 -27
- package/dist/slices/rebuild/runtime-materialize.js +8 -0
- package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
- package/dist/slices/rebuild/runtime-narration-text.js +7 -7
- package/dist/slices/rebuild/runtime-pictures.js +28 -0
- package/dist/slices/rebuild/runtime-piece-label.js +29 -0
- package/dist/slices/rebuild/runtime-provider.js +134 -19
- package/dist/slices/rebuild/runtime-publication.js +21 -5
- package/dist/slices/rebuild/runtime-review.js +237 -0
- package/dist/slices/rebuild/runtime-run.js +17 -11
- package/dist/slices/rebuild/runtime-shorts.js +48 -23
- package/dist/slices/rebuild/runtime-store.js +39 -10
- package/dist/slices/rebuild/runtime-subtitles.js +72 -19
- package/dist/slices/rebuild/runtime-voices.js +103 -0
- package/dist/slices/rebuild/runtime-youtube.js +7 -6
- package/dist/slices/rebuild/service-readiness.js +15 -1
- package/dist/slices/rebuild/soften.js +49 -0
- package/dist/slices/rebuild/transition-repo.js +10 -3
- package/dist/slices/research/run.js +4 -4
- package/dist/slices/research/synthesis.js +2 -2
- package/dist/slices/reviews/model.js +105 -0
- package/dist/slices/reviews/outcome.js +22 -0
- package/dist/slices/reviews/repo.js +106 -0
- package/dist/slices/reviews/rules.js +63 -0
- package/dist/slices/reviews/verdict.js +71 -0
- package/dist/slices/reviews/view.js +30 -0
- package/dist/slices/revisions/adopt-content.js +12 -2
- package/dist/slices/revisions/adopt.js +9 -2
- package/dist/slices/revisions/downloads.js +7 -1
- package/dist/slices/revisions/image-plan.js +183 -0
- package/dist/slices/revisions/mutation-assets.js +43 -9
- package/dist/slices/revisions/mutation-cues.js +71 -0
- package/dist/slices/revisions/mutation-prepare.js +8 -10
- package/dist/slices/revisions/mutation-request.js +2 -1
- package/dist/slices/revisions/mutations.js +97 -3
- package/dist/slices/revisions/schema.js +16 -2
- package/dist/slices/run-cost/limits.js +128 -0
- package/dist/slices/run-cost/meter.js +47 -0
- package/dist/slices/run-cost/panel.js +234 -0
- package/dist/slices/run-cost/pricing.js +92 -0
- package/dist/slices/run-cost/week.js +108 -0
- package/dist/slices/schedules/agenda.js +158 -0
- package/dist/slices/schedules/repo.js +58 -18
- package/dist/slices/schedules/scheduler.js +67 -16
- package/dist/slices/schedules/schema.js +138 -1
- package/dist/slices/schedules/service.js +48 -4
- package/dist/slices/schedules/similar.js +90 -0
- package/dist/slices/schedules/topic-list.js +163 -0
- package/dist/slices/schedules/topics.js +422 -0
- package/dist/slices/settings/first-run.js +102 -0
- package/dist/slices/settings/health.js +190 -0
- package/dist/slices/settings/key-guides.js +128 -0
- package/dist/slices/settings/key-test.js +85 -0
- package/dist/slices/settings/model.js +11 -1
- package/dist/slices/settings/playback.js +15 -0
- package/dist/slices/settings/repo.js +17 -3
- package/dist/slices/settings/voices.js +64 -4
- package/dist/slices/settings/whats-new.js +64 -0
- package/dist/slices/shorts/captions.js +9 -0
- package/dist/slices/shorts/pick.js +3 -3
- package/dist/slices/shorts/prompts.js +1 -1
- package/dist/slices/shorts/render.js +54 -5
- package/dist/slices/storage/asset-name.js +3 -0
- package/dist/slices/storage/backup-export.js +48 -9
- package/dist/slices/storage/backup-format.js +38 -1
- package/dist/slices/storage/backup-import.js +191 -19
- package/dist/slices/storage/delete-project.js +7 -4
- package/dist/slices/storage/documents.js +79 -0
- package/dist/slices/storage/files-location.js +531 -0
- package/dist/slices/storage/layout.js +20 -0
- package/dist/slices/storage/model.js +14 -0
- package/dist/slices/storage/portable.js +95 -12
- package/dist/slices/storage/reconcile.js +19 -2
- package/dist/slices/storage/repo.js +4 -1
- package/dist/slices/storage/schema.js +11 -0
- package/dist/slices/storage/trim.js +146 -0
- package/dist/slices/studio/disclosure.js +74 -0
- package/dist/slices/studio/model.js +28 -0
- package/dist/slices/studio/pack.js +209 -0
- package/dist/slices/studio/settings.js +133 -0
- package/dist/slices/style-preview/images.js +61 -0
- package/dist/slices/style-preview/render.js +161 -0
- package/dist/slices/style-preview/schema.js +45 -0
- package/dist/slices/style-preview/service.js +93 -0
- package/dist/slices/style-preview/settings.js +89 -0
- package/dist/slices/subtitles/captions.js +42 -9
- package/dist/slices/subtitles/model.js +22 -0
- package/dist/slices/subtitles/prepare.js +27 -16
- package/dist/slices/subtitles/transcript.js +1 -1
- package/dist/slices/thumbnail/run.js +7 -7
- package/dist/slices/trash/model.js +25 -0
- package/dist/slices/trash/service.js +273 -0
- package/dist/slices/uploads/repo.js +20 -0
- package/dist/slices/video/ambient-bed-schema.js +15 -0
- package/dist/slices/video/ambient-bed.js +95 -0
- package/dist/slices/video/ambient-mix.js +72 -0
- package/dist/slices/video/audio-inputs.js +2 -2
- package/dist/slices/video/cards.js +5 -2
- package/dist/slices/video/edit-list.js +23 -2
- package/dist/slices/video/edit-settings.js +13 -3
- package/dist/slices/video/ffmpeg.js +63 -9
- package/dist/slices/video/figure-card.js +492 -0
- package/dist/slices/video/figure-spans.js +51 -0
- package/dist/slices/video/plan.js +125 -13
- package/dist/slices/video/run.js +14 -12
- package/dist/slices/video/slideshow.js +39 -4
- package/dist/slices/voices/attribution.js +69 -0
- package/dist/slices/voices/audio-files.js +82 -0
- package/dist/slices/voices/audition.js +25 -0
- package/dist/slices/voices/cast.js +56 -0
- package/dist/slices/voices/grouping.js +49 -0
- package/dist/slices/voices/join.js +35 -0
- package/dist/slices/voices/languages.js +25 -0
- package/dist/slices/voices/model.js +179 -0
- package/dist/slices/voices/palette.js +26 -0
- package/dist/slices/voices/panel.js +91 -0
- package/dist/slices/voices/portraits.js +55 -0
- package/dist/slices/voices/script.js +136 -0
- package/dist/slices/voices/timing.js +58 -0
- package/dist/slices/youtube/answer.js +1 -1
- package/dist/slices/youtube/chapters.js +126 -0
- package/dist/slices/youtube/edits-repo.js +69 -0
- package/dist/slices/youtube/edits.js +89 -0
- package/dist/slices/youtube/placeholders.js +98 -0
- package/dist/slices/youtube/transcript.js +11 -4
- package/dist/updater/service.js +66 -21
- package/dist/web/assets/index-CUA2sUxU.css +1 -0
- package/dist/web/assets/index-CX590kE-.js +303 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/web/assets/{pdf-CAJNT254.js → pdf-BG8n_uc5.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +4 -4
- package/dist/edge/docker-launch.js +0 -81
- package/dist/edge/docker-projects/activation.js +0 -35
- package/dist/edge/docker-projects/claims.js +0 -85
- package/dist/edge/docker-projects/committed.js +0 -40
- package/dist/edge/docker-projects/engine.js +0 -461
- package/dist/edge/docker-projects/install.js +0 -254
- package/dist/edge/docker-projects/prune.js +0 -66
- package/dist/edge/docker-projects/recover.js +0 -100
- package/dist/edge/docker-projects/state.js +0 -225
- package/dist/web/assets/index-BGgYMm2u.css +0 -1
- package/dist/web/assets/index-BVVgtntL.js +0 -171
- package/scripts/docker-run.sh +0 -3
- /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
|
+
}
|