@gentbajko/slopify 3.25.9 → 4.1.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/audio.js +1 -1
- package/dist/adapters/alignment/cache.js +4 -6
- package/dist/adapters/alignment/ctc.js +1 -2
- package/dist/adapters/alignment/lock.js +4 -3
- package/dist/adapters/alignment/multilingual.js +2 -4
- package/dist/adapters/alignment/prefetch.js +2 -1
- package/dist/adapters/alignment/runner.js +1 -1
- package/dist/adapters/alignment/worker.js +3 -2
- package/dist/adapters/explain.js +10 -12
- package/dist/adapters/ffmpeg.js +19 -1
- package/dist/adapters/host-cli/index.js +21 -4
- package/dist/adapters/host-cli/transport.js +2 -1
- package/dist/adapters/http-failure.js +53 -0
- package/dist/adapters/image/bytes.js +2 -1
- package/dist/adapters/image/codex.js +42 -9
- package/dist/adapters/image/fal.js +21 -53
- package/dist/adapters/image/google.js +33 -56
- package/dist/adapters/image/models.js +2 -2
- package/dist/adapters/image/openai.js +22 -48
- package/dist/adapters/image/reference.js +36 -0
- package/dist/adapters/image/replicate.js +21 -49
- package/dist/adapters/image/video.js +2 -1
- package/dist/adapters/interest/search-interest.js +113 -0
- package/dist/adapters/key-probes.js +54 -3
- package/dist/adapters/llm/antigravity-models.js +1 -1
- package/dist/adapters/llm/antigravity.js +11 -6
- 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 +52 -99
- package/dist/adapters/llm/claude-code.js +16 -4
- package/dist/adapters/llm/codex-limits.js +27 -68
- package/dist/adapters/llm/codex-models.js +1 -1
- package/dist/adapters/llm/codex-server-models.js +61 -109
- package/dist/adapters/llm/codex.js +12 -1
- package/dist/adapters/llm/document-workspace.js +3 -2
- package/dist/adapters/llm/image-workspace.js +3 -2
- package/dist/adapters/llm/openrouter.js +12 -48
- package/dist/adapters/llm/run-cli.js +9 -24
- 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 +21 -41
- package/dist/adapters/tts/inworld-async.js +6 -2
- package/dist/adapters/tts/inworld.js +6 -2
- package/dist/adapters/tts/openai.js +20 -50
- package/dist/adapters/tts/system.js +2 -1
- 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 +23 -14
- package/dist/catalog/schema.js +4 -1
- package/dist/catalog/store.js +39 -0
- package/dist/catalog/validate.js +5 -2
- 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 +11 -32
- package/dist/edge/http/actor.js +5 -3
- package/dist/edge/http/app.js +86 -11
- package/dist/edge/http/assistant.js +129 -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 +2 -4
- package/dist/edge/http/channel-memory.js +9 -22
- package/dist/edge/http/channels.js +14 -27
- package/dist/edge/http/checkpoints.js +39 -21
- 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 +5 -14
- package/dist/edge/http/draft-files.js +1 -3
- package/dist/edge/http/draft-problem.js +5 -10
- 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 +47 -14
- 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 +30 -26
- package/dist/edge/http/open-folder.js +5 -10
- package/dist/edge/http/outputs.js +7 -14
- package/dist/edge/http/patch-notes.js +6 -12
- package/dist/edge/http/planning.js +3 -7
- package/dist/edge/http/problem.js +2 -4
- package/dist/edge/http/project-create.js +2 -1
- package/dist/edge/http/project-templates.js +31 -19
- package/dist/edge/http/projects.js +24 -16
- package/dist/edge/http/prompts.js +9 -16
- package/dist/edge/http/pronunciations.js +1 -2
- package/dist/edge/http/providers.js +48 -34
- 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 +12 -25
- package/dist/edge/http/revision-files.js +6 -11
- package/dist/edge/http/revisions.js +14 -19
- package/dist/edge/http/run-cost.js +1 -2
- package/dist/edge/http/same-page.js +80 -0
- package/dist/edge/http/schedules.js +69 -40
- package/dist/edge/http/settings.js +27 -14
- package/dist/edge/http/social.js +283 -0
- package/dist/edge/http/staging.js +3 -5
- package/dist/edge/http/storage-files.js +5 -7
- package/dist/edge/http/storage.js +33 -25
- package/dist/edge/http/studio.js +94 -69
- package/dist/edge/http/style-preview.js +3 -6
- package/dist/edge/http/subtitles.js +3 -17
- package/dist/edge/http/template-studio.js +436 -0
- package/dist/edge/http/trash.js +11 -12
- 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 +3 -4
- 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 +29 -3
- package/dist/edge/mcp/http.js +11 -2
- package/dist/edge/mcp/instructions.js +6 -4
- package/dist/edge/mcp/library-tools.js +9 -5
- package/dist/edge/mcp/remake-tools.js +7 -4
- package/dist/edge/mcp/route-tool.js +35 -15
- package/dist/edge/mcp/server.js +13 -8
- package/dist/edge/mcp/tools-channels.js +11 -9
- package/dist/edge/mcp/tools-designs.js +165 -0
- package/dist/edge/mcp/tools-library.js +19 -7
- package/dist/edge/mcp/tools-planning.js +40 -21
- package/dist/edge/mcp/tools-projects.js +38 -11
- package/dist/edge/mcp/tools-publishing.js +9 -9
- package/dist/edge/mcp/tools-runs.js +23 -10
- package/dist/edge/mcp/tools-schedules.js +32 -8
- package/dist/edge/mcp/tools-setup.js +75 -11
- package/dist/edge/mcp/tools-social.js +47 -0
- package/dist/edge/mcp/tools-storage.js +11 -3
- 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/migrations/0078-uploaded-from-links.sql +7 -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/pipeline.js +12 -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/ports/settings-places.js +31 -0
- package/dist/kernel/ports/system-speech.js +31 -3
- package/dist/kernel/runner/attempt.js +14 -4
- 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 +18 -12
- package/dist/kernel/runner/standalone.js +11 -7
- package/dist/main.js +338 -21
- package/dist/model-catalog.js +20 -0
- package/dist/model-checks.js +166 -0
- package/dist/patch-notes/4.0.0.md +51 -0
- package/dist/patch-notes/4.1.0.md +42 -0
- package/dist/patch-notes/index.json +12 -0
- package/dist/slices/admission/heads.js +2 -1
- package/dist/slices/admission/listing-facts.js +95 -0
- package/dist/slices/admission/repo.js +11 -7
- package/dist/slices/admission/rules.js +50 -15
- 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/state-words.js +34 -5
- 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 +86 -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 +344 -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 +8 -7
- package/dist/slices/channels/cast-images.js +3 -2
- 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 +7 -1
- package/dist/slices/channels/service.js +10 -0
- package/dist/slices/channels/videos.js +3 -15
- package/dist/slices/checkpoints/index.js +0 -1
- package/dist/slices/control/providers.js +2 -28
- 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 +130 -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 +223 -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/document/model.js +7 -4
- package/dist/slices/document/render.js +2 -1
- package/dist/slices/document/theme-schema.js +1 -1
- package/dist/slices/document/theme.js +49 -3
- package/dist/slices/episodes/summarize.js +6 -2
- package/dist/slices/estimate/index.js +72 -28
- package/dist/slices/estimate/requests.js +4 -4
- package/dist/slices/fixes/rules.js +3 -1
- package/dist/slices/fonts/catalog.js +5 -3
- package/dist/slices/fonts/coverage.js +2 -1
- 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 +8 -16
- package/dist/slices/images/scenes.js +102 -28
- package/dist/slices/keywords/digest.js +85 -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 +5 -4
- package/dist/slices/library/used-by.js +25 -10
- package/dist/slices/loudness/loudnorm.js +53 -12
- package/dist/slices/loudness/model.js +3 -2
- package/dist/slices/loudness/sentence-level.js +2 -1
- 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 +125 -0
- package/dist/slices/model-checks/run.js +143 -0
- package/dist/slices/model-checks/service.js +198 -0
- package/dist/slices/model-upkeep/model.js +1 -1
- package/dist/slices/model-upkeep/switch.js +72 -27
- package/dist/slices/model-upkeep/usage.js +39 -13
- package/dist/slices/narration/breaths.js +3 -1
- package/dist/slices/narration/clean-pieces.js +3 -1
- package/dist/slices/narration/concat.js +2 -1
- package/dist/slices/narration/pauses-model.js +3 -2
- package/dist/slices/narration/peaks.js +3 -4
- package/dist/slices/narration/pronunciation-chunks.js +4 -3
- package/dist/slices/narration/pronunciation.js +6 -4
- 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 +4 -3
- package/dist/slices/onboarding/copy.js +21 -3
- package/dist/slices/onboarding/first-run.js +16 -40
- package/dist/slices/onboarding/full-video.js +3 -2
- package/dist/slices/onboarding/install.js +6 -4
- package/dist/slices/onboarding/model.js +26 -37
- package/dist/slices/onboarding/packs.js +22 -22
- package/dist/slices/onboarding/preferred-model.js +14 -0
- package/dist/slices/onboarding/quick-short.js +105 -65
- package/dist/slices/onboarding/sample.js +4 -2
- package/dist/slices/onboarding/state.js +9 -11
- package/dist/slices/onboarding/template.js +8 -6
- package/dist/slices/outputs/add.js +3 -2
- package/dist/slices/outputs/service.js +2 -2
- package/dist/slices/patch-notes/library.js +4 -3
- package/dist/slices/play-drafts/convert.js +4 -0
- package/dist/slices/play-drafts/readiness.js +3 -3
- package/dist/slices/play-drafts/review-inputs.js +9 -3
- package/dist/slices/play-drafts/review.js +5 -4
- package/dist/slices/play-drafts/schema.js +4 -0
- package/dist/slices/play-drafts/service.js +17 -7
- package/dist/slices/project-templates/from-project.js +9 -2
- package/dist/slices/project-templates/next-chapter.js +2 -1
- package/dist/slices/project-templates/provider-defaults.js +22 -0
- package/dist/slices/project-templates/repo.js +6 -1
- package/dist/slices/project-templates/setup-sections.js +26 -0
- package/dist/slices/project-templates/setup.js +14 -1
- 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 +32 -14
- 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 +4 -4
- 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 +8 -5
- 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 +102 -31
- package/dist/slices/rebuild/recipe-visual.js +55 -16
- package/dist/slices/rebuild/recipe-voices.js +3 -2
- package/dist/slices/rebuild/recipe-work.js +19 -7
- package/dist/slices/rebuild/recipe-youtube.js +4 -3
- package/dist/slices/rebuild/recovery-selection.js +3 -1
- package/dist/slices/rebuild/recovery.js +9 -8
- package/dist/slices/rebuild/remake-outdated.js +5 -5
- package/dist/slices/rebuild/repo.js +4 -3
- package/dist/slices/rebuild/review-promise.js +25 -0
- package/dist/slices/rebuild/review-redo.js +3 -2
- 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 +10 -9
- package/dist/slices/rebuild/runtime-article.js +7 -5
- package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
- package/dist/slices/rebuild/runtime-document.js +11 -13
- package/dist/slices/rebuild/runtime-export-bed.js +8 -12
- package/dist/slices/rebuild/runtime-export-edit.js +8 -16
- package/dist/slices/rebuild/runtime-export-inputs.js +12 -11
- package/dist/slices/rebuild/runtime-export-short.js +9 -5
- package/dist/slices/rebuild/runtime-export.js +38 -15
- package/dist/slices/rebuild/runtime-figure-card.js +2 -1
- package/dist/slices/rebuild/runtime-image.js +8 -12
- 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 +17 -22
- 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 +13 -17
- 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 +67 -25
- package/dist/slices/rebuild/runtime-publication.js +10 -9
- package/dist/slices/rebuild/runtime-review.js +39 -33
- package/dist/slices/rebuild/runtime-run.js +4 -1
- package/dist/slices/rebuild/runtime-shorts.js +31 -50
- package/dist/slices/rebuild/runtime-store.js +20 -10
- package/dist/slices/rebuild/runtime-subtitles.js +28 -25
- package/dist/slices/rebuild/runtime-voices.js +10 -16
- package/dist/slices/rebuild/runtime-youtube.js +14 -31
- package/dist/slices/rebuild/service-readiness.js +11 -3
- 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/research/synthesis.js +3 -2
- package/dist/slices/reviews/external-model.js +4 -1
- package/dist/slices/reviews/external-repo.js +2 -1
- package/dist/slices/reviews/external-rows.js +2 -3
- package/dist/slices/reviews/holds.js +5 -4
- package/dist/slices/reviews/model.js +32 -11
- package/dist/slices/reviews/repo.js +2 -1
- package/dist/slices/reviews/rules.js +4 -3
- package/dist/slices/reviews/verdict.js +2 -1
- package/dist/slices/reviews/view.js +5 -2
- package/dist/slices/revisions/downloads.js +4 -5
- package/dist/slices/revisions/image-plan.js +1 -1
- package/dist/slices/revisions/mutation-assets.js +9 -5
- package/dist/slices/revisions/mutation-cues.js +2 -5
- package/dist/slices/revisions/mutations.js +3 -3
- 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 +18 -7
- 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 +30 -21
- package/dist/slices/schedules/rescore.js +35 -0
- package/dist/slices/schedules/scheduler.js +13 -3
- package/dist/slices/schedules/schema.js +57 -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 +53 -41
- package/dist/slices/settings/cli-paths.js +4 -6
- package/dist/slices/settings/first-run.js +8 -43
- package/dist/slices/settings/health.js +30 -15
- package/dist/slices/settings/key-test.js +1 -1
- package/dist/slices/settings/model-reach.js +3 -17
- package/dist/slices/settings/model.js +14 -2
- package/dist/slices/settings/playback.js +34 -23
- 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/keep.js +2 -1
- package/dist/slices/shorts/pick.js +6 -18
- package/dist/slices/shorts/prompts.js +4 -14
- package/dist/slices/shorts/render.js +6 -2
- 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 +132 -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 +56 -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 +129 -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 +22 -4
- 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 +17 -12
- 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 +26 -9
- package/dist/slices/studio/calendar.js +7 -2
- package/dist/slices/studio/disclosure.js +4 -4
- package/dist/slices/studio/model.js +1 -1
- package/dist/slices/studio/pack.js +9 -8
- package/dist/slices/studio/pick.js +2 -11
- package/dist/slices/studio/plan.js +7 -16
- package/dist/slices/studio/queue.js +3 -14
- package/dist/slices/studio/releases.js +26 -0
- package/dist/slices/studio/settings.js +20 -57
- package/dist/slices/studio/videos.js +35 -4
- package/dist/slices/style-preview/render.js +37 -16
- package/dist/slices/style-preview/schema.js +14 -1
- package/dist/slices/style-preview/service.js +2 -1
- package/dist/slices/style-preview/settings.js +33 -5
- package/dist/slices/subtitles/captions.js +1 -1
- 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 +161 -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 +106 -0
- package/dist/slices/template-studio/cards.js +78 -0
- package/dist/slices/template-studio/checks.js +322 -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 +58 -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 +207 -0
- package/dist/slices/template-studio/paid-view.js +66 -0
- package/dist/slices/template-studio/paid.js +355 -0
- package/dist/slices/template-studio/quote.js +155 -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 +32 -0
- package/dist/slices/template-studio/schedule-sample.js +91 -0
- package/dist/slices/template-studio/schema.js +243 -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 +175 -0
- package/dist/slices/template-studio/status.js +191 -0
- package/dist/slices/template-studio/topic.js +223 -0
- package/dist/slices/thumbnail/by-llm.js +1 -1
- 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/uploads/repo.js +37 -1
- package/dist/slices/uploads/rules.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/edit-list.js +2 -2
- package/dist/slices/video/edit-settings.js +1 -1
- package/dist/slices/video/encoding.js +169 -0
- package/dist/slices/video/ffmpeg.js +23 -21
- package/dist/slices/video/figure-card.js +1 -15
- package/dist/slices/video/plan.js +2 -1
- package/dist/slices/video/slideshow.js +43 -18
- package/dist/slices/voices/attribution.js +3 -2
- package/dist/slices/voices/model.js +9 -8
- package/dist/slices/voices/palette.js +0 -3
- package/dist/slices/voices/script.js +2 -1
- package/dist/slices/youtube/answer.js +4 -16
- package/dist/slices/youtube/edits-repo.js +3 -12
- package/dist/tutorials/AI-CLIs.md +25 -25
- package/dist/tutorials/Agents-MCP.md +34 -33
- package/dist/tutorials/Assistant.md +42 -0
- package/dist/tutorials/Backups.md +7 -7
- package/dist/tutorials/Calendar.md +17 -25
- package/dist/tutorials/Cast-Library.md +19 -19
- package/dist/tutorials/Channels.md +42 -35
- package/dist/tutorials/Costs-and-Run-Cost.md +38 -40
- package/dist/tutorials/Create-Article.md +114 -0
- package/dist/tutorials/Create-Extras.md +112 -0
- package/dist/tutorials/Create-Images.md +165 -0
- package/dist/tutorials/Create-Narration.md +178 -0
- package/dist/tutorials/Create-Overview.md +244 -0
- package/dist/tutorials/Create-Video-and-Style.md +144 -0
- package/dist/tutorials/Docker.md +3 -3
- package/dist/tutorials/Document-Themes.md +32 -32
- package/dist/tutorials/Editing-a-Project.md +94 -94
- package/dist/tutorials/FAQ.md +18 -18
- package/dist/tutorials/First-Launch-and-Welcome.md +50 -64
- package/dist/tutorials/Home-and-Projects.md +115 -71
- package/dist/tutorials/Home.md +33 -33
- package/dist/tutorials/Install.md +14 -13
- package/dist/tutorials/Intros-and-Outros.md +33 -34
- package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +62 -58
- package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
- package/dist/tutorials/Models.md +53 -21
- package/dist/tutorials/Multiple-Voices.md +41 -40
- package/dist/tutorials/Narration-Aliases-and-Glossary.md +14 -14
- package/dist/tutorials/Notifications.md +27 -17
- package/dist/tutorials/Other-Languages.md +15 -15
- package/dist/tutorials/PDF-Documents.md +38 -29
- package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
- package/dist/tutorials/Project-Page.md +118 -120
- package/dist/tutorials/Prompts-and-Templates.md +84 -0
- package/dist/tutorials/Prompts.md +48 -44
- package/dist/tutorials/Providers-and-Keys.md +105 -98
- package/dist/tutorials/Publishing-to-YouTube.md +31 -31
- package/dist/tutorials/Recovery-and-Retries.md +31 -31
- package/dist/tutorials/Remote-Access.md +63 -0
- package/dist/tutorials/Reviews-and-Checkpoints.md +98 -90
- package/dist/tutorials/Schedules.md +46 -27
- package/dist/tutorials/Settings-Reference.md +194 -174
- package/dist/tutorials/Shorts.md +34 -33
- package/dist/tutorials/Start-at-Login.md +3 -3
- package/dist/tutorials/Studio-Extension.md +22 -24
- package/dist/tutorials/Template-Studio.md +128 -0
- package/dist/tutorials/Templates.md +51 -77
- package/dist/tutorials/Trash.md +4 -4
- package/dist/tutorials/Troubleshooting.md +44 -44
- package/dist/tutorials/Uninstalling-and-Moving.md +5 -5
- package/dist/tutorials/Updating-and-Patch-Notes.md +18 -18
- package/dist/tutorials/Video-Editing.md +34 -34
- package/dist/tutorials/Where-Your-Files-Live.md +10 -10
- package/dist/tutorials/YouTube-Description.md +26 -26
- package/dist/tutorials/Your-First-Short.md +35 -35
- package/dist/tutorials/_Sidebar.md +19 -14
- package/dist/updater/work-in-progress.js +42 -12
- package/dist/web/assets/index-BNftKQrA.css +1 -0
- package/dist/web/assets/index-Cpa58woP.js +459 -0
- package/dist/web/assets/pdf-BKh8f7R6.js +56 -0
- package/dist/web/assets/pdf.worker.min-BmVo14Nb.mjs +28 -0
- package/dist/web/assets/pdf.worker.min-D7i8o3wR.js +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +6 -2
- 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/tutorials/Library-Overview.md +0 -76
- package/dist/tutorials/Play-Images.md +0 -161
- package/dist/tutorials/Play-Narration.md +0 -167
- package/dist/tutorials/Play-Outputs.md +0 -109
- package/dist/tutorials/Play-Overview.md +0 -227
- package/dist/tutorials/Play-Title-and-Article.md +0 -136
- package/dist/tutorials/Play-Video-and-Style.md +0 -143
- package/dist/web/assets/index-BadsoORk.css +0 -1
- package/dist/web/assets/index-D8LqNsCl.js +0 -326
- package/dist/web/assets/pdf-T7MCwjIv.js +0 -54
- package/dist/web/assets/pdf.worker.min-Dswkl-cV.mjs +0 -29
- package/dist/web/assets/pdf.worker.min-zC6nfbxG.js +0 -1
|
@@ -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,3 +1,4 @@
|
|
|
1
|
+
import { editSettings } from "../../kernel/ports/settings-places.js";
|
|
1
2
|
import { clipBounds } from "./pick.js";
|
|
2
3
|
// Shorts kept through a change of their moments: a count raised or lowered, a new intro or a
|
|
3
4
|
// narration edit that moves the timing. A kept short is remembered by its sentences' text, so
|
|
@@ -87,5 +88,5 @@ export function lostMessage(lost, kept) {
|
|
|
87
88
|
return short === undefined ? `short ${String(at)}` : `short ${String(at)} ("${short.title}")`;
|
|
88
89
|
})
|
|
89
90
|
.join(", ");
|
|
90
|
-
return `The narration of ${names} changed, so its moment can't be kept.
|
|
91
|
+
return `The narration of ${names} changed, so its moment can't be kept. Untick it under Keep finished shorts in ${editSettings.shorts}, so a new moment is picked for it, save, then use Choose what to make again (in the project's More menu).`;
|
|
91
92
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { jsonBetween } from "../../kernel/json-answer.js";
|
|
3
|
+
import { editSettings } from "../../kernel/ports/settings-places.js";
|
|
2
4
|
import { youtubeTimestamp } from "../youtube/timestamps.js";
|
|
3
5
|
import { shortHashtagsMax, shortHashtagsMin, shortTitleMax } from "./model.js";
|
|
4
6
|
export const shortPickSchema = z.object({
|
|
@@ -120,7 +122,7 @@ const answerSchema = z.array(z.object({
|
|
|
120
122
|
hashtags: z.array(z.string()),
|
|
121
123
|
why: z.string().optional(),
|
|
122
124
|
}));
|
|
123
|
-
const fix =
|
|
125
|
+
const fix = `Try again, or choose another model in ${editSettings.writer}.`;
|
|
124
126
|
// Timing noise: the length rules are checked to the hundredth of a second.
|
|
125
127
|
const tolerance = 0.01;
|
|
126
128
|
// Reads the model's answer and keeps the clips that follow the rules: in range, the right
|
|
@@ -128,7 +130,7 @@ const tolerance = 0.01;
|
|
|
128
130
|
// Not being a JSON array of clips is the one failure; everything else is a problem to tell
|
|
129
131
|
// the model about.
|
|
130
132
|
export function checkPicks(text, sentences, limits) {
|
|
131
|
-
const parsed = answerSchema.safeParse(
|
|
133
|
+
const parsed = answerSchema.safeParse(jsonBetween(text, "[", "]"));
|
|
132
134
|
if (!parsed.success)
|
|
133
135
|
return {
|
|
134
136
|
ok: false,
|
|
@@ -220,23 +222,9 @@ export function checkPicks(text, sentences, limits) {
|
|
|
220
222
|
export function noPicksMessage(problems, limits) {
|
|
221
223
|
const shortVideo = limits.durationSeconds < limits.minSeconds;
|
|
222
224
|
if (shortVideo)
|
|
223
|
-
return `The narration is ${String(Math.round(limits.durationSeconds))} seconds long, shorter than the ${String(limits.minSeconds)}-second minimum for a short, so there is nothing to cut. Lower the shortest
|
|
224
|
-
return `The AI model didn't pick any clip Slopify could use as a short, twice (${problems[0] ?? "no clips were listed"}). Try again; if it keeps happening, widen the
|
|
225
|
+
return `The narration is ${String(Math.round(limits.durationSeconds))} seconds long, shorter than the ${String(limits.minSeconds)}-second minimum for a short, so there is nothing to cut. Lower the shortest Length in ${editSettings.shorts}, then Try again.`;
|
|
226
|
+
return `The AI model didn't pick any clip Slopify could use as a short, twice (${problems[0] ?? "no clips were listed"}). Try again; if it keeps happening, widen the Length range in ${editSettings.shorts}, or choose another model in ${editSettings.writer}.`;
|
|
225
227
|
}
|
|
226
228
|
function round(seconds) {
|
|
227
229
|
return Math.round(seconds * 1000) / 1000;
|
|
228
230
|
}
|
|
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,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { jsonBetween } from "../../kernel/json-answer.js";
|
|
3
|
+
import { editSettings } from "../../kernel/ports/settings-places.js";
|
|
2
4
|
export function imagePromptMessages(brief) {
|
|
3
5
|
const plural = brief.count === 1 ? "prompt" : "prompts";
|
|
4
6
|
return [
|
|
@@ -31,11 +33,11 @@ export function imagePromptMessages(brief) {
|
|
|
31
33
|
},
|
|
32
34
|
];
|
|
33
35
|
}
|
|
34
|
-
const fix =
|
|
36
|
+
const fix = `Try again, or choose another model in ${editSettings.writer}.`;
|
|
35
37
|
// The reason is the sentence the stage shows; the provider wrapper's `check` asks the model
|
|
36
38
|
// again while attempts remain.
|
|
37
39
|
export function checkImagePrompts(text, count) {
|
|
38
|
-
const parsed = z.array(z.string()).safeParse(
|
|
40
|
+
const parsed = z.array(z.string()).safeParse(jsonBetween(text, "[", "]"));
|
|
39
41
|
if (!parsed.success)
|
|
40
42
|
return {
|
|
41
43
|
ok: false,
|
|
@@ -54,15 +56,3 @@ export function checkImagePrompts(text, count) {
|
|
|
54
56
|
};
|
|
55
57
|
return { ok: true, prompts };
|
|
56
58
|
}
|
|
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
|
-
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { copyFileSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { editSettings } from "../../kernel/ports/settings-places.js";
|
|
3
4
|
import { editListVersion, } from "../video/edit-list.js";
|
|
4
5
|
import { runFfmpeg } from "../video/ffmpeg.js";
|
|
5
6
|
import { motionFor } from "../video/motion.js";
|
|
@@ -46,7 +47,7 @@ export function shortAudioArgs(timeline, start, end, output, options = {}) {
|
|
|
46
47
|
count += 1;
|
|
47
48
|
}
|
|
48
49
|
if (count === 0 || first === undefined)
|
|
49
|
-
throw new Error(
|
|
50
|
+
throw new Error(`A short's clip falls outside the narration, so it has no sound to cut. Use Pick different moments in ${editSettings.shorts}, then Try again.`);
|
|
50
51
|
const joined = Array.from({ length: count }, (_value, at) => `[a${String(at)}]`).join("");
|
|
51
52
|
chains.push(`${joined}concat=n=${String(count)}:v=0:a=1[joined]`);
|
|
52
53
|
const speed = options.speed ?? 1;
|
|
@@ -89,7 +90,7 @@ export function shortAudioArgs(timeline, start, end, output, options = {}) {
|
|
|
89
90
|
// moving as the video's shots move by their place.
|
|
90
91
|
export function shortEditList(input) {
|
|
91
92
|
if (input.images.length === 0)
|
|
92
|
-
throw new Error(
|
|
93
|
+
throw new Error(`A short has no images to show. Use Make this short again in ${editSettings.shorts}, then Try again.`);
|
|
93
94
|
const total = Math.max(1, Math.round(input.seconds * fps));
|
|
94
95
|
const each = Math.max(1, Math.round(input.imageSeconds * fps));
|
|
95
96
|
const figures = figureFrames(input.figures ?? [], total);
|
|
@@ -167,6 +168,7 @@ export async function renderShort(run) {
|
|
|
167
168
|
}),
|
|
168
169
|
signal: run.signal,
|
|
169
170
|
log: run.log,
|
|
171
|
+
priority: run.machine?.priority,
|
|
170
172
|
onProgress: () => undefined,
|
|
171
173
|
});
|
|
172
174
|
writeFileSync(join(workspace, "subtitles.ass"), shortCaptionsAss(fasterWords(run.words, speed), {
|
|
@@ -205,6 +207,8 @@ export async function renderShort(run) {
|
|
|
205
207
|
signal: run.signal,
|
|
206
208
|
log: run.log,
|
|
207
209
|
onProgress: run.onProgress,
|
|
210
|
+
machine: run.machine,
|
|
211
|
+
notice: run.notice,
|
|
208
212
|
});
|
|
209
213
|
}
|
|
210
214
|
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
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { fillBeforeMs, lateFillMs, nativeSchedule } from "./limits.js";
|
|
2
|
+
// Sites without a scheduler: Instagram, Bluesky and Reddit. A post asked to fill there before
|
|
3
|
+
// its release time waits for it; the extension fills it about two minutes before, and still
|
|
4
|
+
// fills one up to twelve hours late when the browser was closed (flagged late).
|
|
5
|
+
export function waitsForRelease(platform) {
|
|
6
|
+
return nativeSchedule[platform] === undefined;
|
|
7
|
+
}
|
|
8
|
+
// Whether a waiting post can be filled now: always on a site with a scheduler, and on one
|
|
9
|
+
// without when it has no release time or its time is (nearly) here.
|
|
10
|
+
export function readyNow(platform, releaseAt, now) {
|
|
11
|
+
if (!waitsForRelease(platform) || releaseAt === null)
|
|
12
|
+
return true;
|
|
13
|
+
const at = Date.parse(releaseAt);
|
|
14
|
+
return !Number.isFinite(at) || at - fillBeforeMs <= now.getTime();
|
|
15
|
+
}
|
|
16
|
+
// A waiting post's fill time within the next `horizonMs`, or a late one still worth filling;
|
|
17
|
+
// undefined for one that is not due, or too late.
|
|
18
|
+
export function dueAt(platform, releaseAt, now, horizonMs) {
|
|
19
|
+
if (!waitsForRelease(platform) || releaseAt === null)
|
|
20
|
+
return undefined;
|
|
21
|
+
const at = Date.parse(releaseAt);
|
|
22
|
+
if (!Number.isFinite(at))
|
|
23
|
+
return undefined;
|
|
24
|
+
const fill = at - fillBeforeMs;
|
|
25
|
+
const from = now.getTime();
|
|
26
|
+
if (at < from - lateFillMs || fill > from + horizonMs)
|
|
27
|
+
return undefined;
|
|
28
|
+
return { at: new Date(fill).toISOString(), late: at < from };
|
|
29
|
+
}
|
|
30
|
+
// Whether a post that waits for its release time has outlived it.
|
|
31
|
+
export function tooLate(platform, releaseAt, now) {
|
|
32
|
+
if (!waitsForRelease(platform) || releaseAt === null)
|
|
33
|
+
return false;
|
|
34
|
+
const at = Date.parse(releaseAt);
|
|
35
|
+
return Number.isFinite(at) && at < now.getTime() - lateFillMs;
|
|
36
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readJsonSetting, writeSetting } from "../settings/repo.js";
|
|
3
|
+
import { studioPairing } from "../studio/settings.js";
|
|
4
|
+
import { socialPlatforms } from "./model.js";
|
|
5
|
+
const sitesKey = "social.extensionSites";
|
|
6
|
+
const stored = z.object({ sites: z.array(z.enum(socialPlatforms)), at: z.string() });
|
|
7
|
+
// The sites the extension says it may post on, as it last reported them.
|
|
8
|
+
export function writeExtensionSites(db, sites, now) {
|
|
9
|
+
writeSetting(db, sitesKey, JSON.stringify({ sites: [...new Set(sites)], at: now.toISOString() }));
|
|
10
|
+
}
|
|
11
|
+
export function postingExtension(db) {
|
|
12
|
+
const seen = readJsonSetting(db, sitesKey, stored, undefined);
|
|
13
|
+
return {
|
|
14
|
+
paired: studioPairing(db).origin !== null,
|
|
15
|
+
sites: seen?.sites ?? null,
|
|
16
|
+
seenAt: seen?.at ?? null,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// What doesn't fit a site, as plain sentences with the fix. A warning never blocks a fill: the
|
|
2
|
+
// site itself has the last word, and the person may know better. Pure.
|
|
3
|
+
import { limitsFor, nativeSchedule } from "./limits.js";
|
|
4
|
+
import { platformLabels, xTierLabels, } from "./model.js";
|
|
5
|
+
function duration(seconds) {
|
|
6
|
+
const whole = Math.round(seconds);
|
|
7
|
+
const hours = Math.floor(whole / 3600);
|
|
8
|
+
const minutes = Math.floor((whole % 3600) / 60);
|
|
9
|
+
const rest = whole % 60;
|
|
10
|
+
const parts = [
|
|
11
|
+
...(hours > 0 ? [`${String(hours)} h`] : []),
|
|
12
|
+
...(minutes > 0 ? [`${String(minutes)} min`] : []),
|
|
13
|
+
...(rest > 0 || whole === 0 ? [`${String(rest)} s`] : []),
|
|
14
|
+
];
|
|
15
|
+
return parts.join(" ");
|
|
16
|
+
}
|
|
17
|
+
export function mediaWarnings(platform, media, tier) {
|
|
18
|
+
const site = platformLabels[platform];
|
|
19
|
+
const limits = limitsFor(platform, tier);
|
|
20
|
+
const warnings = [];
|
|
21
|
+
const videos = media.filter((one) => one.kind === "video" || one.kind === "short");
|
|
22
|
+
const stills = media.length - videos.length;
|
|
23
|
+
if (videos.length > limits.videos)
|
|
24
|
+
warnings.push({
|
|
25
|
+
code: "too-many-videos",
|
|
26
|
+
sentence: `${site} takes one video per post. Keep the video or the short in the post's media, not both.`,
|
|
27
|
+
});
|
|
28
|
+
if (stills > limits.pictures)
|
|
29
|
+
warnings.push({
|
|
30
|
+
code: "too-many-pictures",
|
|
31
|
+
sentence: `${site} takes up to ${String(limits.pictures)} pictures per post; this one has ${String(stills)}. The extension puts in the first ${String(limits.pictures)}; pick fewer in the post's media to choose which.`,
|
|
32
|
+
});
|
|
33
|
+
if (!limits.mixes && videos.length > 0 && stills > 0)
|
|
34
|
+
warnings.push({
|
|
35
|
+
code: "video-and-pictures",
|
|
36
|
+
sentence: `${site} doesn't take a video and pictures in one post. Keep either the video or the pictures in the post's media.`,
|
|
37
|
+
});
|
|
38
|
+
for (const video of videos) {
|
|
39
|
+
if (video.seconds === undefined || video.seconds <= limits.videoSeconds)
|
|
40
|
+
continue;
|
|
41
|
+
const tierHint = platform === "x" && tier === "free"
|
|
42
|
+
? ` If the channel's X account has Premium, set it in Channels → Brand → Posting.`
|
|
43
|
+
: "";
|
|
44
|
+
warnings.push({
|
|
45
|
+
code: "video-too-long",
|
|
46
|
+
sentence: `${platform === "x" ? `X ${xTierLabels[tier]}` : site} takes videos up to ${duration(limits.videoSeconds)}; this one is ${duration(video.seconds)}. Post a short instead, or the link to the video.${tierHint}`,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if ((platform === "tiktok" || platform === "instagram") && media.length === 0)
|
|
50
|
+
warnings.push({
|
|
51
|
+
code: "no-media",
|
|
52
|
+
sentence: `${site} posts need a video or pictures. Pick a short or pictures in the post's media.`,
|
|
53
|
+
});
|
|
54
|
+
if (platform === "tiktok" && videos.some((one) => one.kind === "video" && one.shape === "16:9"))
|
|
55
|
+
warnings.push({
|
|
56
|
+
code: "full-video",
|
|
57
|
+
sentence: "TikTok shows a 16:9 video small, between bars. Pick a short (9:16) in the post's media.",
|
|
58
|
+
});
|
|
59
|
+
return warnings;
|
|
60
|
+
}
|
|
61
|
+
export function redditWarnings(fields) {
|
|
62
|
+
const warnings = [];
|
|
63
|
+
if (fields.subreddit === "")
|
|
64
|
+
warnings.push({
|
|
65
|
+
code: "subreddit-missing",
|
|
66
|
+
sentence: "The subreddit isn't set, so Reddit will ask where to post. Type it in the post's Subreddit box (like videos, without r/).",
|
|
67
|
+
});
|
|
68
|
+
if (fields.title.trim() === "")
|
|
69
|
+
warnings.push({
|
|
70
|
+
code: "title-missing",
|
|
71
|
+
sentence: "Reddit posts need a title. Type one in the post's Title box.",
|
|
72
|
+
});
|
|
73
|
+
return warnings;
|
|
74
|
+
}
|
|
75
|
+
// When the post goes out, from the YouTube item it carries: the site's scheduler at the release
|
|
76
|
+
// time, the extension filling it at release time on a site without one, or now.
|
|
77
|
+
export function scheduleFor(platform, releaseAt, now) {
|
|
78
|
+
const site = platformLabels[platform];
|
|
79
|
+
if (releaseAt === null)
|
|
80
|
+
return {
|
|
81
|
+
schedule: { mode: "now", at: null },
|
|
82
|
+
warnings: [
|
|
83
|
+
{
|
|
84
|
+
code: "no-release",
|
|
85
|
+
sentence: `This project has no release time, so the post goes out when you press Post on ${site}. Give it a time in the project's Release block to post with the video.`,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
const at = Date.parse(releaseAt);
|
|
90
|
+
const ahead = at - now.getTime();
|
|
91
|
+
if (!Number.isFinite(at) || ahead <= 0)
|
|
92
|
+
return {
|
|
93
|
+
schedule: { mode: "now", at: releaseAt },
|
|
94
|
+
warnings: [
|
|
95
|
+
{
|
|
96
|
+
code: "release-past",
|
|
97
|
+
sentence: `The release time has passed, so the post goes out when you press Post on ${site}.`,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
const window = nativeSchedule[platform];
|
|
102
|
+
if (window === undefined)
|
|
103
|
+
return { schedule: { mode: "at-release", at: releaseAt }, warnings: [] };
|
|
104
|
+
if (ahead < window.minMs || ahead > window.maxMs)
|
|
105
|
+
return {
|
|
106
|
+
schedule: { mode: "now", at: releaseAt },
|
|
107
|
+
warnings: [
|
|
108
|
+
{
|
|
109
|
+
code: "release-outside-window",
|
|
110
|
+
sentence: `${site} schedules posts only ${duration(window.minMs / 1000)} to ${String(Math.round(window.maxMs / 86_400_000))} days ahead, so the extension leaves it to post now. Fill it in again when the release time is within that window, or press Post at the release time.`,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
};
|
|
114
|
+
return { schedule: { mode: "native", at: releaseAt }, warnings: [] };
|
|
115
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// What each site takes, as observed on the site (`checked` is the date, null where it was
|
|
2
|
+
// not seen yet and comes from the site's own help pages). Browser-safe.
|
|
3
|
+
const free = {
|
|
4
|
+
text: 280,
|
|
5
|
+
unit: "weighted",
|
|
6
|
+
pictures: 4,
|
|
7
|
+
videos: 1,
|
|
8
|
+
videoSeconds: 140,
|
|
9
|
+
mixes: true,
|
|
10
|
+
pictureShape: "original",
|
|
11
|
+
hashtags: 10,
|
|
12
|
+
aiLabel: true,
|
|
13
|
+
checked: null,
|
|
14
|
+
source: "help.x.com: posting limits, video length",
|
|
15
|
+
};
|
|
16
|
+
const xLimits = {
|
|
17
|
+
free,
|
|
18
|
+
premium: { ...free, text: 25_000, videoSeconds: 3 * 3600 },
|
|
19
|
+
premium_plus: { ...free, text: 25_000, videoSeconds: 3 * 3600 },
|
|
20
|
+
};
|
|
21
|
+
const others = {
|
|
22
|
+
linkedin: {
|
|
23
|
+
text: 3000,
|
|
24
|
+
unit: "characters",
|
|
25
|
+
pictures: 20,
|
|
26
|
+
videos: 1,
|
|
27
|
+
videoSeconds: 15 * 60,
|
|
28
|
+
mixes: false,
|
|
29
|
+
pictureShape: "original",
|
|
30
|
+
hashtags: 5,
|
|
31
|
+
aiLabel: false,
|
|
32
|
+
checked: null,
|
|
33
|
+
source: "linkedin.com/help: share an update, video specifications",
|
|
34
|
+
},
|
|
35
|
+
instagram: {
|
|
36
|
+
text: 2200,
|
|
37
|
+
unit: "characters",
|
|
38
|
+
pictures: 20,
|
|
39
|
+
videos: 1,
|
|
40
|
+
videoSeconds: 15 * 60,
|
|
41
|
+
mixes: true,
|
|
42
|
+
pictureShape: "4:5",
|
|
43
|
+
hashtags: 30,
|
|
44
|
+
aiLabel: true,
|
|
45
|
+
checked: "2026-10-18",
|
|
46
|
+
source: "Instagram's Create new post dialog (caption counter 0/2,200, crop 4:5)",
|
|
47
|
+
},
|
|
48
|
+
tiktok: {
|
|
49
|
+
text: 4000,
|
|
50
|
+
unit: "characters",
|
|
51
|
+
pictures: 35,
|
|
52
|
+
videos: 1,
|
|
53
|
+
videoSeconds: 60 * 60,
|
|
54
|
+
mixes: false,
|
|
55
|
+
pictureShape: "original",
|
|
56
|
+
hashtags: 10,
|
|
57
|
+
aiLabel: true,
|
|
58
|
+
checked: "2026-10-18",
|
|
59
|
+
source: "TikTok Studio's upload page (60 min videos, 35 photos, 4000 characters)",
|
|
60
|
+
},
|
|
61
|
+
reddit: {
|
|
62
|
+
text: 40_000,
|
|
63
|
+
unit: "characters",
|
|
64
|
+
pictures: 20,
|
|
65
|
+
videos: 1,
|
|
66
|
+
videoSeconds: 15 * 60,
|
|
67
|
+
mixes: false,
|
|
68
|
+
pictureShape: "original",
|
|
69
|
+
hashtags: 0,
|
|
70
|
+
aiLabel: false,
|
|
71
|
+
checked: null,
|
|
72
|
+
source: "reddithelp.com: posting images and videos",
|
|
73
|
+
},
|
|
74
|
+
bluesky: {
|
|
75
|
+
text: 300,
|
|
76
|
+
unit: "graphemes",
|
|
77
|
+
pictures: 4,
|
|
78
|
+
videos: 1,
|
|
79
|
+
videoSeconds: 3 * 60,
|
|
80
|
+
mixes: false,
|
|
81
|
+
pictureShape: "original",
|
|
82
|
+
hashtags: 3,
|
|
83
|
+
aiLabel: false,
|
|
84
|
+
checked: "2026-10-18",
|
|
85
|
+
source: "Bluesky's composer (counter starts at 300); 4 images or one video",
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
export function limitsFor(platform, tier = "free") {
|
|
89
|
+
return platform === "x" ? xLimits[tier] : others[platform];
|
|
90
|
+
}
|
|
91
|
+
// Reddit's title limit.
|
|
92
|
+
export const redditTitleMax = 300;
|
|
93
|
+
export const subredditPattern = /^[A-Za-z0-9][A-Za-z0-9_]{1,20}$/;
|
|
94
|
+
const minute = 60_000;
|
|
95
|
+
const day = 24 * 60 * minute;
|
|
96
|
+
export const nativeSchedule = {
|
|
97
|
+
x: { minMs: 5 * minute, maxMs: 540 * day, checked: null },
|
|
98
|
+
linkedin: { minMs: 60 * minute, maxMs: 90 * day, checked: null },
|
|
99
|
+
tiktok: { minMs: 15 * minute, maxMs: 10 * day, checked: null },
|
|
100
|
+
};
|
|
101
|
+
// How long before the release time the extension fills a post on a site without a scheduler,
|
|
102
|
+
// and how late it still fills one when the browser was closed.
|
|
103
|
+
export const fillBeforeMs = 2 * minute;
|
|
104
|
+
export const lateFillMs = 12 * 60 * minute;
|
|
105
|
+
// Where each site's post form opens.
|
|
106
|
+
export const composeUrls = {
|
|
107
|
+
x: "https://x.com/compose/post",
|
|
108
|
+
linkedin: "https://www.linkedin.com/feed/?shareActive=true",
|
|
109
|
+
instagram: "https://www.instagram.com/",
|
|
110
|
+
tiktok: "https://www.tiktok.com/tiktokstudio/upload",
|
|
111
|
+
reddit: "https://www.reddit.com/submit",
|
|
112
|
+
bluesky: "https://bsky.app/",
|
|
113
|
+
};
|
|
114
|
+
// The address Fill in <site> opens: the post form with "#slopify-post=<projectId>:<platform>".
|
|
115
|
+
// The extension fills only a post form that carries this mark (or the one its alarm opens at
|
|
116
|
+
// release time, `packages/extension/src/social/messages.ts`), and only that project's post; a
|
|
117
|
+
// post form the person opens themselves is never touched.
|
|
118
|
+
export const fillMark = "slopify-post";
|
|
119
|
+
export function fillUrl(composeUrl, projectId, platform) {
|
|
120
|
+
return `${composeUrl.replace(/#.*$/, "")}#${fillMark}=${encodeURIComponent(projectId)}:${platform}`;
|
|
121
|
+
}
|
|
122
|
+
// Reddit's submit page takes the title, text and link in its address.
|
|
123
|
+
export function redditSubmitUrl(fields) {
|
|
124
|
+
const query = new URLSearchParams({ title: fields.title });
|
|
125
|
+
if (fields.text !== undefined && fields.text !== "")
|
|
126
|
+
query.set("text", fields.text);
|
|
127
|
+
if (fields.url !== undefined && fields.url !== "")
|
|
128
|
+
query.set("url", fields.url);
|
|
129
|
+
const sub = fields.subreddit === "" ? "" : `/r/${encodeURIComponent(fields.subreddit)}`;
|
|
130
|
+
return `https://www.reddit.com${sub}/submit?${query.toString()}`;
|
|
131
|
+
}
|
|
132
|
+
// A published post's link on each site, as the person pastes it or the extension reads it.
|
|
133
|
+
export const linkPatterns = {
|
|
134
|
+
x: /^https:\/\/(?:www\.)?(?:x|twitter)\.com\/[A-Za-z0-9_]{1,15}\/status\/\d+(?:[/?#].*)?$/,
|
|
135
|
+
linkedin: /^https:\/\/(?:www\.)?linkedin\.com\/(?:feed\/update\/urn:li:[A-Za-z]+:\d+|posts\/[^\s]+)\/?(?:[?#].*)?$/,
|
|
136
|
+
instagram: /^https:\/\/(?:www\.)?instagram\.com\/(?:[A-Za-z0-9_.]+\/)?(?:p|reel)\/[A-Za-z0-9_-]+\/?(?:[?#].*)?$/,
|
|
137
|
+
tiktok: /^https:\/\/(?:www\.)?tiktok\.com\/@[A-Za-z0-9_.]+\/(?:video|photo)\/\d+\/?(?:[?#].*)?$/,
|
|
138
|
+
reddit: /^https:\/\/(?:www\.|old\.|new\.)?reddit\.com\/r\/[A-Za-z0-9_]+\/comments\/[a-z0-9]+(?:\/[^\s]*)?$/,
|
|
139
|
+
bluesky: /^https:\/\/bsky\.app\/profile\/[^\s/]+\/post\/[a-z0-9]+\/?(?:[?#].*)?$/,
|
|
140
|
+
};
|
|
141
|
+
export const linkExamples = {
|
|
142
|
+
x: "https://x.com/you/status/…",
|
|
143
|
+
linkedin: "https://www.linkedin.com/feed/update/urn:li:activity:…",
|
|
144
|
+
instagram: "https://www.instagram.com/p/…",
|
|
145
|
+
tiktok: "https://www.tiktok.com/@you/video/…",
|
|
146
|
+
reddit: "https://www.reddit.com/r/…/comments/…",
|
|
147
|
+
bluesky: "https://bsky.app/profile/you.bsky.social/post/…",
|
|
148
|
+
};
|
|
149
|
+
// The account names as the channel stores them, each site's own way of writing one.
|
|
150
|
+
export const accountPatterns = {
|
|
151
|
+
x: /^@?[A-Za-z0-9_]{1,15}$/,
|
|
152
|
+
linkedin: /^(?:in|company)\/[A-Za-z0-9-_%]{2,100}$/,
|
|
153
|
+
instagram: /^@?[A-Za-z0-9_.]{1,30}$/,
|
|
154
|
+
tiktok: /^@?[A-Za-z0-9_.]{2,24}$/,
|
|
155
|
+
reddit: /^(?:u\/)?[A-Za-z0-9_-]{3,20}$/,
|
|
156
|
+
bluesky: /^@?[A-Za-z0-9.-]{3,253}$/,
|
|
157
|
+
};
|
|
158
|
+
export const accountExamples = {
|
|
159
|
+
x: "@yourname",
|
|
160
|
+
linkedin: "in/your-name",
|
|
161
|
+
instagram: "yourname",
|
|
162
|
+
tiktok: "@yourname",
|
|
163
|
+
reddit: "u/yourname",
|
|
164
|
+
bluesky: "yourname.bsky.social",
|
|
165
|
+
};
|
|
166
|
+
// The page listing the account's own posts, where the extension finds a post's link.
|
|
167
|
+
export function profileUrl(platform, accounts) {
|
|
168
|
+
const raw = accounts[platform]?.trim() ?? "";
|
|
169
|
+
if (raw === "")
|
|
170
|
+
return null;
|
|
171
|
+
const bare = raw.replace(/^@/, "").replace(/^u\//, "");
|
|
172
|
+
switch (platform) {
|
|
173
|
+
case "x":
|
|
174
|
+
return `https://x.com/${bare}`;
|
|
175
|
+
case "linkedin":
|
|
176
|
+
return `https://www.linkedin.com/${bare}/recent-activity/all/`;
|
|
177
|
+
case "instagram":
|
|
178
|
+
return `https://www.instagram.com/${bare}/`;
|
|
179
|
+
case "tiktok":
|
|
180
|
+
return `https://www.tiktok.com/@${bare}`;
|
|
181
|
+
case "reddit":
|
|
182
|
+
return `https://www.reddit.com/user/${bare}/submitted/`;
|
|
183
|
+
case "bluesky":
|
|
184
|
+
return `https://bsky.app/profile/${bare}`;
|
|
185
|
+
}
|
|
186
|
+
}
|