@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.
Files changed (446) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +21 -40
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +3 -0
  6. package/dist/adapters/alignment/cache.js +4 -1
  7. package/dist/adapters/alignment/ctc.js +25 -23
  8. package/dist/adapters/alignment/index.js +37 -6
  9. package/dist/adapters/alignment/multilingual.js +267 -0
  10. package/dist/adapters/alignment/protocol.js +11 -0
  11. package/dist/adapters/alignment/quality.js +4 -3
  12. package/dist/adapters/alignment/sentences.js +95 -0
  13. package/dist/adapters/alignment/spec.js +24 -0
  14. package/dist/adapters/alignment/spell.js +769 -0
  15. package/dist/adapters/alignment/text.js +56 -9
  16. package/dist/adapters/alignment/window.js +34 -30
  17. package/dist/adapters/alignment/worker.js +23 -21
  18. package/dist/adapters/explain.js +11 -11
  19. package/dist/adapters/host-cli/index.js +44 -5
  20. package/dist/adapters/host-cli/transport.js +7 -5
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +1 -1
  23. package/dist/adapters/image/codex.js +87 -22
  24. package/dist/adapters/image/fal.js +13 -5
  25. package/dist/adapters/image/google.js +11 -8
  26. package/dist/adapters/image/openai.js +14 -7
  27. package/dist/adapters/image/reference.js +34 -0
  28. package/dist/adapters/image/replicate.js +7 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +36 -0
  31. package/dist/adapters/llm/claude-code.js +168 -32
  32. package/dist/adapters/llm/cli-login-error.js +1 -1
  33. package/dist/adapters/llm/codex-limits.js +157 -0
  34. package/dist/adapters/llm/codex.js +99 -12
  35. package/dist/adapters/llm/document-workspace.js +2 -2
  36. package/dist/adapters/llm/gemini-workspace.js +1 -1
  37. package/dist/adapters/llm/gemini.js +16 -3
  38. package/dist/adapters/llm/image-workspace.js +58 -0
  39. package/dist/adapters/llm/openrouter.js +11 -3
  40. package/dist/adapters/llm/run-cli.js +12 -7
  41. package/dist/adapters/tts/cartesia.js +10 -0
  42. package/dist/adapters/tts/elevenlabs.js +31 -9
  43. package/dist/adapters/tts/inworld-async.js +9 -5
  44. package/dist/adapters/tts/inworld.js +17 -3
  45. package/dist/adapters/tts/openai.js +3 -0
  46. package/dist/adapters/tts/voice-languages.js +80 -0
  47. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  48. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  49. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  50. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/OFL.txt +93 -0
  53. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  54. package/dist/assets/sample/sample-audiobook.tar +0 -0
  55. package/dist/assets/sample/sample-podcast.tar +0 -0
  56. package/dist/assets/sample/sample-project.tar +0 -0
  57. package/dist/catalog/merge.js +132 -0
  58. package/dist/catalog/registry.js +13 -13
  59. package/dist/catalog/schema.js +2 -0
  60. package/dist/catalog/store.js +147 -8
  61. package/dist/compose.yaml +71 -0
  62. package/dist/edge/autostart/docker-record.js +32 -0
  63. package/dist/edge/autostart/docker.js +50 -0
  64. package/dist/edge/autostart/index.js +78 -0
  65. package/dist/edge/autostart/launcher.js +176 -0
  66. package/dist/edge/autostart/model.js +3 -0
  67. package/dist/edge/autostart/native.js +210 -0
  68. package/dist/edge/autostart/prompt.js +66 -0
  69. package/dist/edge/autostart/service.js +81 -0
  70. package/dist/edge/cli.js +57 -51
  71. package/dist/edge/docker-install/activation.js +56 -0
  72. package/dist/edge/docker-install/apply.js +458 -0
  73. package/dist/edge/docker-install/engine.js +362 -0
  74. package/dist/edge/docker-install/run.js +154 -0
  75. package/dist/edge/docker-install/state.js +146 -0
  76. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  77. package/dist/edge/docker.js +14 -22
  78. package/dist/edge/host-cli.js +1 -1
  79. package/dist/edge/http/actions.js +32 -10
  80. package/dist/edge/http/app.js +58 -2
  81. package/dist/edge/http/auditions.js +70 -0
  82. package/dist/edge/http/autostart.js +37 -0
  83. package/dist/edge/http/channel-memory.js +92 -0
  84. package/dist/edge/http/channels.js +128 -0
  85. package/dist/edge/http/entries.js +22 -3
  86. package/dist/edge/http/files.js +1 -1
  87. package/dist/edge/http/folder-location.js +1 -1
  88. package/dist/edge/http/home.js +39 -0
  89. package/dist/edge/http/host-cli.js +29 -5
  90. package/dist/edge/http/narration-peaks.js +76 -0
  91. package/dist/edge/http/onboarding.js +152 -0
  92. package/dist/edge/http/open-folder.js +1 -1
  93. package/dist/edge/http/patch-notes.js +45 -0
  94. package/dist/edge/http/planning.js +4 -2
  95. package/dist/edge/http/project-create.js +87 -0
  96. package/dist/edge/http/projects.js +31 -99
  97. package/dist/edge/http/prompts.js +40 -3
  98. package/dist/edge/http/pronunciations.js +23 -4
  99. package/dist/edge/http/providers.js +86 -0
  100. package/dist/edge/http/reviews.js +121 -0
  101. package/dist/edge/http/revision-files.js +1 -1
  102. package/dist/edge/http/revisions.js +1 -1
  103. package/dist/edge/http/run-cost.js +25 -0
  104. package/dist/edge/http/schedules.js +111 -6
  105. package/dist/edge/http/settings.js +84 -6
  106. package/dist/edge/http/storage-files.js +50 -0
  107. package/dist/edge/http/storage.js +29 -2
  108. package/dist/edge/http/studio.js +207 -0
  109. package/dist/edge/http/style-preview.js +100 -0
  110. package/dist/edge/http/subtitles.js +1 -1
  111. package/dist/edge/http/trash.js +69 -0
  112. package/dist/edge/http/update.js +24 -3
  113. package/dist/edge/http/whats-new.js +15 -0
  114. package/dist/edge/http/youtube-edits.js +83 -0
  115. package/dist/edge/native-update.js +62 -0
  116. package/dist/host-cli/open-folder.js +10 -10
  117. package/dist/host-cli/runtime.js +9 -1
  118. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  119. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  120. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  121. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  122. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  123. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  124. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  125. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  126. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  127. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  128. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  129. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  130. package/dist/kernel/paths.js +27 -6
  131. package/dist/kernel/pipeline.js +4 -1
  132. package/dist/kernel/ports/host-cli.js +122 -14
  133. package/dist/kernel/ports/key-probe.js +1 -0
  134. package/dist/kernel/ports/languages.js +157 -0
  135. package/dist/kernel/ports/model.js +5 -2
  136. package/dist/kernel/ports/narration-aliases.js +60 -0
  137. package/dist/kernel/ports/plan-limits.js +36 -0
  138. package/dist/kernel/runner/attempt.js +17 -8
  139. package/dist/kernel/runner/audition.js +36 -0
  140. package/dist/kernel/runner/graph.js +24 -5
  141. package/dist/kernel/runner/index.js +35 -5
  142. package/dist/kernel/runner/meter.js +1 -0
  143. package/dist/kernel/runner/providers.js +142 -7
  144. package/dist/kernel/runner/retry-policy.js +43 -0
  145. package/dist/kernel/runner/standalone.js +134 -0
  146. package/dist/kernel/runner/work-authority.js +28 -2
  147. package/dist/main.js +272 -16
  148. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  149. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  150. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  151. package/dist/patch-notes/3.0.0.md +70 -0
  152. package/dist/patch-notes/index.json +26 -0
  153. package/dist/slices/admission/model.js +27 -0
  154. package/dist/slices/admission/repo.js +24 -4
  155. package/dist/slices/admission/rules.js +79 -1
  156. package/dist/slices/admission/schema.js +58 -1
  157. package/dist/slices/admission/short-mode.js +47 -0
  158. package/dist/slices/admission/start.js +41 -5
  159. package/dist/slices/article/continuation.js +2 -2
  160. package/dist/slices/article/provided-entries.js +2 -2
  161. package/dist/slices/article/run.js +6 -2
  162. package/dist/slices/article/segments.js +3 -3
  163. package/dist/slices/backups/folder.js +14 -6
  164. package/dist/slices/batch/index.js +7 -2
  165. package/dist/slices/cancel/index.js +1 -1
  166. package/dist/slices/channels/cast-images.js +110 -0
  167. package/dist/slices/channels/cast-match.js +32 -0
  168. package/dist/slices/channels/images.js +19 -0
  169. package/dist/slices/channels/model.js +5 -0
  170. package/dist/slices/channels/rebrand.js +100 -0
  171. package/dist/slices/channels/repo.js +218 -0
  172. package/dist/slices/channels/runs.js +129 -0
  173. package/dist/slices/channels/schema.js +129 -0
  174. package/dist/slices/channels/service.js +180 -0
  175. package/dist/slices/channels/studio-csv.js +110 -0
  176. package/dist/slices/channels/videos.js +120 -0
  177. package/dist/slices/checkpoints/recovery.js +10 -0
  178. package/dist/slices/control/index.js +2 -1
  179. package/dist/slices/document/fonts.js +1 -1
  180. package/dist/slices/document/render.js +1 -1
  181. package/dist/slices/episodes/related.js +90 -0
  182. package/dist/slices/episodes/repo.js +60 -0
  183. package/dist/slices/episodes/service.js +58 -0
  184. package/dist/slices/episodes/summarize.js +170 -0
  185. package/dist/slices/estimate/index.js +109 -28
  186. package/dist/slices/estimate/requests.js +60 -2
  187. package/dist/slices/fixes/rules.js +73 -0
  188. package/dist/slices/fonts/coverage.js +82 -0
  189. package/dist/slices/fonts/sfnt.js +74 -0
  190. package/dist/slices/images/run.js +8 -5
  191. package/dist/slices/images/scale.js +84 -0
  192. package/dist/slices/library/diff.js +122 -0
  193. package/dist/slices/library/history.js +100 -0
  194. package/dist/slices/library/model.js +3 -0
  195. package/dist/slices/library/photorealistic.js +49 -0
  196. package/dist/slices/library/repo.js +19 -14
  197. package/dist/slices/library/save.js +62 -12
  198. package/dist/slices/library/slots.js +7 -1
  199. package/dist/slices/library/used-by.js +153 -0
  200. package/dist/slices/loudness/level-pieces.js +41 -0
  201. package/dist/slices/loudness/loudnorm.js +164 -0
  202. package/dist/slices/loudness/model.js +153 -0
  203. package/dist/slices/model-upkeep/model.js +10 -0
  204. package/dist/slices/model-upkeep/switch.js +217 -0
  205. package/dist/slices/model-upkeep/usage.js +183 -0
  206. package/dist/slices/narration/aliases-library.js +31 -0
  207. package/dist/slices/narration/aliases-schema.js +53 -0
  208. package/dist/slices/narration/aliases.js +49 -0
  209. package/dist/slices/narration/blocks.js +352 -0
  210. package/dist/slices/narration/chunk.js +19 -7
  211. package/dist/slices/narration/concat.js +2 -2
  212. package/dist/slices/narration/describe.js +45 -0
  213. package/dist/slices/narration/pauses-model.js +63 -0
  214. package/dist/slices/narration/pauses.js +269 -0
  215. package/dist/slices/narration/peaks-model.js +1 -0
  216. package/dist/slices/narration/peaks.js +94 -0
  217. package/dist/slices/narration/preparation.js +21 -3
  218. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  219. package/dist/slices/narration/pronunciation.js +72 -25
  220. package/dist/slices/narration/run.js +6 -6
  221. package/dist/slices/narration/shared-glossary.js +1 -1
  222. package/dist/slices/narration/steering.js +15 -4
  223. package/dist/slices/notifications/notifier.js +21 -3
  224. package/dist/slices/notifications/rules.js +22 -1
  225. package/dist/slices/onboarding/copy.js +143 -0
  226. package/dist/slices/onboarding/first-run.js +46 -0
  227. package/dist/slices/onboarding/install.js +119 -0
  228. package/dist/slices/onboarding/model.js +64 -0
  229. package/dist/slices/onboarding/packs.js +491 -0
  230. package/dist/slices/onboarding/quick-short.js +129 -0
  231. package/dist/slices/onboarding/sample.js +132 -0
  232. package/dist/slices/onboarding/state.js +68 -0
  233. package/dist/slices/onboarding/template.js +73 -0
  234. package/dist/slices/patch-notes/library.js +73 -0
  235. package/dist/slices/patch-notes/seen.js +77 -0
  236. package/dist/slices/play-drafts/convert.js +125 -6
  237. package/dist/slices/play-drafts/readiness.js +3 -0
  238. package/dist/slices/play-drafts/repo.js +2 -0
  239. package/dist/slices/play-drafts/review-inputs.js +20 -3
  240. package/dist/slices/play-drafts/schema.js +89 -0
  241. package/dist/slices/play-drafts/service.js +4 -1
  242. package/dist/slices/play-drafts/start.js +43 -18
  243. package/dist/slices/project-templates/from-project.js +50 -4
  244. package/dist/slices/project-templates/one-off.js +26 -0
  245. package/dist/slices/project-templates/repo.js +2 -1
  246. package/dist/slices/project-templates/service.js +15 -4
  247. package/dist/slices/project-templates/setup.js +18 -2
  248. package/dist/slices/rebuild/admission-repo.js +1 -1
  249. package/dist/slices/rebuild/model.js +6 -0
  250. package/dist/slices/rebuild/preview-details.js +46 -3
  251. package/dist/slices/rebuild/preview-plan.js +13 -6
  252. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  253. package/dist/slices/rebuild/recipe-audio.js +66 -12
  254. package/dist/slices/rebuild/recipe-build.js +8 -4
  255. package/dist/slices/rebuild/recipe-cast.js +18 -0
  256. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  257. package/dist/slices/rebuild/recipe-describe.js +93 -0
  258. package/dist/slices/rebuild/recipe-edit.js +13 -1
  259. package/dist/slices/rebuild/recipe-exports.js +62 -8
  260. package/dist/slices/rebuild/recipe-input-schema.js +35 -0
  261. package/dist/slices/rebuild/recipe-legacy.js +9 -0
  262. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  263. package/dist/slices/rebuild/recipe-model.js +25 -6
  264. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  265. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  266. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  267. package/dist/slices/rebuild/recipe-reference.js +15 -1
  268. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  269. package/dist/slices/rebuild/recipe-save.js +4 -1
  270. package/dist/slices/rebuild/recipe-shorts.js +23 -3
  271. package/dist/slices/rebuild/recipe-text.js +114 -19
  272. package/dist/slices/rebuild/recipe-validation.js +14 -2
  273. package/dist/slices/rebuild/recipe-visual.js +96 -33
  274. package/dist/slices/rebuild/recipe-voices.js +286 -0
  275. package/dist/slices/rebuild/recipe-work.js +77 -6
  276. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  277. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  278. package/dist/slices/rebuild/recovery-model.js +3 -0
  279. package/dist/slices/rebuild/recovery-selection.js +28 -2
  280. package/dist/slices/rebuild/recovery.js +35 -17
  281. package/dist/slices/rebuild/repo.js +3 -3
  282. package/dist/slices/rebuild/retry.js +18 -0
  283. package/dist/slices/rebuild/review-redo.js +110 -0
  284. package/dist/slices/rebuild/runtime-actions.js +5 -0
  285. package/dist/slices/rebuild/runtime-admission.js +4 -1
  286. package/dist/slices/rebuild/runtime-animate.js +4 -4
  287. package/dist/slices/rebuild/runtime-article.js +5 -5
  288. package/dist/slices/rebuild/runtime-document.js +4 -4
  289. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  290. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  291. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  292. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  293. package/dist/slices/rebuild/runtime-export.js +84 -19
  294. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  295. package/dist/slices/rebuild/runtime-image.js +21 -2
  296. package/dist/slices/rebuild/runtime-local.js +148 -24
  297. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  298. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  299. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  300. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  301. package/dist/slices/rebuild/runtime-piece-label.js +27 -0
  302. package/dist/slices/rebuild/runtime-provider.js +126 -18
  303. package/dist/slices/rebuild/runtime-publication.js +21 -5
  304. package/dist/slices/rebuild/runtime-review.js +237 -0
  305. package/dist/slices/rebuild/runtime-run.js +17 -11
  306. package/dist/slices/rebuild/runtime-shorts.js +44 -17
  307. package/dist/slices/rebuild/runtime-store.js +39 -10
  308. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  309. package/dist/slices/rebuild/runtime-voices.js +103 -0
  310. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  311. package/dist/slices/rebuild/soften.js +49 -0
  312. package/dist/slices/rebuild/transition-repo.js +9 -2
  313. package/dist/slices/research/run.js +4 -4
  314. package/dist/slices/research/synthesis.js +2 -2
  315. package/dist/slices/reviews/model.js +105 -0
  316. package/dist/slices/reviews/outcome.js +22 -0
  317. package/dist/slices/reviews/repo.js +106 -0
  318. package/dist/slices/reviews/rules.js +63 -0
  319. package/dist/slices/reviews/verdict.js +71 -0
  320. package/dist/slices/reviews/view.js +30 -0
  321. package/dist/slices/revisions/adopt-content.js +5 -2
  322. package/dist/slices/revisions/adopt.js +3 -1
  323. package/dist/slices/revisions/downloads.js +7 -1
  324. package/dist/slices/revisions/image-plan.js +183 -0
  325. package/dist/slices/revisions/mutation-assets.js +10 -0
  326. package/dist/slices/revisions/mutation-cues.js +71 -0
  327. package/dist/slices/revisions/mutation-request.js +2 -1
  328. package/dist/slices/revisions/mutations.js +97 -3
  329. package/dist/slices/revisions/schema.js +11 -1
  330. package/dist/slices/run-cost/limits.js +128 -0
  331. package/dist/slices/run-cost/meter.js +47 -0
  332. package/dist/slices/run-cost/panel.js +234 -0
  333. package/dist/slices/run-cost/pricing.js +92 -0
  334. package/dist/slices/run-cost/week.js +108 -0
  335. package/dist/slices/schedules/agenda.js +158 -0
  336. package/dist/slices/schedules/repo.js +58 -18
  337. package/dist/slices/schedules/scheduler.js +67 -16
  338. package/dist/slices/schedules/schema.js +138 -1
  339. package/dist/slices/schedules/service.js +43 -4
  340. package/dist/slices/schedules/similar.js +90 -0
  341. package/dist/slices/schedules/topic-list.js +163 -0
  342. package/dist/slices/schedules/topics.js +422 -0
  343. package/dist/slices/settings/first-run.js +102 -0
  344. package/dist/slices/settings/health.js +190 -0
  345. package/dist/slices/settings/key-guides.js +128 -0
  346. package/dist/slices/settings/key-test.js +85 -0
  347. package/dist/slices/settings/model.js +6 -1
  348. package/dist/slices/settings/playback.js +15 -0
  349. package/dist/slices/settings/repo.js +17 -3
  350. package/dist/slices/settings/voices.js +64 -4
  351. package/dist/slices/settings/whats-new.js +64 -0
  352. package/dist/slices/shorts/captions.js +9 -0
  353. package/dist/slices/shorts/pick.js +3 -3
  354. package/dist/slices/shorts/prompts.js +1 -1
  355. package/dist/slices/shorts/render.js +54 -5
  356. package/dist/slices/storage/asset-name.js +3 -0
  357. package/dist/slices/storage/backup-export.js +48 -9
  358. package/dist/slices/storage/backup-format.js +38 -1
  359. package/dist/slices/storage/backup-import.js +191 -19
  360. package/dist/slices/storage/delete-project.js +7 -4
  361. package/dist/slices/storage/documents.js +79 -0
  362. package/dist/slices/storage/files-location.js +531 -0
  363. package/dist/slices/storage/layout.js +14 -4
  364. package/dist/slices/storage/model.js +11 -0
  365. package/dist/slices/storage/portable.js +92 -12
  366. package/dist/slices/storage/reconcile.js +14 -1
  367. package/dist/slices/storage/repo.js +4 -1
  368. package/dist/slices/storage/schema.js +11 -0
  369. package/dist/slices/storage/trim.js +146 -0
  370. package/dist/slices/studio/disclosure.js +74 -0
  371. package/dist/slices/studio/model.js +28 -0
  372. package/dist/slices/studio/pack.js +209 -0
  373. package/dist/slices/studio/settings.js +133 -0
  374. package/dist/slices/style-preview/images.js +61 -0
  375. package/dist/slices/style-preview/render.js +161 -0
  376. package/dist/slices/style-preview/schema.js +45 -0
  377. package/dist/slices/style-preview/service.js +93 -0
  378. package/dist/slices/style-preview/settings.js +89 -0
  379. package/dist/slices/subtitles/captions.js +42 -9
  380. package/dist/slices/subtitles/model.js +22 -0
  381. package/dist/slices/subtitles/prepare.js +27 -16
  382. package/dist/slices/subtitles/transcript.js +1 -1
  383. package/dist/slices/thumbnail/run.js +7 -7
  384. package/dist/slices/trash/model.js +25 -0
  385. package/dist/slices/trash/service.js +273 -0
  386. package/dist/slices/uploads/repo.js +20 -0
  387. package/dist/slices/video/ambient-bed-schema.js +15 -0
  388. package/dist/slices/video/ambient-bed.js +95 -0
  389. package/dist/slices/video/ambient-mix.js +72 -0
  390. package/dist/slices/video/audio-inputs.js +2 -2
  391. package/dist/slices/video/cards.js +5 -2
  392. package/dist/slices/video/edit-list.js +23 -2
  393. package/dist/slices/video/edit-settings.js +13 -3
  394. package/dist/slices/video/ffmpeg.js +63 -9
  395. package/dist/slices/video/figure-card.js +492 -0
  396. package/dist/slices/video/figure-spans.js +51 -0
  397. package/dist/slices/video/plan.js +125 -13
  398. package/dist/slices/video/run.js +14 -12
  399. package/dist/slices/video/slideshow.js +39 -4
  400. package/dist/slices/voices/attribution.js +69 -0
  401. package/dist/slices/voices/audio-files.js +82 -0
  402. package/dist/slices/voices/audition.js +25 -0
  403. package/dist/slices/voices/cast.js +56 -0
  404. package/dist/slices/voices/grouping.js +49 -0
  405. package/dist/slices/voices/join.js +35 -0
  406. package/dist/slices/voices/languages.js +25 -0
  407. package/dist/slices/voices/model.js +179 -0
  408. package/dist/slices/voices/palette.js +26 -0
  409. package/dist/slices/voices/panel.js +91 -0
  410. package/dist/slices/voices/portraits.js +55 -0
  411. package/dist/slices/voices/script.js +136 -0
  412. package/dist/slices/voices/timing.js +58 -0
  413. package/dist/slices/youtube/answer.js +1 -1
  414. package/dist/slices/youtube/chapters.js +126 -0
  415. package/dist/slices/youtube/edits-repo.js +69 -0
  416. package/dist/slices/youtube/edits.js +89 -0
  417. package/dist/slices/youtube/placeholders.js +98 -0
  418. package/dist/slices/youtube/transcript.js +11 -4
  419. package/dist/updater/service.js +66 -21
  420. package/dist/web/assets/index-CUA2sUxU.css +1 -0
  421. package/dist/web/assets/index-CX590kE-.js +303 -0
  422. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  423. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  424. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  425. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  426. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  427. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  428. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  429. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  430. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  431. package/dist/web/assets/{pdf-BLF2i8c2.js → pdf-BG8n_uc5.js} +1 -1
  432. package/dist/web/index.html +2 -2
  433. package/package.json +4 -4
  434. package/dist/edge/docker-launch.js +0 -81
  435. package/dist/edge/docker-projects/activation.js +0 -35
  436. package/dist/edge/docker-projects/claims.js +0 -85
  437. package/dist/edge/docker-projects/committed.js +0 -40
  438. package/dist/edge/docker-projects/engine.js +0 -461
  439. package/dist/edge/docker-projects/install.js +0 -254
  440. package/dist/edge/docker-projects/prune.js +0 -66
  441. package/dist/edge/docker-projects/recover.js +0 -100
  442. package/dist/edge/docker-projects/state.js +0 -225
  443. package/dist/web/assets/index-CktpxlJd.js +0 -171
  444. package/dist/web/assets/index-DB-vOnGU.css +0 -1
  445. package/scripts/docker-run.sh +0 -3
  446. /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
- if (values["projects-dir"] !== undefined && !values.docker)
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 starts Docker using API keys only; leave the option out to use the AI CLIs installed on this machine.`);
35
- if (values.docker) {
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 docker = await import("./docker.js");
39
- docker.assertManagedDockerHost(process.platform, process.getuid?.(), process.getgid?.());
40
- const { prepareDockerHostCli } = docker;
41
- const { nodeHostSetupRunner } = await import("../host-cli/install.js");
42
- const bridge = await prepareDockerHostCli({
43
- root: join(process.env.XDG_DATA_HOME ?? join(homedir(), ".local/share"), "slopify/host-cli"),
44
- version: readVersion(),
45
- image: process.env.SLOPIFY_DOCKER_IMAGE ?? "ghcr.io/gentbajko/slopify:latest",
46
- disabled: values["host-cli"] === "off",
47
- accepted: values["accept-host-cli"] === true,
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
- console.log(bridge.directory
63
- ? "Host CLI helper ready. Existing CLI logins stay on the host."
64
- : "Starting API-only Docker. Rerun the launcher after installing host CLIs to enable them.");
65
- const result = spawnSync("bash", [fileURLToPath(new URL("../../scripts/docker-run.sh", import.meta.url))], {
66
- stdio: "inherit",
67
- env: {
68
- ...process.env,
69
- SLOPIFY_HOST_CLI_DIR: bridge.directory ?? "",
70
- ...(values.port === undefined ? {} : { SLOPIFY_DOCKER_HOST_PORT: values.port }),
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
- if (result.error)
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 { paths, url, stop } = await boot(config, {
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
+ }