@gentbajko/slopify 2.4.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -21
- package/NOTICE +12 -0
- package/README.md +21 -40
- package/SUBTITLES.md +1 -1
- package/dist/adapter-registry.js +11 -1
- package/dist/adapters/alignment/cache.js +4 -1
- package/dist/adapters/alignment/ctc.js +25 -23
- package/dist/adapters/alignment/index.js +37 -6
- package/dist/adapters/alignment/multilingual.js +267 -0
- package/dist/adapters/alignment/protocol.js +11 -0
- package/dist/adapters/alignment/quality.js +4 -3
- package/dist/adapters/alignment/sentences.js +95 -0
- package/dist/adapters/alignment/spec.js +24 -0
- package/dist/adapters/alignment/spell.js +769 -0
- package/dist/adapters/alignment/text.js +56 -9
- package/dist/adapters/alignment/window.js +34 -30
- package/dist/adapters/alignment/worker.js +23 -21
- package/dist/adapters/explain.js +11 -11
- package/dist/adapters/host-cli/index.js +55 -5
- package/dist/adapters/host-cli/transport.js +13 -6
- package/dist/adapters/image/bytes.js +2 -2
- package/dist/adapters/image/codex-output.js +60 -18
- package/dist/adapters/image/codex.js +166 -25
- package/dist/adapters/image/fal.js +31 -3
- package/dist/adapters/image/google.js +17 -2
- package/dist/adapters/image/openai.js +45 -16
- package/dist/adapters/image/reference.js +41 -0
- package/dist/adapters/image/replicate.js +14 -2
- package/dist/adapters/image/video.js +2 -2
- package/dist/adapters/key-probes.js +36 -0
- package/dist/adapters/llm/claude-code.js +168 -32
- package/dist/adapters/llm/cli-login-error.js +1 -1
- package/dist/adapters/llm/codex-limits.js +157 -0
- package/dist/adapters/llm/codex-models.js +2 -1
- package/dist/adapters/llm/codex-server-models.js +2 -1
- package/dist/adapters/llm/codex.js +99 -12
- package/dist/adapters/llm/document-workspace.js +2 -2
- package/dist/adapters/llm/gemini-workspace.js +1 -1
- package/dist/adapters/llm/gemini.js +16 -3
- package/dist/adapters/llm/image-workspace.js +58 -0
- package/dist/adapters/llm/openrouter.js +11 -3
- package/dist/adapters/llm/run-cli.js +26 -9
- package/dist/adapters/tts/cartesia.js +10 -0
- package/dist/adapters/tts/elevenlabs.js +31 -9
- package/dist/adapters/tts/inworld-async.js +9 -5
- package/dist/adapters/tts/inworld.js +17 -3
- package/dist/adapters/tts/openai.js +3 -0
- package/dist/adapters/tts/voice-languages.js +80 -0
- package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/OFL.txt +93 -0
- package/dist/assets/fonts/noto/SOURCE.txt +18 -0
- package/dist/assets/models.yaml +7 -0
- package/dist/assets/sample/sample-audiobook.tar +0 -0
- package/dist/assets/sample/sample-podcast.tar +0 -0
- package/dist/assets/sample/sample-project.tar +0 -0
- package/dist/catalog/merge.js +132 -0
- package/dist/catalog/registry.js +33 -12
- package/dist/catalog/runtime-models.js +1 -1
- package/dist/catalog/schema.js +12 -1
- package/dist/catalog/store.js +147 -8
- package/dist/catalog/validate.js +17 -2
- package/dist/compose.yaml +71 -0
- package/dist/edge/autostart/docker-record.js +32 -0
- package/dist/edge/autostart/docker.js +50 -0
- package/dist/edge/autostart/index.js +78 -0
- package/dist/edge/autostart/launcher.js +176 -0
- package/dist/edge/autostart/model.js +3 -0
- package/dist/edge/autostart/native.js +210 -0
- package/dist/edge/autostart/prompt.js +66 -0
- package/dist/edge/autostart/service.js +81 -0
- package/dist/edge/cli.js +57 -51
- package/dist/edge/docker-install/activation.js +56 -0
- package/dist/edge/docker-install/apply.js +458 -0
- package/dist/edge/docker-install/engine.js +362 -0
- package/dist/edge/docker-install/run.js +154 -0
- package/dist/edge/docker-install/state.js +146 -0
- package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
- package/dist/edge/docker.js +14 -22
- package/dist/edge/events/hub.js +11 -0
- package/dist/edge/host-cli.js +1 -1
- package/dist/edge/http/actions.js +32 -10
- package/dist/edge/http/app.js +60 -2
- package/dist/edge/http/auditions.js +70 -0
- package/dist/edge/http/autostart.js +37 -0
- package/dist/edge/http/backups.js +40 -0
- package/dist/edge/http/channel-memory.js +92 -0
- package/dist/edge/http/channels.js +128 -0
- package/dist/edge/http/entries.js +22 -3
- package/dist/edge/http/files.js +1 -1
- package/dist/edge/http/folder-location.js +1 -1
- package/dist/edge/http/home.js +39 -0
- package/dist/edge/http/host-cli.js +45 -6
- package/dist/edge/http/narration-peaks.js +76 -0
- package/dist/edge/http/onboarding.js +152 -0
- package/dist/edge/http/open-folder.js +1 -1
- package/dist/edge/http/patch-notes.js +45 -0
- package/dist/edge/http/planning.js +4 -2
- package/dist/edge/http/problem.js +1 -0
- package/dist/edge/http/project-create.js +87 -0
- package/dist/edge/http/projects.js +31 -99
- package/dist/edge/http/prompts.js +40 -3
- package/dist/edge/http/pronunciations.js +23 -4
- package/dist/edge/http/providers.js +86 -0
- package/dist/edge/http/reviews.js +121 -0
- package/dist/edge/http/revision-files.js +1 -1
- package/dist/edge/http/revisions.js +1 -1
- package/dist/edge/http/run-cost.js +25 -0
- package/dist/edge/http/schedules.js +111 -6
- package/dist/edge/http/settings.js +128 -6
- package/dist/edge/http/storage-files.js +50 -0
- package/dist/edge/http/storage.js +29 -2
- package/dist/edge/http/studio.js +207 -0
- package/dist/edge/http/style-preview.js +100 -0
- package/dist/edge/http/subtitles.js +1 -1
- package/dist/edge/http/trash.js +69 -0
- package/dist/edge/http/update.js +24 -3
- package/dist/edge/http/whats-new.js +15 -0
- package/dist/edge/http/youtube-edits.js +83 -0
- package/dist/edge/native-update.js +62 -0
- package/dist/host-cli/open-folder.js +10 -10
- package/dist/host-cli/runtime.js +12 -3
- package/dist/kernel/db/migrations/0022-drop-duplicate-held-work.sql +56 -0
- package/dist/kernel/db/migrations/0024-reference-attachments.sql +18 -0
- package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
- package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
- package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
- package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
- package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
- package/dist/kernel/db/migrations/0032-channels.sql +65 -0
- package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
- package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
- package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
- package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
- package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
- package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
- package/dist/kernel/paths.js +27 -6
- package/dist/kernel/pipeline.js +4 -1
- package/dist/kernel/ports/host-cli.js +139 -16
- package/dist/kernel/ports/image.js +3 -1
- package/dist/kernel/ports/key-probe.js +1 -0
- package/dist/kernel/ports/languages.js +157 -0
- package/dist/kernel/ports/llm.js +3 -1
- package/dist/kernel/ports/model.js +5 -2
- package/dist/kernel/ports/narration-aliases.js +60 -0
- package/dist/kernel/ports/plan-limits.js +36 -0
- package/dist/kernel/runner/attempt.js +18 -9
- package/dist/kernel/runner/audition.js +36 -0
- package/dist/kernel/runner/graph.js +24 -5
- package/dist/kernel/runner/index.js +35 -5
- package/dist/kernel/runner/meter.js +1 -0
- package/dist/kernel/runner/providers.js +164 -9
- package/dist/kernel/runner/retry-policy.js +43 -0
- package/dist/kernel/runner/standalone.js +134 -0
- package/dist/kernel/runner/work-authority.js +28 -2
- package/dist/main.js +321 -20
- package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
- package/dist/patch-notes/1.0-to-2.0.md +74 -0
- package/dist/patch-notes/2.0-to-3.0.md +257 -0
- package/dist/patch-notes/3.0.0.md +70 -0
- package/dist/patch-notes/index.json +26 -0
- package/dist/slices/admission/model.js +35 -0
- package/dist/slices/admission/repo.js +24 -4
- package/dist/slices/admission/rules.js +126 -1
- package/dist/slices/admission/schema.js +68 -1
- package/dist/slices/admission/short-mode.js +47 -0
- package/dist/slices/admission/start.js +90 -16
- package/dist/slices/article/continuation.js +2 -2
- package/dist/slices/article/provided-entries.js +2 -2
- package/dist/slices/article/run.js +6 -2
- package/dist/slices/article/segments.js +3 -3
- package/dist/slices/backups/files.js +135 -0
- package/dist/slices/backups/folder.js +43 -0
- package/dist/slices/backups/model.js +65 -0
- package/dist/slices/backups/repo.js +28 -0
- package/dist/slices/backups/schedule.js +60 -0
- package/dist/slices/backups/service.js +311 -0
- package/dist/slices/batch/index.js +9 -2
- package/dist/slices/cancel/index.js +1 -1
- package/dist/slices/channels/cast-images.js +110 -0
- package/dist/slices/channels/cast-match.js +32 -0
- package/dist/slices/channels/images.js +19 -0
- package/dist/slices/channels/model.js +5 -0
- package/dist/slices/channels/rebrand.js +100 -0
- package/dist/slices/channels/repo.js +218 -0
- package/dist/slices/channels/runs.js +129 -0
- package/dist/slices/channels/schema.js +129 -0
- package/dist/slices/channels/service.js +180 -0
- package/dist/slices/channels/studio-csv.js +110 -0
- package/dist/slices/channels/videos.js +120 -0
- package/dist/slices/checkpoints/recovery.js +10 -0
- package/dist/slices/control/index.js +2 -1
- package/dist/slices/document/fonts.js +1 -1
- package/dist/slices/document/render.js +1 -1
- package/dist/slices/episodes/related.js +90 -0
- package/dist/slices/episodes/repo.js +60 -0
- package/dist/slices/episodes/service.js +58 -0
- package/dist/slices/episodes/summarize.js +170 -0
- package/dist/slices/estimate/index.js +113 -24
- package/dist/slices/estimate/requests.js +60 -2
- package/dist/slices/fixes/rules.js +73 -0
- package/dist/slices/fonts/coverage.js +82 -0
- package/dist/slices/fonts/sfnt.js +74 -0
- package/dist/slices/images/run.js +8 -5
- package/dist/slices/images/scale.js +84 -0
- package/dist/slices/library/diff.js +122 -0
- package/dist/slices/library/history.js +100 -0
- package/dist/slices/library/model.js +3 -0
- package/dist/slices/library/photorealistic.js +49 -0
- package/dist/slices/library/repo.js +19 -14
- package/dist/slices/library/save.js +62 -12
- package/dist/slices/library/slots.js +15 -3
- package/dist/slices/library/snapshot.js +8 -2
- package/dist/slices/library/used-by.js +153 -0
- package/dist/slices/loudness/level-pieces.js +41 -0
- package/dist/slices/loudness/loudnorm.js +164 -0
- package/dist/slices/loudness/model.js +153 -0
- package/dist/slices/model-upkeep/model.js +10 -0
- package/dist/slices/model-upkeep/switch.js +217 -0
- package/dist/slices/model-upkeep/usage.js +183 -0
- package/dist/slices/narration/aliases-library.js +31 -0
- package/dist/slices/narration/aliases-schema.js +53 -0
- package/dist/slices/narration/aliases.js +49 -0
- package/dist/slices/narration/blocks.js +352 -0
- package/dist/slices/narration/chunk.js +19 -7
- package/dist/slices/narration/concat.js +2 -2
- package/dist/slices/narration/describe.js +45 -0
- package/dist/slices/narration/pauses-model.js +63 -0
- package/dist/slices/narration/pauses.js +269 -0
- package/dist/slices/narration/peaks-model.js +1 -0
- package/dist/slices/narration/peaks.js +94 -0
- package/dist/slices/narration/preparation.js +21 -3
- package/dist/slices/narration/pronunciation-chunks.js +3 -3
- package/dist/slices/narration/pronunciation.js +72 -25
- package/dist/slices/narration/run.js +6 -6
- package/dist/slices/narration/shared-glossary.js +1 -1
- package/dist/slices/narration/steering.js +15 -4
- package/dist/slices/notifications/notifier.js +94 -0
- package/dist/slices/notifications/rules.js +102 -0
- package/dist/slices/notifications/send.js +40 -0
- package/dist/slices/notifications/settings.js +34 -0
- package/dist/slices/onboarding/copy.js +143 -0
- package/dist/slices/onboarding/first-run.js +46 -0
- package/dist/slices/onboarding/install.js +119 -0
- package/dist/slices/onboarding/model.js +64 -0
- package/dist/slices/onboarding/packs.js +491 -0
- package/dist/slices/onboarding/quick-short.js +129 -0
- package/dist/slices/onboarding/sample.js +132 -0
- package/dist/slices/onboarding/state.js +68 -0
- package/dist/slices/onboarding/template.js +73 -0
- package/dist/slices/patch-notes/library.js +73 -0
- package/dist/slices/patch-notes/seen.js +77 -0
- package/dist/slices/play-drafts/convert.js +160 -9
- package/dist/slices/play-drafts/readiness.js +3 -0
- package/dist/slices/play-drafts/repo.js +9 -1
- package/dist/slices/play-drafts/review-inputs.js +22 -3
- package/dist/slices/play-drafts/schema.js +109 -2
- package/dist/slices/play-drafts/service.js +10 -1
- package/dist/slices/play-drafts/start.js +43 -18
- package/dist/slices/play-drafts/uploads.js +2 -1
- package/dist/slices/project-templates/from-project.js +70 -4
- package/dist/slices/project-templates/one-off.js +26 -0
- package/dist/slices/project-templates/repo.js +2 -1
- package/dist/slices/project-templates/service.js +15 -4
- package/dist/slices/project-templates/setup.js +29 -2
- package/dist/slices/rebuild/admission-repo.js +1 -1
- package/dist/slices/rebuild/model.js +6 -0
- package/dist/slices/rebuild/preview-details.js +67 -2
- package/dist/slices/rebuild/preview-plan.js +13 -6
- package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
- package/dist/slices/rebuild/recipe-audio.js +66 -12
- package/dist/slices/rebuild/recipe-build.js +15 -5
- package/dist/slices/rebuild/recipe-cast.js +18 -0
- package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
- package/dist/slices/rebuild/recipe-describe.js +93 -0
- package/dist/slices/rebuild/recipe-edit.js +13 -1
- package/dist/slices/rebuild/recipe-exports.js +62 -8
- package/dist/slices/rebuild/recipe-input-schema.js +43 -1
- package/dist/slices/rebuild/recipe-legacy.js +12 -0
- package/dist/slices/rebuild/recipe-loudness.js +45 -0
- package/dist/slices/rebuild/recipe-model.js +25 -6
- package/dist/slices/rebuild/recipe-pauses.js +7 -0
- package/dist/slices/rebuild/recipe-preparation.js +3 -3
- package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
- package/dist/slices/rebuild/recipe-reference.js +58 -0
- package/dist/slices/rebuild/recipe-reviews.js +117 -0
- package/dist/slices/rebuild/recipe-save.js +4 -1
- package/dist/slices/rebuild/recipe-shorts.js +32 -5
- package/dist/slices/rebuild/recipe-text.js +114 -19
- package/dist/slices/rebuild/recipe-validation.js +29 -3
- package/dist/slices/rebuild/recipe-visual.js +104 -29
- package/dist/slices/rebuild/recipe-voices.js +286 -0
- package/dist/slices/rebuild/recipe-work.js +77 -6
- package/dist/slices/rebuild/recipe-youtube.js +2 -0
- package/dist/slices/rebuild/recovery-conflict.js +7 -3
- package/dist/slices/rebuild/recovery-model.js +3 -0
- package/dist/slices/rebuild/recovery-selection.js +28 -2
- package/dist/slices/rebuild/recovery.js +35 -17
- package/dist/slices/rebuild/repo.js +3 -3
- package/dist/slices/rebuild/retry.js +18 -0
- package/dist/slices/rebuild/review-redo.js +110 -0
- package/dist/slices/rebuild/runtime-actions.js +14 -2
- package/dist/slices/rebuild/runtime-admission.js +4 -1
- package/dist/slices/rebuild/runtime-animate.js +4 -4
- package/dist/slices/rebuild/runtime-article.js +5 -5
- package/dist/slices/rebuild/runtime-document.js +4 -4
- package/dist/slices/rebuild/runtime-export-bed.js +43 -0
- package/dist/slices/rebuild/runtime-export-edit.js +70 -11
- package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
- package/dist/slices/rebuild/runtime-export-short.js +85 -0
- package/dist/slices/rebuild/runtime-export.js +84 -19
- package/dist/slices/rebuild/runtime-figure-card.js +87 -0
- package/dist/slices/rebuild/runtime-image.js +69 -0
- package/dist/slices/rebuild/runtime-local.js +154 -27
- package/dist/slices/rebuild/runtime-materialize.js +8 -0
- package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
- package/dist/slices/rebuild/runtime-narration-text.js +7 -7
- package/dist/slices/rebuild/runtime-pictures.js +28 -0
- package/dist/slices/rebuild/runtime-piece-label.js +29 -0
- package/dist/slices/rebuild/runtime-provider.js +134 -19
- package/dist/slices/rebuild/runtime-publication.js +21 -5
- package/dist/slices/rebuild/runtime-review.js +237 -0
- package/dist/slices/rebuild/runtime-run.js +17 -11
- package/dist/slices/rebuild/runtime-shorts.js +48 -23
- package/dist/slices/rebuild/runtime-store.js +39 -10
- package/dist/slices/rebuild/runtime-subtitles.js +72 -19
- package/dist/slices/rebuild/runtime-voices.js +103 -0
- package/dist/slices/rebuild/runtime-youtube.js +7 -6
- package/dist/slices/rebuild/service-readiness.js +15 -1
- package/dist/slices/rebuild/soften.js +49 -0
- package/dist/slices/rebuild/transition-repo.js +10 -3
- package/dist/slices/research/run.js +4 -4
- package/dist/slices/research/synthesis.js +2 -2
- package/dist/slices/reviews/model.js +105 -0
- package/dist/slices/reviews/outcome.js +22 -0
- package/dist/slices/reviews/repo.js +106 -0
- package/dist/slices/reviews/rules.js +63 -0
- package/dist/slices/reviews/verdict.js +71 -0
- package/dist/slices/reviews/view.js +30 -0
- package/dist/slices/revisions/adopt-content.js +12 -2
- package/dist/slices/revisions/adopt.js +9 -2
- package/dist/slices/revisions/downloads.js +7 -1
- package/dist/slices/revisions/image-plan.js +183 -0
- package/dist/slices/revisions/mutation-assets.js +43 -9
- package/dist/slices/revisions/mutation-cues.js +71 -0
- package/dist/slices/revisions/mutation-prepare.js +8 -10
- package/dist/slices/revisions/mutation-request.js +2 -1
- package/dist/slices/revisions/mutations.js +97 -3
- package/dist/slices/revisions/schema.js +16 -2
- package/dist/slices/run-cost/limits.js +128 -0
- package/dist/slices/run-cost/meter.js +47 -0
- package/dist/slices/run-cost/panel.js +234 -0
- package/dist/slices/run-cost/pricing.js +92 -0
- package/dist/slices/run-cost/week.js +108 -0
- package/dist/slices/schedules/agenda.js +158 -0
- package/dist/slices/schedules/repo.js +58 -18
- package/dist/slices/schedules/scheduler.js +67 -16
- package/dist/slices/schedules/schema.js +138 -1
- package/dist/slices/schedules/service.js +48 -4
- package/dist/slices/schedules/similar.js +90 -0
- package/dist/slices/schedules/topic-list.js +163 -0
- package/dist/slices/schedules/topics.js +422 -0
- package/dist/slices/settings/first-run.js +102 -0
- package/dist/slices/settings/health.js +190 -0
- package/dist/slices/settings/key-guides.js +128 -0
- package/dist/slices/settings/key-test.js +85 -0
- package/dist/slices/settings/model.js +11 -1
- package/dist/slices/settings/playback.js +15 -0
- package/dist/slices/settings/repo.js +17 -3
- package/dist/slices/settings/voices.js +64 -4
- package/dist/slices/settings/whats-new.js +64 -0
- package/dist/slices/shorts/captions.js +9 -0
- package/dist/slices/shorts/pick.js +3 -3
- package/dist/slices/shorts/prompts.js +1 -1
- package/dist/slices/shorts/render.js +54 -5
- package/dist/slices/storage/asset-name.js +3 -0
- package/dist/slices/storage/backup-export.js +48 -9
- package/dist/slices/storage/backup-format.js +38 -1
- package/dist/slices/storage/backup-import.js +191 -19
- package/dist/slices/storage/delete-project.js +7 -4
- package/dist/slices/storage/documents.js +79 -0
- package/dist/slices/storage/files-location.js +531 -0
- package/dist/slices/storage/layout.js +20 -0
- package/dist/slices/storage/model.js +14 -0
- package/dist/slices/storage/portable.js +95 -12
- package/dist/slices/storage/reconcile.js +19 -2
- package/dist/slices/storage/repo.js +4 -1
- package/dist/slices/storage/schema.js +11 -0
- package/dist/slices/storage/trim.js +146 -0
- package/dist/slices/studio/disclosure.js +74 -0
- package/dist/slices/studio/model.js +28 -0
- package/dist/slices/studio/pack.js +209 -0
- package/dist/slices/studio/settings.js +133 -0
- package/dist/slices/style-preview/images.js +61 -0
- package/dist/slices/style-preview/render.js +161 -0
- package/dist/slices/style-preview/schema.js +45 -0
- package/dist/slices/style-preview/service.js +93 -0
- package/dist/slices/style-preview/settings.js +89 -0
- package/dist/slices/subtitles/captions.js +42 -9
- package/dist/slices/subtitles/model.js +22 -0
- package/dist/slices/subtitles/prepare.js +27 -16
- package/dist/slices/subtitles/transcript.js +1 -1
- package/dist/slices/thumbnail/run.js +7 -7
- package/dist/slices/trash/model.js +25 -0
- package/dist/slices/trash/service.js +273 -0
- package/dist/slices/uploads/repo.js +20 -0
- package/dist/slices/video/ambient-bed-schema.js +15 -0
- package/dist/slices/video/ambient-bed.js +95 -0
- package/dist/slices/video/ambient-mix.js +72 -0
- package/dist/slices/video/audio-inputs.js +2 -2
- package/dist/slices/video/cards.js +5 -2
- package/dist/slices/video/edit-list.js +23 -2
- package/dist/slices/video/edit-settings.js +13 -3
- package/dist/slices/video/ffmpeg.js +63 -9
- package/dist/slices/video/figure-card.js +492 -0
- package/dist/slices/video/figure-spans.js +51 -0
- package/dist/slices/video/plan.js +125 -13
- package/dist/slices/video/run.js +14 -12
- package/dist/slices/video/slideshow.js +39 -4
- package/dist/slices/voices/attribution.js +69 -0
- package/dist/slices/voices/audio-files.js +82 -0
- package/dist/slices/voices/audition.js +25 -0
- package/dist/slices/voices/cast.js +56 -0
- package/dist/slices/voices/grouping.js +49 -0
- package/dist/slices/voices/join.js +35 -0
- package/dist/slices/voices/languages.js +25 -0
- package/dist/slices/voices/model.js +179 -0
- package/dist/slices/voices/palette.js +26 -0
- package/dist/slices/voices/panel.js +91 -0
- package/dist/slices/voices/portraits.js +55 -0
- package/dist/slices/voices/script.js +136 -0
- package/dist/slices/voices/timing.js +58 -0
- package/dist/slices/youtube/answer.js +1 -1
- package/dist/slices/youtube/chapters.js +126 -0
- package/dist/slices/youtube/edits-repo.js +69 -0
- package/dist/slices/youtube/edits.js +89 -0
- package/dist/slices/youtube/placeholders.js +98 -0
- package/dist/slices/youtube/transcript.js +11 -4
- package/dist/updater/service.js +66 -21
- package/dist/web/assets/index-CUA2sUxU.css +1 -0
- package/dist/web/assets/index-CX590kE-.js +303 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/web/assets/{pdf-CAJNT254.js → pdf-BG8n_uc5.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +4 -4
- package/dist/edge/docker-launch.js +0 -81
- package/dist/edge/docker-projects/activation.js +0 -35
- package/dist/edge/docker-projects/claims.js +0 -85
- package/dist/edge/docker-projects/committed.js +0 -40
- package/dist/edge/docker-projects/engine.js +0 -461
- package/dist/edge/docker-projects/install.js +0 -254
- package/dist/edge/docker-projects/prune.js +0 -66
- package/dist/edge/docker-projects/recover.js +0 -100
- package/dist/edge/docker-projects/state.js +0 -225
- package/dist/web/assets/index-BGgYMm2u.css +0 -1
- package/dist/web/assets/index-BVVgtntL.js +0 -171
- package/scripts/docker-run.sh +0 -3
- /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
package/dist/catalog/schema.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { thinkingModes } from "../kernel/ports/llm.js";
|
|
2
3
|
import { providers } from "../slices/settings/model.js";
|
|
3
4
|
const money = z.number().finite().nonnegative();
|
|
4
5
|
const base = z.object({
|
|
@@ -13,6 +14,8 @@ const base = z.object({
|
|
|
13
14
|
.object({
|
|
14
15
|
inputPerMillionTokens: money.optional(),
|
|
15
16
|
outputPerMillionTokens: money.optional(),
|
|
17
|
+
// Input read from the provider's prompt cache. Absent, cached input is priced as input.
|
|
18
|
+
cachedInputPerMillionTokens: money.optional(),
|
|
16
19
|
perMillionCharacters: money.optional(),
|
|
17
20
|
perImage: money.optional(),
|
|
18
21
|
perMinute: money.optional(),
|
|
@@ -29,7 +32,7 @@ export const llmModelSchema = base
|
|
|
29
32
|
maxOutputTokens: z.number().int().positive().optional(),
|
|
30
33
|
webSearch: z.boolean().default(false),
|
|
31
34
|
thinking: z
|
|
32
|
-
.partialRecord(z.enum(
|
|
35
|
+
.partialRecord(z.enum(thinkingModes), z
|
|
33
36
|
.object({
|
|
34
37
|
budget: z.number().int().min(-1).optional(),
|
|
35
38
|
level: z.enum(["minimal", "low", "medium", "high"]).optional(),
|
|
@@ -95,6 +98,14 @@ export const catalogueSchema = z
|
|
|
95
98
|
export function isVideoModel(model) {
|
|
96
99
|
return model.keywords.includes("video");
|
|
97
100
|
}
|
|
101
|
+
// An image model that also takes an input image - so it can draw with the project's
|
|
102
|
+
// establishing image as its visual reference - carries the `reference` keyword, the same way
|
|
103
|
+
// and for the same reason the image-to-video models carry `video`.
|
|
104
|
+
export function takesReferenceImage(model) {
|
|
105
|
+
return model.keywords.includes("reference");
|
|
106
|
+
}
|
|
107
|
+
// What the run says when the chosen image model cannot take the establishing image.
|
|
108
|
+
export const referenceRefusal = "The chosen image model can't use an establishing image as a reference, so the images can't be drawn to match it. Choose an image model that can (Codex CLI, OpenAI, Google, or a fal.ai model listed with reference) under Images → Model on Play or in Edit project → Providers, or set Establishing image to Off in the Images section.";
|
|
98
109
|
// The image-to-video models a provider offers, as Animate images lists them.
|
|
99
110
|
export function videoModelsOf(catalogue, provider) {
|
|
100
111
|
return catalogue.image.filter((m) => m.provider === provider && m.enabled && !m.deprecated && isVideoModel(m));
|
package/dist/catalog/store.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { parse } from "yaml";
|
|
3
|
+
import { parse, stringify } from "yaml";
|
|
4
4
|
import { isLocalCliProvider } from "../slices/settings/model.js";
|
|
5
|
+
import { applyOpenRouterListing, catalogueFamilies, combineChanges, mergeCatalogue, modelKey, noChanges, } from "./merge.js";
|
|
5
6
|
import { catalogueSchema, isVideoModel } from "./schema.js";
|
|
6
7
|
export const catalogueSource = "https://raw.githubusercontent.com/GentBajko/slopify/main/packages/app/src/assets/models.yaml";
|
|
8
|
+
// OpenRouter's public model list: live ids and per-token prices, no key needed.
|
|
9
|
+
export const openRouterModelsSource = "https://openrouter.ai/api/v1/models";
|
|
10
|
+
// How often the automatic check runs; it also runs once at every start.
|
|
11
|
+
export const catalogueCheckIntervalMs = 24 * 60 * 60 * 1000;
|
|
7
12
|
export function parseCatalogue(text) {
|
|
8
13
|
if (Buffer.byteLength(text) > 1024 * 1024)
|
|
9
14
|
throw new Error("Model catalogue exceeds 1 MB");
|
|
@@ -26,6 +31,9 @@ export function createCatalogueStore(deps) {
|
|
|
26
31
|
let stamp = "";
|
|
27
32
|
let warning = null;
|
|
28
33
|
let refreshing;
|
|
34
|
+
let syncing;
|
|
35
|
+
const syncPath = join(deps.dataDir, "models-sync.json");
|
|
36
|
+
const now = deps.now ?? (() => new Date());
|
|
29
37
|
function read() {
|
|
30
38
|
try {
|
|
31
39
|
const file = statSync(path);
|
|
@@ -40,12 +48,12 @@ export function createCatalogueStore(deps) {
|
|
|
40
48
|
}
|
|
41
49
|
catch {
|
|
42
50
|
warning =
|
|
43
|
-
"Your models.yaml file is missing or has a mistake, so Slopify is still using the last working model list. Fix the file, or use
|
|
51
|
+
"Your models.yaml file is missing or has a mistake, so Slopify is still using the last working model list. Fix the file, or use Replace with published file in Settings → Models.";
|
|
44
52
|
}
|
|
45
53
|
return current;
|
|
46
54
|
}
|
|
47
|
-
async function
|
|
48
|
-
const response = await deps.fetch(
|
|
55
|
+
async function download(url, maxBytes) {
|
|
56
|
+
const response = await deps.fetch(url, {
|
|
49
57
|
signal: AbortSignal.timeout(15000),
|
|
50
58
|
redirect: "error",
|
|
51
59
|
});
|
|
@@ -62,8 +70,8 @@ export function createCatalogueStore(deps) {
|
|
|
62
70
|
if (next.done)
|
|
63
71
|
break;
|
|
64
72
|
size += next.value.byteLength;
|
|
65
|
-
if (size >
|
|
66
|
-
throw new Error("
|
|
73
|
+
if (size > maxBytes)
|
|
74
|
+
throw new Error("Download is too large");
|
|
67
75
|
chunks.push(next.value);
|
|
68
76
|
}
|
|
69
77
|
}
|
|
@@ -71,7 +79,10 @@ export function createCatalogueStore(deps) {
|
|
|
71
79
|
await reader.cancel().catch(() => { });
|
|
72
80
|
reader.releaseLock();
|
|
73
81
|
}
|
|
74
|
-
|
|
82
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
83
|
+
}
|
|
84
|
+
async function refresh() {
|
|
85
|
+
const text = await download(catalogueSource, 1024 * 1024);
|
|
75
86
|
const next = parseCatalogue(text);
|
|
76
87
|
mkdirSync(deps.dataDir, { recursive: true, mode: 0o700 });
|
|
77
88
|
// A manual refresh replaces the local catalogue; retain the previous text for recovery.
|
|
@@ -84,10 +95,91 @@ export function createCatalogueStore(deps) {
|
|
|
84
95
|
warning = null;
|
|
85
96
|
read();
|
|
86
97
|
}
|
|
98
|
+
function readSync() {
|
|
99
|
+
try {
|
|
100
|
+
const parsed = JSON.parse(readFileSync(syncPath, "utf8"));
|
|
101
|
+
if (isSyncFile(parsed))
|
|
102
|
+
return parsed;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// Absent or damaged: the first check starts from the bundled list below.
|
|
106
|
+
}
|
|
107
|
+
return { checkedAt: null, known: null, changes: noChanges, warning: null };
|
|
108
|
+
}
|
|
109
|
+
function writeSync(value) {
|
|
110
|
+
writeFileSync(`${syncPath}.next`, JSON.stringify(value), { mode: 0o600 });
|
|
111
|
+
renameSync(`${syncPath}.next`, syncPath);
|
|
112
|
+
}
|
|
113
|
+
async function openRouterListing() {
|
|
114
|
+
try {
|
|
115
|
+
return parseOpenRouterListing(JSON.parse(await download(openRouterModelsSource, 16 * 1024 * 1024)));
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Optional: the published catalogue still carries OpenRouter's curated prices.
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function sync() {
|
|
123
|
+
const saved = readSync();
|
|
124
|
+
const local = read();
|
|
125
|
+
if (warning !== null) {
|
|
126
|
+
// Never overwrite a file the user is in the middle of fixing.
|
|
127
|
+
writeSync({ ...saved, warning });
|
|
128
|
+
return { checkedAt: saved.checkedAt, changes: saved.changes, warning };
|
|
129
|
+
}
|
|
130
|
+
let published;
|
|
131
|
+
try {
|
|
132
|
+
published = parseCatalogue(await download(catalogueSource, 1024 * 1024));
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
const failed = "Slopify could not download the latest model list, so it is still using the one it has. Check your internet connection, then choose Check now in Settings → Models.";
|
|
136
|
+
writeSync({ ...saved, warning: failed });
|
|
137
|
+
return { checkedAt: saved.checkedAt, changes: saved.changes, warning: failed };
|
|
138
|
+
}
|
|
139
|
+
// Before the first check the models shipped with this version count as published, so a
|
|
140
|
+
// model the user added to the file is never mistaken for a retired one.
|
|
141
|
+
const known = new Set(saved.known ?? keysOf(parseCatalogue(bundled)));
|
|
142
|
+
const merged = mergeCatalogue(local, published, known);
|
|
143
|
+
const live = applyOpenRouterListing(merged.catalogue, await openRouterListing());
|
|
144
|
+
const changes = combineChanges(merged.changes, live.changes);
|
|
145
|
+
const text = `${syncedHeader}${stringify(live.catalogue, { aliasDuplicateObjects: false })}`;
|
|
146
|
+
const next = parseCatalogue(text);
|
|
147
|
+
if (JSON.stringify(next) !== JSON.stringify(local)) {
|
|
148
|
+
if (existsSync(path))
|
|
149
|
+
writeFileSync(`${path}.previous`, readFileSync(path), { mode: 0o600 });
|
|
150
|
+
writeFileSync(`${path}.next`, text, { mode: 0o600 });
|
|
151
|
+
renameSync(`${path}.next`, path);
|
|
152
|
+
current = next;
|
|
153
|
+
stamp = "";
|
|
154
|
+
read();
|
|
155
|
+
}
|
|
156
|
+
const status = { checkedAt: now().toISOString(), changes, warning: null };
|
|
157
|
+
writeSync({ ...status, known: [...merged.known].sort() });
|
|
158
|
+
return status;
|
|
159
|
+
}
|
|
87
160
|
return {
|
|
161
|
+
sync: () => {
|
|
162
|
+
syncing ??= sync().finally(() => {
|
|
163
|
+
syncing = undefined;
|
|
164
|
+
});
|
|
165
|
+
return syncing;
|
|
166
|
+
},
|
|
167
|
+
syncDue: (at) => {
|
|
168
|
+
const last = readSync().checkedAt;
|
|
169
|
+
return last === null || at - Date.parse(last) >= catalogueCheckIntervalMs;
|
|
170
|
+
},
|
|
88
171
|
read,
|
|
89
172
|
models: (provider, family) => read()[family].filter((m) => m.provider === provider && m.enabled && !m.deprecated && !isVideoModel(m)),
|
|
90
|
-
status: () =>
|
|
173
|
+
status: () => {
|
|
174
|
+
const saved = readSync();
|
|
175
|
+
return {
|
|
176
|
+
updatedAt: read().updatedAt,
|
|
177
|
+
path,
|
|
178
|
+
warning,
|
|
179
|
+
source: catalogueSource,
|
|
180
|
+
sync: { checkedAt: saved.checkedAt, changes: saved.changes, warning: saved.warning },
|
|
181
|
+
};
|
|
182
|
+
},
|
|
91
183
|
refresh: () => {
|
|
92
184
|
refreshing ??= refresh().finally(() => {
|
|
93
185
|
refreshing = undefined;
|
|
@@ -96,3 +188,50 @@ export function createCatalogueStore(deps) {
|
|
|
96
188
|
},
|
|
97
189
|
};
|
|
98
190
|
}
|
|
191
|
+
const syncedHeader = "# Slopify's model list, kept up to date automatically from the published catalogue.\n" +
|
|
192
|
+
"# Your edits are kept; retired models stay listed with deprecated: true.\n";
|
|
193
|
+
function isSyncFile(value) {
|
|
194
|
+
if (typeof value !== "object" || value === null)
|
|
195
|
+
return false;
|
|
196
|
+
const row = value;
|
|
197
|
+
const changes = row.changes;
|
|
198
|
+
return ((row.checkedAt === null || typeof row.checkedAt === "string") &&
|
|
199
|
+
(row.known === null || Array.isArray(row.known)) &&
|
|
200
|
+
(row.warning === null || typeof row.warning === "string") &&
|
|
201
|
+
typeof changes === "object" &&
|
|
202
|
+
changes !== null &&
|
|
203
|
+
Array.isArray(changes.added) &&
|
|
204
|
+
Array.isArray(changes.retired) &&
|
|
205
|
+
Array.isArray(changes.priced));
|
|
206
|
+
}
|
|
207
|
+
function keysOf(catalogue) {
|
|
208
|
+
return catalogueFamilies.flatMap((family) => catalogue[family].map((row) => modelKey(family, row.provider, row.id)));
|
|
209
|
+
}
|
|
210
|
+
// `{ data: [{ id, pricing: { prompt, completion } }] }`, prices in USD per token as strings.
|
|
211
|
+
// Rows that do not read cleanly are skipped; a negative price means "varies" and is ignored.
|
|
212
|
+
export function parseOpenRouterListing(value) {
|
|
213
|
+
if (typeof value !== "object" || value === null)
|
|
214
|
+
return [];
|
|
215
|
+
const data = value.data;
|
|
216
|
+
if (!Array.isArray(data))
|
|
217
|
+
return [];
|
|
218
|
+
const price = (raw) => {
|
|
219
|
+
const number = typeof raw === "string" || typeof raw === "number" ? Number(raw) : Number.NaN;
|
|
220
|
+
return Number.isFinite(number) && number >= 0 ? number : undefined;
|
|
221
|
+
};
|
|
222
|
+
const rows = [];
|
|
223
|
+
for (const row of data) {
|
|
224
|
+
if (typeof row !== "object" || row === null)
|
|
225
|
+
continue;
|
|
226
|
+
const { id, pricing } = row;
|
|
227
|
+
if (typeof id !== "string" || id === "")
|
|
228
|
+
continue;
|
|
229
|
+
const prices = typeof pricing === "object" && pricing !== null ? pricing : {};
|
|
230
|
+
rows.push({
|
|
231
|
+
id,
|
|
232
|
+
promptPerToken: price(prices.prompt),
|
|
233
|
+
completionPerToken: price(prices.completion),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return rows;
|
|
237
|
+
}
|
package/dist/catalog/validate.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { usesNarrationPreparation, usesShorts, usesYoutubeDescription, } from "../slices/admission/rules.js";
|
|
1
|
+
import { usesNarrationPreparation, usesReference, usesShorts, usesYoutubeDescription, } from "../slices/admission/rules.js";
|
|
2
2
|
import { isLocalCliProvider } from "../slices/settings/model.js";
|
|
3
3
|
import { usesAnimation } from "../slices/video/edit-settings.js";
|
|
4
|
-
import { videoModelsOf } from "./schema.js";
|
|
4
|
+
import { referenceRefusal, takesReferenceImage, videoModelsOf } from "./schema.js";
|
|
5
5
|
export function modelFields(draft, catalogue) {
|
|
6
6
|
if (!catalogue)
|
|
7
7
|
return [];
|
|
@@ -45,6 +45,11 @@ export function modelFields(draft, catalogue) {
|
|
|
45
45
|
field,
|
|
46
46
|
message: "This model does not support that thinking setting. Choose another.",
|
|
47
47
|
});
|
|
48
|
+
else if ("image" in model && choice.thinking)
|
|
49
|
+
fields.push({
|
|
50
|
+
field: "images.thinking",
|
|
51
|
+
message: "This image model has no effort setting. Set Effort back to Model default.",
|
|
52
|
+
});
|
|
48
53
|
else if ("llm" in model && draft.sources.research === "generate" && !model.llm.webSearch)
|
|
49
54
|
fields.push({
|
|
50
55
|
field,
|
|
@@ -65,6 +70,16 @@ export function modelFields(draft, catalogue) {
|
|
|
65
70
|
message: "This image model cannot make the vertical (9:16) images Shorts need. Choose another image model, or turn Shorts off.",
|
|
66
71
|
});
|
|
67
72
|
}
|
|
73
|
+
// The establishing image needs a model that takes an input image; the catalogue says which.
|
|
74
|
+
// The Codex CLI always can, so only the keyed providers are looked up.
|
|
75
|
+
if (usesReference(draft) &&
|
|
76
|
+
draft.images !== undefined &&
|
|
77
|
+
!isLocalCliProvider(draft.images.provider)) {
|
|
78
|
+
const images = draft.images;
|
|
79
|
+
const model = catalogue.models(images.provider, "image").find((m) => m.id === images.model);
|
|
80
|
+
if (model !== undefined && !takesReferenceImage(model))
|
|
81
|
+
fields.push({ field: "reference.source", message: referenceRefusal });
|
|
82
|
+
}
|
|
68
83
|
// Animate images runs on the image provider's image-to-video models.
|
|
69
84
|
if (usesAnimation(draft) && draft.images !== undefined) {
|
|
70
85
|
const model = draft.videoEdit?.animateModel ?? "";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Slopify on Docker: one service, one private data volume, one host folder for project files.
|
|
2
|
+
#
|
|
3
|
+
# `npx @gentbajko/slopify --docker` copies this file to ~/.local/share/slopify/docker/<name>/
|
|
4
|
+
# and writes the .env beside it, then runs `docker compose up`. You can also run it by hand; see
|
|
5
|
+
# docs/docker.md for the variables. The data volume is external on purpose: `docker compose down`
|
|
6
|
+
# (even with -v) never deletes it.
|
|
7
|
+
name: ${SLOPIFY_NAME:-slopify}
|
|
8
|
+
|
|
9
|
+
services:
|
|
10
|
+
slopify:
|
|
11
|
+
image: ${SLOPIFY_IMAGE:-ghcr.io/gentbajko/slopify:latest}
|
|
12
|
+
container_name: ${SLOPIFY_NAME:-slopify}
|
|
13
|
+
# Your own user id, so project files on the host belong to you (0:0 under rootless Docker).
|
|
14
|
+
user: ${SLOPIFY_USER:-1000:1000}
|
|
15
|
+
restart: unless-stopped
|
|
16
|
+
# Localhost only: anyone who reaches this port controls the app and its provider keys.
|
|
17
|
+
# An empty SLOPIFY_PORT picks a free port.
|
|
18
|
+
ports:
|
|
19
|
+
- 127.0.0.1:${SLOPIFY_PORT-6969}:6969
|
|
20
|
+
volumes:
|
|
21
|
+
- type: volume
|
|
22
|
+
source: data
|
|
23
|
+
target: /data
|
|
24
|
+
- type: bind
|
|
25
|
+
source: ${SLOPIFY_PROJECTS_DIR:?set SLOPIFY_PROJECTS_DIR to the folder for your project files}
|
|
26
|
+
target: /data/projects
|
|
27
|
+
# Scheduled backups' default folder, beside Projects (new installs from 3.0). Without
|
|
28
|
+
# SLOPIFY_BACKUPS_DIR it is an empty folder nobody reads and backups stay in
|
|
29
|
+
# Projects/Backups.
|
|
30
|
+
- type: bind
|
|
31
|
+
source: ${SLOPIFY_BACKUPS_DIR:-./backups-off}
|
|
32
|
+
target: /data/backups
|
|
33
|
+
bind:
|
|
34
|
+
create_host_path: true
|
|
35
|
+
# The host CLI bridge: a folder holding the helper's socket and token, nothing else.
|
|
36
|
+
# With the bridge off it is an empty folder and Settings shows the CLIs as unavailable.
|
|
37
|
+
- type: bind
|
|
38
|
+
source: ${SLOPIFY_HOST_CLI_SHARE:-./host-cli-off}
|
|
39
|
+
target: /opt/slopify-host
|
|
40
|
+
read_only: true
|
|
41
|
+
bind:
|
|
42
|
+
create_host_path: true
|
|
43
|
+
# The update handshake: activation.json says when the installer has committed this version.
|
|
44
|
+
- type: bind
|
|
45
|
+
source: ${SLOPIFY_ACTIVATION_DIR:-./activation}
|
|
46
|
+
target: /opt/slopify-install
|
|
47
|
+
read_only: true
|
|
48
|
+
bind:
|
|
49
|
+
create_host_path: true
|
|
50
|
+
environment:
|
|
51
|
+
SLOPIFY_DOCKER_PROJECTS_DIR: ${SLOPIFY_PROJECTS_DIR}
|
|
52
|
+
SLOPIFY_DOCKER_BACKUPS_DIR: ${SLOPIFY_BACKUPS_DIR:-}
|
|
53
|
+
SLOPIFY_HOST_CLI_DIR: /opt/slopify-host
|
|
54
|
+
SLOPIFY_DOCKER_INSTALL_STATE: /opt/slopify-install/activation.json
|
|
55
|
+
SLOPIFY_UPDATE_TOKEN: ${SLOPIFY_UPDATE_TOKEN:-}
|
|
56
|
+
SLOPIFY_UPDATE_PENDING: ${SLOPIFY_UPDATE_PENDING:-0}
|
|
57
|
+
healthcheck:
|
|
58
|
+
test:
|
|
59
|
+
- CMD
|
|
60
|
+
- node
|
|
61
|
+
- -e
|
|
62
|
+
- "fetch('http://127.0.0.1:6969/api/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"
|
|
63
|
+
interval: 30s
|
|
64
|
+
timeout: 5s
|
|
65
|
+
start_period: 120s
|
|
66
|
+
start_interval: 2s
|
|
67
|
+
|
|
68
|
+
volumes:
|
|
69
|
+
data:
|
|
70
|
+
external: true
|
|
71
|
+
name: ${SLOPIFY_VOLUME:-slopify-data}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { installSchema, readState, writeState } from "../docker-install/state.js";
|
|
3
|
+
import { checkDockerStart, loginStartFile, loginStartReport, loginStartSchema, } from "./docker.js";
|
|
4
|
+
import { autostartQuestion } from "./prompt.js";
|
|
5
|
+
/**
|
|
6
|
+
* The --docker installer's part: ask "Start Slopify when you log in?" once (or take
|
|
7
|
+
* --autostart / --no-autostart), look whether Docker starts by itself, and leave the answer in
|
|
8
|
+
* the installation's activation folder, which the container reads at
|
|
9
|
+
* /opt/slopify-install/login-start.json. Docker's own settings are only read, never changed.
|
|
10
|
+
*/
|
|
11
|
+
export async function recordLoginStart(o) {
|
|
12
|
+
const install = await readState(join(o.directory, "install.json"), installSchema, o.uid);
|
|
13
|
+
// Rootless Docker runs the container as 0:0 (see compose.yaml's SLOPIFY_USER).
|
|
14
|
+
const manager = install?.user === "0:0" ? "rootless" : "system";
|
|
15
|
+
const path = join(o.directory, "activation", loginStartFile);
|
|
16
|
+
const previous = await readState(path, loginStartSchema, o.uid).catch(() => null);
|
|
17
|
+
let wanted = o.flag ?? previous?.wanted ?? null;
|
|
18
|
+
if (wanted === null && o.interactive)
|
|
19
|
+
wanted = (await o.ask(autostartQuestion)) ?? null;
|
|
20
|
+
const record = {
|
|
21
|
+
version: 1,
|
|
22
|
+
checkedAt: o.now().toISOString(),
|
|
23
|
+
docker: await checkDockerStart(o.exec, manager),
|
|
24
|
+
manager,
|
|
25
|
+
wanted,
|
|
26
|
+
};
|
|
27
|
+
await writeState(path, record);
|
|
28
|
+
o.report(wanted === false
|
|
29
|
+
? `Slopify in Docker still starts whenever Docker does, because its container restarts with Docker. When you don't want it running: docker stop ${o.name}`
|
|
30
|
+
: loginStartReport(record));
|
|
31
|
+
return record;
|
|
32
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Slopify in Docker restarts with Docker (`restart: unless-stopped` in compose.yaml), so it
|
|
3
|
+
// starts at login exactly when Docker does. Slopify never changes Docker's own setting; the
|
|
4
|
+
// installer looks once, on this machine, and leaves the answer where the container can read
|
|
5
|
+
// it (the read-only /opt/slopify-install folder), because a container can't see its host.
|
|
6
|
+
export const loginStartFile = "login-start.json";
|
|
7
|
+
export const containerLoginStart = `/opt/slopify-install/${loginStartFile}`;
|
|
8
|
+
export const loginStartSchema = z
|
|
9
|
+
.object({
|
|
10
|
+
version: z.literal(1),
|
|
11
|
+
checkedAt: z.string().max(40),
|
|
12
|
+
// yes: Docker starts by itself; no: it doesn't; unknown: this machine couldn't say.
|
|
13
|
+
docker: z.enum(["yes", "no", "unknown"]),
|
|
14
|
+
// Which Docker the installation uses: the system one, or rootless Docker in your account.
|
|
15
|
+
manager: z.enum(["system", "rootless"]),
|
|
16
|
+
// The installer's "Start Slopify when you log in?" answer, or null when it wasn't asked.
|
|
17
|
+
wanted: z.boolean().nullable(),
|
|
18
|
+
})
|
|
19
|
+
.strict();
|
|
20
|
+
export function dockerHowTo(manager) {
|
|
21
|
+
return manager === "rootless"
|
|
22
|
+
? "Turn on Docker's start at login in a terminal: systemctl --user enable docker (rootless Docker). Slopify then starts with it."
|
|
23
|
+
: "Turn on Docker's start with the computer in a terminal: sudo systemctl enable docker. Slopify then starts with it.";
|
|
24
|
+
}
|
|
25
|
+
export const desktopHowTo = "With Docker Desktop: Docker Desktop → Settings → General → Start Docker Desktop when you sign in. With Docker Engine on Linux: sudo systemctl enable docker. Slopify starts whenever Docker does.";
|
|
26
|
+
/**
|
|
27
|
+
* Reads, never changes, whether Docker starts by itself. `systemctl is-enabled` only reports;
|
|
28
|
+
* it answers "enabled" or "disabled" (exit code 1), and fails without systemd.
|
|
29
|
+
*/
|
|
30
|
+
export async function checkDockerStart(exec, manager) {
|
|
31
|
+
const result = await exec("systemctl", manager === "rootless"
|
|
32
|
+
? ["--user", "is-enabled", "docker.service"]
|
|
33
|
+
: ["is-enabled", "docker.service"]);
|
|
34
|
+
const answer = result.stdout.trim().split("\n")[0]?.trim() ?? "";
|
|
35
|
+
if (["enabled", "enabled-runtime", "alias"].includes(answer))
|
|
36
|
+
return "yes";
|
|
37
|
+
if (["disabled", "masked", "masked-runtime"].includes(answer))
|
|
38
|
+
return "no";
|
|
39
|
+
return "unknown";
|
|
40
|
+
}
|
|
41
|
+
// The terminal line the installer prints after its question.
|
|
42
|
+
export function loginStartReport(record) {
|
|
43
|
+
if (record.docker === "yes")
|
|
44
|
+
return record.manager === "rootless"
|
|
45
|
+
? "Slopify starts when you log in: rootless Docker starts with your login and restarts Slopify."
|
|
46
|
+
: "Slopify starts with this computer: Docker starts at boot and restarts Slopify.";
|
|
47
|
+
if (record.docker === "no")
|
|
48
|
+
return `Docker doesn't start by itself on this computer, so neither does Slopify. ${dockerHowTo(record.manager)}`;
|
|
49
|
+
return `Slopify couldn't tell whether Docker starts by itself here (systemctl didn't answer). ${dockerHowTo(record.manager)}`;
|
|
50
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { access, realpath } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join, sep } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { readState } from "../docker-install/state.js";
|
|
6
|
+
import { isWsl } from "../open-browser.js";
|
|
7
|
+
import { containerLoginStart, loginStartSchema } from "./docker.js";
|
|
8
|
+
import { createNativeAutostart, nodeAutostartExec, nodeAutostartFs } from "./native.js";
|
|
9
|
+
import { createAutostartService } from "./service.js";
|
|
10
|
+
/** The switch for this process: the container's report in Docker, the login entry otherwise. */
|
|
11
|
+
export async function createAutostart(context) {
|
|
12
|
+
if (context.env.SLOPIFY_CONTAINER === "1")
|
|
13
|
+
return createAutostartService(context.db, {
|
|
14
|
+
kind: "docker",
|
|
15
|
+
record: () => context.env.SLOPIFY_DOCKER_INSTALL_STATE === undefined
|
|
16
|
+
? Promise.resolve(null)
|
|
17
|
+
: readLoginStart(containerLoginStart),
|
|
18
|
+
});
|
|
19
|
+
return createAutostartService(context.db, {
|
|
20
|
+
kind: "native",
|
|
21
|
+
native: createNativeAutostart({
|
|
22
|
+
platform: process.platform,
|
|
23
|
+
home: homedir(),
|
|
24
|
+
env: context.env,
|
|
25
|
+
wsl: isWsl(process.platform, context.env),
|
|
26
|
+
fs: nodeAutostartFs,
|
|
27
|
+
exec: nodeAutostartExec,
|
|
28
|
+
node: process.execPath,
|
|
29
|
+
entry: await stableEntry(),
|
|
30
|
+
npxCli: await npxCli(),
|
|
31
|
+
version: context.version,
|
|
32
|
+
dataDir: context.dataDir,
|
|
33
|
+
port: context.port,
|
|
34
|
+
host: context.host,
|
|
35
|
+
log: join(context.logs, "autostart.log"),
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export async function readLoginStart(path) {
|
|
40
|
+
try {
|
|
41
|
+
return await readState(path, loginStartSchema, process.getuid?.() ?? -1);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Damaged, or not the installer's: the same as not knowing.
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// This package's cli.js, unless it lives in the npx cache, which npx may clean at any time.
|
|
49
|
+
async function stableEntry() {
|
|
50
|
+
const entry = fileURLToPath(new URL("../cli.js", import.meta.url));
|
|
51
|
+
try {
|
|
52
|
+
await access(entry);
|
|
53
|
+
const real = await realpath(entry);
|
|
54
|
+
return real.includes(`${sep}_npx${sep}`) ? null : real;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Running from source (tsx): there is no built entry to start.
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// npm's npx script beside this Node (the Windows and the Unix installation layouts).
|
|
62
|
+
async function npxCli() {
|
|
63
|
+
const nodeDir = dirname(process.execPath);
|
|
64
|
+
for (const candidate of [
|
|
65
|
+
join(nodeDir, "node_modules", "npm", "bin", "npx-cli.js"),
|
|
66
|
+
join(nodeDir, "..", "lib", "node_modules", "npm", "bin", "npx-cli.js"),
|
|
67
|
+
]) {
|
|
68
|
+
try {
|
|
69
|
+
await access(candidate);
|
|
70
|
+
// Not resolved: Homebrew's links stay put while the folders they point to change.
|
|
71
|
+
return candidate;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Try the next layout.
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|