@gentbajko/slopify 3.25.8 → 4.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 (604) hide show
  1. package/dist/adapters/account-limits.js +30 -0
  2. package/dist/adapters/alignment/cache.js +3 -5
  3. package/dist/adapters/alignment/ctc.js +1 -2
  4. package/dist/adapters/alignment/lock.js +4 -3
  5. package/dist/adapters/alignment/multilingual.js +0 -3
  6. package/dist/adapters/alignment/prefetch.js +2 -1
  7. package/dist/adapters/alignment/worker.js +2 -1
  8. package/dist/adapters/explain.js +0 -3
  9. package/dist/adapters/ffmpeg.js +19 -1
  10. package/dist/adapters/host-cli/index.js +19 -3
  11. package/dist/adapters/http-failure.js +53 -0
  12. package/dist/adapters/image/codex.js +36 -4
  13. package/dist/adapters/image/fal.js +17 -50
  14. package/dist/adapters/image/google.js +31 -55
  15. package/dist/adapters/image/openai.js +20 -47
  16. package/dist/adapters/image/reference.js +36 -0
  17. package/dist/adapters/image/replicate.js +18 -47
  18. package/dist/adapters/interest/search-interest.js +113 -0
  19. package/dist/adapters/key-probes.js +53 -2
  20. package/dist/adapters/llm/antigravity.js +4 -0
  21. package/dist/adapters/llm/assistant-cli.js +388 -0
  22. package/dist/adapters/llm/child-session.js +83 -0
  23. package/dist/adapters/llm/claude-code-models.js +51 -98
  24. package/dist/adapters/llm/claude-code.js +13 -2
  25. package/dist/adapters/llm/codex-limits.js +27 -68
  26. package/dist/adapters/llm/codex-server-models.js +61 -109
  27. package/dist/adapters/llm/codex.js +12 -1
  28. package/dist/adapters/llm/openrouter.js +10 -47
  29. package/dist/adapters/llm/run-cli.js +6 -22
  30. package/dist/adapters/llm/sse-lines.js +5 -1
  31. package/dist/adapters/model-access.js +37 -0
  32. package/dist/adapters/tts/cartesia.js +15 -46
  33. package/dist/adapters/tts/elevenlabs.js +20 -52
  34. package/dist/adapters/tts/gemini.js +19 -40
  35. package/dist/adapters/tts/inworld-async.js +4 -1
  36. package/dist/adapters/tts/inworld.js +3 -0
  37. package/dist/adapters/tts/openai.js +20 -50
  38. package/dist/assets/models-lifecycle.yaml +6 -0
  39. package/dist/assistant-wiring.js +60 -0
  40. package/dist/autopilot-timers.js +59 -0
  41. package/dist/catalog/lifecycle.js +52 -0
  42. package/dist/catalog/propose.js +204 -0
  43. package/dist/catalog/registry.js +3 -1
  44. package/dist/catalog/schema.js +2 -0
  45. package/dist/catalog/store.js +39 -0
  46. package/dist/edge/autostart/native.js +3 -5
  47. package/dist/edge/autostart/prompt.js +2 -1
  48. package/dist/edge/autostart/service.js +3 -4
  49. package/dist/edge/cli-args.js +3 -4
  50. package/dist/edge/cli.js +2 -1
  51. package/dist/edge/docker-install/apply.js +11 -11
  52. package/dist/edge/docker-install/run.js +2 -1
  53. package/dist/edge/docker-install/state.js +4 -3
  54. package/dist/edge/docker-install/tree.js +2 -4
  55. package/dist/edge/docker-install/volume.js +3 -2
  56. package/dist/edge/docker.js +5 -4
  57. package/dist/edge/events/hub.js +5 -5
  58. package/dist/edge/http/actions.js +4 -25
  59. package/dist/edge/http/actor.js +5 -3
  60. package/dist/edge/http/app.js +85 -10
  61. package/dist/edge/http/assistant.js +127 -0
  62. package/dist/edge/http/audio-preview.js +4 -11
  63. package/dist/edge/http/auditions.js +1 -1
  64. package/dist/edge/http/autostart.js +5 -5
  65. package/dist/edge/http/backups.js +1 -3
  66. package/dist/edge/http/channel-memory.js +7 -20
  67. package/dist/edge/http/channels.js +13 -26
  68. package/dist/edge/http/checkpoints.js +33 -17
  69. package/dist/edge/http/design-templates.js +167 -0
  70. package/dist/edge/http/designs.js +178 -0
  71. package/dist/edge/http/diagnostics.js +5 -1
  72. package/dist/edge/http/document-themes.js +2 -11
  73. package/dist/edge/http/draft-files.js +1 -3
  74. package/dist/edge/http/draft-problem.js +4 -9
  75. package/dist/edge/http/drafts.js +3 -3
  76. package/dist/edge/http/entries.js +6 -9
  77. package/dist/edge/http/extension-auth.js +27 -0
  78. package/dist/edge/http/external-reviews.js +12 -11
  79. package/dist/edge/http/files.js +5 -11
  80. package/dist/edge/http/folder-location.js +1 -4
  81. package/dist/edge/http/fonts.js +0 -7
  82. package/dist/edge/http/home.js +38 -13
  83. package/dist/edge/http/host-cli.js +18 -8
  84. package/dist/edge/http/ids.js +8 -0
  85. package/dist/edge/http/keywords.js +35 -0
  86. package/dist/edge/http/model-checks.js +132 -0
  87. package/dist/edge/http/narration-peaks.js +5 -13
  88. package/dist/edge/http/onboarding.js +1 -14
  89. package/dist/edge/http/open-folder.js +5 -10
  90. package/dist/edge/http/outputs.js +4 -12
  91. package/dist/edge/http/patch-notes.js +4 -10
  92. package/dist/edge/http/planning.js +2 -6
  93. package/dist/edge/http/problem.js +2 -4
  94. package/dist/edge/http/project-create.js +1 -0
  95. package/dist/edge/http/project-templates.js +29 -17
  96. package/dist/edge/http/projects.js +2 -12
  97. package/dist/edge/http/prompts.js +7 -14
  98. package/dist/edge/http/pronunciations.js +1 -2
  99. package/dist/edge/http/providers.js +39 -24
  100. package/dist/edge/http/refusal.js +29 -0
  101. package/dist/edge/http/remote-access.js +40 -0
  102. package/dist/edge/http/remote-guard.js +71 -0
  103. package/dist/edge/http/request-guard.js +91 -0
  104. package/dist/edge/http/reviews.js +11 -24
  105. package/dist/edge/http/revision-files.js +5 -10
  106. package/dist/edge/http/revisions.js +10 -15
  107. package/dist/edge/http/run-cost.js +1 -2
  108. package/dist/edge/http/same-page.js +80 -0
  109. package/dist/edge/http/schedules.js +59 -32
  110. package/dist/edge/http/settings.js +22 -13
  111. package/dist/edge/http/social.js +282 -0
  112. package/dist/edge/http/staging.js +2 -4
  113. package/dist/edge/http/storage-files.js +5 -7
  114. package/dist/edge/http/storage.js +23 -23
  115. package/dist/edge/http/studio.js +23 -50
  116. package/dist/edge/http/style-preview.js +1 -4
  117. package/dist/edge/http/subtitles.js +3 -17
  118. package/dist/edge/http/template-studio.js +433 -0
  119. package/dist/edge/http/trash.js +10 -11
  120. package/dist/edge/http/tutorial.js +1 -2
  121. package/dist/edge/http/tutorials.js +5 -11
  122. package/dist/edge/http/update.js +7 -13
  123. package/dist/edge/http/waveform.js +3 -6
  124. package/dist/edge/http/youtube-edits.js +2 -3
  125. package/dist/edge/mcp/assistant.js +279 -0
  126. package/dist/edge/mcp/attribution.js +8 -2
  127. package/dist/edge/mcp/call-route.js +11 -2
  128. package/dist/edge/mcp/excluded-routes.js +25 -0
  129. package/dist/edge/mcp/http.js +11 -2
  130. package/dist/edge/mcp/instructions.js +5 -3
  131. package/dist/edge/mcp/library-tools.js +1 -0
  132. package/dist/edge/mcp/route-tool.js +35 -15
  133. package/dist/edge/mcp/server.js +13 -8
  134. package/dist/edge/mcp/tools-channels.js +2 -0
  135. package/dist/edge/mcp/tools-designs.js +165 -0
  136. package/dist/edge/mcp/tools-library.js +14 -2
  137. package/dist/edge/mcp/tools-planning.js +23 -4
  138. package/dist/edge/mcp/tools-projects.js +28 -1
  139. package/dist/edge/mcp/tools-runs.js +14 -1
  140. package/dist/edge/mcp/tools-schedules.js +28 -4
  141. package/dist/edge/mcp/tools-setup.js +61 -3
  142. package/dist/edge/mcp/tools-social.js +46 -0
  143. package/dist/edge/mcp/tools-storage.js +8 -0
  144. package/dist/edge/mcp/tools-template-studio.js +166 -0
  145. package/dist/edge/mcp/tools.js +6 -0
  146. package/dist/edge/remote-access/cli.js +71 -0
  147. package/dist/edge/remote-access/model.js +32 -0
  148. package/dist/edge/remote-access/rules.js +207 -0
  149. package/dist/edge/remote-access/service.js +203 -0
  150. package/dist/edge/remote-access/store.js +44 -0
  151. package/dist/edge/remote-access/views.js +43 -0
  152. package/dist/extension/slopify-studio-chrome.zip +0 -0
  153. package/dist/extension/slopify-studio-firefox.zip +0 -0
  154. package/dist/host-cli/install.js +2 -2
  155. package/dist/host-cli/open-folder.js +2 -2
  156. package/dist/host-cli/paths.js +2 -4
  157. package/dist/host-cli/server.js +4 -4
  158. package/dist/host-cli/service.js +3 -3
  159. package/dist/host-cli/status.js +7 -7
  160. package/dist/kernel/db/migrations/0063-model-checks.sql +20 -0
  161. package/dist/kernel/db/migrations/0064-template-studio.sql +46 -0
  162. package/dist/kernel/db/migrations/0065-promise-and-assistant-reviews.sql +58 -0
  163. package/dist/kernel/db/migrations/0066-assistant.sql +44 -0
  164. package/dist/kernel/db/migrations/0067-autopilot.sql +6 -0
  165. package/dist/kernel/db/migrations/0068-topic-scores.sql +17 -0
  166. package/dist/kernel/db/migrations/0070-designs.sql +53 -0
  167. package/dist/kernel/db/migrations/0071-design-usage.sql +33 -0
  168. package/dist/kernel/db/migrations/0072-design-templates.sql +33 -0
  169. package/dist/kernel/db/migrations/0073-schedule-design-kind.sql +5 -0
  170. package/dist/kernel/db/migrations/0074-template-bakeoffs.sql +44 -0
  171. package/dist/kernel/db/migrations/0075-social-posts.sql +25 -0
  172. package/dist/kernel/db/migrations/0076-channel-social.sql +5 -0
  173. package/dist/kernel/db/migrations/0077-post-prompts.sql +17 -0
  174. package/dist/kernel/db/tx.js +2 -4
  175. package/dist/kernel/errors.js +27 -1
  176. package/dist/kernel/json-answer.js +15 -0
  177. package/dist/kernel/lock.js +4 -9
  178. package/dist/kernel/paths.js +8 -0
  179. package/dist/kernel/ports/assistant-cli.js +4 -0
  180. package/dist/kernel/ports/host-cli.js +3 -0
  181. package/dist/kernel/ports/interest.js +4 -0
  182. package/dist/kernel/ports/languages.js +0 -3
  183. package/dist/kernel/ports/model.js +2 -0
  184. package/dist/kernel/runner/attempt.js +3 -1
  185. package/dist/kernel/runner/audition.js +3 -1
  186. package/dist/kernel/runner/checkpoint-authority.js +2 -1
  187. package/dist/kernel/runner/index.js +8 -10
  188. package/dist/kernel/runner/model-access.js +40 -0
  189. package/dist/kernel/runner/model-check.js +153 -0
  190. package/dist/kernel/runner/providers.js +15 -10
  191. package/dist/kernel/runner/standalone.js +9 -5
  192. package/dist/main.js +334 -19
  193. package/dist/model-checks.js +166 -0
  194. package/dist/patch-notes/3.25.9.md +5 -0
  195. package/dist/patch-notes/4.0.0.md +51 -0
  196. package/dist/patch-notes/index.json +12 -0
  197. package/dist/slices/admission/heads.js +2 -1
  198. package/dist/slices/admission/repo.js +2 -1
  199. package/dist/slices/admission/rules.js +43 -8
  200. package/dist/slices/admission/schema.js +8 -0
  201. package/dist/slices/admission/stage-writes.js +0 -3
  202. package/dist/slices/admission/start.js +48 -31
  203. package/dist/slices/admission/substitute.js +7 -4
  204. package/dist/slices/agents/attention-posts.js +31 -0
  205. package/dist/slices/agents/attention-templates.js +33 -0
  206. package/dist/slices/agents/attention.js +10 -1
  207. package/dist/slices/agents/connection.js +2 -6
  208. package/dist/slices/agents/model.js +6 -3
  209. package/dist/slices/agents/repo.js +15 -7
  210. package/dist/slices/article/continuation.js +0 -63
  211. package/dist/slices/article/plain.js +3 -2
  212. package/dist/slices/article/segments.js +2 -62
  213. package/dist/slices/assistant/access.js +1 -0
  214. package/dist/slices/assistant/asks.js +80 -0
  215. package/dist/slices/assistant/cost.js +54 -0
  216. package/dist/slices/assistant/gate.js +222 -0
  217. package/dist/slices/assistant/model.js +79 -0
  218. package/dist/slices/assistant/names.js +23 -0
  219. package/dist/slices/assistant/prompt.js +74 -0
  220. package/dist/slices/assistant/repo.js +74 -0
  221. package/dist/slices/assistant/tokens.js +18 -0
  222. package/dist/slices/assistant/turn.js +342 -0
  223. package/dist/slices/autopilot/buffer.js +76 -0
  224. package/dist/slices/autopilot/model.js +14 -0
  225. package/dist/slices/autopilot/notify.js +27 -0
  226. package/dist/slices/autopilot/repo.js +34 -0
  227. package/dist/slices/autopilot/signals.js +194 -0
  228. package/dist/slices/autopilot/warnings.js +85 -0
  229. package/dist/slices/autopilot/weekly.js +100 -0
  230. package/dist/slices/backups/files.js +2 -6
  231. package/dist/slices/backups/repo.js +3 -15
  232. package/dist/slices/backups/service.js +4 -3
  233. package/dist/slices/channels/cast-images.js +2 -1
  234. package/dist/slices/channels/model.js +0 -1
  235. package/dist/slices/channels/rebrand.js +0 -3
  236. package/dist/slices/channels/repo.js +4 -0
  237. package/dist/slices/channels/schema.js +6 -0
  238. package/dist/slices/channels/service.js +10 -0
  239. package/dist/slices/channels/videos.js +2 -14
  240. package/dist/slices/checkpoints/index.js +0 -1
  241. package/dist/slices/control/providers.js +0 -26
  242. package/dist/slices/designs/artwork.js +66 -0
  243. package/dist/slices/designs/budget.js +23 -0
  244. package/dist/slices/designs/calls.js +124 -0
  245. package/dist/slices/designs/check-context.js +38 -0
  246. package/dist/slices/designs/check-text.js +43 -0
  247. package/dist/slices/designs/fields.js +88 -0
  248. package/dist/slices/designs/frames.js +34 -0
  249. package/dist/slices/designs/gate.js +128 -0
  250. package/dist/slices/designs/layout.js +293 -0
  251. package/dist/slices/designs/model.js +35 -0
  252. package/dist/slices/designs/pdf-job.js +23 -0
  253. package/dist/slices/designs/pdf-pages.js +30 -0
  254. package/dist/slices/designs/pdf-runner.js +68 -0
  255. package/dist/slices/designs/pdf-worker.js +24 -0
  256. package/dist/slices/designs/picture-size.js +79 -0
  257. package/dist/slices/designs/pictures.js +55 -0
  258. package/dist/slices/designs/quote.js +24 -0
  259. package/dist/slices/designs/render-args.js +36 -0
  260. package/dist/slices/designs/render-input.js +59 -0
  261. package/dist/slices/designs/render.js +215 -0
  262. package/dist/slices/designs/repo.js +101 -0
  263. package/dist/slices/designs/requests.js +80 -0
  264. package/dist/slices/designs/runner.js +239 -0
  265. package/dist/slices/designs/sample-repo.js +91 -0
  266. package/dist/slices/designs/sample.js +222 -0
  267. package/dist/slices/designs/schema.js +87 -0
  268. package/dist/slices/designs/sections.js +44 -0
  269. package/dist/slices/designs/service.js +233 -0
  270. package/dist/slices/designs/steps-repo.js +102 -0
  271. package/dist/slices/designs/studio-kind.js +39 -0
  272. package/dist/slices/designs/template-checks.js +44 -0
  273. package/dist/slices/designs/template-make.js +118 -0
  274. package/dist/slices/designs/templates.js +218 -0
  275. package/dist/slices/document/fonts.js +2 -1
  276. package/dist/slices/episodes/summarize.js +5 -1
  277. package/dist/slices/estimate/index.js +45 -7
  278. package/dist/slices/fonts/catalog.js +3 -2
  279. package/dist/slices/fonts/discovery.js +3 -2
  280. package/dist/slices/fonts/files.js +2 -6
  281. package/dist/slices/fonts/index.js +0 -1
  282. package/dist/slices/fonts/sfnt.js +45 -10
  283. package/dist/slices/images/appearance.js +6 -15
  284. package/dist/slices/images/scenes.js +100 -27
  285. package/dist/slices/keywords/digest.js +84 -0
  286. package/dist/slices/keywords/facts.js +92 -0
  287. package/dist/slices/keywords/fill.js +80 -0
  288. package/dist/slices/keywords/model.js +75 -0
  289. package/dist/slices/keywords/rules.js +187 -0
  290. package/dist/slices/keywords/schema.js +28 -0
  291. package/dist/slices/keywords/specs.js +260 -0
  292. package/dist/slices/keywords/values.js +74 -0
  293. package/dist/slices/library/lint.js +29 -12
  294. package/dist/slices/library/model.js +2 -0
  295. package/dist/slices/library/photorealistic.js +4 -12
  296. package/dist/slices/library/rename.js +9 -7
  297. package/dist/slices/library/save.js +14 -10
  298. package/dist/slices/library/slots.js +37 -11
  299. package/dist/slices/library/transfer.js +2 -1
  300. package/dist/slices/library/used-by.js +25 -10
  301. package/dist/slices/loudness/loudnorm.js +51 -11
  302. package/dist/slices/model-checks/cost.js +61 -0
  303. package/dist/slices/model-checks/listing.js +124 -0
  304. package/dist/slices/model-checks/model.js +20 -0
  305. package/dist/slices/model-checks/record.js +32 -0
  306. package/dist/slices/model-checks/repo.js +120 -0
  307. package/dist/slices/model-checks/rules.js +112 -0
  308. package/dist/slices/model-checks/run.js +143 -0
  309. package/dist/slices/model-checks/service.js +198 -0
  310. package/dist/slices/model-upkeep/switch.js +70 -25
  311. package/dist/slices/model-upkeep/usage.js +26 -11
  312. package/dist/slices/narration/breaths.js +2 -1
  313. package/dist/slices/narration/clean-pieces.js +2 -1
  314. package/dist/slices/narration/concat.js +2 -1
  315. package/dist/slices/narration/peaks.js +3 -4
  316. package/dist/slices/narration/pronunciation-chunks.js +4 -3
  317. package/dist/slices/narration/pronunciation.js +3 -2
  318. package/dist/slices/narration/steering.js +4 -3
  319. package/dist/slices/notifications/notifier.js +4 -3
  320. package/dist/slices/notifications/rules.js +5 -2
  321. package/dist/slices/notifications/settings.js +4 -12
  322. package/dist/slices/notifications/subject.js +2 -1
  323. package/dist/slices/onboarding/copy.js +2 -1
  324. package/dist/slices/onboarding/first-run.js +3 -2
  325. package/dist/slices/onboarding/full-video.js +3 -2
  326. package/dist/slices/onboarding/install.js +4 -3
  327. package/dist/slices/onboarding/model.js +0 -16
  328. package/dist/slices/onboarding/packs.js +22 -22
  329. package/dist/slices/onboarding/quick-short.js +2 -1
  330. package/dist/slices/onboarding/sample.js +0 -1
  331. package/dist/slices/onboarding/state.js +2 -11
  332. package/dist/slices/onboarding/template.js +2 -1
  333. package/dist/slices/patch-notes/library.js +3 -2
  334. package/dist/slices/play-drafts/convert.js +4 -0
  335. package/dist/slices/play-drafts/review-inputs.js +8 -2
  336. package/dist/slices/play-drafts/review.js +5 -4
  337. package/dist/slices/play-drafts/schema.js +4 -0
  338. package/dist/slices/project-templates/from-project.js +9 -2
  339. package/dist/slices/project-templates/repo.js +6 -1
  340. package/dist/slices/project-templates/setup.js +8 -0
  341. package/dist/slices/project-templates/stand-ins.js +22 -0
  342. package/dist/slices/rebuild/activity.js +2 -0
  343. package/dist/slices/rebuild/admission-repo.js +3 -2
  344. package/dist/slices/rebuild/narration-history.js +4 -5
  345. package/dist/slices/rebuild/narration-retry.js +2 -1
  346. package/dist/slices/rebuild/preview-details.js +27 -9
  347. package/dist/slices/rebuild/preview-plan.js +3 -8
  348. package/dist/slices/rebuild/recipe-build.js +8 -3
  349. package/dist/slices/rebuild/recipe-describe.js +3 -3
  350. package/dist/slices/rebuild/recipe-digest.js +58 -0
  351. package/dist/slices/rebuild/recipe-input-schema.js +10 -0
  352. package/dist/slices/rebuild/recipe-keywords.js +71 -0
  353. package/dist/slices/rebuild/recipe-legacy.js +5 -1
  354. package/dist/slices/rebuild/recipe-model.js +4 -0
  355. package/dist/slices/rebuild/recipe-narration-text.js +2 -1
  356. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  357. package/dist/slices/rebuild/recipe-reference.js +8 -4
  358. package/dist/slices/rebuild/recipe-reviews.js +27 -7
  359. package/dist/slices/rebuild/recipe-save.js +6 -3
  360. package/dist/slices/rebuild/recipe-scenes.js +40 -11
  361. package/dist/slices/rebuild/recipe-shorts.js +8 -8
  362. package/dist/slices/rebuild/recipe-social.js +46 -0
  363. package/dist/slices/rebuild/recipe-text.js +64 -20
  364. package/dist/slices/rebuild/recipe-validation.js +98 -28
  365. package/dist/slices/rebuild/recipe-visual.js +55 -16
  366. package/dist/slices/rebuild/recipe-work.js +15 -3
  367. package/dist/slices/rebuild/recipe-youtube.js +4 -3
  368. package/dist/slices/rebuild/recovery-selection.js +3 -1
  369. package/dist/slices/rebuild/recovery.js +3 -2
  370. package/dist/slices/rebuild/repo.js +4 -3
  371. package/dist/slices/rebuild/review-promise.js +25 -0
  372. package/dist/slices/rebuild/review-redo.js +2 -1
  373. package/dist/slices/rebuild/runtime-admission.js +4 -3
  374. package/dist/slices/rebuild/runtime-agent-review.js +11 -5
  375. package/dist/slices/rebuild/runtime-animate.js +9 -8
  376. package/dist/slices/rebuild/runtime-article.js +3 -2
  377. package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
  378. package/dist/slices/rebuild/runtime-document.js +8 -11
  379. package/dist/slices/rebuild/runtime-export-bed.js +6 -11
  380. package/dist/slices/rebuild/runtime-export-edit.js +6 -15
  381. package/dist/slices/rebuild/runtime-export-inputs.js +7 -7
  382. package/dist/slices/rebuild/runtime-export-short.js +8 -4
  383. package/dist/slices/rebuild/runtime-export.js +34 -12
  384. package/dist/slices/rebuild/runtime-figure-card.js +2 -1
  385. package/dist/slices/rebuild/runtime-image.js +4 -9
  386. package/dist/slices/rebuild/runtime-instructions.js +2 -0
  387. package/dist/slices/rebuild/runtime-keywords.js +59 -0
  388. package/dist/slices/rebuild/runtime-lines.js +2 -11
  389. package/dist/slices/rebuild/runtime-local.js +14 -20
  390. package/dist/slices/rebuild/runtime-narration-publication.js +2 -5
  391. package/dist/slices/rebuild/runtime-narration-reuse.js +9 -13
  392. package/dist/slices/rebuild/runtime-narration-text.js +10 -15
  393. package/dist/slices/rebuild/runtime-pictures.js +2 -4
  394. package/dist/slices/rebuild/runtime-piece-label.js +10 -6
  395. package/dist/slices/rebuild/runtime-plan.js +31 -5
  396. package/dist/slices/rebuild/runtime-provider.js +60 -19
  397. package/dist/slices/rebuild/runtime-publication.js +10 -9
  398. package/dist/slices/rebuild/runtime-review.js +33 -28
  399. package/dist/slices/rebuild/runtime-run.js +4 -1
  400. package/dist/slices/rebuild/runtime-shorts.js +34 -50
  401. package/dist/slices/rebuild/runtime-store.js +3 -8
  402. package/dist/slices/rebuild/runtime-subtitles.js +16 -14
  403. package/dist/slices/rebuild/runtime-voices.js +8 -15
  404. package/dist/slices/rebuild/runtime-youtube.js +11 -29
  405. package/dist/slices/rebuild/transition-repo.js +6 -5
  406. package/dist/slices/rebuild/work-records.js +6 -0
  407. package/dist/slices/reruns/cascade.js +1 -2
  408. package/dist/slices/reruns/index.js +6 -9
  409. package/dist/slices/reviews/external-model.js +3 -0
  410. package/dist/slices/reviews/external-repo.js +2 -1
  411. package/dist/slices/reviews/external-rows.js +2 -3
  412. package/dist/slices/reviews/model.js +26 -8
  413. package/dist/slices/reviews/repo.js +2 -1
  414. package/dist/slices/reviews/rules.js +1 -0
  415. package/dist/slices/reviews/view.js +5 -2
  416. package/dist/slices/revisions/downloads.js +4 -5
  417. package/dist/slices/revisions/mutation-assets.js +7 -4
  418. package/dist/slices/revisions/mutation-cues.js +2 -5
  419. package/dist/slices/revisions/mutations.js +0 -1
  420. package/dist/slices/revisions/repo.js +3 -1
  421. package/dist/slices/revisions/schema.js +1 -0
  422. package/dist/slices/revisions/view.js +20 -0
  423. package/dist/slices/run-cost/limits.js +5 -2
  424. package/dist/slices/run-cost/meter.js +12 -5
  425. package/dist/slices/run-cost/panel.js +8 -74
  426. package/dist/slices/run-cost/plan-readings.js +127 -0
  427. package/dist/slices/run-cost/plan-use.js +169 -0
  428. package/dist/slices/run-cost/pricing.js +3 -3
  429. package/dist/slices/run-cost/week.js +32 -4
  430. package/dist/slices/schedules/agenda.js +11 -2
  431. package/dist/slices/schedules/by-date.js +42 -0
  432. package/dist/slices/schedules/calendar.js +5 -0
  433. package/dist/slices/schedules/design-agenda.js +36 -0
  434. package/dist/slices/schedules/design-check.js +41 -0
  435. package/dist/slices/schedules/design-run.js +126 -0
  436. package/dist/slices/schedules/next-run.js +62 -0
  437. package/dist/slices/schedules/prepare.js +18 -2
  438. package/dist/slices/schedules/repo.js +24 -16
  439. package/dist/slices/schedules/rescore.js +35 -0
  440. package/dist/slices/schedules/scheduler.js +13 -3
  441. package/dist/slices/schedules/schema.js +54 -3
  442. package/dist/slices/schedules/score.js +107 -0
  443. package/dist/slices/schedules/service.js +57 -24
  444. package/dist/slices/schedules/topic-list.js +15 -0
  445. package/dist/slices/schedules/topic-scores.js +104 -0
  446. package/dist/slices/schedules/topics.js +48 -36
  447. package/dist/slices/settings/cli-paths.js +4 -6
  448. package/dist/slices/settings/first-run.js +2 -11
  449. package/dist/slices/settings/model-reach.js +3 -17
  450. package/dist/slices/settings/model.js +10 -0
  451. package/dist/slices/settings/playback.js +33 -22
  452. package/dist/slices/settings/probe-read.js +16 -0
  453. package/dist/slices/settings/repo.js +33 -0
  454. package/dist/slices/settings/tutorial.js +2 -2
  455. package/dist/slices/settings/voices.js +4 -12
  456. package/dist/slices/shorts/pick.js +2 -15
  457. package/dist/slices/shorts/prompts.js +2 -13
  458. package/dist/slices/shorts/render.js +3 -0
  459. package/dist/slices/social/channel.js +44 -0
  460. package/dist/slices/social/defaults.js +17 -0
  461. package/dist/slices/social/due.js +36 -0
  462. package/dist/slices/social/extension.js +18 -0
  463. package/dist/slices/social/fit.js +115 -0
  464. package/dist/slices/social/limits.js +186 -0
  465. package/dist/slices/social/links.js +21 -0
  466. package/dist/slices/social/media.js +131 -0
  467. package/dist/slices/social/model.js +75 -0
  468. package/dist/slices/social/notice.js +19 -0
  469. package/dist/slices/social/pack.js +176 -0
  470. package/dist/slices/social/post-prompt.js +55 -0
  471. package/dist/slices/social/queue.js +31 -0
  472. package/dist/slices/social/repo.js +133 -0
  473. package/dist/slices/social/reshape.js +97 -0
  474. package/dist/slices/social/rules.js +5 -0
  475. package/dist/slices/social/schema.js +70 -0
  476. package/dist/slices/social/serve.js +51 -0
  477. package/dist/slices/social/service.js +128 -0
  478. package/dist/slices/social/settings.js +21 -0
  479. package/dist/slices/social/sources.js +114 -0
  480. package/dist/slices/social/state.js +110 -0
  481. package/dist/slices/social/text.js +149 -0
  482. package/dist/slices/storage/arrange.js +3 -1
  483. package/dist/slices/storage/assets.js +8 -10
  484. package/dist/slices/storage/backup-designs.js +74 -0
  485. package/dist/slices/storage/backup-export.js +8 -3
  486. package/dist/slices/storage/backup-format.js +10 -0
  487. package/dist/slices/storage/backup-import.js +20 -2
  488. package/dist/slices/storage/delete-project.js +6 -5
  489. package/dist/slices/storage/files-location.js +6 -8
  490. package/dist/slices/storage/layout.js +13 -2
  491. package/dist/slices/storage/model.js +3 -0
  492. package/dist/slices/storage/portable.js +16 -11
  493. package/dist/slices/storage/schema.js +2 -0
  494. package/dist/slices/storage/staging-refs.js +2 -1
  495. package/dist/slices/storage/staging.js +6 -9
  496. package/dist/slices/storage/trim.js +10 -3
  497. package/dist/slices/studio/calendar.js +4 -1
  498. package/dist/slices/studio/pick.js +2 -11
  499. package/dist/slices/studio/plan.js +6 -15
  500. package/dist/slices/studio/queue.js +3 -14
  501. package/dist/slices/studio/releases.js +26 -0
  502. package/dist/slices/studio/settings.js +14 -51
  503. package/dist/slices/studio/videos.js +12 -0
  504. package/dist/slices/style-preview/render.js +34 -13
  505. package/dist/slices/style-preview/schema.js +13 -0
  506. package/dist/slices/style-preview/service.js +2 -1
  507. package/dist/slices/style-preview/settings.js +33 -5
  508. package/dist/slices/telemetry/collector-client.js +2 -4
  509. package/dist/slices/telemetry/flush.js +2 -4
  510. package/dist/slices/telemetry/model.js +0 -7
  511. package/dist/slices/telemetry/record.js +10 -3
  512. package/dist/slices/template-studio/anatomy-view.js +118 -0
  513. package/dist/slices/template-studio/anatomy.js +73 -0
  514. package/dist/slices/template-studio/bakeoff-make.js +225 -0
  515. package/dist/slices/template-studio/bakeoff-repo.js +83 -0
  516. package/dist/slices/template-studio/bakeoff.js +333 -0
  517. package/dist/slices/template-studio/budget.js +103 -0
  518. package/dist/slices/template-studio/checks.js +273 -0
  519. package/dist/slices/template-studio/document-sample.js +56 -0
  520. package/dist/slices/template-studio/edit.js +15 -0
  521. package/dist/slices/template-studio/for-all.js +161 -0
  522. package/dist/slices/template-studio/free-video.js +31 -0
  523. package/dist/slices/template-studio/free.js +176 -0
  524. package/dist/slices/template-studio/gate.js +40 -0
  525. package/dist/slices/template-studio/hidden.js +20 -0
  526. package/dist/slices/template-studio/jobs.js +79 -0
  527. package/dist/slices/template-studio/loudness.js +111 -0
  528. package/dist/slices/template-studio/make.js +40 -0
  529. package/dist/slices/template-studio/model.js +54 -0
  530. package/dist/slices/template-studio/pack-import.js +253 -0
  531. package/dist/slices/template-studio/pack-samples.js +60 -0
  532. package/dist/slices/template-studio/pack.js +205 -0
  533. package/dist/slices/template-studio/paid-view.js +66 -0
  534. package/dist/slices/template-studio/paid.js +348 -0
  535. package/dist/slices/template-studio/quote.js +148 -0
  536. package/dist/slices/template-studio/ready.js +104 -0
  537. package/dist/slices/template-studio/repo.js +134 -0
  538. package/dist/slices/template-studio/reviews-path.js +58 -0
  539. package/dist/slices/template-studio/runner.js +84 -0
  540. package/dist/slices/template-studio/save.js +236 -0
  541. package/dist/slices/template-studio/say-it-as.js +248 -0
  542. package/dist/slices/template-studio/schedule-gate.js +34 -0
  543. package/dist/slices/template-studio/schedule-price.js +30 -0
  544. package/dist/slices/template-studio/schedule-sample.js +87 -0
  545. package/dist/slices/template-studio/schema.js +218 -0
  546. package/dist/slices/template-studio/scope.js +124 -0
  547. package/dist/slices/template-studio/sections.js +176 -0
  548. package/dist/slices/template-studio/service.js +173 -0
  549. package/dist/slices/template-studio/status.js +174 -0
  550. package/dist/slices/template-studio/topic.js +223 -0
  551. package/dist/slices/trash/designs.js +76 -0
  552. package/dist/slices/trash/model.js +13 -4
  553. package/dist/slices/trash/service.js +52 -2
  554. package/dist/slices/tutorials/library.js +3 -2
  555. package/dist/slices/video/ass-text.js +17 -0
  556. package/dist/slices/video/audio-export.js +2 -64
  557. package/dist/slices/video/encoding.js +160 -0
  558. package/dist/slices/video/ffmpeg.js +23 -21
  559. package/dist/slices/video/figure-card.js +1 -15
  560. package/dist/slices/video/slideshow.js +43 -18
  561. package/dist/slices/voices/palette.js +0 -3
  562. package/dist/slices/youtube/answer.js +2 -15
  563. package/dist/slices/youtube/edits-repo.js +2 -11
  564. package/dist/tutorials/Agents-MCP.md +2 -1
  565. package/dist/tutorials/Assistant.md +42 -0
  566. package/dist/tutorials/Channels.md +2 -0
  567. package/dist/tutorials/Home-and-Projects.md +19 -0
  568. package/dist/tutorials/Install.md +1 -0
  569. package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
  570. package/dist/tutorials/Models.md +33 -1
  571. package/dist/tutorials/Notifications.md +6 -0
  572. package/dist/tutorials/Play-Images.md +4 -0
  573. package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
  574. package/dist/tutorials/Prompts.md +10 -6
  575. package/dist/tutorials/Remote-Access.md +63 -0
  576. package/dist/tutorials/Reviews-and-Checkpoints.md +13 -2
  577. package/dist/tutorials/Schedules.md +21 -1
  578. package/dist/tutorials/Settings-Reference.md +50 -8
  579. package/dist/tutorials/Template-Studio.md +125 -0
  580. package/dist/tutorials/Templates.md +3 -0
  581. package/dist/tutorials/_Sidebar.md +5 -0
  582. package/dist/updater/work-in-progress.js +42 -12
  583. package/dist/web/assets/index-CkAI_2E7.css +1 -0
  584. package/dist/web/assets/index-DcD0cdfo.js +334 -0
  585. package/dist/web/assets/{pdf-T7MCwjIv.js → pdf-TODRwF31.js} +1 -1
  586. package/dist/web/index.html +2 -2
  587. package/package.json +5 -1
  588. package/dist/slices/article/provided-entries.js +0 -66
  589. package/dist/slices/article/run.js +0 -145
  590. package/dist/slices/images/run.js +0 -234
  591. package/dist/slices/narration/legacy-plan.js +0 -58
  592. package/dist/slices/narration/run.js +0 -270
  593. package/dist/slices/rebuild/runtime-actions.js +0 -90
  594. package/dist/slices/research/run.js +0 -224
  595. package/dist/slices/subtitles/prepare.js +0 -194
  596. package/dist/slices/subtitles/transcript.js +0 -34
  597. package/dist/slices/thumbnail/run.js +0 -219
  598. package/dist/slices/video/audio-inputs.js +0 -33
  599. package/dist/slices/video/reuse-audio.js +0 -62
  600. package/dist/slices/video/run.js +0 -78
  601. package/dist/slices/video/write-export.js +0 -158
  602. package/dist/slices/video/write-subtitles.js +0 -59
  603. package/dist/web/assets/index-BadsoORk.css +0 -1
  604. package/dist/web/assets/index-D8LqNsCl.js +0 -326
@@ -0,0 +1,207 @@
1
+ import { z } from "zod";
2
+ import { httpsPortChoices } from "./model.js";
3
+ export const installLink = "https://tailscale.com/download";
4
+ export const dnsAdminLink = "https://login.tailscale.com/admin/dns";
5
+ const statusShape = z
6
+ .object({
7
+ BackendState: z.string(),
8
+ Self: z
9
+ .object({ DNSName: z.string().optional(), UserID: z.number().optional() })
10
+ .loose()
11
+ .optional(),
12
+ User: z
13
+ .record(z.string(), z.object({ LoginName: z.string().optional() }).loose())
14
+ .nullable()
15
+ .optional(),
16
+ CertDomains: z.array(z.string()).nullable().optional(),
17
+ })
18
+ .loose();
19
+ const handlerShape = z.object({ Proxy: z.string().optional() }).loose();
20
+ const webShape = z.object({ Handlers: z.record(z.string(), handlerShape).optional() }).loose();
21
+ const tcpShape = z.object({ HTTPS: z.boolean().optional(), HTTP: z.boolean().optional() }).loose();
22
+ const configShape = z
23
+ .object({
24
+ TCP: z.record(z.string(), tcpShape).nullable().optional(),
25
+ Web: z.record(z.string(), webShape).nullable().optional(),
26
+ AllowFunnel: z.record(z.string(), z.boolean()).nullable().optional(),
27
+ })
28
+ .loose();
29
+ const serveShape = configShape
30
+ .extend({ Foreground: z.record(z.string(), configShape).nullable().optional() })
31
+ .loose();
32
+ const deniedPattern = /access denied|permission denied|operator|must be root|not permitted/iu;
33
+ const notRunningPattern = /failed to connect|doesn't appear to be running|is tailscaled running|connection refused|no such file or directory|cannot connect/iu;
34
+ // A failed command's own reason, in the person's words.
35
+ export function problemOf(result, platform) {
36
+ if (result.missing)
37
+ return {
38
+ state: "not-installed",
39
+ summary: "Tailscale isn't installed on this computer, so Slopify can't open it on your other devices.",
40
+ fix: "Install Tailscale from tailscale.com/download, sign in, then press Check again.",
41
+ link: installLink,
42
+ };
43
+ if (result.timedOut)
44
+ return {
45
+ state: "failed",
46
+ summary: "Tailscale didn't answer within 10 seconds.",
47
+ fix: "Make sure the Tailscale app or service is running (open the Tailscale app, or run tailscale status in a terminal), then press Check again.",
48
+ link: null,
49
+ };
50
+ const text = `${result.stderr}\n${result.stdout}`.trim();
51
+ if (deniedPattern.test(text))
52
+ return deniedProblem(platform);
53
+ if (notRunningPattern.test(text))
54
+ return notRunningProblem();
55
+ return {
56
+ state: "failed",
57
+ summary: `Tailscale refused: ${firstLine(text) || `it stopped with code ${result.code}`}.`,
58
+ fix: "Run tailscale status in a terminal to see what Tailscale reports, fix that, then press Check again.",
59
+ link: null,
60
+ };
61
+ }
62
+ export function deniedProblem(platform) {
63
+ return {
64
+ state: "denied",
65
+ summary: "Tailscale won't let your user change its serve settings.",
66
+ fix: platform === "linux"
67
+ ? "On Linux, allow your user once: run sudo tailscale set --operator=$USER in a terminal, then press Check again."
68
+ : "Open the Tailscale app as the user who signed in to it, then press Check again.",
69
+ link: null,
70
+ };
71
+ }
72
+ function notRunningProblem() {
73
+ return {
74
+ state: "not-running",
75
+ summary: "Tailscale is installed but not running.",
76
+ fix: "Open the Tailscale app and connect, or run tailscale up in a terminal, then press Check again.",
77
+ link: null,
78
+ };
79
+ }
80
+ function firstLine(text) {
81
+ return (text.split("\n").find((line) => line.trim() !== "") ?? "").trim().replace(/\.$/u, "");
82
+ }
83
+ // Which machine this is, or why Tailscale can't serve it yet.
84
+ export function machineOf(stdout) {
85
+ let parsed;
86
+ try {
87
+ parsed = statusShape.parse(JSON.parse(stdout));
88
+ }
89
+ catch {
90
+ return {
91
+ state: "failed",
92
+ summary: "Slopify couldn't read tailscale status --json.",
93
+ fix: "Update Tailscale to a current version (tailscale.com/download), then press Check again.",
94
+ link: installLink,
95
+ };
96
+ }
97
+ if (parsed.BackendState === "NeedsLogin" || parsed.BackendState === "NeedsMachineAuth")
98
+ return {
99
+ state: "signed-out",
100
+ summary: parsed.BackendState === "NeedsMachineAuth"
101
+ ? "This computer is waiting for approval in your tailnet."
102
+ : "Tailscale is signed out on this computer.",
103
+ fix: parsed.BackendState === "NeedsMachineAuth"
104
+ ? "Approve this computer in the Tailscale admin console (login.tailscale.com/admin/machines), then press Check again."
105
+ : "Open the Tailscale app and sign in, or run tailscale up in a terminal, then press Check again.",
106
+ link: null,
107
+ };
108
+ if (parsed.BackendState !== "Running")
109
+ return notRunningProblem();
110
+ const dnsName = (parsed.Self?.DNSName ?? "").replace(/\.$/u, "");
111
+ const certs = parsed.CertDomains ?? [];
112
+ if (dnsName === "" || !certs.includes(dnsName))
113
+ return {
114
+ state: "https-off",
115
+ summary: "Your tailnet has no HTTPS certificates, so Tailscale can't give Slopify an https address.",
116
+ fix: "In the Tailscale admin console open DNS (login.tailscale.com/admin/dns), turn on MagicDNS if it is off, then under HTTPS Certificates press Enable HTTPS. Then press Check again.",
117
+ link: dnsAdminLink,
118
+ };
119
+ const userId = parsed.Self?.UserID;
120
+ const owner = userId === undefined ? undefined : parsed.User?.[String(userId)]?.LoginName;
121
+ return owner === undefined || owner === "" ? { dnsName } : { dnsName, owner };
122
+ }
123
+ export function isProblem(value) {
124
+ return "state" in value;
125
+ }
126
+ export function parseServeStatus(stdout) {
127
+ const text = stdout.trim();
128
+ if (text === "")
129
+ return {};
130
+ try {
131
+ return serveShape.parse(JSON.parse(text));
132
+ }
133
+ catch {
134
+ return undefined;
135
+ }
136
+ }
137
+ export const hostPort = (dnsName, httpsPort) => `${dnsName}:${httpsPort}`;
138
+ // The address a browser opens; 443 is https's own port, so it isn't written.
139
+ export function addressOf(dnsName, httpsPort) {
140
+ return httpsPort === 443 ? `https://${dnsName}` : `https://${dnsName}:${httpsPort}`;
141
+ }
142
+ const normal = (url) => url.replace(/\/+$/u, "");
143
+ function configsOf(status) {
144
+ return [status, ...Object.values(status.Foreground ?? {})];
145
+ }
146
+ // What uses an HTTPS port on this machine now, in the background config or a foreground
147
+ // `tailscale serve` still running in someone's terminal.
148
+ export function portUse(status, dnsName, httpsPort) {
149
+ const key = hostPort(dnsName, httpsPort);
150
+ const used = configsOf(status).filter((config) => config.TCP?.[String(httpsPort)] !== undefined || config.Web?.[key] !== undefined);
151
+ const [only] = used;
152
+ if (only === undefined)
153
+ return { kind: "free" };
154
+ const handlers = Object.entries(only.Web?.[key]?.Handlers ?? {});
155
+ const [first] = handlers;
156
+ if (used.length === 1 &&
157
+ only.TCP?.[String(httpsPort)]?.HTTPS === true &&
158
+ handlers.length === 1 &&
159
+ first !== undefined &&
160
+ first[0] === "/" &&
161
+ first[1].Proxy !== undefined)
162
+ return {
163
+ kind: "proxy",
164
+ target: normal(first[1].Proxy),
165
+ funnel: configsOf(status).some((config) => config.AllowFunnel?.[key] === true),
166
+ };
167
+ return {
168
+ kind: "other",
169
+ what: handlers.length === 0
170
+ ? "a TCP forwarder"
171
+ : handlers
172
+ .map(([path, handler]) => `${path} → ${handler.Proxy ?? "a file or text"}`)
173
+ .join(", "),
174
+ };
175
+ }
176
+ export function sameTarget(a, b) {
177
+ return normal(a) === normal(b);
178
+ }
179
+ export function freePort(status, dnsName, except) {
180
+ return (httpsPortChoices.find((port) => port !== except && portUse(status, dnsName, port).kind === "free") ?? null);
181
+ }
182
+ export function portTakenProblem(httpsPort, use, target, suggestedPort) {
183
+ const instead = suggestedPort === null
184
+ ? "Choose another HTTPS port below"
185
+ : `Press Use port ${suggestedPort} to give Slopify its own address`;
186
+ const what = use.kind === "proxy" ? `a proxy to ${use.target}` : use.what;
187
+ const yours = use.kind === "proxy" && sameTarget(use.target, target)
188
+ ? " It already points at this Slopify, but you set it up outside Slopify, so Slopify leaves it alone."
189
+ : "";
190
+ return {
191
+ state: "port-taken",
192
+ summary: `HTTPS port ${httpsPort} on your tailnet address is already used by another Tailscale serve setting (${what}).${yours}`,
193
+ fix: `${instead}, or remove the other setting with tailscale serve --https=${httpsPort} off and press Check again.`,
194
+ link: null,
195
+ };
196
+ }
197
+ export function serveRefusedProblem(result, platform) {
198
+ const text = `${result.stderr}\n${result.stdout}`;
199
+ if (!result.missing && !result.timedOut && /https|cert/iu.test(text) && !deniedPattern.test(text))
200
+ return {
201
+ state: "https-off",
202
+ summary: `Tailscale couldn't give Slopify an https address: ${firstLine(text)}.`,
203
+ fix: "In the Tailscale admin console open DNS (login.tailscale.com/admin/dns), turn on MagicDNS if it is off, then under HTTPS Certificates press Enable HTTPS. Then press Check again.",
204
+ link: dnsAdminLink,
205
+ };
206
+ return problemOf(result, platform);
207
+ }
@@ -0,0 +1,203 @@
1
+ import { tailscaleCommands } from "./cli.js";
2
+ import { freePort, isProblem, machineOf, parseServeStatus, portTakenProblem, portUse, problemOf, sameTarget, serveRefusedProblem, } from "./rules.js";
3
+ import { remoteAccessStore } from "./store.js";
4
+ import { remoteAccessViews } from "./views.js";
5
+ // ceiling: a view is a few CLI calls; an open Settings tab and agents reading it together
6
+ // share one answer for this long.
7
+ const cacheMs = 5_000;
8
+ export function createRemoteAccessService(deps) {
9
+ const store = remoteAccessStore(deps.db);
10
+ const { saved, record } = store;
11
+ const { loopbackTarget, base, fromProblem, dockerView, offView, funnelProblem, onView } = remoteAccessViews(deps, saved);
12
+ const diagnose = async () => {
13
+ const status = await deps.run(tailscaleCommands.status());
14
+ // `status --json` exits 1 while signed out but still prints the state.
15
+ const machine = status.stdout.trim().startsWith("{") && !status.missing
16
+ ? machineOf(status.stdout)
17
+ : problemOf(status, deps.platform);
18
+ if (isProblem(machine))
19
+ return machine;
20
+ const serveStatus = await deps.run(tailscaleCommands.serveStatus());
21
+ if (serveStatus.code !== 0)
22
+ return problemOf(serveStatus, deps.platform);
23
+ const serve = parseServeStatus(serveStatus.stdout);
24
+ if (serve === undefined)
25
+ return {
26
+ state: "failed",
27
+ summary: "Slopify couldn't read tailscale serve status --json.",
28
+ fix: "Update Tailscale to a current version (tailscale.com/download), then press Check again.",
29
+ link: "https://tailscale.com/download",
30
+ };
31
+ return { machine, serve };
32
+ };
33
+ const ready = (value) => !("state" in value);
34
+ const ownerOf = (machine) => machine.owner === undefined ? {} : { owner: machine.owner };
35
+ // Removes the handler Slopify added, only while it is still exactly that.
36
+ const removeOurs = async (now) => {
37
+ const ours = store.applied();
38
+ if (ours === undefined)
39
+ return undefined;
40
+ if (ours.dnsName === now.machine.dnsName) {
41
+ const use = portUse(now.serve, ours.dnsName, ours.httpsPort);
42
+ if (use.kind === "proxy" && sameTarget(use.target, ours.target)) {
43
+ const off = await deps.run(tailscaleCommands.serveOff(ours.httpsPort));
44
+ if (off.code !== 0)
45
+ return problemOf(off, deps.platform);
46
+ }
47
+ else if (use.kind !== "free")
48
+ deps.log.write("info", "remote-access.left", {
49
+ detail: `port ${ours.httpsPort} was changed outside Slopify, so it is left as it is`,
50
+ });
51
+ }
52
+ record(undefined);
53
+ return undefined;
54
+ };
55
+ const apply = async () => {
56
+ const want = saved();
57
+ const target = loopbackTarget();
58
+ let now = await diagnose();
59
+ if (!ready(now))
60
+ return fromProblem(now);
61
+ const ours = store.applied();
62
+ if (ours !== undefined &&
63
+ (ours.httpsPort !== want.httpsPort ||
64
+ !sameTarget(ours.target, target) ||
65
+ ours.dnsName !== now.machine.dnsName)) {
66
+ const removed = await removeOurs(now);
67
+ if (removed !== undefined)
68
+ return fromProblem(removed);
69
+ now = await diagnose();
70
+ if (!ready(now))
71
+ return fromProblem(now);
72
+ }
73
+ const { dnsName } = now.machine;
74
+ const use = portUse(now.serve, dnsName, want.httpsPort);
75
+ const before = store.applied();
76
+ const mine = before !== undefined && use.kind === "proxy" && sameTarget(use.target, before.target);
77
+ if (!mine && use.kind !== "free")
78
+ return fromProblem(portTakenProblem(want.httpsPort, use, target, freePort(now.serve, dnsName, want.httpsPort)), { suggestedPort: freePort(now.serve, dnsName, want.httpsPort) });
79
+ if (!mine) {
80
+ const on = await deps.run(tailscaleCommands.serveOn(want.httpsPort, target));
81
+ if (on.code !== 0)
82
+ return fromProblem(serveRefusedProblem(on, deps.platform));
83
+ record({ dnsName, httpsPort: want.httpsPort, target, ...ownerOf(now.machine) });
84
+ }
85
+ else if (before !== undefined &&
86
+ before.owner === undefined &&
87
+ now.machine.owner !== undefined)
88
+ record({ ...before, owner: now.machine.owner });
89
+ const after = await diagnose();
90
+ if (!ready(after))
91
+ return fromProblem(after);
92
+ const check = portUse(after.serve, dnsName, want.httpsPort);
93
+ if (check.kind !== "proxy" || !sameTarget(check.target, target))
94
+ return base("not-applied", "Tailscale accepted the command, but Slopify's address isn't in its serve settings.", {
95
+ fix: "Run tailscale serve status in a terminal to see what Tailscale has, then press Check again.",
96
+ });
97
+ if (check.funnel) {
98
+ await removeOurs(after);
99
+ return fromProblem(funnelProblem(want.httpsPort));
100
+ }
101
+ return onView(dnsName, want.httpsPort);
102
+ };
103
+ // A read: what is true now, without changing Tailscale.
104
+ const read = async () => {
105
+ if (deps.container)
106
+ return dockerView();
107
+ const want = saved();
108
+ if (!want.enabled)
109
+ return offView();
110
+ const now = await diagnose();
111
+ if (!ready(now))
112
+ return fromProblem(now);
113
+ const { dnsName } = now.machine;
114
+ const use = portUse(now.serve, dnsName, want.httpsPort);
115
+ const ours = store.applied();
116
+ if (ours !== undefined &&
117
+ ours.httpsPort === want.httpsPort &&
118
+ use.kind === "proxy" &&
119
+ sameTarget(use.target, ours.target))
120
+ return use.funnel
121
+ ? fromProblem(funnelProblem(want.httpsPort))
122
+ : onView(dnsName, want.httpsPort);
123
+ if (use.kind !== "free") {
124
+ const suggested = freePort(now.serve, dnsName, want.httpsPort);
125
+ return fromProblem(portTakenProblem(want.httpsPort, use, loopbackTarget(), suggested), {
126
+ suggestedPort: suggested,
127
+ });
128
+ }
129
+ return base("not-applied", "The switch is on, but Slopify's address isn't in Tailscale right now.", {
130
+ fix: "Press Check again to set it up again.",
131
+ });
132
+ };
133
+ // One change to Tailscale at a time.
134
+ let queue = Promise.resolve();
135
+ const serial = (work) => {
136
+ const next = queue.then(work, work);
137
+ queue = next.catch(() => undefined);
138
+ return next;
139
+ };
140
+ let cached;
141
+ const fresh = (view) => {
142
+ cached = { at: deps.now(), view };
143
+ view.catch(() => {
144
+ cached = undefined;
145
+ });
146
+ return view;
147
+ };
148
+ const turnOff = async () => {
149
+ if (store.applied() === undefined)
150
+ return offView();
151
+ const now = await diagnose();
152
+ const problem = ready(now) ? await removeOurs(now) : now;
153
+ if (problem === undefined)
154
+ return offView();
155
+ const port = store.applied()?.httpsPort ?? saved().httpsPort;
156
+ return fromProblem(problem, {
157
+ state: problem.state,
158
+ summary: `The switch is off, but Slopify couldn't remove its address from Tailscale yet: ${problem.summary} Slopify tries again at every start.`,
159
+ fix: `${problem.fix} Or remove it yourself with tailscale serve --https=${port} off.`,
160
+ });
161
+ };
162
+ return {
163
+ view: () => {
164
+ if (cached !== undefined && deps.now() - cached.at < cacheMs)
165
+ return cached.view;
166
+ return fresh(serial(read));
167
+ },
168
+ set: (enabled, httpsPort) => fresh(serial(async () => {
169
+ if (deps.container)
170
+ return dockerView();
171
+ store.save({ enabled, httpsPort: httpsPort ?? saved().httpsPort });
172
+ return enabled ? apply() : turnOff();
173
+ })),
174
+ start: () => serial(async () => {
175
+ cached = undefined;
176
+ if (deps.container)
177
+ return;
178
+ const view = saved().enabled ? await apply() : await turnOff();
179
+ if (view.state !== "on" && view.state !== "off")
180
+ deps.log.write("warn", "remote-access.start", {
181
+ detail: `${view.summary} ${view.fix ?? ""}`,
182
+ });
183
+ }),
184
+ stop: () => serial(async () => {
185
+ cached = undefined;
186
+ if (deps.container || store.applied() === undefined)
187
+ return;
188
+ const now = await diagnose();
189
+ const problem = ready(now) ? await removeOurs(now) : now;
190
+ if (problem !== undefined)
191
+ deps.log.write("warn", "remote-access.stop", { detail: problem.summary });
192
+ }),
193
+ owner: () => store.applied()?.owner,
194
+ remoteHost: () => {
195
+ const ours = store.applied();
196
+ return ours === undefined
197
+ ? undefined
198
+ : ours.httpsPort === 443
199
+ ? ours.dnsName
200
+ : `${ours.dnsName}:${ours.httpsPort}`;
201
+ },
202
+ };
203
+ }
@@ -0,0 +1,44 @@
1
+ import { z } from "zod";
2
+ import { deleteSetting, readSetting, writeSetting } from "../../slices/settings/repo.js";
3
+ import { defaultHttpsPort, remoteAccessAppliedKey, remoteAccessKey } from "./model.js";
4
+ // The two settings rows remote access keeps: the switch as the person set it, and the exact
5
+ // serve handler Slopify added (removed again only when it is still exactly that).
6
+ const savedShape = z.object({
7
+ enabled: z.boolean(),
8
+ httpsPort: z.number().int().min(1).max(65535),
9
+ });
10
+ const appliedShape = z.object({
11
+ dnsName: z.string(),
12
+ httpsPort: z.number().int(),
13
+ target: z.string(),
14
+ // The Tailscale account the computer was signed in to when Slopify added its address: the
15
+ // only one let in over the tailnet. Absent in what older versions saved.
16
+ owner: z.string().optional(),
17
+ });
18
+ function readJson(db, key, shape) {
19
+ const raw = readSetting(db, key);
20
+ if (raw === undefined)
21
+ return undefined;
22
+ try {
23
+ const parsed = shape.safeParse(JSON.parse(raw));
24
+ return parsed.success ? parsed.data : undefined;
25
+ }
26
+ catch {
27
+ return undefined;
28
+ }
29
+ }
30
+ export function remoteAccessStore(db) {
31
+ let applied = readJson(db, remoteAccessAppliedKey, appliedShape);
32
+ return {
33
+ saved: () => readJson(db, remoteAccessKey, savedShape) ?? { enabled: false, httpsPort: defaultHttpsPort },
34
+ applied: () => applied,
35
+ record: (next) => {
36
+ applied = next;
37
+ if (next === undefined)
38
+ deleteSetting(db, remoteAccessAppliedKey);
39
+ else
40
+ writeSetting(db, remoteAccessAppliedKey, JSON.stringify(next));
41
+ },
42
+ save: (next) => writeSetting(db, remoteAccessKey, JSON.stringify(next)),
43
+ };
44
+ }
@@ -0,0 +1,43 @@
1
+ import { addressOf } from "./rules.js";
2
+ export function remoteAccessViews(deps, saved) {
3
+ const loopbackTarget = () => {
4
+ const host = deps.listenHost;
5
+ const name = host === "" || host === "0.0.0.0" || host === "localhost" || host === "127.0.0.1"
6
+ ? "127.0.0.1"
7
+ : host === "::" || host === "::1"
8
+ ? "[::1]"
9
+ : host.includes(":")
10
+ ? `[${host}]`
11
+ : host;
12
+ return `http://${name}:${deps.port()}`;
13
+ };
14
+ const local = () => loopbackTarget();
15
+ const base = (state, summary, rest = {}) => ({
16
+ available: !deps.container,
17
+ enabled: saved().enabled,
18
+ state,
19
+ address: null,
20
+ httpsPort: saved().httpsPort,
21
+ suggestedPort: null,
22
+ summary,
23
+ fix: null,
24
+ link: null,
25
+ local: local(),
26
+ changeable: !deps.container,
27
+ ...rest,
28
+ });
29
+ const fromProblem = (problem, rest = {}) => base(problem.state, problem.summary, { fix: problem.fix, link: problem.link, ...rest });
30
+ const dockerView = () => base("docker", "Slopify runs in Docker here, so it can't set up Tailscale on the computer itself.", {
31
+ enabled: false,
32
+ fix: "Remote access needs Slopify started on the computer itself (npx @gentbajko/slopify). Or set Tailscale up yourself on the host: tailscale serve --bg --https=443 http://127.0.0.1:<Slopify's port>.",
33
+ });
34
+ const offView = () => base("off", `Off: Slopify answers only on this computer, at ${local()}.`);
35
+ const funnelProblem = (httpsPort) => ({
36
+ state: "funnel",
37
+ summary: "Tailscale Funnel was on for Slopify's address, which would put Slopify on the public internet. Slopify removed its address and refuses every request that comes through Funnel.",
38
+ fix: `Turn Funnel off with tailscale funnel --https=${httpsPort} off, then press Check again.`,
39
+ link: null,
40
+ });
41
+ const onView = (dnsName, httpsPort) => base("on", "On: any device in your tailnet can open this address, never the public internet. It works while Slopify runs: Slopify removes it when it stops and adds it back when it starts.", { address: addressOf(dnsName, httpsPort) });
42
+ return { loopbackTarget, base, fromProblem, dockerView, offView, funnelProblem, onView };
43
+ }
@@ -2,9 +2,9 @@ import { execFile } from "node:child_process";
2
2
  import { access, lstat, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
3
3
  import { basename, join } from "node:path";
4
4
  import { z } from "zod";
5
+ import { hasErrnoCode } from "../kernel/errors.js";
5
6
  import { isStableVersion, updatePackage } from "../updater/model.js";
6
7
  import { installArgs, npmCommand } from "../updater/plan.js";
7
- import { hasCode } from "./paths.js";
8
8
  export const nodeHostSetupRunner = {
9
9
  exec: (file, args, signal) => new Promise((resolve, reject) => {
10
10
  execFile(file, [...args], { signal, timeout: 15 * 60_000, maxBuffer: 1024 * 1024, windowsHide: true }, (error, stdout, stderr) => {
@@ -55,7 +55,7 @@ export async function installHostPackage(options) {
55
55
  return { entry: await entryAt(directory, version) };
56
56
  }
57
57
  catch (error) {
58
- if (!hasCode(error, "ENOENT"))
58
+ if (!hasErrnoCode(error, "ENOENT"))
59
59
  throw new Error(`The installed host helper in ${directory} is damaged. Delete that folder and run the launcher again. Nothing else was changed.`);
60
60
  }
61
61
  await mkdir(versions, { recursive: true, mode: 0o700 });
@@ -2,8 +2,8 @@ import { spawn } from "node:child_process";
2
2
  import { lstat, readdir } from "node:fs/promises";
3
3
  import { isAbsolute, join, relative, sep } from "node:path";
4
4
  import { installSchema, readState } from "../edge/docker-install/state.js";
5
+ import { hasErrnoCode } from "../kernel/errors.js";
5
6
  import { HostFolderRefused } from "../kernel/ports/host-cli.js";
6
- import { hasCode } from "./paths.js";
7
7
  const again = "run the Docker install again (npx @gentbajko/slopify@latest --docker)";
8
8
  /** Starts the desktop's opener and returns once it is running; the file manager is not awaited. */
9
9
  export function launchXdgOpen(directory) {
@@ -14,7 +14,7 @@ export function launchXdgOpen(directory) {
14
14
  stdio: "ignore",
15
15
  });
16
16
  child.once("error", (error) => {
17
- reject(new Error(hasCode(error, "ENOENT")
17
+ reject(new Error(hasErrnoCode(error, "ENOENT")
18
18
  ? "xdg-open isn't installed on this machine, so the Slopify host helper can't open a file manager. Install xdg-utils (for example sudo pacman -S xdg-utils or sudo apt install xdg-utils), then use Open folder again."
19
19
  : `The Slopify host helper could not start xdg-open to open ${directory}. Check that you are signed in to this computer's desktop, then use Open folder again.`));
20
20
  });
@@ -2,6 +2,7 @@ import { randomBytes } from "node:crypto";
2
2
  import { constants } from "node:fs";
3
3
  import { chmod, lstat, mkdir, open } from "node:fs/promises";
4
4
  import { isAbsolute, join, resolve } from "node:path";
5
+ import { hasErrnoCode } from "../kernel/errors.js";
5
6
  export async function prepareHostPaths(root) {
6
7
  if (!isAbsolute(root) || /[\p{Cc}]/u.test(root))
7
8
  throw new Error(`Unsafe host helper directory ${JSON.stringify(root)}: it must be a full path. Check XDG_DATA_HOME and HOME, then run the launcher again.`);
@@ -55,7 +56,7 @@ export async function ensureBridgeToken(path) {
55
56
  }
56
57
  }
57
58
  catch (error) {
58
- if (!hasCode(error, "EEXIST"))
59
+ if (!hasErrnoCode(error, "EEXIST"))
59
60
  throw error;
60
61
  }
61
62
  const entry = await lstat(path);
@@ -63,6 +64,3 @@ export async function ensureBridgeToken(path) {
63
64
  throw new Error(`The host helper token file ${path} is a link or belongs to another user. Delete it and run the launcher again.`);
64
65
  return readBridgeToken(path);
65
66
  }
66
- export function hasCode(error, code) {
67
- return typeof error === "object" && error !== null && "code" in error && error.code === code;
68
- }
@@ -4,7 +4,7 @@ import { connect } from "node:net";
4
4
  import { join } from "node:path";
5
5
  import { createAdaptorServer } from "@hono/node-server";
6
6
  import { hostCliRoutes, hostGate } from "../edge/http/host-cli.js";
7
- import { hasCode } from "./paths.js";
7
+ import { hasErrnoCode } from "../kernel/errors.js";
8
8
  export async function startHostServer(options) {
9
9
  const socket = join(options.directory, "cli.sock");
10
10
  if (Buffer.byteLength(socket) > 100)
@@ -23,7 +23,7 @@ export async function startHostServer(options) {
23
23
  await unlink(socket);
24
24
  }
25
25
  catch (error) {
26
- if (!hasCode(error, "ENOENT"))
26
+ if (!hasErrnoCode(error, "ENOENT"))
27
27
  throw error;
28
28
  }
29
29
  const gate = hostGate();
@@ -67,7 +67,7 @@ export async function startHostServer(options) {
67
67
  await unlink(socket);
68
68
  }
69
69
  catch (error) {
70
- if (!hasCode(error, "ENOENT"))
70
+ if (!hasErrnoCode(error, "ENOENT"))
71
71
  throw error;
72
72
  }
73
73
  })();
@@ -89,7 +89,7 @@ function listening(path) {
89
89
  });
90
90
  socket.once("error", (error) => {
91
91
  socket.destroy();
92
- if (hasCode(error, "ECONNREFUSED"))
92
+ if (hasErrnoCode(error, "ECONNREFUSED"))
93
93
  resolve(false);
94
94
  else
95
95
  reject(new Error("Could not verify existing host socket."));
@@ -3,9 +3,9 @@ import { lstat, mkdir, open, rename, unlink } from "node:fs/promises";
3
3
  import { isAbsolute, join } from "node:path";
4
4
  import { setTimeout as delay } from "node:timers/promises";
5
5
  import { hostRequest, readHostBytes } from "../adapters/host-cli/transport.js";
6
+ import { hasErrnoCode } from "../kernel/errors.js";
6
7
  import { bridgeLimits, hostHealthSchema } from "../kernel/ports/host-cli.js";
7
8
  import { hostEnvironment } from "./environment.js";
8
- import { hasCode } from "./paths.js";
9
9
  const marker = "# Managed by Slopify host CLI bridge, protocol 1";
10
10
  export const hostUnit = "slopify-cli-bridge.service";
11
11
  function unitArg(value) {
@@ -51,7 +51,7 @@ export async function privateRead(path, uid) {
51
51
  }
52
52
  }
53
53
  catch (error) {
54
- if (hasCode(error, "ENOENT"))
54
+ if (hasErrnoCode(error, "ENOENT"))
55
55
  return undefined;
56
56
  throw error;
57
57
  }
@@ -71,7 +71,7 @@ export async function privateWrite(path, text) {
71
71
  }
72
72
  finally {
73
73
  await unlink(temporary).catch((error) => {
74
- if (!hasCode(error, "ENOENT"))
74
+ if (!hasErrnoCode(error, "ENOENT"))
75
75
  throw error;
76
76
  });
77
77
  }