@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
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// The files "Start Slopify when I log in" writes, as pure text. Nothing here touches the disk,
|
|
2
|
+
// so every platform's output is tested on any machine (`launcher.test.ts`).
|
|
3
|
+
//
|
|
4
|
+
// The login entry never names the npx cache: npx may clean it or move it on its next run. It
|
|
5
|
+
// runs a small launcher kept in the data folder instead, which starts, in order:
|
|
6
|
+
// 1. the installed Slopify entry, when Slopify runs from a real install (npm i -g, a local
|
|
7
|
+
// checkout), not from the npx cache;
|
|
8
|
+
// 2. npx's own script next to this Node, asking for the version that was running when the
|
|
9
|
+
// switch was turned on (`@gentbajko/slopify@<version>`), so a login works offline once
|
|
10
|
+
// that version is in the cache and never jumps to a new release by itself;
|
|
11
|
+
// 3. plain `npx` from PATH, the same way.
|
|
12
|
+
// In-app updates still apply: the started version forwards to the newest one installed in the
|
|
13
|
+
// data folder (`updater/forward.ts`). Each start with the switch on writes the launcher again,
|
|
14
|
+
// so it follows the Node, the entry and the version actually in use.
|
|
15
|
+
export const packageName = "@gentbajko/slopify";
|
|
16
|
+
export const launcherMarker = "Written by Slopify: Settings";
|
|
17
|
+
export const desktopMarker = "X-Slopify-Autostart=true";
|
|
18
|
+
export const agentLabel = "stream.slopify.app";
|
|
19
|
+
export const runKey = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
|
20
|
+
export const runValue = "Slopify";
|
|
21
|
+
// The flags a login start runs with: quiet (no browser tab), and the data folder, port and
|
|
22
|
+
// host of the Slopify that turned the switch on, since no terminal is there to pass them.
|
|
23
|
+
export function launchArgs(spec) {
|
|
24
|
+
return [
|
|
25
|
+
"--no-open",
|
|
26
|
+
"--data-dir",
|
|
27
|
+
spec.dataDir,
|
|
28
|
+
"--port",
|
|
29
|
+
String(spec.port),
|
|
30
|
+
...(spec.host === "127.0.0.1" ? [] : ["--host", spec.host]),
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
// POSIX sh single quotes: nothing inside is special except the quote itself.
|
|
34
|
+
export function shQuote(value) {
|
|
35
|
+
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
36
|
+
}
|
|
37
|
+
export function shellLauncher(spec) {
|
|
38
|
+
const args = launchArgs(spec).map(shQuote).join(" ");
|
|
39
|
+
const pkg = shQuote(`${packageName}@${spec.version}`);
|
|
40
|
+
const lines = [
|
|
41
|
+
"#!/bin/sh",
|
|
42
|
+
"# Starts Slopify quietly when you log in: no browser tab, open it from your bookmark.",
|
|
43
|
+
`# ${launcherMarker} → General → "Start Slopify when I log in".`,
|
|
44
|
+
"# Turning that switch off deletes this file and the login entry that runs it.",
|
|
45
|
+
`exec >>${shQuote(spec.log)} 2>&1`,
|
|
46
|
+
`echo "$(date) Starting Slopify at login"`,
|
|
47
|
+
`NODE=${shQuote(spec.node)}`,
|
|
48
|
+
'[ -x "$NODE" ] || NODE=$(command -v node) || {',
|
|
49
|
+
` echo "Slopify could not start at login: Node.js was not found. Install Node.js from https://nodejs.org, then start Slopify once with npx ${packageName}."`,
|
|
50
|
+
" exit 1",
|
|
51
|
+
"}",
|
|
52
|
+
'PATH=$(dirname "$NODE"):$PATH',
|
|
53
|
+
"export PATH",
|
|
54
|
+
];
|
|
55
|
+
if (spec.entry !== null)
|
|
56
|
+
lines.push(`if [ -f ${shQuote(spec.entry)} ]; then exec "$NODE" ${shQuote(spec.entry)} ${args}; fi`);
|
|
57
|
+
if (spec.npxCli !== null)
|
|
58
|
+
lines.push(`if [ -f ${shQuote(spec.npxCli)} ]; then exec "$NODE" ${shQuote(spec.npxCli)} --yes ${pkg} ${args}; fi`);
|
|
59
|
+
lines.push(`exec npx --yes ${pkg} ${args}`, "");
|
|
60
|
+
return lines.join("\n");
|
|
61
|
+
}
|
|
62
|
+
// cmd.exe: inside double quotes only `%` still expands, so it is doubled. A path can't hold a
|
|
63
|
+
// double quote on Windows, and `windowsPathProblem` refuses the rest before anything is written.
|
|
64
|
+
function cmdQuote(value) {
|
|
65
|
+
return `"${value.replaceAll("%", "%%")}"`;
|
|
66
|
+
}
|
|
67
|
+
export function cmdLauncher(spec) {
|
|
68
|
+
const args = launchArgs(spec)
|
|
69
|
+
.map((arg) => (/^[\w.:-]+$/u.test(arg) ? arg : cmdQuote(arg)))
|
|
70
|
+
.join(" ");
|
|
71
|
+
const pkg = `${packageName}@${spec.version}`;
|
|
72
|
+
const log = `>>${cmdQuote(spec.log)} 2>&1`;
|
|
73
|
+
const nodeDir = spec.node.slice(0, Math.max(spec.node.lastIndexOf("\\"), 0));
|
|
74
|
+
const lines = [
|
|
75
|
+
"@echo off",
|
|
76
|
+
"rem Starts Slopify quietly when you log in: no browser tab, open it from your bookmark.",
|
|
77
|
+
`rem ${launcherMarker} > General > "Start Slopify when I log in".`,
|
|
78
|
+
"rem Turning that switch off deletes this file and the login entry that runs it.",
|
|
79
|
+
"rem Closing this window stops Slopify.",
|
|
80
|
+
"chcp 65001 >nul",
|
|
81
|
+
"title Slopify",
|
|
82
|
+
`set "NODE=${spec.node.replaceAll("%", "%%")}"`,
|
|
83
|
+
`set "PATH=${nodeDir.replaceAll("%", "%%")};%PATH%"`,
|
|
84
|
+
'if not exist "%NODE%" set "NODE=node"',
|
|
85
|
+
];
|
|
86
|
+
if (spec.entry !== null)
|
|
87
|
+
lines.push(`if not exist ${cmdQuote(spec.entry)} goto npxcli`, `"%NODE%" ${cmdQuote(spec.entry)} ${args} ${log}`, "exit /b");
|
|
88
|
+
lines.push(":npxcli");
|
|
89
|
+
if (spec.npxCli !== null)
|
|
90
|
+
lines.push(`if not exist ${cmdQuote(spec.npxCli)} goto npx`, `"%NODE%" ${cmdQuote(spec.npxCli)} --yes ${pkg} ${args} ${log}`, "exit /b");
|
|
91
|
+
lines.push(":npx", `call npx --yes ${pkg} ${args} ${log}`, "");
|
|
92
|
+
return lines.join("\r\n");
|
|
93
|
+
}
|
|
94
|
+
// The registry's Run value: a minimised console running the launcher, so a login shows a
|
|
95
|
+
// taskbar button named Slopify rather than a window in the way. REG_SZ doesn't expand
|
|
96
|
+
// variables, and `start` takes the first quoted argument as the window title.
|
|
97
|
+
export function windowsRunCommand(launcher) {
|
|
98
|
+
return `cmd.exe /d /c start "Slopify" /min "${launcher}"`;
|
|
99
|
+
}
|
|
100
|
+
// Characters cmd.exe would read as its own inside the Run value (`%` expands even in quotes
|
|
101
|
+
// there, where a batch file's `%%` doesn't apply). Windows paths can't contain `"`.
|
|
102
|
+
export function windowsPathProblem(path) {
|
|
103
|
+
return /[%"\r\n]/u.test(path)
|
|
104
|
+
? `The Slopify data folder ${path} contains a % sign, which Windows' start-up list can't run. Start Slopify with another folder (npx ${packageName} --data-dir C:\\Slopify) and turn the switch on there.`
|
|
105
|
+
: undefined;
|
|
106
|
+
}
|
|
107
|
+
// Desktop Entry spec: an argument with spaces or reserved characters is double-quoted, with
|
|
108
|
+
// `"`, `` ` ``, `$` and `\` backslash-escaped inside; the value as a whole then escapes `\`
|
|
109
|
+
// again, and `%` is a field code unless doubled.
|
|
110
|
+
export function desktopExecArg(value) {
|
|
111
|
+
if (/^[A-Za-z0-9_./+,:@=-]+$/u.test(value))
|
|
112
|
+
return value;
|
|
113
|
+
const quoted = `"${value.replace(/["`$\\]/gu, (char) => `\\${char}`)}"`;
|
|
114
|
+
return quoted.replaceAll("\\", "\\\\").replaceAll("%", "%%");
|
|
115
|
+
}
|
|
116
|
+
// Freedesktop keys escape newlines, tabs and backslashes in plain string values.
|
|
117
|
+
function desktopString(value) {
|
|
118
|
+
return value
|
|
119
|
+
.replaceAll("\\", "\\\\")
|
|
120
|
+
.replaceAll("\n", "\\n")
|
|
121
|
+
.replaceAll("\t", "\\t")
|
|
122
|
+
.replaceAll("\r", "\\r");
|
|
123
|
+
}
|
|
124
|
+
export function desktopEntry(launcher) {
|
|
125
|
+
return [
|
|
126
|
+
"[Desktop Entry]",
|
|
127
|
+
"Type=Application",
|
|
128
|
+
"Name=Slopify",
|
|
129
|
+
desktopString(`Comment=Starts Slopify quietly when you log in. Turn it off in Slopify: Settings → General.`),
|
|
130
|
+
`Exec=/bin/sh ${desktopExecArg(launcher)}`,
|
|
131
|
+
"Terminal=false",
|
|
132
|
+
"NoDisplay=true",
|
|
133
|
+
"Hidden=false",
|
|
134
|
+
"X-GNOME-Autostart-enabled=true",
|
|
135
|
+
desktopMarker,
|
|
136
|
+
"",
|
|
137
|
+
].join("\n");
|
|
138
|
+
}
|
|
139
|
+
export function xmlText(value) {
|
|
140
|
+
return value
|
|
141
|
+
.replaceAll("&", "&")
|
|
142
|
+
.replaceAll("<", "<")
|
|
143
|
+
.replaceAll(">", ">")
|
|
144
|
+
.replaceAll('"', """);
|
|
145
|
+
}
|
|
146
|
+
// A per-user LaunchAgent: launchd loads every agent in ~/Library/LaunchAgents at login and
|
|
147
|
+
// RunAtLoad starts it once. No KeepAlive, so quitting Slopify keeps it quit until next login.
|
|
148
|
+
export function launchAgent(launcher, log) {
|
|
149
|
+
return [
|
|
150
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
151
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
152
|
+
`<!-- ${launcherMarker} → General → "Start Slopify when I log in". -->`,
|
|
153
|
+
'<plist version="1.0">',
|
|
154
|
+
"<dict>",
|
|
155
|
+
" <key>Label</key>",
|
|
156
|
+
` <string>${agentLabel}</string>`,
|
|
157
|
+
" <key>ProgramArguments</key>",
|
|
158
|
+
" <array>",
|
|
159
|
+
" <string>/bin/sh</string>",
|
|
160
|
+
` <string>${xmlText(launcher)}</string>`,
|
|
161
|
+
" </array>",
|
|
162
|
+
" <key>RunAtLoad</key>",
|
|
163
|
+
" <true/>",
|
|
164
|
+
" <key>KeepAlive</key>",
|
|
165
|
+
" <false/>",
|
|
166
|
+
" <key>ProcessType</key>",
|
|
167
|
+
" <string>Interactive</string>",
|
|
168
|
+
" <key>StandardOutPath</key>",
|
|
169
|
+
` <string>${xmlText(log)}</string>`,
|
|
170
|
+
" <key>StandardErrorPath</key>",
|
|
171
|
+
` <string>${xmlText(log)}</string>`,
|
|
172
|
+
"</dict>",
|
|
173
|
+
"</plist>",
|
|
174
|
+
"",
|
|
175
|
+
].join("\n");
|
|
176
|
+
}
|
|
@@ -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
|
+
}
|