@gentbajko/slopify 3.25.9 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/account-limits.js +30 -0
- package/dist/adapters/alignment/cache.js +3 -5
- package/dist/adapters/alignment/ctc.js +1 -2
- package/dist/adapters/alignment/lock.js +4 -3
- package/dist/adapters/alignment/multilingual.js +0 -3
- package/dist/adapters/alignment/prefetch.js +2 -1
- package/dist/adapters/alignment/worker.js +2 -1
- package/dist/adapters/explain.js +0 -3
- package/dist/adapters/ffmpeg.js +19 -1
- package/dist/adapters/host-cli/index.js +19 -3
- package/dist/adapters/http-failure.js +53 -0
- package/dist/adapters/image/codex.js +36 -4
- package/dist/adapters/image/fal.js +17 -50
- package/dist/adapters/image/google.js +31 -55
- package/dist/adapters/image/openai.js +20 -47
- package/dist/adapters/image/reference.js +36 -0
- package/dist/adapters/image/replicate.js +18 -47
- package/dist/adapters/interest/search-interest.js +113 -0
- package/dist/adapters/key-probes.js +53 -2
- package/dist/adapters/llm/antigravity.js +4 -0
- package/dist/adapters/llm/assistant-cli.js +388 -0
- package/dist/adapters/llm/child-session.js +83 -0
- package/dist/adapters/llm/claude-code-models.js +51 -98
- package/dist/adapters/llm/claude-code.js +13 -2
- package/dist/adapters/llm/codex-limits.js +27 -68
- package/dist/adapters/llm/codex-server-models.js +61 -109
- package/dist/adapters/llm/codex.js +12 -1
- package/dist/adapters/llm/openrouter.js +10 -47
- package/dist/adapters/llm/run-cli.js +6 -22
- package/dist/adapters/llm/sse-lines.js +5 -1
- package/dist/adapters/model-access.js +37 -0
- package/dist/adapters/tts/cartesia.js +15 -46
- package/dist/adapters/tts/elevenlabs.js +20 -52
- package/dist/adapters/tts/gemini.js +19 -40
- package/dist/adapters/tts/inworld-async.js +4 -1
- package/dist/adapters/tts/inworld.js +3 -0
- package/dist/adapters/tts/openai.js +20 -50
- package/dist/assets/models-lifecycle.yaml +6 -0
- package/dist/assistant-wiring.js +60 -0
- package/dist/autopilot-timers.js +59 -0
- package/dist/catalog/lifecycle.js +52 -0
- package/dist/catalog/propose.js +204 -0
- package/dist/catalog/registry.js +3 -1
- package/dist/catalog/schema.js +2 -0
- package/dist/catalog/store.js +39 -0
- package/dist/edge/autostart/native.js +3 -5
- package/dist/edge/autostart/prompt.js +2 -1
- package/dist/edge/autostart/service.js +3 -4
- package/dist/edge/cli-args.js +3 -4
- package/dist/edge/cli.js +2 -1
- package/dist/edge/docker-install/apply.js +11 -11
- package/dist/edge/docker-install/run.js +2 -1
- package/dist/edge/docker-install/state.js +4 -3
- package/dist/edge/docker-install/tree.js +2 -4
- package/dist/edge/docker-install/volume.js +3 -2
- package/dist/edge/docker.js +5 -4
- package/dist/edge/events/hub.js +5 -5
- package/dist/edge/http/actions.js +4 -25
- package/dist/edge/http/actor.js +5 -3
- package/dist/edge/http/app.js +85 -10
- package/dist/edge/http/assistant.js +127 -0
- package/dist/edge/http/audio-preview.js +4 -11
- package/dist/edge/http/auditions.js +1 -1
- package/dist/edge/http/autostart.js +5 -5
- package/dist/edge/http/backups.js +1 -3
- package/dist/edge/http/channel-memory.js +7 -20
- package/dist/edge/http/channels.js +13 -26
- package/dist/edge/http/checkpoints.js +33 -17
- package/dist/edge/http/design-templates.js +167 -0
- package/dist/edge/http/designs.js +178 -0
- package/dist/edge/http/diagnostics.js +5 -1
- package/dist/edge/http/document-themes.js +2 -11
- package/dist/edge/http/draft-files.js +1 -3
- package/dist/edge/http/draft-problem.js +4 -9
- package/dist/edge/http/drafts.js +3 -3
- package/dist/edge/http/entries.js +6 -9
- package/dist/edge/http/extension-auth.js +27 -0
- package/dist/edge/http/external-reviews.js +12 -11
- package/dist/edge/http/files.js +5 -11
- package/dist/edge/http/folder-location.js +1 -4
- package/dist/edge/http/fonts.js +0 -7
- package/dist/edge/http/home.js +38 -13
- package/dist/edge/http/host-cli.js +18 -8
- package/dist/edge/http/ids.js +8 -0
- package/dist/edge/http/keywords.js +35 -0
- package/dist/edge/http/model-checks.js +132 -0
- package/dist/edge/http/narration-peaks.js +5 -13
- package/dist/edge/http/onboarding.js +1 -14
- package/dist/edge/http/open-folder.js +5 -10
- package/dist/edge/http/outputs.js +4 -12
- package/dist/edge/http/patch-notes.js +4 -10
- package/dist/edge/http/planning.js +2 -6
- package/dist/edge/http/problem.js +2 -4
- package/dist/edge/http/project-create.js +1 -0
- package/dist/edge/http/project-templates.js +29 -17
- package/dist/edge/http/projects.js +2 -12
- package/dist/edge/http/prompts.js +7 -14
- package/dist/edge/http/pronunciations.js +1 -2
- package/dist/edge/http/providers.js +39 -24
- package/dist/edge/http/refusal.js +29 -0
- package/dist/edge/http/remote-access.js +40 -0
- package/dist/edge/http/remote-guard.js +71 -0
- package/dist/edge/http/request-guard.js +91 -0
- package/dist/edge/http/reviews.js +11 -24
- package/dist/edge/http/revision-files.js +5 -10
- package/dist/edge/http/revisions.js +10 -15
- package/dist/edge/http/run-cost.js +1 -2
- package/dist/edge/http/same-page.js +80 -0
- package/dist/edge/http/schedules.js +59 -32
- package/dist/edge/http/settings.js +22 -13
- package/dist/edge/http/social.js +282 -0
- package/dist/edge/http/staging.js +2 -4
- package/dist/edge/http/storage-files.js +5 -7
- package/dist/edge/http/storage.js +23 -23
- package/dist/edge/http/studio.js +23 -50
- package/dist/edge/http/style-preview.js +1 -4
- package/dist/edge/http/subtitles.js +3 -17
- package/dist/edge/http/template-studio.js +433 -0
- package/dist/edge/http/trash.js +10 -11
- package/dist/edge/http/tutorial.js +1 -2
- package/dist/edge/http/tutorials.js +5 -11
- package/dist/edge/http/update.js +7 -13
- package/dist/edge/http/waveform.js +3 -6
- package/dist/edge/http/youtube-edits.js +2 -3
- package/dist/edge/mcp/assistant.js +279 -0
- package/dist/edge/mcp/attribution.js +8 -2
- package/dist/edge/mcp/call-route.js +11 -2
- package/dist/edge/mcp/excluded-routes.js +25 -0
- package/dist/edge/mcp/http.js +11 -2
- package/dist/edge/mcp/instructions.js +5 -3
- package/dist/edge/mcp/library-tools.js +1 -0
- package/dist/edge/mcp/route-tool.js +35 -15
- package/dist/edge/mcp/server.js +13 -8
- package/dist/edge/mcp/tools-channels.js +2 -0
- package/dist/edge/mcp/tools-designs.js +165 -0
- package/dist/edge/mcp/tools-library.js +14 -2
- package/dist/edge/mcp/tools-planning.js +23 -4
- package/dist/edge/mcp/tools-projects.js +28 -1
- package/dist/edge/mcp/tools-runs.js +14 -1
- package/dist/edge/mcp/tools-schedules.js +28 -4
- package/dist/edge/mcp/tools-setup.js +61 -3
- package/dist/edge/mcp/tools-social.js +46 -0
- package/dist/edge/mcp/tools-storage.js +8 -0
- package/dist/edge/mcp/tools-template-studio.js +166 -0
- package/dist/edge/mcp/tools.js +6 -0
- package/dist/edge/remote-access/cli.js +71 -0
- package/dist/edge/remote-access/model.js +32 -0
- package/dist/edge/remote-access/rules.js +207 -0
- package/dist/edge/remote-access/service.js +203 -0
- package/dist/edge/remote-access/store.js +44 -0
- package/dist/edge/remote-access/views.js +43 -0
- package/dist/extension/slopify-studio-chrome.zip +0 -0
- package/dist/extension/slopify-studio-firefox.zip +0 -0
- package/dist/host-cli/install.js +2 -2
- package/dist/host-cli/open-folder.js +2 -2
- package/dist/host-cli/paths.js +2 -4
- package/dist/host-cli/server.js +4 -4
- package/dist/host-cli/service.js +3 -3
- package/dist/host-cli/status.js +7 -7
- package/dist/kernel/db/migrations/0063-model-checks.sql +20 -0
- package/dist/kernel/db/migrations/0064-template-studio.sql +46 -0
- package/dist/kernel/db/migrations/0065-promise-and-assistant-reviews.sql +58 -0
- package/dist/kernel/db/migrations/0066-assistant.sql +44 -0
- package/dist/kernel/db/migrations/0067-autopilot.sql +6 -0
- package/dist/kernel/db/migrations/0068-topic-scores.sql +17 -0
- package/dist/kernel/db/migrations/0070-designs.sql +53 -0
- package/dist/kernel/db/migrations/0071-design-usage.sql +33 -0
- package/dist/kernel/db/migrations/0072-design-templates.sql +33 -0
- package/dist/kernel/db/migrations/0073-schedule-design-kind.sql +5 -0
- package/dist/kernel/db/migrations/0074-template-bakeoffs.sql +44 -0
- package/dist/kernel/db/migrations/0075-social-posts.sql +25 -0
- package/dist/kernel/db/migrations/0076-channel-social.sql +5 -0
- package/dist/kernel/db/migrations/0077-post-prompts.sql +17 -0
- package/dist/kernel/db/tx.js +2 -4
- package/dist/kernel/errors.js +27 -1
- package/dist/kernel/json-answer.js +15 -0
- package/dist/kernel/lock.js +4 -9
- package/dist/kernel/paths.js +8 -0
- package/dist/kernel/ports/assistant-cli.js +4 -0
- package/dist/kernel/ports/host-cli.js +3 -0
- package/dist/kernel/ports/interest.js +4 -0
- package/dist/kernel/ports/languages.js +0 -3
- package/dist/kernel/ports/model.js +2 -0
- package/dist/kernel/runner/attempt.js +3 -1
- package/dist/kernel/runner/audition.js +3 -1
- package/dist/kernel/runner/checkpoint-authority.js +2 -1
- package/dist/kernel/runner/index.js +8 -10
- package/dist/kernel/runner/model-access.js +40 -0
- package/dist/kernel/runner/model-check.js +153 -0
- package/dist/kernel/runner/providers.js +15 -10
- package/dist/kernel/runner/standalone.js +9 -5
- package/dist/main.js +334 -19
- package/dist/model-checks.js +166 -0
- package/dist/patch-notes/4.0.0.md +51 -0
- package/dist/patch-notes/index.json +6 -0
- package/dist/slices/admission/heads.js +2 -1
- package/dist/slices/admission/repo.js +2 -1
- package/dist/slices/admission/rules.js +43 -8
- package/dist/slices/admission/schema.js +8 -0
- package/dist/slices/admission/stage-writes.js +0 -3
- package/dist/slices/admission/start.js +48 -31
- package/dist/slices/admission/substitute.js +7 -4
- package/dist/slices/agents/attention-posts.js +31 -0
- package/dist/slices/agents/attention-templates.js +33 -0
- package/dist/slices/agents/attention.js +10 -1
- package/dist/slices/agents/connection.js +2 -6
- package/dist/slices/agents/model.js +6 -3
- package/dist/slices/agents/repo.js +15 -7
- package/dist/slices/article/continuation.js +0 -63
- package/dist/slices/article/plain.js +3 -2
- package/dist/slices/article/segments.js +2 -62
- package/dist/slices/assistant/access.js +1 -0
- package/dist/slices/assistant/asks.js +80 -0
- package/dist/slices/assistant/cost.js +54 -0
- package/dist/slices/assistant/gate.js +222 -0
- package/dist/slices/assistant/model.js +79 -0
- package/dist/slices/assistant/names.js +23 -0
- package/dist/slices/assistant/prompt.js +74 -0
- package/dist/slices/assistant/repo.js +74 -0
- package/dist/slices/assistant/tokens.js +18 -0
- package/dist/slices/assistant/turn.js +342 -0
- package/dist/slices/autopilot/buffer.js +76 -0
- package/dist/slices/autopilot/model.js +14 -0
- package/dist/slices/autopilot/notify.js +27 -0
- package/dist/slices/autopilot/repo.js +34 -0
- package/dist/slices/autopilot/signals.js +194 -0
- package/dist/slices/autopilot/warnings.js +85 -0
- package/dist/slices/autopilot/weekly.js +100 -0
- package/dist/slices/backups/files.js +2 -6
- package/dist/slices/backups/repo.js +3 -15
- package/dist/slices/backups/service.js +4 -3
- package/dist/slices/channels/cast-images.js +2 -1
- package/dist/slices/channels/model.js +0 -1
- package/dist/slices/channels/rebrand.js +0 -3
- package/dist/slices/channels/repo.js +4 -0
- package/dist/slices/channels/schema.js +6 -0
- package/dist/slices/channels/service.js +10 -0
- package/dist/slices/channels/videos.js +2 -14
- package/dist/slices/checkpoints/index.js +0 -1
- package/dist/slices/control/providers.js +0 -26
- package/dist/slices/designs/artwork.js +66 -0
- package/dist/slices/designs/budget.js +23 -0
- package/dist/slices/designs/calls.js +124 -0
- package/dist/slices/designs/check-context.js +38 -0
- package/dist/slices/designs/check-text.js +43 -0
- package/dist/slices/designs/fields.js +88 -0
- package/dist/slices/designs/frames.js +34 -0
- package/dist/slices/designs/gate.js +128 -0
- package/dist/slices/designs/layout.js +293 -0
- package/dist/slices/designs/model.js +35 -0
- package/dist/slices/designs/pdf-job.js +23 -0
- package/dist/slices/designs/pdf-pages.js +30 -0
- package/dist/slices/designs/pdf-runner.js +68 -0
- package/dist/slices/designs/pdf-worker.js +24 -0
- package/dist/slices/designs/picture-size.js +79 -0
- package/dist/slices/designs/pictures.js +55 -0
- package/dist/slices/designs/quote.js +24 -0
- package/dist/slices/designs/render-args.js +36 -0
- package/dist/slices/designs/render-input.js +59 -0
- package/dist/slices/designs/render.js +215 -0
- package/dist/slices/designs/repo.js +101 -0
- package/dist/slices/designs/requests.js +80 -0
- package/dist/slices/designs/runner.js +239 -0
- package/dist/slices/designs/sample-repo.js +91 -0
- package/dist/slices/designs/sample.js +222 -0
- package/dist/slices/designs/schema.js +87 -0
- package/dist/slices/designs/sections.js +44 -0
- package/dist/slices/designs/service.js +233 -0
- package/dist/slices/designs/steps-repo.js +102 -0
- package/dist/slices/designs/studio-kind.js +39 -0
- package/dist/slices/designs/template-checks.js +44 -0
- package/dist/slices/designs/template-make.js +118 -0
- package/dist/slices/designs/templates.js +218 -0
- package/dist/slices/document/fonts.js +2 -1
- package/dist/slices/episodes/summarize.js +5 -1
- package/dist/slices/estimate/index.js +45 -7
- package/dist/slices/fonts/catalog.js +3 -2
- package/dist/slices/fonts/discovery.js +3 -2
- package/dist/slices/fonts/files.js +2 -6
- package/dist/slices/fonts/index.js +0 -1
- package/dist/slices/fonts/sfnt.js +45 -10
- package/dist/slices/images/appearance.js +6 -15
- package/dist/slices/images/scenes.js +100 -27
- package/dist/slices/keywords/digest.js +84 -0
- package/dist/slices/keywords/facts.js +92 -0
- package/dist/slices/keywords/fill.js +80 -0
- package/dist/slices/keywords/model.js +75 -0
- package/dist/slices/keywords/rules.js +187 -0
- package/dist/slices/keywords/schema.js +28 -0
- package/dist/slices/keywords/specs.js +260 -0
- package/dist/slices/keywords/values.js +74 -0
- package/dist/slices/library/lint.js +29 -12
- package/dist/slices/library/model.js +2 -0
- package/dist/slices/library/photorealistic.js +4 -12
- package/dist/slices/library/rename.js +9 -7
- package/dist/slices/library/save.js +14 -10
- package/dist/slices/library/slots.js +37 -11
- package/dist/slices/library/transfer.js +2 -1
- package/dist/slices/library/used-by.js +25 -10
- package/dist/slices/loudness/loudnorm.js +51 -11
- package/dist/slices/model-checks/cost.js +61 -0
- package/dist/slices/model-checks/listing.js +124 -0
- package/dist/slices/model-checks/model.js +20 -0
- package/dist/slices/model-checks/record.js +32 -0
- package/dist/slices/model-checks/repo.js +120 -0
- package/dist/slices/model-checks/rules.js +112 -0
- package/dist/slices/model-checks/run.js +143 -0
- package/dist/slices/model-checks/service.js +198 -0
- package/dist/slices/model-upkeep/switch.js +70 -25
- package/dist/slices/model-upkeep/usage.js +26 -11
- package/dist/slices/narration/breaths.js +2 -1
- package/dist/slices/narration/clean-pieces.js +2 -1
- package/dist/slices/narration/concat.js +2 -1
- package/dist/slices/narration/peaks.js +3 -4
- package/dist/slices/narration/pronunciation-chunks.js +4 -3
- package/dist/slices/narration/pronunciation.js +3 -2
- package/dist/slices/narration/steering.js +4 -3
- package/dist/slices/notifications/notifier.js +4 -3
- package/dist/slices/notifications/rules.js +5 -2
- package/dist/slices/notifications/settings.js +4 -12
- package/dist/slices/notifications/subject.js +2 -1
- package/dist/slices/onboarding/copy.js +2 -1
- package/dist/slices/onboarding/first-run.js +3 -2
- package/dist/slices/onboarding/full-video.js +3 -2
- package/dist/slices/onboarding/install.js +4 -3
- package/dist/slices/onboarding/model.js +0 -16
- package/dist/slices/onboarding/packs.js +22 -22
- package/dist/slices/onboarding/quick-short.js +2 -1
- package/dist/slices/onboarding/sample.js +0 -1
- package/dist/slices/onboarding/state.js +2 -11
- package/dist/slices/onboarding/template.js +2 -1
- package/dist/slices/patch-notes/library.js +3 -2
- package/dist/slices/play-drafts/convert.js +4 -0
- package/dist/slices/play-drafts/review-inputs.js +8 -2
- package/dist/slices/play-drafts/review.js +5 -4
- package/dist/slices/play-drafts/schema.js +4 -0
- package/dist/slices/project-templates/from-project.js +9 -2
- package/dist/slices/project-templates/repo.js +6 -1
- package/dist/slices/project-templates/setup.js +8 -0
- package/dist/slices/project-templates/stand-ins.js +22 -0
- package/dist/slices/rebuild/activity.js +2 -0
- package/dist/slices/rebuild/admission-repo.js +3 -2
- package/dist/slices/rebuild/narration-history.js +4 -5
- package/dist/slices/rebuild/narration-retry.js +2 -1
- package/dist/slices/rebuild/preview-details.js +27 -9
- package/dist/slices/rebuild/preview-plan.js +3 -8
- package/dist/slices/rebuild/recipe-build.js +8 -3
- package/dist/slices/rebuild/recipe-describe.js +3 -3
- package/dist/slices/rebuild/recipe-digest.js +58 -0
- package/dist/slices/rebuild/recipe-input-schema.js +10 -0
- package/dist/slices/rebuild/recipe-keywords.js +71 -0
- package/dist/slices/rebuild/recipe-legacy.js +5 -1
- package/dist/slices/rebuild/recipe-model.js +4 -0
- package/dist/slices/rebuild/recipe-narration-text.js +2 -1
- package/dist/slices/rebuild/recipe-preparation.js +3 -3
- package/dist/slices/rebuild/recipe-reference.js +8 -4
- package/dist/slices/rebuild/recipe-reviews.js +27 -7
- package/dist/slices/rebuild/recipe-save.js +6 -3
- package/dist/slices/rebuild/recipe-scenes.js +40 -11
- package/dist/slices/rebuild/recipe-shorts.js +8 -8
- package/dist/slices/rebuild/recipe-social.js +46 -0
- package/dist/slices/rebuild/recipe-text.js +64 -20
- package/dist/slices/rebuild/recipe-validation.js +98 -28
- package/dist/slices/rebuild/recipe-visual.js +55 -16
- package/dist/slices/rebuild/recipe-work.js +15 -3
- package/dist/slices/rebuild/recipe-youtube.js +4 -3
- package/dist/slices/rebuild/recovery-selection.js +3 -1
- package/dist/slices/rebuild/recovery.js +3 -2
- package/dist/slices/rebuild/repo.js +4 -3
- package/dist/slices/rebuild/review-promise.js +25 -0
- package/dist/slices/rebuild/review-redo.js +2 -1
- package/dist/slices/rebuild/runtime-admission.js +4 -3
- package/dist/slices/rebuild/runtime-agent-review.js +11 -5
- package/dist/slices/rebuild/runtime-animate.js +9 -8
- package/dist/slices/rebuild/runtime-article.js +3 -2
- package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
- package/dist/slices/rebuild/runtime-document.js +8 -11
- package/dist/slices/rebuild/runtime-export-bed.js +6 -11
- package/dist/slices/rebuild/runtime-export-edit.js +6 -15
- package/dist/slices/rebuild/runtime-export-inputs.js +7 -7
- package/dist/slices/rebuild/runtime-export-short.js +8 -4
- package/dist/slices/rebuild/runtime-export.js +34 -12
- package/dist/slices/rebuild/runtime-figure-card.js +2 -1
- package/dist/slices/rebuild/runtime-image.js +4 -9
- package/dist/slices/rebuild/runtime-instructions.js +2 -0
- package/dist/slices/rebuild/runtime-keywords.js +59 -0
- package/dist/slices/rebuild/runtime-lines.js +2 -11
- package/dist/slices/rebuild/runtime-local.js +14 -20
- package/dist/slices/rebuild/runtime-narration-publication.js +2 -5
- package/dist/slices/rebuild/runtime-narration-reuse.js +9 -13
- package/dist/slices/rebuild/runtime-narration-text.js +10 -15
- package/dist/slices/rebuild/runtime-pictures.js +2 -4
- package/dist/slices/rebuild/runtime-piece-label.js +10 -6
- package/dist/slices/rebuild/runtime-plan.js +31 -5
- package/dist/slices/rebuild/runtime-provider.js +60 -19
- package/dist/slices/rebuild/runtime-publication.js +10 -9
- package/dist/slices/rebuild/runtime-review.js +33 -28
- package/dist/slices/rebuild/runtime-run.js +4 -1
- package/dist/slices/rebuild/runtime-shorts.js +25 -45
- package/dist/slices/rebuild/runtime-store.js +3 -8
- package/dist/slices/rebuild/runtime-subtitles.js +16 -14
- package/dist/slices/rebuild/runtime-voices.js +8 -15
- package/dist/slices/rebuild/runtime-youtube.js +11 -29
- package/dist/slices/rebuild/transition-repo.js +6 -5
- package/dist/slices/rebuild/work-records.js +6 -0
- package/dist/slices/reruns/cascade.js +1 -2
- package/dist/slices/reruns/index.js +6 -9
- package/dist/slices/reviews/external-model.js +3 -0
- package/dist/slices/reviews/external-repo.js +2 -1
- package/dist/slices/reviews/external-rows.js +2 -3
- package/dist/slices/reviews/model.js +26 -8
- package/dist/slices/reviews/repo.js +2 -1
- package/dist/slices/reviews/rules.js +1 -0
- package/dist/slices/reviews/view.js +5 -2
- package/dist/slices/revisions/downloads.js +4 -5
- package/dist/slices/revisions/mutation-assets.js +7 -4
- package/dist/slices/revisions/mutation-cues.js +2 -5
- package/dist/slices/revisions/mutations.js +0 -1
- package/dist/slices/revisions/repo.js +3 -1
- package/dist/slices/revisions/schema.js +1 -0
- package/dist/slices/revisions/view.js +20 -0
- package/dist/slices/run-cost/limits.js +5 -2
- package/dist/slices/run-cost/meter.js +12 -5
- package/dist/slices/run-cost/panel.js +8 -74
- package/dist/slices/run-cost/plan-readings.js +127 -0
- package/dist/slices/run-cost/plan-use.js +169 -0
- package/dist/slices/run-cost/pricing.js +3 -3
- package/dist/slices/run-cost/week.js +32 -4
- package/dist/slices/schedules/agenda.js +11 -2
- package/dist/slices/schedules/by-date.js +42 -0
- package/dist/slices/schedules/calendar.js +5 -0
- package/dist/slices/schedules/design-agenda.js +36 -0
- package/dist/slices/schedules/design-check.js +41 -0
- package/dist/slices/schedules/design-run.js +126 -0
- package/dist/slices/schedules/next-run.js +62 -0
- package/dist/slices/schedules/prepare.js +18 -2
- package/dist/slices/schedules/repo.js +24 -16
- package/dist/slices/schedules/rescore.js +35 -0
- package/dist/slices/schedules/scheduler.js +13 -3
- package/dist/slices/schedules/schema.js +54 -3
- package/dist/slices/schedules/score.js +107 -0
- package/dist/slices/schedules/service.js +57 -24
- package/dist/slices/schedules/topic-list.js +15 -0
- package/dist/slices/schedules/topic-scores.js +104 -0
- package/dist/slices/schedules/topics.js +48 -36
- package/dist/slices/settings/cli-paths.js +4 -6
- package/dist/slices/settings/first-run.js +2 -11
- package/dist/slices/settings/model-reach.js +3 -17
- package/dist/slices/settings/model.js +10 -0
- package/dist/slices/settings/playback.js +33 -22
- package/dist/slices/settings/probe-read.js +16 -0
- package/dist/slices/settings/repo.js +33 -0
- package/dist/slices/settings/tutorial.js +2 -2
- package/dist/slices/settings/voices.js +4 -12
- package/dist/slices/shorts/pick.js +2 -15
- package/dist/slices/shorts/prompts.js +2 -13
- package/dist/slices/shorts/render.js +3 -0
- package/dist/slices/social/channel.js +44 -0
- package/dist/slices/social/defaults.js +17 -0
- package/dist/slices/social/due.js +36 -0
- package/dist/slices/social/extension.js +18 -0
- package/dist/slices/social/fit.js +115 -0
- package/dist/slices/social/limits.js +186 -0
- package/dist/slices/social/links.js +21 -0
- package/dist/slices/social/media.js +131 -0
- package/dist/slices/social/model.js +75 -0
- package/dist/slices/social/notice.js +19 -0
- package/dist/slices/social/pack.js +176 -0
- package/dist/slices/social/post-prompt.js +55 -0
- package/dist/slices/social/queue.js +31 -0
- package/dist/slices/social/repo.js +133 -0
- package/dist/slices/social/reshape.js +97 -0
- package/dist/slices/social/rules.js +5 -0
- package/dist/slices/social/schema.js +70 -0
- package/dist/slices/social/serve.js +51 -0
- package/dist/slices/social/service.js +128 -0
- package/dist/slices/social/settings.js +21 -0
- package/dist/slices/social/sources.js +114 -0
- package/dist/slices/social/state.js +110 -0
- package/dist/slices/social/text.js +149 -0
- package/dist/slices/storage/arrange.js +3 -1
- package/dist/slices/storage/assets.js +8 -10
- package/dist/slices/storage/backup-designs.js +74 -0
- package/dist/slices/storage/backup-export.js +8 -3
- package/dist/slices/storage/backup-format.js +10 -0
- package/dist/slices/storage/backup-import.js +20 -2
- package/dist/slices/storage/delete-project.js +6 -5
- package/dist/slices/storage/files-location.js +6 -8
- package/dist/slices/storage/layout.js +13 -2
- package/dist/slices/storage/model.js +3 -0
- package/dist/slices/storage/portable.js +16 -11
- package/dist/slices/storage/schema.js +2 -0
- package/dist/slices/storage/staging-refs.js +2 -1
- package/dist/slices/storage/staging.js +6 -9
- package/dist/slices/storage/trim.js +10 -3
- package/dist/slices/studio/calendar.js +4 -1
- package/dist/slices/studio/pick.js +2 -11
- package/dist/slices/studio/plan.js +6 -15
- package/dist/slices/studio/queue.js +3 -14
- package/dist/slices/studio/releases.js +26 -0
- package/dist/slices/studio/settings.js +14 -51
- package/dist/slices/studio/videos.js +12 -0
- package/dist/slices/style-preview/render.js +34 -13
- package/dist/slices/style-preview/schema.js +13 -0
- package/dist/slices/style-preview/service.js +2 -1
- package/dist/slices/style-preview/settings.js +33 -5
- package/dist/slices/telemetry/collector-client.js +2 -4
- package/dist/slices/telemetry/flush.js +2 -4
- package/dist/slices/telemetry/model.js +0 -7
- package/dist/slices/telemetry/record.js +10 -3
- package/dist/slices/template-studio/anatomy-view.js +118 -0
- package/dist/slices/template-studio/anatomy.js +73 -0
- package/dist/slices/template-studio/bakeoff-make.js +225 -0
- package/dist/slices/template-studio/bakeoff-repo.js +83 -0
- package/dist/slices/template-studio/bakeoff.js +333 -0
- package/dist/slices/template-studio/budget.js +103 -0
- package/dist/slices/template-studio/checks.js +273 -0
- package/dist/slices/template-studio/document-sample.js +56 -0
- package/dist/slices/template-studio/edit.js +15 -0
- package/dist/slices/template-studio/for-all.js +161 -0
- package/dist/slices/template-studio/free-video.js +31 -0
- package/dist/slices/template-studio/free.js +176 -0
- package/dist/slices/template-studio/gate.js +40 -0
- package/dist/slices/template-studio/hidden.js +20 -0
- package/dist/slices/template-studio/jobs.js +79 -0
- package/dist/slices/template-studio/loudness.js +111 -0
- package/dist/slices/template-studio/make.js +40 -0
- package/dist/slices/template-studio/model.js +54 -0
- package/dist/slices/template-studio/pack-import.js +253 -0
- package/dist/slices/template-studio/pack-samples.js +60 -0
- package/dist/slices/template-studio/pack.js +205 -0
- package/dist/slices/template-studio/paid-view.js +66 -0
- package/dist/slices/template-studio/paid.js +348 -0
- package/dist/slices/template-studio/quote.js +148 -0
- package/dist/slices/template-studio/ready.js +104 -0
- package/dist/slices/template-studio/repo.js +134 -0
- package/dist/slices/template-studio/reviews-path.js +58 -0
- package/dist/slices/template-studio/runner.js +84 -0
- package/dist/slices/template-studio/save.js +236 -0
- package/dist/slices/template-studio/say-it-as.js +248 -0
- package/dist/slices/template-studio/schedule-gate.js +34 -0
- package/dist/slices/template-studio/schedule-price.js +30 -0
- package/dist/slices/template-studio/schedule-sample.js +87 -0
- package/dist/slices/template-studio/schema.js +218 -0
- package/dist/slices/template-studio/scope.js +124 -0
- package/dist/slices/template-studio/sections.js +176 -0
- package/dist/slices/template-studio/service.js +173 -0
- package/dist/slices/template-studio/status.js +174 -0
- package/dist/slices/template-studio/topic.js +223 -0
- package/dist/slices/trash/designs.js +76 -0
- package/dist/slices/trash/model.js +13 -4
- package/dist/slices/trash/service.js +52 -2
- package/dist/slices/tutorials/library.js +3 -2
- package/dist/slices/video/ass-text.js +17 -0
- package/dist/slices/video/audio-export.js +2 -64
- package/dist/slices/video/encoding.js +160 -0
- package/dist/slices/video/ffmpeg.js +23 -21
- package/dist/slices/video/figure-card.js +1 -15
- package/dist/slices/video/slideshow.js +43 -18
- package/dist/slices/voices/palette.js +0 -3
- package/dist/slices/youtube/answer.js +2 -15
- package/dist/slices/youtube/edits-repo.js +2 -11
- package/dist/tutorials/Agents-MCP.md +2 -1
- package/dist/tutorials/Assistant.md +42 -0
- package/dist/tutorials/Channels.md +2 -0
- package/dist/tutorials/Home-and-Projects.md +19 -0
- package/dist/tutorials/Install.md +1 -0
- package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
- package/dist/tutorials/Models.md +33 -1
- package/dist/tutorials/Notifications.md +6 -0
- package/dist/tutorials/Play-Images.md +4 -0
- package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
- package/dist/tutorials/Prompts.md +10 -6
- package/dist/tutorials/Remote-Access.md +63 -0
- package/dist/tutorials/Reviews-and-Checkpoints.md +13 -2
- package/dist/tutorials/Schedules.md +21 -1
- package/dist/tutorials/Settings-Reference.md +50 -8
- package/dist/tutorials/Template-Studio.md +125 -0
- package/dist/tutorials/Templates.md +3 -0
- package/dist/tutorials/_Sidebar.md +5 -0
- package/dist/updater/work-in-progress.js +42 -12
- package/dist/web/assets/index-CkAI_2E7.css +1 -0
- package/dist/web/assets/index-DcD0cdfo.js +334 -0
- package/dist/web/assets/{pdf-T7MCwjIv.js → pdf-TODRwF31.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +5 -1
- package/dist/slices/article/provided-entries.js +0 -66
- package/dist/slices/article/run.js +0 -145
- package/dist/slices/images/run.js +0 -234
- package/dist/slices/narration/legacy-plan.js +0 -58
- package/dist/slices/narration/run.js +0 -270
- package/dist/slices/rebuild/runtime-actions.js +0 -90
- package/dist/slices/research/run.js +0 -224
- package/dist/slices/subtitles/prepare.js +0 -194
- package/dist/slices/subtitles/transcript.js +0 -34
- package/dist/slices/thumbnail/run.js +0 -219
- package/dist/slices/video/audio-inputs.js +0 -33
- package/dist/slices/video/reuse-audio.js +0 -62
- package/dist/slices/video/run.js +0 -78
- package/dist/slices/video/write-export.js +0 -158
- package/dist/slices/video/write-subtitles.js +0 -59
- package/dist/web/assets/index-BadsoORk.css +0 -1
- package/dist/web/assets/index-D8LqNsCl.js +0 -326
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { nextOccurrence } from "../schedules/calendar.js";
|
|
2
|
+
const dayMs = 24 * 60 * 60_000;
|
|
3
|
+
// How far ahead the content is: each run the schedules will make is a slot, filled by a run
|
|
4
|
+
// made ahead or else by the next finished video not uploaded yet, in time order. A paused
|
|
5
|
+
// schedule's runs fill nothing and run dry never: they wait for Resume.
|
|
6
|
+
export function bufferForecast(input) {
|
|
7
|
+
const slots = input.runs
|
|
8
|
+
.filter((run) => !run.paused)
|
|
9
|
+
.toSorted((a, b) => a.at.localeCompare(b.at));
|
|
10
|
+
const prepared = slots.filter((run) => run.prepared !== null).length;
|
|
11
|
+
let spare = input.ready;
|
|
12
|
+
let coveredUntil = null;
|
|
13
|
+
let firstGap = null;
|
|
14
|
+
for (const slot of slots) {
|
|
15
|
+
if (slot.prepared === null) {
|
|
16
|
+
if (spare === 0) {
|
|
17
|
+
firstGap = slot.at;
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
spare -= 1;
|
|
21
|
+
}
|
|
22
|
+
coveredUntil = slot.at;
|
|
23
|
+
}
|
|
24
|
+
const daysTo = (at) => Math.max(0, Math.floor((at.valueOf() - input.now.valueOf()) / dayMs));
|
|
25
|
+
const perSchedule = input.schedules.map((schedule) => scheduleForecast(schedule, input.runs.filter((run) => run.scheduleId === schedule.id)));
|
|
26
|
+
const dry = perSchedule
|
|
27
|
+
.flatMap((one) => (one.runsDryAt === null ? [] : [one.runsDryAt]))
|
|
28
|
+
.toSorted();
|
|
29
|
+
return {
|
|
30
|
+
readyCount: input.ready + prepared,
|
|
31
|
+
daysAhead: slots.length === 0
|
|
32
|
+
? null
|
|
33
|
+
: firstGap === null
|
|
34
|
+
? daysTo(input.until)
|
|
35
|
+
: daysTo(new Date(firstGap)),
|
|
36
|
+
coveredUntil,
|
|
37
|
+
runsDryAt: dry[0] ?? null,
|
|
38
|
+
coveredBeyondWindow: slots.length > 0 && firstGap === null,
|
|
39
|
+
perSchedule,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function scheduleForecast(schedule, runs) {
|
|
43
|
+
const base = {
|
|
44
|
+
scheduleId: schedule.id,
|
|
45
|
+
name: schedule.name,
|
|
46
|
+
paused: schedule.paused,
|
|
47
|
+
queued: schedule.queued,
|
|
48
|
+
generating: schedule.generation,
|
|
49
|
+
generationFailing: schedule.generationFailing,
|
|
50
|
+
};
|
|
51
|
+
if (schedule.paused)
|
|
52
|
+
return { ...base, runsDryAt: null };
|
|
53
|
+
const sorted = runs.toSorted((a, b) => a.at.localeCompare(b.at));
|
|
54
|
+
const firstUnqueued = sorted.find((run) => run.topicSource !== "queued")?.at ?? null;
|
|
55
|
+
switch (schedule.generation) {
|
|
56
|
+
// It completes when its queue empties: the occurrence after the last queued run is the
|
|
57
|
+
// first with nothing to make. With no queue it runs its template as saved, every time.
|
|
58
|
+
case "off": {
|
|
59
|
+
if (schedule.queued === 0)
|
|
60
|
+
return { ...base, runsDryAt: null };
|
|
61
|
+
const queued = sorted.filter((run) => run.topicSource === "queued");
|
|
62
|
+
const last = queued.at(-1);
|
|
63
|
+
// The queue reaches past the window: it doesn't run dry within it.
|
|
64
|
+
if (last === undefined || queued.length < schedule.queued)
|
|
65
|
+
return { ...base, runsDryAt: null };
|
|
66
|
+
const next = nextOccurrence(schedule.cadence, schedule.timezone, new Date(new Date(last.at).valueOf() + 1000));
|
|
67
|
+
return { ...base, runsDryAt: next === null ? null : next.toISOString() };
|
|
68
|
+
}
|
|
69
|
+
// New topics wait for approval, so a run past the approved queue has nothing yet.
|
|
70
|
+
case "hold":
|
|
71
|
+
return { ...base, runsDryAt: firstUnqueued };
|
|
72
|
+
// It refills its queue by itself, unless finding topics keeps failing.
|
|
73
|
+
case "queue":
|
|
74
|
+
return { ...base, runsDryAt: schedule.generationFailing ? firstUnqueued : null };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Autopilot health: how far ahead the schedules' content is, and what is about to stop them
|
|
2
|
+
// (a CLI signed out, a plan or key running low, the disk filling, a queue of topics running
|
|
3
|
+
// out, topic generation failing, a model retiring). Browser-safe: Home reads these shapes.
|
|
4
|
+
// ceiling: the forecast looks two months ahead; the calendar can read 92 days.
|
|
5
|
+
export const forecastDays = 60;
|
|
6
|
+
// ceiling: a hidden warning comes back after a week if it is still true.
|
|
7
|
+
export const snoozeDays = 7;
|
|
8
|
+
// ceiling: a queue with fewer runs than this left, and nothing to refill it, is a warning.
|
|
9
|
+
export const fewTopicsLeft = 3;
|
|
10
|
+
// ceiling: the plan or key share at which work may soon stop.
|
|
11
|
+
export const lowPercent = 80;
|
|
12
|
+
// ceiling: free disk below either of these is a warning.
|
|
13
|
+
export const diskLowBytes = 5 * 1024 ** 3;
|
|
14
|
+
export const diskLowShare = 0.1;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { webhookBody } from "../notifications/address.js";
|
|
2
|
+
import { sendFailureText } from "../notifications/send.js";
|
|
3
|
+
import { readNotificationUrl } from "../notifications/settings.js";
|
|
4
|
+
import { markWeeklySent, weeklyNotifyOf, weeklySentFor } from "./repo.js";
|
|
5
|
+
import { weeklyDue, weeklyNoticeText, weeklyReport } from "./weekly.js";
|
|
6
|
+
const weekMs = 7 * 24 * 60 * 60_000;
|
|
7
|
+
// The Monday report, sent to the Notification URL once a week while Weekly report is on. A
|
|
8
|
+
// send that fails is tried again at the next check; one that went out is not sent again.
|
|
9
|
+
export async function sendWeeklyReport(deps) {
|
|
10
|
+
if (!weeklyNotifyOf(deps.db))
|
|
11
|
+
return "off";
|
|
12
|
+
const now = deps.clock.now();
|
|
13
|
+
const { due, week } = weeklyDue(now, deps.timeZone, weeklySentFor(deps.db));
|
|
14
|
+
if (!due)
|
|
15
|
+
return "not-due";
|
|
16
|
+
const url = readNotificationUrl(deps.db);
|
|
17
|
+
if (url === null)
|
|
18
|
+
return "no-url";
|
|
19
|
+
const report = weeklyReport(deps.db, new Date(now.valueOf() - weekMs).toISOString(), now.toISOString());
|
|
20
|
+
const result = await deps.send(url, webhookBody(weeklyNoticeText(report)));
|
|
21
|
+
if (!result.ok) {
|
|
22
|
+
deps.log.write("warn", "autopilot.weekly", { detail: sendFailureText(result) });
|
|
23
|
+
return "failed";
|
|
24
|
+
}
|
|
25
|
+
markWeeklySent(deps.db, week);
|
|
26
|
+
return "sent";
|
|
27
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readJsonSetting, writeSetting } from "../settings/repo.js";
|
|
3
|
+
const weeklyNotifyKey = "autopilot.weekly-notify";
|
|
4
|
+
const weeklySentKey = "autopilot.weekly-sent-for";
|
|
5
|
+
// The hidden warnings still hidden at `now`; ended ones are removed as they are read.
|
|
6
|
+
export function readSnoozes(db, now) {
|
|
7
|
+
const at = now.toISOString();
|
|
8
|
+
db.prepare("DELETE FROM autopilot_snoozes WHERE until<=?").run(at);
|
|
9
|
+
return new Map(db
|
|
10
|
+
.prepare("SELECT warning_key, until FROM autopilot_snoozes")
|
|
11
|
+
.all()
|
|
12
|
+
.map((row) => {
|
|
13
|
+
const parsed = z.object({ warning_key: z.string(), until: z.string() }).parse(row);
|
|
14
|
+
return [parsed.warning_key, parsed.until];
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
export function snoozeWarning(db, key, until) {
|
|
18
|
+
db.prepare("INSERT INTO autopilot_snoozes(warning_key, until) VALUES (?,?) ON CONFLICT(warning_key) DO UPDATE SET until=excluded.until").run(key, until.toISOString());
|
|
19
|
+
}
|
|
20
|
+
// Settings → Notifications' Weekly report. Off unless the person turned it on.
|
|
21
|
+
export function weeklyNotifyOf(db) {
|
|
22
|
+
return readJsonSetting(db, weeklyNotifyKey, z.boolean(), false);
|
|
23
|
+
}
|
|
24
|
+
export function setWeeklyNotify(db, enabled) {
|
|
25
|
+
writeSetting(db, weeklyNotifyKey, JSON.stringify(enabled));
|
|
26
|
+
}
|
|
27
|
+
// The ISO week the last report was sent for ("2026-W42").
|
|
28
|
+
export function weeklySentFor(db) {
|
|
29
|
+
const week = readJsonSetting(db, weeklySentKey, z.string(), null);
|
|
30
|
+
return week ?? undefined;
|
|
31
|
+
}
|
|
32
|
+
export function markWeeklySent(db, week) {
|
|
33
|
+
writeSetting(db, weeklySentKey, JSON.stringify(week));
|
|
34
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { errorText } from "../../kernel/errors.js";
|
|
2
|
+
import { defaultChannelId } from "../channels/model.js";
|
|
3
|
+
import { projectChannels, templateChannelId } from "../channels/repo.js";
|
|
4
|
+
import { choiceSites, retiredModelUsage, slotLabels } from "../model-upkeep/usage.js";
|
|
5
|
+
import { weekSummary } from "../run-cost/week.js";
|
|
6
|
+
import { calendarRange } from "../schedules/agenda.js";
|
|
7
|
+
import { scheduleRows } from "../schedules/repo.js";
|
|
8
|
+
import { bufferForecast } from "./buffer.js";
|
|
9
|
+
import { forecastDays } from "./model.js";
|
|
10
|
+
import { readSnoozes, weeklyNotifyOf } from "./repo.js";
|
|
11
|
+
import { warningsOf } from "./warnings.js";
|
|
12
|
+
import { weeklyReport } from "./weekly.js";
|
|
13
|
+
const dayMs = 24 * 60 * 60_000;
|
|
14
|
+
// ceiling: one probe that hangs is skipped after this, so the hourly check always ends.
|
|
15
|
+
export const probeTimeoutMs = 15_000;
|
|
16
|
+
const clis = {
|
|
17
|
+
"claude-code": { name: "Claude Code", signIn: "claude auth login" },
|
|
18
|
+
codex: { name: "Codex", signIn: "codex login" },
|
|
19
|
+
gemini: { name: "Antigravity CLI", signIn: "agy" },
|
|
20
|
+
};
|
|
21
|
+
const cliOfProvider = (provider) => provider === "codex-image"
|
|
22
|
+
? "codex"
|
|
23
|
+
: provider === "claude-code" || provider === "codex" || provider === "gemini"
|
|
24
|
+
? provider
|
|
25
|
+
: undefined;
|
|
26
|
+
const live = (schedule) => schedule.deletedAt === null && (schedule.status === "active" || schedule.status === "paused");
|
|
27
|
+
// A design schedule makes designed pieces from dated rows: no video fills a slot, no topic runs
|
|
28
|
+
// out and no CLI writes it, so the forecast and its warnings leave it out entirely.
|
|
29
|
+
const makesVideos = (schedule) => schedule.templateKind !== "design";
|
|
30
|
+
// The CLIs an active schedule's template or topic finding chooses: the only ones worth asking.
|
|
31
|
+
export function clisInUse(db) {
|
|
32
|
+
const schedules = scheduleRows(db).filter((one) => one.deletedAt === null && one.status === "active" && makesVideos(one));
|
|
33
|
+
const templates = new Set(schedules.map((one) => one.templateId));
|
|
34
|
+
const ids = new Set(schedules.map((one) => one.id));
|
|
35
|
+
const found = new Set();
|
|
36
|
+
for (const site of choiceSites(db)) {
|
|
37
|
+
const used = (site.kind === "template" && templates.has(site.id)) ||
|
|
38
|
+
(site.kind === "schedule" && ids.has(site.id));
|
|
39
|
+
if (!used)
|
|
40
|
+
continue;
|
|
41
|
+
for (const choice of Object.values(site.choices)) {
|
|
42
|
+
const cli = choice === undefined ? undefined : cliOfProvider(choice.provider);
|
|
43
|
+
if (cli !== undefined)
|
|
44
|
+
found.add(cli);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const schedule of schedules) {
|
|
48
|
+
const cli = cliOfProvider(schedule.topicGeneration.llm?.provider ?? "");
|
|
49
|
+
if (schedule.topicGeneration.mode !== "off" && cli !== undefined)
|
|
50
|
+
found.add(cli);
|
|
51
|
+
}
|
|
52
|
+
return [...found].sort();
|
|
53
|
+
}
|
|
54
|
+
async function within(deps, what, probe) {
|
|
55
|
+
const signal = AbortSignal.timeout(probeTimeoutMs);
|
|
56
|
+
try {
|
|
57
|
+
return await Promise.race([
|
|
58
|
+
probe(signal),
|
|
59
|
+
new Promise((_resolve, reject) => {
|
|
60
|
+
signal.addEventListener("abort", () => reject(new Error(`${what} took too long`)), {
|
|
61
|
+
once: true,
|
|
62
|
+
});
|
|
63
|
+
}),
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
deps.log.write("warn", "autopilot.probe", { detail: `${what}: ${errorText(error)}` });
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export async function collectProbes(deps) {
|
|
72
|
+
const asked = await Promise.all(clisInUse(deps.db).map(async (id) => {
|
|
73
|
+
const login = deps.cliLogin;
|
|
74
|
+
if (login === undefined)
|
|
75
|
+
return undefined;
|
|
76
|
+
const answer = await within(deps, `${clis[id].name} sign-in`, (signal) => login(id, signal));
|
|
77
|
+
return answer === undefined ? undefined : [id, answer];
|
|
78
|
+
}));
|
|
79
|
+
const key = deps.openRouterKey;
|
|
80
|
+
const disk = deps.disk;
|
|
81
|
+
const [openRouter, space] = await Promise.all([
|
|
82
|
+
key === undefined ? undefined : within(deps, "OpenRouter key", key),
|
|
83
|
+
disk === undefined ? undefined : within(deps, "free disk space", () => disk()),
|
|
84
|
+
]);
|
|
85
|
+
return {
|
|
86
|
+
checkedAt: deps.clock.now().toISOString(),
|
|
87
|
+
clis: new Map(asked.flatMap((one) => (one === undefined ? [] : [one]))),
|
|
88
|
+
openRouter: openRouter ?? null,
|
|
89
|
+
disk: space ?? null,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// The hourly check's last answers, held in memory: Home reads them and never waits for a CLI.
|
|
93
|
+
export function createAutopilotHealth(deps) {
|
|
94
|
+
let latest;
|
|
95
|
+
let running;
|
|
96
|
+
return {
|
|
97
|
+
latest: () => latest,
|
|
98
|
+
refresh: () => {
|
|
99
|
+
running ??= collectProbes(deps)
|
|
100
|
+
.then((probes) => {
|
|
101
|
+
latest = probes;
|
|
102
|
+
return probes;
|
|
103
|
+
})
|
|
104
|
+
.finally(() => {
|
|
105
|
+
running = undefined;
|
|
106
|
+
});
|
|
107
|
+
return running;
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// Home's autopilot block for every channel, or one: the forecast and warnings from the
|
|
112
|
+
// database now and the probes' last answers, and the last seven days.
|
|
113
|
+
export function autopilotView(deps, probes, channelId) {
|
|
114
|
+
const now = deps.clock.now();
|
|
115
|
+
const until = new Date(now.valueOf() + forecastDays * dayMs);
|
|
116
|
+
const inChannel = (templateId) => channelId === undefined || templateChannelId(deps.db, templateId) === channelId;
|
|
117
|
+
const schedules = scheduleRows(deps.db).filter((one) => live(one) && makesVideos(one) && inChannel(one.templateId));
|
|
118
|
+
const ahead = calendarRange(deps, now, until);
|
|
119
|
+
// Finished videos not uploaded: the calendar's longest look back.
|
|
120
|
+
const behind = calendarRange(deps, new Date(now.valueOf() - 91 * dayMs), now);
|
|
121
|
+
const channels = projectChannels(deps.db);
|
|
122
|
+
const ready = behind.ok
|
|
123
|
+
? behind.value.projects.filter((one) => one.readyToUpload === true &&
|
|
124
|
+
one.setAside !== true &&
|
|
125
|
+
(channelId === undefined || (channels.get(one.id) ?? defaultChannelId) === channelId)).length
|
|
126
|
+
: 0;
|
|
127
|
+
// A run its template has no version for is skipped: it fills no slot.
|
|
128
|
+
const runs = ahead.ok
|
|
129
|
+
? ahead.value.runs.filter((run) => makesVideos(run) && inChannel(run.templateId) && run.notReady !== true)
|
|
130
|
+
: [];
|
|
131
|
+
const forecast = bufferForecast({
|
|
132
|
+
now,
|
|
133
|
+
until,
|
|
134
|
+
runs,
|
|
135
|
+
ready,
|
|
136
|
+
schedules: schedules.map((one) => ({
|
|
137
|
+
id: one.id,
|
|
138
|
+
name: one.name,
|
|
139
|
+
paused: one.status === "paused",
|
|
140
|
+
queued: one.items.length,
|
|
141
|
+
generation: one.topicGeneration.mode,
|
|
142
|
+
generationFailing: one.topics.error !== null,
|
|
143
|
+
cadence: one.cadence,
|
|
144
|
+
timezone: one.timezone,
|
|
145
|
+
})),
|
|
146
|
+
});
|
|
147
|
+
const active = new Set(schedules.filter((one) => one.status === "active").map((one) => one.id));
|
|
148
|
+
const templates = new Set(schedules.filter((one) => active.has(one.id)).map((one) => one.templateId));
|
|
149
|
+
const catalogue = deps.catalogue?.();
|
|
150
|
+
const signals = {
|
|
151
|
+
clis: [...(probes?.clis ?? new Map())].map(([id, login]) => ({
|
|
152
|
+
id,
|
|
153
|
+
...clis[id],
|
|
154
|
+
login,
|
|
155
|
+
})),
|
|
156
|
+
plans: weekSummary(deps.db, now.toISOString()).plans,
|
|
157
|
+
openRouter: probes?.openRouter ?? null,
|
|
158
|
+
disk: probes?.disk ?? null,
|
|
159
|
+
schedules: schedules.map((one) => ({
|
|
160
|
+
id: one.id,
|
|
161
|
+
name: one.name,
|
|
162
|
+
paused: one.status === "paused",
|
|
163
|
+
queued: one.items.length,
|
|
164
|
+
generation: one.topicGeneration.mode,
|
|
165
|
+
topicsError: one.topics.error,
|
|
166
|
+
notReadyTemplate: deps.notReady?.(one.templateId) === undefined
|
|
167
|
+
? null
|
|
168
|
+
: (deps.template(one.templateId)?.name ?? "this schedule's template"),
|
|
169
|
+
})),
|
|
170
|
+
retired: catalogue === undefined
|
|
171
|
+
? []
|
|
172
|
+
: retiredModelUsage(deps.db, catalogue).flatMap((use) => (use.kind === "template" && templates.has(use.id)) ||
|
|
173
|
+
(use.kind === "schedule" && active.has(use.id))
|
|
174
|
+
? [
|
|
175
|
+
{
|
|
176
|
+
kind: use.kind,
|
|
177
|
+
id: use.id,
|
|
178
|
+
name: use.name,
|
|
179
|
+
slot: slotLabels[use.slot],
|
|
180
|
+
model: use.model,
|
|
181
|
+
why: use.why,
|
|
182
|
+
replacement: use.replacement?.name ?? null,
|
|
183
|
+
},
|
|
184
|
+
]
|
|
185
|
+
: []),
|
|
186
|
+
};
|
|
187
|
+
return {
|
|
188
|
+
forecast,
|
|
189
|
+
warnings: warningsOf(signals, readSnoozes(deps.db, now), now),
|
|
190
|
+
checkedAt: probes?.checkedAt ?? null,
|
|
191
|
+
weekly: weeklyReport(deps.db, new Date(now.valueOf() - 7 * dayMs).toISOString(), now.toISOString(), channelId),
|
|
192
|
+
weeklyNotify: weeklyNotifyOf(deps.db),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { diskLowBytes, diskLowShare, fewTopicsLeft, lowPercent, } from "./model.js";
|
|
2
|
+
const gb = (bytes) => `${(bytes / 1024 ** 3).toFixed(1)} GB`;
|
|
3
|
+
const dollars = (value) => `$${value.toFixed(2)}`;
|
|
4
|
+
// Each warning says what is wrong, why it stops the autopilot, and where the fix is. A
|
|
5
|
+
// warning hidden with Hide stays hidden until its snooze ends.
|
|
6
|
+
export function warningsOf(signals, snoozes, now) {
|
|
7
|
+
const found = [];
|
|
8
|
+
for (const cli of signals.clis)
|
|
9
|
+
if (cli.login === "signed-out")
|
|
10
|
+
found.push({
|
|
11
|
+
key: `cli-signed-out:${cli.id}`,
|
|
12
|
+
severity: "problem",
|
|
13
|
+
text: `${cli.name} is signed out, so scheduled runs that use it will fail. Open a terminal on this computer, run "${cli.signIn}" and sign in.`,
|
|
14
|
+
fix: { kind: "settings", section: "providers", label: "Open Providers" },
|
|
15
|
+
});
|
|
16
|
+
for (const plan of signals.plans) {
|
|
17
|
+
const weekly = plan.weeklyPercent ?? 0;
|
|
18
|
+
const fiveHour = plan.fiveHourPercent ?? 0;
|
|
19
|
+
if (weekly < lowPercent && fiveHour < lowPercent)
|
|
20
|
+
continue;
|
|
21
|
+
const window = weekly >= fiveHour ? "weekly" : "5-hour";
|
|
22
|
+
found.push({
|
|
23
|
+
key: `plan-low:${plan.name}`,
|
|
24
|
+
severity: "warning",
|
|
25
|
+
text: `${plan.name} has used ${String(Math.round(Math.max(weekly, fiveHour)))}% of its ${window} limit, so scheduled runs on it may wait until the limit resets. Check when it resets, or move a template to another provider.`,
|
|
26
|
+
fix: { kind: "settings", section: "usage", label: "See plan usage" },
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const key = signals.openRouter;
|
|
30
|
+
if (key !== null && key.limit > 0 && key.remaining / key.limit < 1 - lowPercent / 100)
|
|
31
|
+
found.push({
|
|
32
|
+
key: "openrouter-low",
|
|
33
|
+
severity: key.remaining <= 0 ? "problem" : "warning",
|
|
34
|
+
text: `Your OpenRouter key has ${dollars(Math.max(0, key.remaining))} left of its ${dollars(key.limit)} limit, so runs that use OpenRouter stop when it runs out. Raise the key's limit or add credit on openrouter.ai.`,
|
|
35
|
+
fix: { kind: "settings", section: "providers", label: "Open Providers" },
|
|
36
|
+
});
|
|
37
|
+
const disk = signals.disk;
|
|
38
|
+
if (disk !== null &&
|
|
39
|
+
(disk.freeBytes < diskLowBytes ||
|
|
40
|
+
(disk.totalBytes > 0 && disk.freeBytes / disk.totalBytes < diskLowShare)))
|
|
41
|
+
found.push({
|
|
42
|
+
key: "disk-low",
|
|
43
|
+
severity: disk.freeBytes < diskLowBytes ? "problem" : "warning",
|
|
44
|
+
text: `Only ${gb(disk.freeBytes)} is free on the disk Slopify saves to, so renders and downloads may fail. Free some space, or delete old versions and empty the Trash in Settings → Backup & storage.`,
|
|
45
|
+
fix: { kind: "settings", section: "storage", label: "Open Backup & storage" },
|
|
46
|
+
});
|
|
47
|
+
for (const schedule of signals.schedules) {
|
|
48
|
+
if (schedule.paused)
|
|
49
|
+
continue;
|
|
50
|
+
if (schedule.notReadyTemplate !== null)
|
|
51
|
+
found.push({
|
|
52
|
+
key: `template-not-ready:${schedule.id}`,
|
|
53
|
+
severity: "problem",
|
|
54
|
+
text: `“${schedule.name}” skips its runs because its template “${schedule.notReadyTemplate}” has no version marked ready. Open it in Library → Templates → Open studio and press Mark ready.`,
|
|
55
|
+
fix: { kind: "templates", label: "Open Templates" },
|
|
56
|
+
});
|
|
57
|
+
if (schedule.topicsError !== null && schedule.generation !== "off")
|
|
58
|
+
found.push({
|
|
59
|
+
key: `topics-failing:${schedule.id}`,
|
|
60
|
+
severity: "problem",
|
|
61
|
+
text: `“${schedule.name}” couldn't find new topics: ${schedule.topicsError} Its queue won't refill until that is fixed. Open the schedule, fix what the error says, then find topics again.`,
|
|
62
|
+
fix: { kind: "schedule", scheduleId: schedule.id, label: "Open the schedule" },
|
|
63
|
+
});
|
|
64
|
+
if (schedule.generation === "off" && schedule.queued > 0 && schedule.queued < fewTopicsLeft)
|
|
65
|
+
found.push({
|
|
66
|
+
key: `few-topics:${schedule.id}:${String(schedule.queued)}`,
|
|
67
|
+
severity: "warning",
|
|
68
|
+
text: `“${schedule.name}” has ${schedule.queued === 1 ? "1 topic" : `${String(schedule.queued)} topics`} left and doesn't find new ones, so it stops after ${schedule.queued === 1 ? "its next run" : `its next ${String(schedule.queued)} runs`}. Add topics to its queue, or turn on topic generation.`,
|
|
69
|
+
fix: { kind: "schedule", scheduleId: schedule.id, label: "Open the schedule" },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
for (const use of signals.retired)
|
|
73
|
+
found.push({
|
|
74
|
+
key: `retired:${use.kind}:${use.id}:${use.model}`,
|
|
75
|
+
severity: "warning",
|
|
76
|
+
text: `The ${use.slot.toLowerCase()} “${use.model}” in ${use.kind} “${use.name}” is ${use.why === "retired" ? "being retired" : "no longer offered"}, so its runs will fail once it is gone. Switch it to ${use.replacement === null ? "another model" : `“${use.replacement}”`}.`,
|
|
77
|
+
fix: use.kind === "schedule"
|
|
78
|
+
? { kind: "schedule", scheduleId: use.id, label: "Open the schedule" }
|
|
79
|
+
: { kind: "templates", label: "Open Templates" },
|
|
80
|
+
});
|
|
81
|
+
const at = now.toISOString();
|
|
82
|
+
return found
|
|
83
|
+
.filter((warning) => (snoozes.get(warning.key) ?? "") <= at)
|
|
84
|
+
.toSorted((a, b) => Number(b.severity === "problem") - Number(a.severity === "problem"));
|
|
85
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/// <reference lib="esnext.temporal" />
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { liveProject } from "../admission/repo.js";
|
|
4
|
+
import { defaultChannelId } from "../channels/model.js";
|
|
5
|
+
import { projectChannels } from "../channels/repo.js";
|
|
6
|
+
import { weekSummary } from "../run-cost/week.js";
|
|
7
|
+
import { validTimeZone } from "../schedules/calendar.js";
|
|
8
|
+
import { notTemplateSample } from "../template-studio/repo.js";
|
|
9
|
+
// ceiling: the report names this many failed steps; the rest are counted.
|
|
10
|
+
const failedShown = 10;
|
|
11
|
+
// The hour on Monday, in the person's time zone, from which the week's report is sent.
|
|
12
|
+
const sendHour = 9;
|
|
13
|
+
const stageNames = {
|
|
14
|
+
research: "Research",
|
|
15
|
+
article: "Article",
|
|
16
|
+
audio: "Narration",
|
|
17
|
+
images: "Images",
|
|
18
|
+
thumbnail: "Thumbnail",
|
|
19
|
+
video: "Video",
|
|
20
|
+
document: "Document",
|
|
21
|
+
};
|
|
22
|
+
const failedRow = z.object({
|
|
23
|
+
project_id: z.string(),
|
|
24
|
+
title: z.string(),
|
|
25
|
+
kind: z.string(),
|
|
26
|
+
failure_reason: z.string().nullable(),
|
|
27
|
+
});
|
|
28
|
+
// The last week in one read: what finished and what failed, what it cost and where the plans
|
|
29
|
+
// stand. The same stored calls Home's This week reads; nothing is estimated.
|
|
30
|
+
export function weeklyReport(db, since, until, channelId) {
|
|
31
|
+
const summary = weekSummary(db, since, channelId);
|
|
32
|
+
const channels = channelId === undefined ? undefined : projectChannels(db);
|
|
33
|
+
const failed = db
|
|
34
|
+
.prepare(`SELECT stages.project_id, projects.title, stages.kind, stages.failure_reason
|
|
35
|
+
FROM stages JOIN projects ON projects.id=stages.project_id
|
|
36
|
+
WHERE stages.state='failed' AND stages.finished_at>=? AND ${liveProject()}
|
|
37
|
+
AND ${notTemplateSample()}
|
|
38
|
+
ORDER BY stages.finished_at DESC, stages.id`)
|
|
39
|
+
.all(since)
|
|
40
|
+
.map((raw) => failedRow.parse(raw))
|
|
41
|
+
.filter((row) => channels === undefined || (channels.get(row.project_id) ?? defaultChannelId) === channelId)
|
|
42
|
+
.map((row) => ({
|
|
43
|
+
projectId: row.project_id,
|
|
44
|
+
title: row.title.trim() === "" ? "Untitled project" : row.title,
|
|
45
|
+
stage: stageNames[row.kind] ?? row.kind,
|
|
46
|
+
reason: row.failure_reason ?? "It stopped without a reason.",
|
|
47
|
+
}));
|
|
48
|
+
return {
|
|
49
|
+
since,
|
|
50
|
+
until,
|
|
51
|
+
finished: summary.videos,
|
|
52
|
+
failed: failed.slice(0, failedShown),
|
|
53
|
+
failedMore: Math.max(0, failed.length - failedShown),
|
|
54
|
+
cost: summary.cost,
|
|
55
|
+
unpriced: summary.unpriced,
|
|
56
|
+
apiEquivalent: summary.apiEquivalent,
|
|
57
|
+
plans: summary.plans.map((plan) => ({
|
|
58
|
+
name: plan.name,
|
|
59
|
+
weeklyPercent: plan.weeklyPercent,
|
|
60
|
+
fiveHourPercent: plan.fiveHourPercent,
|
|
61
|
+
})),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// The ISO week a moment falls in, in the person's time zone: "2026-W42".
|
|
65
|
+
export function isoWeekOf(now, timeZone) {
|
|
66
|
+
const zone = validTimeZone(timeZone) ? timeZone : "UTC";
|
|
67
|
+
const date = Temporal.Instant.fromEpochMilliseconds(now.valueOf()).toZonedDateTimeISO(zone);
|
|
68
|
+
return `${String(date.yearOfWeek)}-W${String(date.weekOfYear).padStart(2, "0")}`;
|
|
69
|
+
}
|
|
70
|
+
// Whether this week's report is still to be sent: from Monday 09:00 in the person's time zone,
|
|
71
|
+
// once a week. A Slopify that was off on Monday sends it when it next runs that week.
|
|
72
|
+
export function weeklyDue(now, timeZone, sentFor) {
|
|
73
|
+
const zone = validTimeZone(timeZone) ? timeZone : "UTC";
|
|
74
|
+
const date = Temporal.Instant.fromEpochMilliseconds(now.valueOf()).toZonedDateTimeISO(zone);
|
|
75
|
+
const week = isoWeekOf(now, zone);
|
|
76
|
+
const started = date.dayOfWeek > 1 || date.hour >= sendHour;
|
|
77
|
+
return { due: started && sentFor !== week, week };
|
|
78
|
+
}
|
|
79
|
+
const money = (value) => `$${value.toFixed(2)}`;
|
|
80
|
+
export function weeklyNoticeText(report) {
|
|
81
|
+
const failures = report.failed.length + report.failedMore;
|
|
82
|
+
const plans = report.plans
|
|
83
|
+
.filter((plan) => plan.weeklyPercent !== null)
|
|
84
|
+
.map((plan) => `${plan.name} ${String(Math.round(plan.weeklyPercent ?? 0))}% of its week`);
|
|
85
|
+
const named = report.failed
|
|
86
|
+
.slice(0, 3)
|
|
87
|
+
.map((one) => `“${one.title}” (${one.stage}): ${one.reason.replace(/[.!?\s]+$/, "")}`);
|
|
88
|
+
return {
|
|
89
|
+
headline: `Slopify last week: ${report.finished === 1 ? "1 video" : `${String(report.finished)} videos`} made, ${failures === 1 ? "1 step" : `${String(failures)} steps`} failed`,
|
|
90
|
+
detail: [
|
|
91
|
+
`Cost ${money(report.cost)}${report.unpriced > 0 ? ` plus ${String(report.unpriced)} calls with no known price` : ""}.`,
|
|
92
|
+
plans.length === 0 ? "" : `Plans: ${plans.join(", ")}.`,
|
|
93
|
+
named.length === 0
|
|
94
|
+
? ""
|
|
95
|
+
: `Failed: ${named.join("; ")}${failures > named.length ? `; and ${String(failures - named.length)} more` : ""}. Open each project and use the button beside the failed step.`,
|
|
96
|
+
]
|
|
97
|
+
.filter((line) => line !== "")
|
|
98
|
+
.join(" "),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { constants } from "node:fs";
|
|
2
2
|
import { lstat, mkdir, open, readdir, rename, rm } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { errnoCode } from "../../kernel/errors.js";
|
|
4
5
|
// Only files whose whole name matches these are this feature's. Pruning and the leftover sweep
|
|
5
6
|
// touch nothing else in the folder: not an Export everything download
|
|
6
7
|
// (slopify-backup-2026-09-27.tar), not a file the user renamed, not a symlink.
|
|
@@ -92,7 +93,7 @@ export async function listBackups(folder) {
|
|
|
92
93
|
names = await readdir(folder);
|
|
93
94
|
}
|
|
94
95
|
catch (error) {
|
|
95
|
-
if (
|
|
96
|
+
if (errnoCode(error) === "ENOENT")
|
|
96
97
|
return [];
|
|
97
98
|
throw error;
|
|
98
99
|
}
|
|
@@ -128,8 +129,3 @@ export async function removeLeftovers(folder) {
|
|
|
128
129
|
}
|
|
129
130
|
return removed;
|
|
130
131
|
}
|
|
131
|
-
export function codeOf(error) {
|
|
132
|
-
return error instanceof Error && "code" in error && typeof error.code === "string"
|
|
133
|
-
? error.code
|
|
134
|
-
: undefined;
|
|
135
|
-
}
|
|
@@ -1,28 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readJsonSetting, writeSetting } from "../settings/repo.js";
|
|
2
2
|
import { backupStatusSchema, configKey, defaultConfig, emptyStatus, statusKey, storedConfigSchema, } from "./model.js";
|
|
3
3
|
// A value this build cannot read (hand-edited, or written by a newer Slopify) falls back to
|
|
4
4
|
// the default rather than stopping the app; the next save writes a good one.
|
|
5
5
|
export function readConfig(db, fallbackTimeZone) {
|
|
6
|
-
|
|
7
|
-
return parsed.success ? parsed.data : defaultConfig(fallbackTimeZone);
|
|
6
|
+
return readJsonSetting(db, configKey, storedConfigSchema, defaultConfig(fallbackTimeZone));
|
|
8
7
|
}
|
|
9
8
|
export function writeConfig(db, config) {
|
|
10
9
|
writeSetting(db, configKey, JSON.stringify(storedConfigSchema.parse(config)));
|
|
11
10
|
}
|
|
12
11
|
export function readStatus(db) {
|
|
13
|
-
|
|
14
|
-
return parsed.success ? parsed.data : emptyStatus;
|
|
12
|
+
return readJsonSetting(db, statusKey, backupStatusSchema, emptyStatus);
|
|
15
13
|
}
|
|
16
14
|
export function writeStatus(db, status) {
|
|
17
15
|
writeSetting(db, statusKey, JSON.stringify(backupStatusSchema.parse(status)));
|
|
18
16
|
}
|
|
19
|
-
function parseJson(value) {
|
|
20
|
-
if (value === undefined)
|
|
21
|
-
return undefined;
|
|
22
|
-
try {
|
|
23
|
-
return JSON.parse(value);
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { statfs } from "node:fs/promises";
|
|
2
|
+
import { errnoCode } from "../../kernel/errors.js";
|
|
2
3
|
import { BackupBusyError, planBackup, streamBackup, } from "../storage/backup-export.js";
|
|
3
4
|
import { defaultBackupsDir } from "../storage/layout.js";
|
|
4
|
-
import { backupFileName,
|
|
5
|
+
import { backupFileName, ensureFolder, listBackups, pruneBackups, removeLeftovers, writeAtomically, } from "./files.js";
|
|
5
6
|
import { effectiveFolder, folderProblem, hostFolder } from "./folder.js";
|
|
6
7
|
import { readConfig, readStatus, writeConfig, writeStatus } from "./repo.js";
|
|
7
8
|
import { decideBackup, nextSlot } from "./schedule.js";
|
|
@@ -59,7 +60,7 @@ export function createBackupService(deps) {
|
|
|
59
60
|
}
|
|
60
61
|
catch (error) {
|
|
61
62
|
deps.log.write("warn", "backups.prune", { detail: String(error) });
|
|
62
|
-
detail = `The backup was saved, but older backups in ${folder} could not be removed (${
|
|
63
|
+
detail = `The backup was saved, but older backups in ${folder} could not be removed (${errnoCode(error) ?? "unknown error"}), so more than ${settings.keep} are kept. Check Slopify can delete files in that folder, or pick another Folder in Settings → Backups.`;
|
|
63
64
|
}
|
|
64
65
|
record({
|
|
65
66
|
...attempt,
|
|
@@ -279,7 +280,7 @@ export function explain(error, folder, retries, signal) {
|
|
|
279
280
|
return "The backup was not saved: Slopify stopped while writing it, and the unfinished file was removed. It runs again after Slopify starts.";
|
|
280
281
|
if (error instanceof NoSpaceError)
|
|
281
282
|
return `The backup was not saved: it needs about ${bytes(error.needed)}, but the disk holding ${folder} has ${bytes(error.free)} free. Free up space there, lower "Keep last" in Settings → Backups, or pick another Folder.`;
|
|
282
|
-
switch (
|
|
283
|
+
switch (errnoCode(error)) {
|
|
283
284
|
case "ENOSPC":
|
|
284
285
|
case "EDQUOT":
|
|
285
286
|
return `The backup was not saved: the disk holding ${folder} ran out of space. Free up space there, lower "Keep last" in Settings → Backups, or pick another Folder.`;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { errorText } from "../../kernel/errors.js";
|
|
1
2
|
import { castImageMaxBytes, castImagesPerMember, sniffImage, storeImageBlob } from "./images.js";
|
|
2
3
|
import { castMemberById } from "./repo.js";
|
|
3
4
|
import { castGenerateSchema } from "./schema.js";
|
|
@@ -58,7 +59,7 @@ export function generateCastImage(deps, memberId, input) {
|
|
|
58
59
|
.prepare("UPDATE cast_images SET state='ready',sha256=? WHERE id=? AND state='generating'")
|
|
59
60
|
.run(sha256, id);
|
|
60
61
|
}, (error) => {
|
|
61
|
-
const reason =
|
|
62
|
+
const reason = errorText(error);
|
|
62
63
|
deps.log?.write("warn", "channels.cast-image.failed", { detail: reason.slice(0, 500) });
|
|
63
64
|
fail(deps, id, `The picture couldn't be made: ${reason.slice(0, 500)} Check the image provider in Settings → Providers, then press Generate again.`);
|
|
64
65
|
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// Every install has this channel (migration 0032). A template or project whose channel is
|
|
2
2
|
// NULL, or names a channel this install does not have, belongs to it.
|
|
3
3
|
export const defaultChannelId = "00000000-0000-4000-8000-000000000001";
|
|
4
|
-
export const defaultChannelName = "My channel";
|
|
5
4
|
export const castKinds = ["character", "creature", "place", "object"];
|
|
@@ -8,9 +8,6 @@ import { defaultDocumentTheme } from "../document/model.js";
|
|
|
8
8
|
import { entryByName } from "../library/repo.js";
|
|
9
9
|
import { channelById, resolveChannelId } from "./repo.js";
|
|
10
10
|
import { brandDocument, castSnapshot } from "./runs.js";
|
|
11
|
-
export function channelChanged(db, before, after) {
|
|
12
|
-
return resolveChannelId(db, before.channelId) !== resolveChannelId(db, after.channelId);
|
|
13
|
-
}
|
|
14
11
|
export function rebrandedEdit(db, base, edit) {
|
|
15
12
|
const oldId = resolveChannelId(db, base.channelId);
|
|
16
13
|
const newId = resolveChannelId(db, edit.config.channelId);
|