@gentbajko/slopify 2.5.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 +3 -0
- 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 +44 -5
- package/dist/adapters/host-cli/transport.js +7 -5
- package/dist/adapters/image/bytes.js +2 -2
- package/dist/adapters/image/codex-output.js +1 -1
- package/dist/adapters/image/codex.js +87 -22
- package/dist/adapters/image/fal.js +13 -5
- package/dist/adapters/image/google.js +11 -8
- package/dist/adapters/image/openai.js +14 -7
- package/dist/adapters/image/reference.js +34 -0
- package/dist/adapters/image/replicate.js +7 -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.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 +12 -7
- 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/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 +13 -13
- package/dist/catalog/schema.js +2 -0
- package/dist/catalog/store.js +147 -8
- 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/host-cli.js +1 -1
- package/dist/edge/http/actions.js +32 -10
- package/dist/edge/http/app.js +58 -2
- package/dist/edge/http/auditions.js +70 -0
- package/dist/edge/http/autostart.js +37 -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 +29 -5
- 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/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 +84 -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 +9 -1
- 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 +122 -14
- package/dist/kernel/ports/key-probe.js +1 -0
- package/dist/kernel/ports/languages.js +157 -0
- 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 +17 -8
- 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 +142 -7
- 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 +272 -16
- 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 +27 -0
- package/dist/slices/admission/repo.js +24 -4
- package/dist/slices/admission/rules.js +79 -1
- package/dist/slices/admission/schema.js +58 -1
- package/dist/slices/admission/short-mode.js +47 -0
- package/dist/slices/admission/start.js +41 -5
- 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/folder.js +14 -6
- package/dist/slices/batch/index.js +7 -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 +109 -28
- 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 +7 -1
- 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 +21 -3
- package/dist/slices/notifications/rules.js +22 -1
- 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 +125 -6
- package/dist/slices/play-drafts/readiness.js +3 -0
- package/dist/slices/play-drafts/repo.js +2 -0
- package/dist/slices/play-drafts/review-inputs.js +20 -3
- package/dist/slices/play-drafts/schema.js +89 -0
- package/dist/slices/play-drafts/service.js +4 -1
- package/dist/slices/play-drafts/start.js +43 -18
- package/dist/slices/project-templates/from-project.js +50 -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 +18 -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 +46 -3
- 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 +8 -4
- 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 +35 -0
- package/dist/slices/rebuild/recipe-legacy.js +9 -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 +15 -1
- 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 +23 -3
- package/dist/slices/rebuild/recipe-text.js +114 -19
- package/dist/slices/rebuild/recipe-validation.js +14 -2
- package/dist/slices/rebuild/recipe-visual.js +96 -33
- 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 +5 -0
- 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 +21 -2
- package/dist/slices/rebuild/runtime-local.js +148 -24
- 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 +27 -0
- package/dist/slices/rebuild/runtime-provider.js +126 -18
- 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 +44 -17
- 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/soften.js +49 -0
- package/dist/slices/rebuild/transition-repo.js +9 -2
- 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 +5 -2
- package/dist/slices/revisions/adopt.js +3 -1
- package/dist/slices/revisions/downloads.js +7 -1
- package/dist/slices/revisions/image-plan.js +183 -0
- package/dist/slices/revisions/mutation-assets.js +10 -0
- package/dist/slices/revisions/mutation-cues.js +71 -0
- package/dist/slices/revisions/mutation-request.js +2 -1
- package/dist/slices/revisions/mutations.js +97 -3
- package/dist/slices/revisions/schema.js +11 -1
- 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 +43 -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 +6 -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 +14 -4
- package/dist/slices/storage/model.js +11 -0
- package/dist/slices/storage/portable.js +92 -12
- package/dist/slices/storage/reconcile.js +14 -1
- 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-BLF2i8c2.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-CktpxlJd.js +0 -171
- package/dist/web/assets/index-DB-vOnGU.css +0 -1
- package/scripts/docker-run.sh +0 -3
- /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { mkdir, readFile, rename, rmdir, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { posix, win32 } from "node:path";
|
|
4
|
+
import { agentLabel, cmdLauncher, desktopEntry, desktopExecArg, desktopMarker, launchAgent, launcherMarker, runKey, runValue, shellLauncher, windowsPathProblem, windowsRunCommand, xmlText, } from "./launcher.js";
|
|
5
|
+
const launcherDir = "autostart";
|
|
6
|
+
// How each entry spells the launcher's path.
|
|
7
|
+
function launcherRef(platform, launcher) {
|
|
8
|
+
return platform === "linux"
|
|
9
|
+
? desktopExecArg(launcher)
|
|
10
|
+
: platform === "darwin"
|
|
11
|
+
? xmlText(launcher)
|
|
12
|
+
: launcher;
|
|
13
|
+
}
|
|
14
|
+
// Where each platform keeps the per-user login entry. None of them needs administrator
|
|
15
|
+
// rights, and none is a systemd unit: a desktop login starts them.
|
|
16
|
+
function locations(o, platform) {
|
|
17
|
+
const path = platform === "win32" ? win32 : posix;
|
|
18
|
+
const dir = path.join(o.dataDir, launcherDir);
|
|
19
|
+
const launcher = path.join(dir, platform === "win32" ? "start-slopify.cmd" : "start-slopify.sh");
|
|
20
|
+
const configHome = o.env.XDG_CONFIG_HOME && posix.isAbsolute(o.env.XDG_CONFIG_HOME)
|
|
21
|
+
? o.env.XDG_CONFIG_HOME
|
|
22
|
+
: posix.join(o.home, ".config");
|
|
23
|
+
const entry = platform === "linux"
|
|
24
|
+
? posix.join(configHome, "autostart", "slopify.desktop")
|
|
25
|
+
: platform === "darwin"
|
|
26
|
+
? posix.join(o.home, "Library", "LaunchAgents", `${agentLabel}.plist`)
|
|
27
|
+
: null;
|
|
28
|
+
return {
|
|
29
|
+
dir,
|
|
30
|
+
launcher,
|
|
31
|
+
entry,
|
|
32
|
+
where: entry ?? `${runKey} → ${runValue}`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function supported(o) {
|
|
36
|
+
if (o.wsl)
|
|
37
|
+
return "Slopify runs inside WSL here, and Windows doesn't start Linux programs when you log in. Start Slopify from a Windows terminal instead (npx @gentbajko/slopify), then turn this switch on there.";
|
|
38
|
+
if (o.platform === "linux" || o.platform === "darwin" || o.platform === "win32")
|
|
39
|
+
return o.platform;
|
|
40
|
+
return `Starting at login isn't supported on ${o.platform}. Start Slopify yourself with npx @gentbajko/slopify.`;
|
|
41
|
+
}
|
|
42
|
+
export function createNativeAutostart(o) {
|
|
43
|
+
const platform = supported(o);
|
|
44
|
+
if (platform !== "linux" && platform !== "darwin" && platform !== "win32") {
|
|
45
|
+
const off = { available: false, enabled: false, where: null, summary: platform };
|
|
46
|
+
return {
|
|
47
|
+
status: async () => off,
|
|
48
|
+
enable: () => Promise.reject(new Error(platform)),
|
|
49
|
+
disable: async () => off,
|
|
50
|
+
refresh: async () => { },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const at = locations(o, platform);
|
|
54
|
+
const spec = { ...o, platform };
|
|
55
|
+
const launcherText = platform === "win32" ? cmdLauncher(spec) : shellLauncher(spec);
|
|
56
|
+
const entryText = platform === "linux"
|
|
57
|
+
? desktopEntry(at.launcher)
|
|
58
|
+
: platform === "darwin"
|
|
59
|
+
? launchAgent(at.launcher, o.log)
|
|
60
|
+
: windowsRunCommand(at.launcher);
|
|
61
|
+
// The entry as it is now: absent, Slopify's (with its current text), or someone else's.
|
|
62
|
+
async function entryNow() {
|
|
63
|
+
if (platform === "win32") {
|
|
64
|
+
const result = await o.exec("reg", ["query", runKey, "/v", runValue]);
|
|
65
|
+
if (result.code !== 0)
|
|
66
|
+
return { state: "none" };
|
|
67
|
+
const data = /REG_(?:EXPAND_)?SZ\s+(.*)$/mu.exec(result.stdout)?.[1]?.trim();
|
|
68
|
+
if (data === undefined)
|
|
69
|
+
return { state: "foreign" };
|
|
70
|
+
return data.includes("start-slopify.cmd")
|
|
71
|
+
? { state: "ours", text: data }
|
|
72
|
+
: { state: "foreign", text: data };
|
|
73
|
+
}
|
|
74
|
+
const text = await o.fs.read(at.entry);
|
|
75
|
+
if (text === undefined)
|
|
76
|
+
return { state: "none" };
|
|
77
|
+
const ours = text.includes(platform === "linux" ? desktopMarker : launcherMarker);
|
|
78
|
+
return { state: ours ? "ours" : "foreign", text };
|
|
79
|
+
}
|
|
80
|
+
function state(enabled) {
|
|
81
|
+
return {
|
|
82
|
+
available: true,
|
|
83
|
+
enabled,
|
|
84
|
+
where: at.where,
|
|
85
|
+
summary: enabled
|
|
86
|
+
? "Slopify starts quietly when you log in to this computer, without opening a browser tab."
|
|
87
|
+
: "Slopify starts only when you start it.",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async function writeLauncher() {
|
|
91
|
+
if ((await o.fs.read(at.launcher)) !== launcherText)
|
|
92
|
+
await o.fs.write(at.launcher, launcherText, 0o700);
|
|
93
|
+
}
|
|
94
|
+
async function writeEntry(current) {
|
|
95
|
+
if (current === entryText)
|
|
96
|
+
return;
|
|
97
|
+
if (platform === "win32") {
|
|
98
|
+
const result = await o.exec("reg", [
|
|
99
|
+
"add",
|
|
100
|
+
runKey,
|
|
101
|
+
"/v",
|
|
102
|
+
runValue,
|
|
103
|
+
"/t",
|
|
104
|
+
"REG_SZ",
|
|
105
|
+
"/d",
|
|
106
|
+
entryText,
|
|
107
|
+
"/f",
|
|
108
|
+
]);
|
|
109
|
+
if (result.code !== 0)
|
|
110
|
+
throw new Error(`Windows refused to add Slopify to the programs that start when you log in (reg add ${runKey}: ${result.stderr.trim() || `exit code ${result.code}`}). Nothing needs administrator rights here, so try the switch again; if it keeps failing, check that a security program isn't blocking changes to your start-up list.`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
await o.fs.write(at.entry, entryText, 0o644);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
status: async () => state((await entryNow()).state === "ours"),
|
|
117
|
+
enable: async () => {
|
|
118
|
+
if (platform === "win32") {
|
|
119
|
+
const problem = windowsPathProblem(o.dataDir);
|
|
120
|
+
if (problem !== undefined)
|
|
121
|
+
throw new Error(problem);
|
|
122
|
+
}
|
|
123
|
+
const now = await entryNow();
|
|
124
|
+
if (now.state === "foreign")
|
|
125
|
+
throw new Error(platform === "win32"
|
|
126
|
+
? `Your start-up list already has an entry named ${runValue} that Slopify didn't make (${runKey}). Slopify left it alone. Remove it in Task Manager → Startup apps, or rename it, then turn the switch on again.`
|
|
127
|
+
: `${at.entry} already exists and wasn't made by Slopify, so Slopify left it alone. Delete or rename that file, then turn the switch on again.`);
|
|
128
|
+
await writeLauncher();
|
|
129
|
+
await writeEntry(now.text);
|
|
130
|
+
return state(true);
|
|
131
|
+
},
|
|
132
|
+
disable: async () => {
|
|
133
|
+
const now = await entryNow();
|
|
134
|
+
if (now.state === "ours") {
|
|
135
|
+
if (platform === "win32") {
|
|
136
|
+
const result = await o.exec("reg", ["delete", runKey, "/v", runValue, "/f"]);
|
|
137
|
+
if (result.code !== 0)
|
|
138
|
+
throw new Error(`Windows refused to remove Slopify from the programs that start when you log in (reg delete ${runKey}: ${result.stderr.trim() || `exit code ${result.code}`}). Turn it off in Task Manager → Startup apps → Slopify instead.`);
|
|
139
|
+
}
|
|
140
|
+
else
|
|
141
|
+
await o.fs.remove(at.entry);
|
|
142
|
+
}
|
|
143
|
+
// The launcher is only ever Slopify's own; a hand-edited one without the marker stays.
|
|
144
|
+
const launcher = await o.fs.read(at.launcher);
|
|
145
|
+
if (launcher?.includes(launcherMarker))
|
|
146
|
+
await o.fs.remove(at.launcher);
|
|
147
|
+
await o.fs.removeEmptyDir(at.dir);
|
|
148
|
+
return state(false);
|
|
149
|
+
},
|
|
150
|
+
refresh: async () => {
|
|
151
|
+
const now = await entryNow();
|
|
152
|
+
// Only the entry that runs this data folder's launcher: a Slopify started with another
|
|
153
|
+
// --data-dir must not take over the one the switch was turned on from.
|
|
154
|
+
if (now.state !== "ours" || !now.text?.includes(launcherRef(platform, at.launcher)))
|
|
155
|
+
return;
|
|
156
|
+
await writeLauncher();
|
|
157
|
+
await writeEntry(now.text);
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
export const nodeAutostartFs = {
|
|
162
|
+
read: async (path) => {
|
|
163
|
+
try {
|
|
164
|
+
return await readFile(path, "utf8");
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
if (isMissing(error))
|
|
168
|
+
return undefined;
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
write: async (path, text, mode) => {
|
|
173
|
+
const directory = path.slice(0, Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")));
|
|
174
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
175
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
176
|
+
await writeFile(temporary, text, { mode });
|
|
177
|
+
await rename(temporary, path);
|
|
178
|
+
},
|
|
179
|
+
remove: async (path) => {
|
|
180
|
+
try {
|
|
181
|
+
await unlink(path);
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
if (!isMissing(error))
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
removeEmptyDir: async (path) => {
|
|
189
|
+
try {
|
|
190
|
+
await rmdir(path);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// Missing, or holds something else: either way it stays as it is.
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
export const nodeAutostartExec = (file, args) => new Promise((resolve) => {
|
|
198
|
+
execFile(file, [...args], { windowsHide: true, timeout: 15_000, encoding: "utf8" }, (error, stdout, stderr) => {
|
|
199
|
+
const code = error === null
|
|
200
|
+
? 0
|
|
201
|
+
: typeof error.code === "number"
|
|
202
|
+
? error.code
|
|
203
|
+
: // A missing command (ENOENT) or a timeout: no exit code of its own.
|
|
204
|
+
127;
|
|
205
|
+
resolve({ code, stdout, stderr: stderr || (error?.message ?? "") });
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
function isMissing(error) {
|
|
209
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
210
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
export const autostartQuestion = "Start Slopify when you log in? (Y/n) ";
|
|
3
|
+
// The two flags as one answer: undefined when neither was given.
|
|
4
|
+
export function autostartFlag(values) {
|
|
5
|
+
if (values.autostart === true && values["no-autostart"] === true)
|
|
6
|
+
throw new Error("--autostart and --no-autostart say opposite things. Keep the one you mean and try again.");
|
|
7
|
+
return values.autostart === true ? true : values["no-autostart"] === true ? false : undefined;
|
|
8
|
+
}
|
|
9
|
+
// Y, yes or just Enter is yes; n or no is no; anything else is no answer.
|
|
10
|
+
export function readAnswer(text) {
|
|
11
|
+
const answer = text.trim().toLowerCase();
|
|
12
|
+
if (answer === "" || answer === "y" || answer === "yes")
|
|
13
|
+
return true;
|
|
14
|
+
if (answer === "n" || answer === "no")
|
|
15
|
+
return false;
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
// One question on the terminal. Ctrl+C still stops Slopify: the question gives up and the
|
|
19
|
+
// signal goes on to the handler that shuts the server down.
|
|
20
|
+
export async function askTerminal(question) {
|
|
21
|
+
const terminal = createInterface({ input: process.stdin, output: process.stdout });
|
|
22
|
+
const interrupted = new Promise((resolve) => {
|
|
23
|
+
terminal.once("SIGINT", () => {
|
|
24
|
+
terminal.close();
|
|
25
|
+
resolve(undefined);
|
|
26
|
+
process.kill(process.pid, "SIGINT");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
try {
|
|
30
|
+
return await Promise.race([terminal.question(question).then(readAnswer), interrupted]);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
terminal.close();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* `npx @gentbajko/slopify`: --autostart / --no-autostart set the switch; without them an
|
|
41
|
+
* interactive terminal is asked once, unless the first-run screen or Settings already asked.
|
|
42
|
+
* Never fails the start: a refusal is printed with what to do.
|
|
43
|
+
*/
|
|
44
|
+
export async function settleAutostart(options) {
|
|
45
|
+
try {
|
|
46
|
+
let wanted = options.flag;
|
|
47
|
+
if (wanted === undefined) {
|
|
48
|
+
if (!options.interactive || !(await options.autostart.unanswered()))
|
|
49
|
+
return;
|
|
50
|
+
wanted = await options.ask(autostartQuestion);
|
|
51
|
+
if (wanted === undefined)
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const view = await options.autostart.set(wanted);
|
|
55
|
+
if (!view.available) {
|
|
56
|
+
options.report(view.summary);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
options.report(view.enabled === true
|
|
60
|
+
? `${view.summary} Login entry: ${view.where}. Turn it off in Settings → General.`
|
|
61
|
+
: `${view.summary} Turn it on any time in Settings → General.`);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
options.warn(error instanceof Error ? error.message : String(error));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { readSetting, writeSetting } from "../../slices/settings/repo.js";
|
|
2
|
+
import { desktopHowTo, dockerHowTo } from "./docker.js";
|
|
3
|
+
import { autostartAnsweredKey } from "./model.js";
|
|
4
|
+
export class AutostartRefusal extends Error {
|
|
5
|
+
}
|
|
6
|
+
export function createAutostartService(db, source) {
|
|
7
|
+
const answered = () => readSetting(db, autostartAnsweredKey) === "1";
|
|
8
|
+
const markAnswered = () => writeSetting(db, autostartAnsweredKey, "1");
|
|
9
|
+
async function view() {
|
|
10
|
+
if (source.kind === "docker") {
|
|
11
|
+
const record = await source.record();
|
|
12
|
+
return {
|
|
13
|
+
kind: "docker",
|
|
14
|
+
available: false,
|
|
15
|
+
enabled: record === null || record.docker === "unknown" ? null : record.docker === "yes",
|
|
16
|
+
summary: record === null
|
|
17
|
+
? "Slopify runs in Docker and starts whenever Docker starts. From inside the container it can't see whether Docker starts when you log in."
|
|
18
|
+
: record.docker === "yes"
|
|
19
|
+
? record.manager === "rootless"
|
|
20
|
+
? "Slopify runs in Docker and starts when you log in: rootless Docker starts with your login and restarts Slopify."
|
|
21
|
+
: "Slopify runs in Docker and starts with this computer: Docker starts at boot and restarts Slopify."
|
|
22
|
+
: record.docker === "no"
|
|
23
|
+
? "Slopify runs in Docker, and Docker doesn't start by itself on this computer, so Slopify doesn't either."
|
|
24
|
+
: "Slopify runs in Docker and starts whenever Docker starts. The installer couldn't tell whether Docker starts by itself here.",
|
|
25
|
+
where: null,
|
|
26
|
+
howTo: record === null
|
|
27
|
+
? desktopHowTo
|
|
28
|
+
: `${dockerHowTo(record.manager)} To check again after changing it, run npx @gentbajko/slopify --docker.`,
|
|
29
|
+
checkedAt: record?.checkedAt ?? null,
|
|
30
|
+
offer: false,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const state = await source.native.status();
|
|
34
|
+
return {
|
|
35
|
+
kind: "native",
|
|
36
|
+
available: state.available,
|
|
37
|
+
enabled: state.enabled,
|
|
38
|
+
summary: state.summary,
|
|
39
|
+
where: state.where,
|
|
40
|
+
howTo: null,
|
|
41
|
+
checkedAt: null,
|
|
42
|
+
offer: state.available && !state.enabled && !answered(),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
view,
|
|
47
|
+
set: async (enabled) => {
|
|
48
|
+
if (source.kind === "docker") {
|
|
49
|
+
const current = await view();
|
|
50
|
+
throw new AutostartRefusal(`Slopify in Docker starts whenever Docker starts, so this switch can't change it. ${current.howTo ?? desktopHowTo}`);
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
await (enabled ? source.native.enable() : source.native.disable());
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
throw new AutostartRefusal(plain(error, enabled));
|
|
57
|
+
}
|
|
58
|
+
markAnswered();
|
|
59
|
+
return view();
|
|
60
|
+
},
|
|
61
|
+
answer: async () => {
|
|
62
|
+
markAnswered();
|
|
63
|
+
return view();
|
|
64
|
+
},
|
|
65
|
+
unanswered: async () => (await view()).offer,
|
|
66
|
+
refresh: () => (source.kind === "native" ? source.native.refresh() : Promise.resolve()),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// The native steps already speak in sentences; a file error from the disk doesn't.
|
|
70
|
+
function plain(error, enabled) {
|
|
71
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
72
|
+
const code = error instanceof Error && "code" in error && typeof error.code === "string"
|
|
73
|
+
? error.code
|
|
74
|
+
: undefined;
|
|
75
|
+
if (code === undefined)
|
|
76
|
+
return message;
|
|
77
|
+
const path = error instanceof Error && "path" in error && typeof error.path === "string"
|
|
78
|
+
? ` ${error.path}`
|
|
79
|
+
: "";
|
|
80
|
+
return `Slopify couldn't ${enabled ? "add" : "remove"} its login entry: writing${path} failed (${message}). Make sure your user owns that folder and can write to it, then press Start Slopify when I log in in Settings → General again.`;
|
|
81
|
+
}
|
package/dist/edge/cli.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { createInterface } from "node:readline/promises";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
2
|
import { parseArgs } from "node:util";
|
|
8
3
|
import { configFrom } from "../kernel/config/index.js";
|
|
9
4
|
import { readVersion } from "../kernel/version.js";
|
|
10
5
|
import { boot } from "../main.js";
|
|
11
6
|
import { forwardManagedUpdate } from "../updater/forward.js";
|
|
7
|
+
import { askTerminal, autostartFlag, settleAutostart } from "./autostart/prompt.js";
|
|
12
8
|
import { openBrowser } from "./open-browser.js";
|
|
13
9
|
import { installSignalShutdown } from "./signal-shutdown.js";
|
|
14
|
-
const { values } = parseArgs({
|
|
10
|
+
const { values, positionals } = parseArgs({
|
|
11
|
+
allowPositionals: true,
|
|
15
12
|
options: {
|
|
16
13
|
port: { type: "string" },
|
|
17
14
|
host: { type: "string" },
|
|
@@ -21,72 +18,72 @@ const { values } = parseArgs({
|
|
|
21
18
|
docker: { type: "boolean" },
|
|
22
19
|
"host-cli": { type: "string" },
|
|
23
20
|
"accept-host-cli": { type: "boolean" },
|
|
21
|
+
autostart: { type: "boolean" },
|
|
22
|
+
"no-autostart": { type: "boolean" },
|
|
24
23
|
},
|
|
25
24
|
});
|
|
26
25
|
let config;
|
|
27
26
|
try {
|
|
28
|
-
|
|
27
|
+
const autostart = autostartFlag(values);
|
|
28
|
+
const [action, ...extra] = positionals;
|
|
29
|
+
if (extra.length > 0 || (action !== undefined && action !== "install" && action !== "update"))
|
|
30
|
+
throw new Error(`Unknown command "${positionals.join(" ")}". Use npx @gentbajko/slopify to start Slopify, npx @gentbajko/slopify --docker to install it in Docker, or npx @gentbajko/slopify@latest update to update it.`);
|
|
31
|
+
if (action === "install" && !values.docker)
|
|
32
|
+
throw new Error("Without Docker there is nothing to install: npx @gentbajko/slopify starts Slopify directly. To install it in Docker, run npx @gentbajko/slopify install --docker.");
|
|
33
|
+
// `update` goes to the Docker installation when this user has one.
|
|
34
|
+
const docker = values.docker === true ||
|
|
35
|
+
(action === "update" &&
|
|
36
|
+
(await (await import("./docker-install/run.js")).hasDockerInstall(process.env)));
|
|
37
|
+
if (values["projects-dir"] !== undefined && !docker)
|
|
29
38
|
throw new Error("--projects-dir only works together with --docker. Without Docker, choose where Slopify keeps its files with --data-dir <folder>.");
|
|
30
|
-
if ((values["host-cli"] !== undefined || values["accept-host-cli"] !== undefined) &&
|
|
31
|
-
!values.docker)
|
|
39
|
+
if ((values["host-cli"] !== undefined || values["accept-host-cli"] !== undefined) && !docker)
|
|
32
40
|
throw new Error("--host-cli and --accept-host-cli only work together with --docker. Add --docker, or remove those options.");
|
|
33
41
|
if (values["host-cli"] !== undefined && values["host-cli"] !== "off")
|
|
34
|
-
throw new Error(`--host-cli=${values["host-cli"]} is not supported. The only value is --host-cli=off, which
|
|
35
|
-
if (
|
|
42
|
+
throw new Error(`--host-cli=${values["host-cli"]} is not supported. The only value is --host-cli=off, which installs Docker using API keys only; leave the option out to use the AI CLIs installed on this machine.`);
|
|
43
|
+
if (docker) {
|
|
36
44
|
if (values.host !== undefined || values["data-dir"] !== undefined)
|
|
37
45
|
throw new Error("--host and --data-dir can't be used with --docker: the Docker version always listens on localhost and keeps its data in a Docker volume. Use --projects-dir <folder> for project files and --port <number> for the port.");
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
interactive: process.stdin.isTTY === true,
|
|
49
|
-
env: process.env,
|
|
50
|
-
signal: AbortSignal.timeout(20 * 60_000),
|
|
51
|
-
runner: nodeHostSetupRunner,
|
|
52
|
-
prompt: async (message) => {
|
|
53
|
-
const terminal = createInterface({ input: process.stdin, output: process.stdout });
|
|
54
|
-
try {
|
|
55
|
-
return /^(?:y|yes)$/i.test((await terminal.question(message)).trim());
|
|
56
|
-
}
|
|
57
|
-
finally {
|
|
58
|
-
terminal.close();
|
|
59
|
-
}
|
|
60
|
-
},
|
|
46
|
+
const { runDockerCommand } = await import("./docker-install/run.js");
|
|
47
|
+
await runDockerCommand({
|
|
48
|
+
mode: action === "update" ? "update" : "install",
|
|
49
|
+
...(values.port === undefined ? {} : { port: values.port }),
|
|
50
|
+
...(values["projects-dir"] === undefined ? {} : { projectsDir: values["projects-dir"] }),
|
|
51
|
+
...(values["host-cli"] === undefined ? {} : { hostCli: values["host-cli"] }),
|
|
52
|
+
...(values["accept-host-cli"] === undefined
|
|
53
|
+
? {}
|
|
54
|
+
: { acceptHostCli: values["accept-host-cli"] }),
|
|
55
|
+
...(autostart === undefined ? {} : { autostart }),
|
|
61
56
|
});
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
...(values["projects-dir"] === undefined
|
|
72
|
-
? {}
|
|
73
|
-
: { SLOPIFY_DOCKER_PROJECTS_DIR: values["projects-dir"] }),
|
|
74
|
-
},
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
if (action === "update") {
|
|
60
|
+
config = configFrom(values, process.env);
|
|
61
|
+
const { runNativeUpdate } = await import("./native-update.js");
|
|
62
|
+
await runNativeUpdate({
|
|
63
|
+
origin: `http://${config.host === "0.0.0.0" ? "127.0.0.1" : config.host}:${config.port}`,
|
|
64
|
+
fetch: globalThis.fetch,
|
|
65
|
+
report: (line) => console.log(line),
|
|
75
66
|
});
|
|
76
|
-
|
|
77
|
-
throw new Error(`Could not start the Docker launcher script (${result.error.message}). Make sure bash is installed and on your PATH, then try again.`);
|
|
78
|
-
process.exit(result.status ?? 1);
|
|
67
|
+
process.exit(0);
|
|
79
68
|
}
|
|
80
69
|
config = configFrom(values, process.env);
|
|
81
70
|
const forwarded = await forwardManagedUpdate(config.dataDir, readVersion(), process.argv.slice(2));
|
|
82
71
|
if (forwarded !== undefined)
|
|
83
72
|
process.exit(forwarded);
|
|
84
|
-
const
|
|
73
|
+
const booted = await boot(config, {
|
|
74
|
+
refreshAutostart: true,
|
|
85
75
|
prefetchSubtitleModel: ["", "0", "false"].includes((process.env.SLOPIFY_NO_MODEL_PREFETCH ?? "").trim().toLowerCase()),
|
|
86
76
|
subtitleModelSeed: process.env.SLOPIFY_SUBTITLE_MODEL_SEED?.trim() || undefined,
|
|
77
|
+
seedSample: true,
|
|
78
|
+
// A data dir chosen with --data-dir or SLOPIFY_DATA_DIR keeps a new install's files inside
|
|
79
|
+
// it, so a second or throwaway install never shares a Documents folder with the main one.
|
|
80
|
+
filesInDocuments: values["data-dir"] === undefined && (process.env.SLOPIFY_DATA_DIR ?? "") === "",
|
|
81
|
+
refreshModels: ["", "0", "false"].includes((process.env.SLOPIFY_NO_MODEL_REFRESH ?? "").trim().toLowerCase()),
|
|
87
82
|
});
|
|
83
|
+
const { paths, url, stop } = booted;
|
|
88
84
|
console.log(`Slopify is running at ${url}`);
|
|
89
85
|
console.log(`Slopify data directory: ${paths.dataDir}`);
|
|
86
|
+
console.log(`Projects: ${paths.projects}`);
|
|
90
87
|
console.log(`Database: ${paths.db}`);
|
|
91
88
|
console.log(`Logs: ${paths.logs}`);
|
|
92
89
|
if (config.host !== "127.0.0.1") {
|
|
@@ -102,6 +99,15 @@ try {
|
|
|
102
99
|
stop,
|
|
103
100
|
exit: (code) => process.exit(code),
|
|
104
101
|
});
|
|
102
|
+
// After the server is up, so the question never holds Slopify back.
|
|
103
|
+
await settleAutostart({
|
|
104
|
+
autostart: booted.autostart,
|
|
105
|
+
flag: autostart,
|
|
106
|
+
interactive: process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
107
|
+
ask: askTerminal,
|
|
108
|
+
report: (line) => console.log(line),
|
|
109
|
+
warn: (line) => console.warn(line),
|
|
110
|
+
});
|
|
105
111
|
}
|
|
106
112
|
catch (error) {
|
|
107
113
|
console.error(explainStartupError(error, config));
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
3
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { backupsFolderName } from "../../slices/storage/layout.js";
|
|
6
|
+
import { readState } from "./state.js";
|
|
7
|
+
const activationSchema = z
|
|
8
|
+
.object({
|
|
9
|
+
version: z.literal(1),
|
|
10
|
+
token: z.string().regex(/^[a-f0-9]{64}$/),
|
|
11
|
+
committed: z.boolean(),
|
|
12
|
+
})
|
|
13
|
+
.strict();
|
|
14
|
+
export async function dockerActivationCommitted(path, token) {
|
|
15
|
+
if (!/^[a-f0-9]{64}$/.test(token))
|
|
16
|
+
return false;
|
|
17
|
+
const marker = await readState(path, activationSchema, process.getuid?.() ?? -1);
|
|
18
|
+
return (marker?.committed === true && timingSafeEqual(Buffer.from(marker.token), Buffer.from(token)));
|
|
19
|
+
}
|
|
20
|
+
// Where a container keeps the user-visible files. The installer decides them on the host (the
|
|
21
|
+
// Projects bind, and from 3.0 a Backups bind beside it), so they are never stored in settings.
|
|
22
|
+
export function dockerFilesLayout(env, dataDir) {
|
|
23
|
+
const projects = join(dataDir, "projects");
|
|
24
|
+
return {
|
|
25
|
+
projects,
|
|
26
|
+
backups: env.SLOPIFY_DOCKER_BACKUPS_DIR
|
|
27
|
+
? join(dataDir, "backups")
|
|
28
|
+
: join(projects, backupsFolderName),
|
|
29
|
+
exports: null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export async function dockerFolderConfiguration(env, projectsRoot) {
|
|
33
|
+
if (env.SLOPIFY_CONTAINER !== "1")
|
|
34
|
+
return { container: false, hostProjects: null, hostBackups: null };
|
|
35
|
+
const host = env.SLOPIFY_DOCKER_PROJECTS_DIR;
|
|
36
|
+
if (!host || env.SLOPIFY_DOCKER_INSTALL_STATE !== "/opt/slopify-install/activation.json")
|
|
37
|
+
return { container: true, hostProjects: null, hostBackups: null };
|
|
38
|
+
const backups = env.SLOPIFY_DOCKER_BACKUPS_DIR || null;
|
|
39
|
+
for (const path of [host, backups])
|
|
40
|
+
if (path !== null && (!isAbsolute(path) || resolve(path) !== path || /[\p{Cc},]/u.test(path)))
|
|
41
|
+
throw new Error("Invalid Docker host project path.");
|
|
42
|
+
const root = await lstat(projectsRoot);
|
|
43
|
+
const mounts = (await readFile("/proc/self/mountinfo", "utf8"))
|
|
44
|
+
.split("\n")
|
|
45
|
+
.map((line) => line.split(" ")[4]);
|
|
46
|
+
if (projectsRoot !== "/data/projects" ||
|
|
47
|
+
!root.isDirectory() ||
|
|
48
|
+
root.isSymbolicLink() ||
|
|
49
|
+
!mounts.includes("/data/projects"))
|
|
50
|
+
throw new Error("Managed Docker project bind is missing.");
|
|
51
|
+
return {
|
|
52
|
+
container: true,
|
|
53
|
+
hostProjects: host,
|
|
54
|
+
hostBackups: backups !== null && mounts.includes("/data/backups") ? backups : null,
|
|
55
|
+
};
|
|
56
|
+
}
|