@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
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { transact } from "../../kernel/db/tx.js";
|
|
3
|
+
import { errorText } from "../../kernel/errors.js";
|
|
4
|
+
import { jsonBetween } from "../../kernel/json-answer.js";
|
|
3
5
|
import { liveProject } from "../admission/repo.js";
|
|
4
6
|
import { defaultChannelId } from "../channels/model.js";
|
|
5
7
|
import { projectChannels, scheduleChannel } from "../channels/repo.js";
|
|
6
8
|
import { channelVideoTitles } from "../channels/videos.js";
|
|
7
|
-
import {
|
|
9
|
+
import { readJsonSetting, writeSetting } from "../settings/repo.js";
|
|
10
|
+
import { notTemplateSample } from "../template-studio/repo.js";
|
|
8
11
|
import { scheduleById } from "./repo.js";
|
|
9
12
|
import { heldTopicEditSchema, heldTopicSchema, queueMax, topicMoveSchema, topicQueueSchema, topicTitleSchema, topicTransferSchema, } from "./schema.js";
|
|
10
13
|
import { newTopics } from "./similar.js";
|
|
11
|
-
import {
|
|
14
|
+
import { scheduleKeywords, topicRowProblems } from "./topic-list.js";
|
|
15
|
+
import { pinTopic, rankedItems, saveScores, scoreTopics } from "./topic-scores.js";
|
|
12
16
|
// A schedule that finds its own topics asks an LLM whenever its queue holds fewer than
|
|
13
17
|
// `keepAtLeast`. Only one generation per schedule runs at a time: a lease on the schedule row
|
|
14
18
|
// (`topics_generating_at`) is taken before the LLM is asked and cleared after, so a tick and a
|
|
@@ -129,26 +133,40 @@ async function ask(deps, schedule, options) {
|
|
|
129
133
|
catch (error) {
|
|
130
134
|
if (options.signal?.aborted === true)
|
|
131
135
|
return { ok: false, reason: "failed", message: "Slopify was closing." };
|
|
132
|
-
const detail =
|
|
136
|
+
const detail = errorText(error);
|
|
133
137
|
return fail(`${llm.provider} (${llm.model}) did not answer: ${detail.trim().slice(0, 300)}. Check it in Settings → Providers, or pick another LLM under Edit → Topic generation.`);
|
|
134
138
|
}
|
|
135
139
|
const suggested = parseTopics(answer);
|
|
136
140
|
if (suggested.length === 0)
|
|
137
141
|
return fail(`${llm.provider} (${llm.model}) answered without a list of topics. Try again, or pick another LLM under Edit → Topic generation.`);
|
|
142
|
+
// Scored outside the transaction: search may take seconds, and never holds the database.
|
|
143
|
+
const scores = schedule.topicScoring === "off"
|
|
144
|
+
? undefined
|
|
145
|
+
: await scoreTopics(deps, schedule.topicScoring, newTopics(suggested, known), known, template?.document.form.language ?? "en", options.signal);
|
|
138
146
|
return transact(deps.db, () => {
|
|
139
147
|
const current = scheduleById(deps.db, schedule.id);
|
|
140
148
|
if (current === undefined || current.deletedAt !== null)
|
|
141
149
|
return { ok: false, reason: "not-found", message: "This schedule no longer exists." };
|
|
142
150
|
// Checked again against what is there now: a topic added while the LLM was thinking counts.
|
|
143
|
-
const
|
|
151
|
+
const candidates = newTopics(suggested, knownTitles(deps, schedule.id));
|
|
152
|
+
// With scoring, the best-scored are kept; without, the LLM's own order, as before.
|
|
153
|
+
const fresh = (scores === undefined
|
|
154
|
+
? candidates
|
|
155
|
+
: candidates.toSorted((a, b) => (scores.get(b)?.score ?? -1) - (scores.get(a)?.score ?? -1))).slice(0, Math.max(0, Math.min(wanted, queueMax - current.items.length)));
|
|
144
156
|
if (fresh.length === 0)
|
|
145
157
|
return fail("every topic the LLM suggested was already made, queued or turned down for this schedule. Add more detail to the series brief under Edit, so it has new ground to cover.");
|
|
146
158
|
const at = deps.clock.now().toISOString();
|
|
147
159
|
const mode = current.topicGeneration.mode === "hold" ? "hold" : "queue";
|
|
160
|
+
if (scores !== undefined)
|
|
161
|
+
saveScores(deps.db, schedule.id, new Map(fresh.flatMap((title) => {
|
|
162
|
+
const score = scores.get(title);
|
|
163
|
+
return score === undefined ? [] : [[title, score]];
|
|
164
|
+
})), at);
|
|
148
165
|
if (mode === "queue") {
|
|
166
|
+
const items = [...current.items, ...fresh.map((title) => ({ title, values: {} }))];
|
|
149
167
|
deps.db
|
|
150
168
|
.prepare("UPDATE schedules SET items_json=?,version=version+1,updated_at=? WHERE id=? AND deleted_at IS NULL")
|
|
151
|
-
.run(JSON.stringify(
|
|
169
|
+
.run(JSON.stringify(scores === undefined ? items : rankedItems(deps.db, schedule.id, items)), at, schedule.id);
|
|
152
170
|
}
|
|
153
171
|
else {
|
|
154
172
|
const start = nextRank(deps, schedule.id);
|
|
@@ -158,6 +176,8 @@ async function ask(deps, schedule, options) {
|
|
|
158
176
|
VALUES (?,?,?,'held',?,?)`)
|
|
159
177
|
.run(deps.uuid(), schedule.id, title, start + index, at);
|
|
160
178
|
});
|
|
179
|
+
if (scores !== undefined)
|
|
180
|
+
rankHeld(deps, schedule.id);
|
|
161
181
|
}
|
|
162
182
|
deps.db
|
|
163
183
|
.prepare("UPDATE schedules SET topics_generated_at=?,topics_failed_at=NULL,topics_error=NULL WHERE id=?")
|
|
@@ -180,6 +200,17 @@ async function ask(deps, schedule, options) {
|
|
|
180
200
|
return { ok: true, added: fresh.length, mode };
|
|
181
201
|
});
|
|
182
202
|
}
|
|
203
|
+
// Held topics in score order: unscored ones first as they were, then the scored, best first.
|
|
204
|
+
export function rankHeld(deps, scheduleId) {
|
|
205
|
+
const held = deps.db
|
|
206
|
+
.prepare("SELECT id,title FROM schedule_topics WHERE schedule_id=? AND state='held' ORDER BY rank,created_at,id")
|
|
207
|
+
.all(scheduleId)
|
|
208
|
+
.map((row) => z.object({ id: z.string(), title: z.string() }).parse(row));
|
|
209
|
+
const rank = deps.db.prepare("UPDATE schedule_topics SET rank=? WHERE id=?");
|
|
210
|
+
rankedItems(deps.db, scheduleId, held).forEach((topic, index) => {
|
|
211
|
+
rank.run(index, topic.id);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
183
214
|
function nextRank(deps, scheduleId) {
|
|
184
215
|
const row = deps.db
|
|
185
216
|
.prepare("SELECT max(rank) AS rank FROM schedule_topics WHERE schedule_id=? AND state='held'")
|
|
@@ -200,7 +231,8 @@ export function knownTitles(deps, scheduleId) {
|
|
|
200
231
|
const channel = scheduleChannel(deps.db, scheduleId);
|
|
201
232
|
const channels = projectChannels(deps.db);
|
|
202
233
|
const projects = deps.db
|
|
203
|
-
|
|
234
|
+
// A template's studio sample is no video of the channel's.
|
|
235
|
+
.prepare(`SELECT id,title FROM projects WHERE ${liveProject()} AND ${notTemplateSample()} ORDER BY created_at DESC`)
|
|
204
236
|
.all()
|
|
205
237
|
.map((row) => z.object({ id: z.string(), title: z.string() }).parse(row))
|
|
206
238
|
.filter((row) => channel === undefined || (channels.get(row.id) ?? defaultChannelId) === channel.id)
|
|
@@ -254,20 +286,8 @@ export function topicMessages(schedule, templateTitle, known, count) {
|
|
|
254
286
|
}
|
|
255
287
|
// A JSON array of strings, or failing that one topic per line with bullets and numbers dropped.
|
|
256
288
|
export function parseTopics(answer) {
|
|
257
|
-
const
|
|
258
|
-
const
|
|
259
|
-
let lines;
|
|
260
|
-
if (start !== -1 && end > start) {
|
|
261
|
-
try {
|
|
262
|
-
const parsed = JSON.parse(answer.slice(start, end + 1));
|
|
263
|
-
if (Array.isArray(parsed))
|
|
264
|
-
lines = parsed;
|
|
265
|
-
}
|
|
266
|
-
catch {
|
|
267
|
-
lines = undefined;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
lines ??= answer.split(/\r?\n/);
|
|
289
|
+
const parsed = jsonBetween(answer, "[", "]");
|
|
290
|
+
const lines = Array.isArray(parsed) ? parsed : answer.split(/\r?\n/);
|
|
271
291
|
return lines
|
|
272
292
|
.map((line) => typeof line === "string"
|
|
273
293
|
? line
|
|
@@ -286,16 +306,7 @@ export function parseTopics(answer) {
|
|
|
286
306
|
export const heldValuesKey = "schedules.held-topic-values";
|
|
287
307
|
const heldValuesSchema = z.record(z.string(), z.record(z.string(), z.string()));
|
|
288
308
|
function readHeldValues(db) {
|
|
289
|
-
|
|
290
|
-
if (raw === undefined)
|
|
291
|
-
return {};
|
|
292
|
-
try {
|
|
293
|
-
const parsed = heldValuesSchema.safeParse(JSON.parse(raw));
|
|
294
|
-
return parsed.success ? parsed.data : {};
|
|
295
|
-
}
|
|
296
|
-
catch {
|
|
297
|
-
return {};
|
|
298
|
-
}
|
|
309
|
+
return readJsonSetting(db, heldValuesKey, heldValuesSchema, {});
|
|
299
310
|
}
|
|
300
311
|
// Entries of topics that are no longer held (approved, turned down, or gone with their
|
|
301
312
|
// schedule) are dropped on every write.
|
|
@@ -394,11 +405,11 @@ export function editHeldTopic(deps, scheduleId, topicId, input) {
|
|
|
394
405
|
const schedule = scheduleById(deps.db, scheduleId);
|
|
395
406
|
if (schedule === undefined)
|
|
396
407
|
return { ok: false, reason: "not-found" };
|
|
397
|
-
const
|
|
398
|
-
if (
|
|
408
|
+
const known = scheduleKeywords(deps, schedule.templateId);
|
|
409
|
+
if (known === undefined)
|
|
399
410
|
return { ok: false, reason: "missing-template" };
|
|
400
411
|
const problems = topicRowProblems([{ title: parsed.data, values }], {
|
|
401
|
-
keywords:
|
|
412
|
+
keywords: known.filter((name) => name !== schedule.topicKeyword),
|
|
402
413
|
topicKeyword: schedule.topicKeyword,
|
|
403
414
|
});
|
|
404
415
|
if (problems.length > 0)
|
|
@@ -447,6 +458,7 @@ export function moveTopic(deps, scheduleId, input) {
|
|
|
447
458
|
return { ok: false, reason: "invalid-input" };
|
|
448
459
|
items.splice(Math.min(parsed.data.to, items.length), 0, topic);
|
|
449
460
|
writeItems(deps, scheduleId, schedule.version, items);
|
|
461
|
+
pinTopic(deps.db, scheduleId, topic.title);
|
|
450
462
|
return { ok: true, value: scheduleById(deps.db, scheduleId) };
|
|
451
463
|
});
|
|
452
464
|
}
|
|
@@ -463,11 +475,11 @@ export function replaceTopics(deps, scheduleId, input) {
|
|
|
463
475
|
const schedule = open.value;
|
|
464
476
|
if (schedule.version !== parsed.data.baseVersion)
|
|
465
477
|
return { ok: false, reason: "conflict" };
|
|
466
|
-
const
|
|
467
|
-
if (
|
|
478
|
+
const keywords = scheduleKeywords(deps, schedule.templateId);
|
|
479
|
+
if (keywords === undefined)
|
|
468
480
|
return { ok: false, reason: "missing-template" };
|
|
469
481
|
const problems = topicRowProblems(parsed.data.items, {
|
|
470
|
-
keywords
|
|
482
|
+
keywords,
|
|
471
483
|
topicKeyword: schedule.topicKeyword,
|
|
472
484
|
kept: schedule.items,
|
|
473
485
|
});
|
|
@@ -2,9 +2,10 @@ import { constants } from "node:fs";
|
|
|
2
2
|
import { access, stat } from "node:fs/promises";
|
|
3
3
|
import { extname, isAbsolute } from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { isUnreachablePath } from "../../kernel/errors.js";
|
|
5
6
|
import { cliProbeTimeoutMs, readinessFromProbe } from "./cli-status.js";
|
|
6
7
|
import { providerById } from "./model.js";
|
|
7
|
-
import {
|
|
8
|
+
import { readJsonSetting, writeSetting } from "./repo.js";
|
|
8
9
|
export const cliPathMaxLength = 4096;
|
|
9
10
|
const pathSchema = z
|
|
10
11
|
.string()
|
|
@@ -20,8 +21,7 @@ export function cliPathStatus(db, id) {
|
|
|
20
21
|
const provider = providerById(id);
|
|
21
22
|
if (provider.auth !== "cli")
|
|
22
23
|
throw new Error(`${id} is not a CLI provider`);
|
|
23
|
-
const
|
|
24
|
-
const configured = stored === undefined ? null : storedPathSchema.parse(JSON.parse(stored));
|
|
24
|
+
const configured = readJsonSetting(db, `cli.path.${installationId(id)}`, storedPathSchema, null);
|
|
25
25
|
return { configured, command: configured ?? provider.binary };
|
|
26
26
|
}
|
|
27
27
|
// Read on each attempt, so a saved path reaches the next invocation without a restart.
|
|
@@ -82,9 +82,7 @@ async function save(deps, id, raw) {
|
|
|
82
82
|
: constants.X_OK);
|
|
83
83
|
}
|
|
84
84
|
catch (error) {
|
|
85
|
-
if (error
|
|
86
|
-
"code" in error &&
|
|
87
|
-
["ENOENT", "ENOTDIR", "EACCES", "EPERM", "ELOOP"].includes(String(error.code)))
|
|
85
|
+
if (isUnreachablePath(error))
|
|
88
86
|
return {
|
|
89
87
|
ok: false,
|
|
90
88
|
message: "Slopify cannot find or run a program at this path. Check the path is correct and the file is allowed to run.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { readinessIsUsable } from "../../kernel/ports/model.js";
|
|
3
|
-
import { keyedProviders, readSetting, writeSetting } from "./repo.js";
|
|
3
|
+
import { keyedProviders, readJsonSetting, readSetting, writeSetting } from "./repo.js";
|
|
4
4
|
// What a fresh Play form picks before the user picks anything. Written once, on the first
|
|
5
5
|
// launch that finds a command-line tool, and read by Play for every new draft.
|
|
6
6
|
const choiceSchema = z.object({ provider: z.string().min(1), model: z.string() }).strict();
|
|
@@ -13,16 +13,7 @@ const doneKey = "first-run.done";
|
|
|
13
13
|
// The order a text tool is preferred in when several are found.
|
|
14
14
|
const textPreference = ["claude-code", "codex", "gemini"];
|
|
15
15
|
export function readProviderDefaults(db) {
|
|
16
|
-
|
|
17
|
-
if (raw === undefined)
|
|
18
|
-
return {};
|
|
19
|
-
try {
|
|
20
|
-
const parsed = providerDefaultsSchema.safeParse(JSON.parse(raw));
|
|
21
|
-
return parsed.success ? parsed.data : {};
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
return {};
|
|
25
|
-
}
|
|
16
|
+
return readJsonSetting(db, defaultsKey, providerDefaultsSchema, {});
|
|
26
17
|
}
|
|
27
18
|
export function dismissFirstRun(db) {
|
|
28
19
|
writeSetting(db, doneKey, "1");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { probeRead } from "./probe-read.js";
|
|
1
2
|
import { keyOf } from "./repo.js";
|
|
2
3
|
// ceiling: a provider chosen in many templates still costs a handful of reads, not one per
|
|
3
4
|
// model anywhere in the library.
|
|
@@ -12,23 +13,8 @@ export async function modelReach(deps, provider, models) {
|
|
|
12
13
|
const known = reads.get(url);
|
|
13
14
|
if (known !== undefined)
|
|
14
15
|
return known;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const response = await deps.fetch(url, {
|
|
18
|
-
headers: probe.headers(key),
|
|
19
|
-
signal: AbortSignal.timeout(15_000),
|
|
20
|
-
redirect: "error",
|
|
21
|
-
});
|
|
22
|
-
// A list is read whole; a single model's page only for its status.
|
|
23
|
-
const body = probe.model?.lists === undefined ? "" : await response.text();
|
|
24
|
-
if (probe.model?.lists === undefined)
|
|
25
|
-
await response.body?.cancel().catch(() => { });
|
|
26
|
-
return { status: response.status, body };
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
return undefined;
|
|
30
|
-
}
|
|
31
|
-
})();
|
|
16
|
+
// A list is read whole; a single model's page only for its status.
|
|
17
|
+
const started = probeRead(deps.fetch, url, probe.headers(key), probe.model?.lists !== undefined);
|
|
32
18
|
reads.set(url, started);
|
|
33
19
|
return started;
|
|
34
20
|
};
|
|
@@ -37,6 +37,13 @@ export const systemVoiceProvider = "system-voice";
|
|
|
37
37
|
export function isUncataloguedProvider(id) {
|
|
38
38
|
return isLocalCliProvider(id) || id === systemVoiceProvider;
|
|
39
39
|
}
|
|
40
|
+
// The local stand-ins the bundled samples were made with (`src/sample-build/`): no catalogue
|
|
41
|
+
// lists them, they cost nothing, and they exist only while the sample archives are built, so
|
|
42
|
+
// a sample project names them but nothing new can run on them.
|
|
43
|
+
export const sampleStandInProviders = ["sample-writer", "sample-artist", "sample-voice"];
|
|
44
|
+
export function isSampleStandIn(id) {
|
|
45
|
+
return sampleStandInProviders.includes(id);
|
|
46
|
+
}
|
|
40
47
|
export const providers = [
|
|
41
48
|
{ id: "openrouter", family: "llm", displayName: "OpenRouter", auth: "key" },
|
|
42
49
|
{
|
|
@@ -137,4 +144,7 @@ export const defaultSettings = {
|
|
|
137
144
|
appearance: "system",
|
|
138
145
|
loudness: defaultLoudness,
|
|
139
146
|
subtitleModel: defaultEnglishSubtitleModel,
|
|
147
|
+
videoEncoder: "cpu",
|
|
148
|
+
clipEncodeSpeed: "thorough",
|
|
149
|
+
renderPriority: "background",
|
|
140
150
|
};
|
|
@@ -2,17 +2,24 @@ import { z } from "zod";
|
|
|
2
2
|
import { englishSubtitleModels } from "../../kernel/ports/subtitles.js";
|
|
3
3
|
import { silenceGapSecondsMax } from "../admission/rules.js";
|
|
4
4
|
import { loudnessFields } from "../loudness/model.js";
|
|
5
|
+
import { clipEncodeSpeeds, renderPriorities, videoEncoders } from "../video/encoding.js";
|
|
5
6
|
import { appearances, defaultSettings } from "./model.js";
|
|
6
|
-
import {
|
|
7
|
+
import { readJsonSetting, writeSetting } from "./repo.js";
|
|
7
8
|
// The `settings` table is key/value, so each setting is its own row and a value the app
|
|
8
9
|
// does not understand costs only that one setting.
|
|
9
10
|
const silenceGapKey = "silenceGapSeconds";
|
|
10
11
|
const appearanceKey = "appearance";
|
|
11
12
|
const loudnessKey = "loudness";
|
|
12
13
|
const subtitleModelKey = "subtitleModel";
|
|
14
|
+
const videoEncoderKey = "videoEncoder";
|
|
15
|
+
const clipEncodeSpeedKey = "clipEncodeSpeed";
|
|
16
|
+
const renderPriorityKey = "renderPriority";
|
|
13
17
|
const silenceGapValue = z.number().int().min(0).max(silenceGapSecondsMax);
|
|
14
18
|
const appearanceValue = z.enum(appearances);
|
|
15
19
|
const subtitleModelValue = z.enum(englishSubtitleModels);
|
|
20
|
+
const videoEncoderValue = z.enum(videoEncoders);
|
|
21
|
+
const clipEncodeSpeedValue = z.enum(clipEncodeSpeeds);
|
|
22
|
+
const renderPriorityValue = z.enum(renderPriorities);
|
|
16
23
|
const loudnessValue = z.object({
|
|
17
24
|
enabled: z.boolean(),
|
|
18
25
|
videoLufs: z.number(),
|
|
@@ -24,6 +31,9 @@ export function readSettings(deps) {
|
|
|
24
31
|
appearance: parsed(deps, appearanceKey, appearanceValue, defaultSettings.appearance),
|
|
25
32
|
loudness: parsed(deps, loudnessKey, loudnessValue, defaultSettings.loudness),
|
|
26
33
|
subtitleModel: parsed(deps, subtitleModelKey, subtitleModelValue, defaultSettings.subtitleModel),
|
|
34
|
+
videoEncoder: parsed(deps, videoEncoderKey, videoEncoderValue, defaultSettings.videoEncoder),
|
|
35
|
+
clipEncodeSpeed: parsed(deps, clipEncodeSpeedKey, clipEncodeSpeedValue, defaultSettings.clipEncodeSpeed),
|
|
36
|
+
renderPriority: parsed(deps, renderPriorityKey, renderPriorityValue, defaultSettings.renderPriority),
|
|
27
37
|
};
|
|
28
38
|
}
|
|
29
39
|
export function saveSettings(deps, settings) {
|
|
@@ -49,6 +59,24 @@ export function saveSettings(deps, settings) {
|
|
|
49
59
|
message: "Choose Most accurate, Balanced or Smallest in Settings → Production defaults → Subtitle timing model.",
|
|
50
60
|
});
|
|
51
61
|
}
|
|
62
|
+
if (!videoEncoderValue.safeParse(settings.videoEncoder).success) {
|
|
63
|
+
fields.push({
|
|
64
|
+
field: "videoEncoder",
|
|
65
|
+
message: "Choose Processor (x264) or NVIDIA graphics card (NVENC) in Settings → Production defaults → Video encoder.",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (!clipEncodeSpeedValue.safeParse(settings.clipEncodeSpeed).success) {
|
|
69
|
+
fields.push({
|
|
70
|
+
field: "clipEncodeSpeed",
|
|
71
|
+
message: "Choose Thorough or Fast in Settings → Production defaults → Clip encoding.",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (!renderPriorityValue.safeParse(settings.renderPriority).success) {
|
|
75
|
+
fields.push({
|
|
76
|
+
field: "renderPriority",
|
|
77
|
+
message: "Choose Normal or Background in Settings → Production defaults → Render priority.",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
52
80
|
if (fields.length > 0) {
|
|
53
81
|
return { ok: false, fields };
|
|
54
82
|
}
|
|
@@ -56,31 +84,14 @@ export function saveSettings(deps, settings) {
|
|
|
56
84
|
writeSetting(deps.db, appearanceKey, JSON.stringify(settings.appearance));
|
|
57
85
|
writeSetting(deps.db, loudnessKey, JSON.stringify(settings.loudness));
|
|
58
86
|
writeSetting(deps.db, subtitleModelKey, JSON.stringify(settings.subtitleModel));
|
|
87
|
+
writeSetting(deps.db, videoEncoderKey, JSON.stringify(settings.videoEncoder));
|
|
88
|
+
writeSetting(deps.db, clipEncodeSpeedKey, JSON.stringify(settings.clipEncodeSpeed));
|
|
89
|
+
writeSetting(deps.db, renderPriorityKey, JSON.stringify(settings.renderPriority));
|
|
59
90
|
return { ok: true, settings };
|
|
60
91
|
}
|
|
61
92
|
// A stored value is JSON, and a hand-edited database is the only way it can be anything
|
|
62
93
|
// but what was saved. Such a row falls back to the default and says so in the log rather
|
|
63
94
|
// than reaching the app or taking the settings page down.
|
|
64
95
|
function parsed(deps, key, schema, fallback) {
|
|
65
|
-
|
|
66
|
-
if (stored === undefined) {
|
|
67
|
-
return fallback;
|
|
68
|
-
}
|
|
69
|
-
const result = schema.safeParse(jsonOf(stored));
|
|
70
|
-
if (result.success) {
|
|
71
|
-
return result.data;
|
|
72
|
-
}
|
|
73
|
-
deps.log.write("warn", "settings.invalid", {
|
|
74
|
-
detail: `setting ${key} is not a value this build understands; using the default`,
|
|
75
|
-
});
|
|
76
|
-
return fallback;
|
|
77
|
-
}
|
|
78
|
-
function jsonOf(stored) {
|
|
79
|
-
try {
|
|
80
|
-
return JSON.parse(stored);
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
// Not JSON at all: the same answer as JSON of the wrong shape, decided by the caller.
|
|
84
|
-
return undefined;
|
|
85
|
-
}
|
|
96
|
+
return readJsonSetting(deps.db, key, schema, fallback, deps.log);
|
|
86
97
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export async function probeRead(fetch, url, headers, withBody) {
|
|
2
|
+
try {
|
|
3
|
+
const response = await fetch(url, {
|
|
4
|
+
headers,
|
|
5
|
+
signal: AbortSignal.timeout(15_000),
|
|
6
|
+
redirect: "error",
|
|
7
|
+
});
|
|
8
|
+
const body = withBody ? await response.text() : "";
|
|
9
|
+
if (!withBody)
|
|
10
|
+
await response.body?.cancel().catch(() => { });
|
|
11
|
+
return { status: response.status, body };
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -59,6 +59,39 @@ export function readSetting(db, key) {
|
|
|
59
59
|
const row = db.prepare("SELECT value FROM settings WHERE key = ?").get(key);
|
|
60
60
|
return row === undefined ? undefined : settingRow.parse(row).value;
|
|
61
61
|
}
|
|
62
|
+
// A list of strings that keeps its strings when something else sits among them.
|
|
63
|
+
export const stringListSchema = z
|
|
64
|
+
.array(z.unknown())
|
|
65
|
+
.transform((items) => items.filter((one) => typeof one === "string"));
|
|
66
|
+
const warnedSettings = new Set();
|
|
67
|
+
// A row that is absent reads as `fallback`. So does a row that is not JSON or does not fit the
|
|
68
|
+
// schema (a hand-edited database, or a newer Slopify's), which also leaves one warning naming the
|
|
69
|
+
// setting, never its value, so a damaged row neither stops the app nor goes unnoticed. With no
|
|
70
|
+
// `log` to hand the warning goes to the process warnings, once per setting.
|
|
71
|
+
export function readJsonSetting(db, key, schema, fallback, log) {
|
|
72
|
+
const stored = readSetting(db, key);
|
|
73
|
+
if (stored === undefined)
|
|
74
|
+
return fallback;
|
|
75
|
+
try {
|
|
76
|
+
const parsed = schema.safeParse(JSON.parse(stored));
|
|
77
|
+
if (parsed.success)
|
|
78
|
+
return parsed.data;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Not JSON: reported below with the rows that parse but do not fit.
|
|
82
|
+
}
|
|
83
|
+
const detail = `setting ${key} is not a value this build understands; using the default`;
|
|
84
|
+
if (log !== undefined)
|
|
85
|
+
log.write("warn", "settings.invalid", { detail });
|
|
86
|
+
else if (!warnedSettings.has(key)) {
|
|
87
|
+
warnedSettings.add(key);
|
|
88
|
+
process.emitWarning(detail, { code: "SLOPIFY_SETTING_INVALID" });
|
|
89
|
+
}
|
|
90
|
+
return fallback;
|
|
91
|
+
}
|
|
92
|
+
export function deleteSetting(db, key) {
|
|
93
|
+
db.prepare("DELETE FROM settings WHERE key = ?").run(key);
|
|
94
|
+
}
|
|
62
95
|
export function writeSetting(db, key, value) {
|
|
63
96
|
db.prepare("INSERT INTO settings (key, value) VALUES (?, ?)" +
|
|
64
97
|
" ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(key, value);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { transact } from "../../kernel/db/tx.js";
|
|
3
3
|
import { fingerprint } from "../../kernel/runner/work.js";
|
|
4
|
-
import { readSetting, writeSetting } from "./repo.js";
|
|
4
|
+
import { deleteSetting, readSetting, writeSetting } from "./repo.js";
|
|
5
5
|
import { tutorialSessionSchema, tutorialWriteSchema, } from "./tutorial-schema.js";
|
|
6
6
|
export { tutorialSessionSchema, tutorialWriteSchema, } from "./tutorial-schema.js";
|
|
7
7
|
const recordSchema = z
|
|
@@ -61,6 +61,6 @@ export function saveTutorial(db, input) {
|
|
|
61
61
|
}
|
|
62
62
|
export function resetTutorial(db) {
|
|
63
63
|
transact(db, () => {
|
|
64
|
-
db
|
|
64
|
+
deleteSetting(db, "tutorial.session");
|
|
65
65
|
});
|
|
66
66
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isUniqueConstraint } from "../../kernel/db/index.js";
|
|
2
2
|
import { providerById } from "./model.js";
|
|
3
|
-
import { deleteVoice, insertVoice, listVoices,
|
|
3
|
+
import { deleteSetting, deleteVoice, insertVoice, listVoices, readJsonSetting, stringListSchema, updateVoiceLanguages, writeSetting, } from "./repo.js";
|
|
4
4
|
export const voiceNameMax = 200;
|
|
5
5
|
export const voiceIdMax = 200;
|
|
6
6
|
// A non-empty name and a non-empty voice ID, the ID unique within its provider, names free to
|
|
@@ -80,20 +80,12 @@ export function voices(deps) {
|
|
|
80
80
|
// their ids, so the flag needs no column of its own and travels with a backup.
|
|
81
81
|
export const realPersonVoicesKey = "voices.realPerson";
|
|
82
82
|
export function realPersonVoiceIds(db) {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
return new Set();
|
|
86
|
-
try {
|
|
87
|
-
const value = JSON.parse(stored);
|
|
88
|
-
return new Set(Array.isArray(value) ? value.filter((one) => typeof one === "string") : []);
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
return new Set();
|
|
92
|
-
}
|
|
83
|
+
const value = readJsonSetting(db, realPersonVoicesKey, stringListSchema, []);
|
|
84
|
+
return new Set(value);
|
|
93
85
|
}
|
|
94
86
|
function writeRealPersonVoices(db, ids) {
|
|
95
87
|
if (ids.length === 0)
|
|
96
|
-
db
|
|
88
|
+
deleteSetting(db, realPersonVoicesKey);
|
|
97
89
|
else
|
|
98
90
|
writeSetting(db, realPersonVoicesKey, JSON.stringify([...new Set(ids)].sort()));
|
|
99
91
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { jsonBetween } from "../../kernel/json-answer.js";
|
|
2
3
|
import { youtubeTimestamp } from "../youtube/timestamps.js";
|
|
3
4
|
import { shortHashtagsMax, shortHashtagsMin, shortTitleMax } from "./model.js";
|
|
4
5
|
export const shortPickSchema = z.object({
|
|
@@ -128,7 +129,7 @@ const tolerance = 0.01;
|
|
|
128
129
|
// Not being a JSON array of clips is the one failure; everything else is a problem to tell
|
|
129
130
|
// the model about.
|
|
130
131
|
export function checkPicks(text, sentences, limits) {
|
|
131
|
-
const parsed = answerSchema.safeParse(
|
|
132
|
+
const parsed = answerSchema.safeParse(jsonBetween(text, "[", "]"));
|
|
132
133
|
if (!parsed.success)
|
|
133
134
|
return {
|
|
134
135
|
ok: false,
|
|
@@ -226,17 +227,3 @@ export function noPicksMessage(problems, limits) {
|
|
|
226
227
|
function round(seconds) {
|
|
227
228
|
return Math.round(seconds * 1000) / 1000;
|
|
228
229
|
}
|
|
229
|
-
// The array in the answer, wherever the model put it: bare, in a ```json fence, or after a
|
|
230
|
-
// sentence of its own.
|
|
231
|
-
function jsonArrayOf(text) {
|
|
232
|
-
const start = text.indexOf("[");
|
|
233
|
-
const end = text.lastIndexOf("]");
|
|
234
|
-
if (start === -1 || end <= start)
|
|
235
|
-
return undefined;
|
|
236
|
-
try {
|
|
237
|
-
return JSON.parse(text.slice(start, end + 1));
|
|
238
|
-
}
|
|
239
|
-
catch {
|
|
240
|
-
return undefined;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { jsonBetween } from "../../kernel/json-answer.js";
|
|
2
3
|
export function imagePromptMessages(brief) {
|
|
3
4
|
const plural = brief.count === 1 ? "prompt" : "prompts";
|
|
4
5
|
return [
|
|
@@ -35,7 +36,7 @@ const fix = "Try again, or choose another model in Edit project → Providers.";
|
|
|
35
36
|
// The reason is the sentence the stage shows; the provider wrapper's `check` asks the model
|
|
36
37
|
// again while attempts remain.
|
|
37
38
|
export function checkImagePrompts(text, count) {
|
|
38
|
-
const parsed = z.array(z.string()).safeParse(
|
|
39
|
+
const parsed = z.array(z.string()).safeParse(jsonBetween(text, "[", "]"));
|
|
39
40
|
if (!parsed.success)
|
|
40
41
|
return {
|
|
41
42
|
ok: false,
|
|
@@ -54,15 +55,3 @@ export function checkImagePrompts(text, count) {
|
|
|
54
55
|
};
|
|
55
56
|
return { ok: true, prompts };
|
|
56
57
|
}
|
|
57
|
-
function jsonArrayOf(text) {
|
|
58
|
-
const start = text.indexOf("[");
|
|
59
|
-
const end = text.lastIndexOf("]");
|
|
60
|
-
if (start === -1 || end <= start)
|
|
61
|
-
return undefined;
|
|
62
|
-
try {
|
|
63
|
-
return JSON.parse(text.slice(start, end + 1));
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
@@ -167,6 +167,7 @@ export async function renderShort(run) {
|
|
|
167
167
|
}),
|
|
168
168
|
signal: run.signal,
|
|
169
169
|
log: run.log,
|
|
170
|
+
priority: run.machine?.priority,
|
|
170
171
|
onProgress: () => undefined,
|
|
171
172
|
});
|
|
172
173
|
writeFileSync(join(workspace, "subtitles.ass"), shortCaptionsAss(fasterWords(run.words, speed), {
|
|
@@ -205,6 +206,8 @@ export async function renderShort(run) {
|
|
|
205
206
|
signal: run.signal,
|
|
206
207
|
log: run.log,
|
|
207
208
|
onProgress: run.onProgress,
|
|
209
|
+
machine: run.machine,
|
|
210
|
+
notice: run.notice,
|
|
208
211
|
});
|
|
209
212
|
}
|
|
210
213
|
finally {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { accountExamples, accountPatterns } from "./limits.js";
|
|
3
|
+
import { platformLabels, socialPlatforms, xTiers, } from "./model.js";
|
|
4
|
+
const accountsSchema = z.partialRecord(z.enum(socialPlatforms), z.string().max(260));
|
|
5
|
+
export function channelSocial(db, channelId) {
|
|
6
|
+
const row = db.prepare("SELECT x_tier, social_accounts FROM channels WHERE id=?").get(channelId);
|
|
7
|
+
return channelSocialOf(row?.x_tier, row?.social_accounts);
|
|
8
|
+
}
|
|
9
|
+
// The two columns as a channel row holds them; anything unreadable is Free and no accounts.
|
|
10
|
+
export function channelSocialOf(tier, accounts) {
|
|
11
|
+
const xTier = xTiers.find((one) => one === tier) ?? "free";
|
|
12
|
+
try {
|
|
13
|
+
const parsed = accountsSchema.safeParse(JSON.parse(typeof accounts === "string" ? accounts : "{}"));
|
|
14
|
+
return { xTier, accounts: parsed.success ? parsed.data : {} };
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return { xTier, accounts: {} };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// An account name a site wouldn't take, as the sentence the Posting block shows; undefined
|
|
21
|
+
// when every name fits. A blank name is no account.
|
|
22
|
+
export function accountsProblem(accounts) {
|
|
23
|
+
for (const platform of socialPlatforms) {
|
|
24
|
+
const name = accounts[platform]?.trim() ?? "";
|
|
25
|
+
if (name !== "" && !accountPatterns[platform].test(name))
|
|
26
|
+
return `"${name}" isn't ${/^[AEIOU]/u.test(platformLabels[platform]) ? "an" : "a"} ${platformLabels[platform]} account name. Type it the way ${platformLabels[platform]} shows it (like ${accountExamples[platform]}), without the site's address, then Save.`;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
export function tidyAccounts(accounts) {
|
|
31
|
+
const tidy = {};
|
|
32
|
+
for (const platform of socialPlatforms) {
|
|
33
|
+
const name = accounts[platform]?.trim() ?? "";
|
|
34
|
+
if (name !== "")
|
|
35
|
+
tidy[platform] = name;
|
|
36
|
+
}
|
|
37
|
+
return tidy;
|
|
38
|
+
}
|
|
39
|
+
export function writeChannelSocial(db, channelId, change) {
|
|
40
|
+
if (change.xTier !== undefined)
|
|
41
|
+
db.prepare("UPDATE channels SET x_tier=? WHERE id=?").run(change.xTier, channelId);
|
|
42
|
+
if (change.accounts !== undefined)
|
|
43
|
+
db.prepare("UPDATE channels SET social_accounts=? WHERE id=?").run(JSON.stringify(tidyAccounts(change.accounts)), channelId);
|
|
44
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// What each site gets when a template turns it on, as the owner chose: the summary with the
|
|
2
|
+
// video on X and LinkedIn, a 4:5 picture carousel on Instagram, the first short on TikTok, the
|
|
3
|
+
// summary with up to four pictures on Bluesky, and a link post on Reddit.
|
|
4
|
+
export const defaultChoices = {
|
|
5
|
+
x: { text: "summary", media: { video: "long" }, hashtags: 2, link: false },
|
|
6
|
+
linkedin: { text: "summary", media: { video: "long" }, hashtags: 3, link: false },
|
|
7
|
+
instagram: { text: "summary", media: { pictures: 10 }, hashtags: 5, link: false },
|
|
8
|
+
tiktok: { text: "summary", media: { video: "short", short: 1 }, hashtags: 5, link: false },
|
|
9
|
+
bluesky: { text: "summary", media: { pictures: 4 }, hashtags: 2, link: false },
|
|
10
|
+
reddit: { text: "none", media: {}, hashtags: 0, link: true },
|
|
11
|
+
};
|
|
12
|
+
export const defaultReddit = (title) => ({
|
|
13
|
+
subreddit: "",
|
|
14
|
+
title,
|
|
15
|
+
flair: "",
|
|
16
|
+
kind: "link",
|
|
17
|
+
});
|