@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
@@ -17,7 +17,7 @@ import { keyStatus, removeProviderKey, saveProviderKey } from "../../slices/sett
17
17
  import { isUncataloguedProvider, providerById, providerIds } from "../../slices/settings/model.js";
18
18
  import { createModelCatalog } from "../../slices/settings/models.js";
19
19
  import { providerStatuses } from "../../slices/settings/readiness.js";
20
- import { onInvalid, problem, titleOf } from "./problem.js";
20
+ import { onInvalid, problem } from "./problem.js";
21
21
  const providerParam = z.object({ id: z.enum(providerIds) });
22
22
  const healthQuery = z.object({ provider: z.enum(providerIds).optional() });
23
23
  // ceiling: no format check is allowed on a key, so the only thing said about the value is
@@ -26,7 +26,7 @@ const healthQuery = z.object({ provider: z.enum(providerIds).optional() });
26
26
  const pathBody = z.object({ path: z.string().max(cliPathMaxLength) });
27
27
  const keyBody = z.object({ key: z.string().min(1).max(4096) });
28
28
  const modelId = z.string().min(1).max(200);
29
- const switchBody = z
29
+ export const switchBody = z
30
30
  .object({
31
31
  kind: z.enum(usageKinds),
32
32
  id: z.string().min(1).max(64),
@@ -53,6 +53,15 @@ export function providerRoutes(deps) {
53
53
  report: (provider) => deps.log.write("warn", "model-catalog", { detail: `Could not load models for ${provider}` }),
54
54
  });
55
55
  const upkeep = () => ({ ...deps, uuid: deps.drafts?.uuid ?? randomUUID });
56
+ // A picker opening reads the key's own model list again when it is due; never waited for.
57
+ const relist = (id) => {
58
+ if (deps.autoModelChecks !== true)
59
+ return;
60
+ void deps.modelListing?.refresh(id).catch((error) => {
61
+ deps.log.write("warn", "model-checks.listing", { detail: `${id}: ${String(error)}` });
62
+ });
63
+ };
64
+ const standingsOf = (id, models) => deps.modelChecks?.standings(id, models);
56
65
  return (new Hono()
57
66
  // The step lists Settings → Providers shows beside each key field.
58
67
  .get("/key-guides", (c) => c.json({ guides: keyGuides }))
@@ -70,7 +79,7 @@ export function providerRoutes(deps) {
70
79
  // or a sign-in fix-it's).
71
80
  .post("/health", zValidator("query", healthQuery, onInvalid), async (c) => {
72
81
  if (deps.fetch === undefined)
73
- return problem(c, { status: 503, title: titleOf(503), detail: noFetch });
82
+ return problem(c, { status: 503, detail: noFetch });
74
83
  const catalogue = deps.catalogue;
75
84
  return c.json(await checkProviderHealth({
76
85
  ...readiness,
@@ -87,7 +96,6 @@ export function providerRoutes(deps) {
87
96
  if (!deps.catalogue?.sync)
88
97
  return problem(c, {
89
98
  status: 503,
90
- title: titleOf(503),
91
99
  detail: "The model list (models.yaml) is not loaded yet. Wait a moment and reload the page; if it stays like this, restart Slopify.",
92
100
  });
93
101
  await deps.catalogue.sync();
@@ -97,21 +105,23 @@ export function providerRoutes(deps) {
97
105
  .get("/catalogue/retired", (c) => {
98
106
  c.header("Cache-Control", "no-store");
99
107
  return c.json({
100
- usages: deps.catalogue === undefined ? [] : retiredModelUsage(deps.db, deps.catalogue.read()),
108
+ usages: deps.catalogue === undefined
109
+ ? []
110
+ : retiredModelUsage(deps.db, deps.catalogue.read(), deps.catalogue.lifecycle?.()),
101
111
  });
102
112
  })
103
113
  .post("/catalogue/retired/switch", zValidator("json", switchBody, onInvalid), async (c) => {
104
114
  if (deps.catalogue === undefined)
105
- return problem(c, { status: 503, title: titleOf(503), detail: noFetch });
115
+ return problem(c, { status: 503, detail: noFetch });
106
116
  const result = await switchRetiredModel(upkeep(), deps.catalogue.read(), c.req.valid("json"));
107
117
  if (!result.ok)
108
- return problem(c, { status: 409, title: titleOf(409), detail: result.message });
118
+ return problem(c, { status: 409, detail: result.message });
109
119
  return c.json(result);
110
120
  })
111
121
  .post("/catalogue/retired/switch-all", async (c) => {
112
122
  if (deps.catalogue === undefined)
113
- return problem(c, { status: 503, title: titleOf(503), detail: noFetch });
114
- return c.json(await switchAllRetired(upkeep(), deps.catalogue.read()));
123
+ return problem(c, { status: 503, detail: noFetch });
124
+ return c.json(await switchAllRetired(upkeep(), deps.catalogue.read(), deps.catalogue.lifecycle?.()));
115
125
  })
116
126
  .get("/catalogue", (c) => c.json(deps.catalogue?.status() ?? {
117
127
  warning: "The model list (models.yaml) is not loaded yet. Wait a moment and reload the page; if it stays like this, restart Slopify.",
@@ -120,7 +130,6 @@ export function providerRoutes(deps) {
120
130
  if (!deps.catalogue)
121
131
  return problem(c, {
122
132
  status: 503,
123
- title: titleOf(503),
124
133
  detail: "The model list (models.yaml) is not loaded yet. Wait a moment and reload the page; if it stays like this, restart Slopify.",
125
134
  });
126
135
  try {
@@ -129,7 +138,6 @@ export function providerRoutes(deps) {
129
138
  catch {
130
139
  return problem(c, {
131
140
  status: 502,
132
- title: titleOf(502),
133
141
  detail: "Slopify could not download the latest model list. Your current list is still in use; check your internet connection and try Refresh again.",
134
142
  });
135
143
  }
@@ -155,30 +163,36 @@ export function providerRoutes(deps) {
155
163
  const { id } = c.req.valid("param");
156
164
  // Animate images lists the provider's image-to-video models, which only the catalogue
157
165
  // names.
158
- if (c.req.query("video") === "1")
166
+ relist(id);
167
+ if (c.req.query("video") === "1") {
168
+ const videos = deps.catalogue === undefined ? [] : videoModelsOf(deps.catalogue.read(), id);
159
169
  return c.json({
160
- models: deps.catalogue === undefined ? [] : videoModelsOf(deps.catalogue.read(), id),
170
+ models: videos,
161
171
  allowsCustom: false,
172
+ standings: standingsOf(id, videos),
162
173
  });
163
- if (deps.catalogue && !isUncataloguedProvider(id))
174
+ }
175
+ if (deps.catalogue && !isUncataloguedProvider(id)) {
176
+ const models = deps.catalogue
177
+ .models(id, providerById(id).family)
178
+ .map((m) => ({ ...m, ...catalogueModelInfo(m) }));
164
179
  return c.json({
165
- models: deps.catalogue
166
- .models(id, providerById(id).family)
167
- .map((m) => ({ ...m, ...catalogueModelInfo(m) })),
180
+ models,
168
181
  allowsCustom: false,
169
182
  notice: "Curated active models from models.yaml. Edit it locally or refresh in Settings.",
170
183
  warning: deps.catalogue.status().warning ?? undefined,
184
+ standings: standingsOf(id, models),
171
185
  });
186
+ }
172
187
  const result = await catalog.get(id, providerById(id).family, c.req.query("refresh") === "1");
173
188
  c.header("Cache-Control", "no-store");
174
- return c.json(result);
189
+ return c.json({ ...result, standings: standingsOf(id, result.models) });
175
190
  })
176
191
  .put("/:id/path", zValidator("param", providerParam, onInvalid), zValidator("json", pathBody, onInvalid), async (c) => {
177
192
  const result = await saveCliPath(readiness, c.req.valid("param").id, c.req.valid("json").path);
178
193
  if (!result.ok)
179
194
  return problem(c, {
180
195
  status: 400,
181
- title: titleOf(400),
182
196
  detail: result.message,
183
197
  extensions: { fields: [{ field: "path", message: result.message }] },
184
198
  });
@@ -192,6 +206,11 @@ export function providerRoutes(deps) {
192
206
  return refusal(c, id, result.reason);
193
207
  }
194
208
  catalog.invalidate(id);
209
+ // A new key: read its model list and check what is free to check, in the background.
210
+ if (deps.autoModelChecks === true)
211
+ void deps.modelChecks?.automatic("key-added", id).catch((error) => {
212
+ deps.log.write("warn", "model-checks", { detail: `${id}: ${String(error)}` });
213
+ });
195
214
  // The response says a key is stored and shows the mask, never the value.
196
215
  return c.json(keyStatus(keys, id));
197
216
  })
@@ -203,13 +222,12 @@ export function providerRoutes(deps) {
203
222
  if (providerById(id).auth !== "key")
204
223
  return refusal(c, id, "cli-provider");
205
224
  if (deps.fetch === undefined)
206
- return problem(c, { status: 503, title: titleOf(503), detail: noFetch });
225
+ return problem(c, { status: 503, detail: noFetch });
207
226
  const raw = await c.req.json().catch(() => undefined);
208
227
  const candidate = raw === undefined ? undefined : keyBody.safeParse(raw);
209
228
  if (candidate !== undefined && !candidate.success)
210
229
  return problem(c, {
211
230
  status: 400,
212
- title: titleOf(400),
213
231
  detail: `The pasted ${providerById(id).displayName} key could not be tested: it is empty or longer than any key. Paste the whole key in Settings → Providers → ${providerById(id).displayName}, then choose Test again.`,
214
232
  });
215
233
  return c.json(await testProviderKey({ db: deps.db, clock: deps.clock, fetch: deps.fetch, probes: keyProbes }, id, candidate?.data.key));
@@ -229,7 +247,6 @@ function refusal(c, id, reason) {
229
247
  if (reason === "cli-provider") {
230
248
  return problem(c, {
231
249
  status: 400,
232
- title: titleOf(400),
233
250
  detail: providerById(id).auth === "local"
234
251
  ? `${name} is your computer's own speech, so it has no API key to save. It works as soon as a speech program is installed.`
235
252
  : `${name} signs in through its own command-line tool, so it has no API key to save here. Sign in with that tool on your computer instead.`,
@@ -238,13 +255,11 @@ function refusal(c, id, reason) {
238
255
  if (reason === "absent") {
239
256
  return problem(c, {
240
257
  status: 404,
241
- title: titleOf(404),
242
258
  detail: `There is no saved API key for ${name}, so there is nothing to remove.`,
243
259
  });
244
260
  }
245
261
  return problem(c, {
246
262
  status: 400,
247
- title: titleOf(400),
248
263
  detail: "This API key cannot be saved yet. Paste the key and try again.",
249
264
  extensions: {
250
265
  fields: [{ field: "key", message: "Paste the API key from your provider's account page." }],
@@ -0,0 +1,29 @@
1
+ import { problem } from "./problem.js";
2
+ export function refusal(c, reason, rules) {
3
+ return problem(c, {
4
+ status: rules.statuses[reason] ?? rules.otherwise,
5
+ detail: rules.detail,
6
+ extensions: { reason, ...rules.extensions },
7
+ });
8
+ }
9
+ export function revisionRequired(c) {
10
+ return problem(c, {
11
+ status: 409,
12
+ detail: "This change is now made from the Edit tab. Choose Edit project, save your change, then use Choose what to remake (in the project's More menu).",
13
+ extensions: { reason: "revision-required" },
14
+ });
15
+ }
16
+ // A build without its bundled files says so in plain words; anything else is the app's error handler's.
17
+ export function bundleMissing(c, error, Missing) {
18
+ if (!(error instanceof Missing))
19
+ throw error;
20
+ return problem(c, { status: 500, detail: error.message });
21
+ }
22
+ export async function jsonBody(c) {
23
+ try {
24
+ return await c.req.json();
25
+ }
26
+ catch {
27
+ return undefined;
28
+ }
29
+ }
@@ -0,0 +1,40 @@
1
+ import { zValidator } from "@hono/zod-validator";
2
+ import { Hono } from "hono";
3
+ import { z } from "zod";
4
+ import { actorOf } from "./actor.js";
5
+ import { onInvalid, problem } from "./problem.js";
6
+ import { viaProxy } from "./same-page.js";
7
+ const body = z.object({
8
+ enabled: z.boolean(),
9
+ httpsPort: z.number().int().min(1).max(65535).optional(),
10
+ });
11
+ // Settings → General → Remote access: Slopify's page on the person's other devices through
12
+ // Tailscale serve. Reading it is open to agents (get_remote_access); turning it on or off is
13
+ // the person's alone, from this computer.
14
+ export function remoteAccessRoutes(service) {
15
+ const unavailable = "Remote access can't be set from this Slopify. Start it with npx @gentbajko/slopify and try Settings → General → Remote access again.";
16
+ return new Hono()
17
+ .get("/", async (c) => {
18
+ if (service === undefined)
19
+ return problem(c, { status: 404, detail: unavailable });
20
+ c.header("Cache-Control", "no-store");
21
+ const view = await service.view();
22
+ return c.json(viaProxy(c.req.raw.headers) ? { ...view, changeable: false } : view);
23
+ })
24
+ .put("/", zValidator("json", body, onInvalid), async (c) => {
25
+ if (service === undefined)
26
+ return problem(c, { status: 404, detail: unavailable });
27
+ if (actorOf(c) !== "person")
28
+ return problem(c, {
29
+ status: 403,
30
+ detail: "Remote access is the person's to turn on or off, in Settings → General → Remote access. Agents and the assistant can only read it (get_remote_access).",
31
+ });
32
+ if (viaProxy(c.req.raw.headers))
33
+ return problem(c, {
34
+ status: 403,
35
+ detail: "Remote access is turned on or off only on the computer Slopify runs on. Open Settings → General → Remote access there, at http://127.0.0.1 and Slopify's port.",
36
+ });
37
+ const { enabled, httpsPort } = c.req.valid("json");
38
+ return c.json(await service.set(enabled, httpsPort));
39
+ });
40
+ }
@@ -0,0 +1,71 @@
1
+ import { problem } from "./problem.js";
2
+ import { viaProxy } from "./same-page.js";
3
+ const id = "[^/]+";
4
+ export const hostOnlyWrites = [
5
+ {
6
+ method: "PUT|DELETE",
7
+ path: new RegExp(`^/api/providers/${id}/key/?$`),
8
+ what: "Provider keys are saved and removed (Settings → Providers)",
9
+ },
10
+ {
11
+ method: "PUT",
12
+ path: new RegExp(`^/api/providers/${id}/path/?$`),
13
+ what: "A CLI's path is set (Settings → Providers)",
14
+ },
15
+ {
16
+ method: "PUT",
17
+ path: /^\/api\/storage\/import\/?$/,
18
+ what: "A backup is imported (Settings → Backup & storage)",
19
+ },
20
+ {
21
+ method: "POST",
22
+ path: new RegExp(`^/api/storage/(?:cleanup|old-versions/delete|projects/${id}/(?:old-versions/delete|keep-outputs))/?$`),
23
+ what: "Clean-ups that delete files are run (Settings → Backup & storage)",
24
+ },
25
+ {
26
+ method: "POST",
27
+ path: /^\/api\/storage\/files\/move\/?$/,
28
+ what: "The Files folder is moved (Settings → Backup & storage)",
29
+ },
30
+ {
31
+ method: "PUT",
32
+ path: /^\/api\/backups\/?$/,
33
+ what: "The backup folder is set (Settings → Backup & storage)",
34
+ },
35
+ {
36
+ method: "POST|DELETE",
37
+ path: /^\/api\/update(?:\/activate)?\/?$/,
38
+ what: "Updates are installed (Settings → About)",
39
+ },
40
+ {
41
+ method: "PUT",
42
+ path: /^\/api\/settings\/autostart\/?$/,
43
+ what: "Start with this computer is turned on or off (Settings → General)",
44
+ },
45
+ ];
46
+ export const otherAccount = "Slopify refused this request: over remote access it answers only the Tailscale account that turned remote access on. Sign in to Tailscale on this device with that account, or open Slopify on the computer it runs on.";
47
+ export const hostOnlySentence = (what) => `${what} only on the computer Slopify runs on, not over remote access: open Slopify there, at http://127.0.0.1 and Slopify's port, and do it there.`;
48
+ export function remoteGuard(remoteAccess) {
49
+ return async (c, next) => {
50
+ const headers = c.req.raw.headers;
51
+ // The agents' endpoints refuse every proxied request themselves, with their own sentence.
52
+ if (!viaProxy(headers) || /^\/mcp(?:\/|$)/.test(c.req.path))
53
+ return next();
54
+ const owner = remoteAccess?.owner();
55
+ const remote = remoteAccess?.remoteHost();
56
+ const throughServe = headers.has("tailscale-user-login") ||
57
+ (remote !== undefined &&
58
+ (headers.get("x-forwarded-host") === remote || headers.get("host") === remote));
59
+ if (owner !== undefined && throughServe && headers.get("tailscale-user-login") !== owner)
60
+ return problem(c, { status: 403, detail: otherAccount });
61
+ const path = c.req.path;
62
+ const write = hostOnlyWrites.find((rule) => rule.method.split("|").includes(c.req.method) && rule.path.test(path));
63
+ if (write !== undefined)
64
+ return problem(c, {
65
+ status: 403,
66
+ detail: hostOnlySentence(write.what),
67
+ extensions: { reason: "host-only" },
68
+ });
69
+ return next();
70
+ };
71
+ }
@@ -0,0 +1,91 @@
1
+ import { isIP } from "node:net";
2
+ import { hostname } from "node:os";
3
+ import { actorOf } from "./actor.js";
4
+ import { problem } from "./problem.js";
5
+ import { forwardedHost, fromSlopifyPage } from "./same-page.js";
6
+ const nameOf = (host) => {
7
+ const trimmed = host.trim().toLowerCase();
8
+ if (trimmed.startsWith("["))
9
+ return trimmed.slice(1, trimmed.indexOf("]"));
10
+ const colon = trimmed.indexOf(":");
11
+ return colon === trimmed.lastIndexOf(":") && colon >= 0 ? trimmed.slice(0, colon) : trimmed;
12
+ };
13
+ const ownNames = () => {
14
+ const own = hostname().toLowerCase();
15
+ return own === "" ? [] : [own, `${own}.local`];
16
+ };
17
+ export function hostAllowed(host, rules) {
18
+ // An in-process request, or an HTTP/1.0 client, names no host.
19
+ if (host === undefined)
20
+ return true;
21
+ const name = nameOf(host);
22
+ if (name === "")
23
+ return false;
24
+ if (isIP(name) !== 0)
25
+ return true;
26
+ if (name === "localhost" || name.endsWith(".localhost"))
27
+ return true;
28
+ // This computer's tailnet name, also when the person serves Slopify through Tailscale
29
+ // themselves: a ts.net name only resolves inside a tailnet, never to someone's loopback.
30
+ if (name.endsWith(".ts.net"))
31
+ return true;
32
+ const named = [
33
+ rules.listenHost,
34
+ rules.remoteHost === undefined ? undefined : nameOf(rules.remoteHost),
35
+ ...rules.extraHosts,
36
+ ...ownNames(),
37
+ ].flatMap((one) => (one === undefined || one.trim() === "" ? [] : [one.trim().toLowerCase()]));
38
+ return named.includes(name);
39
+ }
40
+ const formTypes = ["text/plain", "application/x-www-form-urlencoded", "multipart/form-data"];
41
+ const safeMethods = ["GET", "HEAD", "OPTIONS"];
42
+ // The paired extension's own routes: token-checked, with CORS for its origin alone.
43
+ const extensionRoutes = /^\/api\/(?:studio|social)\/ext(?:\/|$)/;
44
+ export function requestGuard(deps) {
45
+ return async (c, next) => {
46
+ if (actorOf(c) !== "person")
47
+ return next();
48
+ const rules = {
49
+ listenHost: deps.listenHost,
50
+ remoteHost: deps.remoteHost?.(),
51
+ extraHosts: deps.extraHosts,
52
+ };
53
+ const host = c.req.header("host");
54
+ const forwarded = forwardedHost(c);
55
+ const refused = !hostAllowed(host, rules)
56
+ ? (host ?? "")
57
+ : forwarded !== undefined && !hostAllowed(forwarded, rules)
58
+ ? forwarded
59
+ : undefined;
60
+ if (refused !== undefined) {
61
+ const shown = refused.replace(/[^\w.:[\]-]/g, "").slice(0, 100);
62
+ return problem(c, {
63
+ status: 403,
64
+ detail: `Slopify refused this request: it was addressed to ${shown}, a name this Slopify doesn't answer to, which is how a web page could reach it through your browser. Open Slopify at http://127.0.0.1 and its port. If you reach it by this name on purpose, start Slopify with SLOPIFY_ALLOWED_HOSTS=${nameOf(shown)}.`,
65
+ extensions: { reason: "foreign-host" },
66
+ });
67
+ }
68
+ if (safeMethods.includes(c.req.method) ||
69
+ !c.req.path.startsWith("/api/") ||
70
+ extensionRoutes.test(c.req.path))
71
+ return next();
72
+ const origin = c.req.header("origin");
73
+ if (origin !== undefined) {
74
+ if (fromSlopifyPage(c, deps.remoteHost))
75
+ return next();
76
+ return problem(c, {
77
+ status: 403,
78
+ detail: `Slopify refused this request: it came from a web page at another address (${origin.slice(0, 100)}). Changes are made only from Slopify's own page; open Slopify and try again there.`,
79
+ extensions: { reason: "foreign-origin" },
80
+ });
81
+ }
82
+ const type = (c.req.header("content-type") ?? "").split(";")[0]?.trim().toLowerCase() ?? "";
83
+ if (formTypes.includes(type) && c.req.header("x-slopify-client") === undefined)
84
+ return problem(c, {
85
+ status: 403,
86
+ detail: "Slopify refused this request: it looks like a form another web page sent. Slopify's page sends its changes itself; open Slopify and try again there. A script on this computer can send the header X-Slopify-Client: script.",
87
+ extensions: { reason: "form-request" },
88
+ });
89
+ return next();
90
+ };
91
+ }
@@ -1,6 +1,6 @@
1
1
  import { zValidator } from "@hono/zod-validator";
2
2
  import { Hono } from "hono";
3
- import { z } from "zod";
3
+ import { errorText } from "../../kernel/errors.js";
4
4
  import { revisionControlSchema } from "../../slices/control/revision-control-schema.js";
5
5
  import { recoverProject } from "../../slices/rebuild/recovery.js";
6
6
  import { readRecovery } from "../../slices/rebuild/recovery-repo.js";
@@ -10,13 +10,11 @@ import { latestReviews } from "../../slices/reviews/view.js";
10
10
  import { currentRevisionId } from "../../slices/revisions/repo.js";
11
11
  import { getRevisionView } from "../../slices/revisions/view.js";
12
12
  import { actorOf } from "./actor.js";
13
- import { onInvalid, problem, titleOf } from "./problem.js";
14
- const id = z
15
- .string()
16
- .min(1)
17
- .max(64)
18
- .regex(/^[0-9A-Za-z_-]+$/);
19
- const projectParam = z.object({ id });
13
+ import { idParam, idString } from "./ids.js";
14
+ import { onInvalid, problem } from "./problem.js";
15
+ import { refusal } from "./refusal.js";
16
+ const id = idString;
17
+ const projectParam = idParam;
20
18
  const verdictParam = projectParam.extend({ verdictId: id });
21
19
  // The project page's review column: each reviewed item's latest verdict with its reasons,
22
20
  // Overrule to accept a failed item as it is, and Redo to have it made again through the
@@ -24,13 +22,11 @@ const verdictParam = projectParam.extend({ verdictId: id });
24
22
  export function reviewRoutes(deps) {
25
23
  const gone = (c) => problem(c, {
26
24
  status: 404,
27
- title: titleOf(404),
28
25
  detail: "This review no longer exists; the project may have been deleted or restored from a backup. Reload the project page.",
29
26
  });
30
27
  const decided = (c, verdict) => problem(c, {
31
28
  status: 409,
32
- title: titleOf(409),
33
- detail: `This verdict was already decided: the item was ${verdict.action === "overruled" ? "kept as it is" : "sent to be made again"}${verdict.actionBy === "agent" ? " by an agent" : ""}, so nothing changed. Reload the project page (or call needs_attention) to see what waits now.`,
29
+ detail: `This verdict was already decided: the item was ${verdict.action === "overruled" ? "kept as it is" : "sent to be made again"}${verdict.actionBy === "agent" ? " by an agent" : verdict.actionBy === "assistant" ? " by the assistant" : ""}, so nothing changed. Reload the project page (or call needs_attention) to see what waits now.`,
34
30
  extensions: { reason: "decided" },
35
31
  });
36
32
  const notify = (projectId) => {
@@ -40,7 +36,7 @@ export function reviewRoutes(deps) {
40
36
  catch (error) {
41
37
  deps.log.write("error", "review.notify", {
42
38
  projectId,
43
- detail: error instanceof Error ? error.message : String(error),
39
+ detail: errorText(error),
44
40
  });
45
41
  }
46
42
  };
@@ -62,7 +58,6 @@ export function reviewRoutes(deps) {
62
58
  const { id: projectId, verdictId } = c.req.valid("param");
63
59
  const busy = () => problem(c, {
64
60
  status: 409,
65
- title: titleOf(409),
66
61
  detail: "This verdict can't be overruled: it passed, or Slopify has already started making the item again. Wait for the new version to land, then reload the project page and overrule its review if it fails again.",
67
62
  extensions: { reason: "conflict" },
68
63
  });
@@ -105,12 +100,10 @@ export function reviewRoutes(deps) {
105
100
  if (deps.rebuild === undefined)
106
101
  return problem(c, {
107
102
  status: 503,
108
- title: titleOf(503),
109
103
  detail: "Redo is not ready yet because Slopify is still starting. Wait a moment, reload the page and try again.",
110
104
  });
111
105
  const already = () => problem(c, {
112
106
  status: 409,
113
- title: titleOf(409),
114
107
  detail: "Slopify is already making this item again. Wait for it to land, then reload the project page.",
115
108
  extensions: { reason: "conflict" },
116
109
  });
@@ -138,11 +131,6 @@ export function reviewRoutes(deps) {
138
131
  }
139
132
  });
140
133
  function refused(c, result, projectId) {
141
- const status = result.reason === "no-project"
142
- ? 404
143
- : result.reason === "invalid-selection" || result.reason === "invalid-edit"
144
- ? 400
145
- : 409;
146
134
  const lead = result.reason === "running"
147
135
  ? "This item, or work that depends on it, is still running. Wait for it to finish, or Pause the project, then use Redo again."
148
136
  : result.reason === "readiness"
@@ -152,12 +140,11 @@ export function reviewRoutes(deps) {
152
140
  : result.reason === "invalid-selection"
153
141
  ? "This item can't be made again."
154
142
  : "Slopify couldn't make this item again.";
155
- return problem(c, {
156
- status,
157
- title: titleOf(status),
143
+ return refusal(c, result.reason, {
144
+ statuses: { "no-project": 404, "invalid-selection": 400, "invalid-edit": 400 },
145
+ otherwise: 409,
158
146
  detail: [lead, ...(result.fields ?? []).map((row) => row.message)].join(" "),
159
147
  extensions: {
160
- reason: result.reason,
161
148
  fields: result.fields ?? [],
162
149
  currentRevisionId: currentRevisionId(deps.db, projectId) ?? null,
163
150
  },
@@ -4,13 +4,11 @@ import { z } from "zod";
4
4
  import { findRevisionDownload, revisionSetZip, } from "../../slices/revisions/downloads.js";
5
5
  import { fileResponse } from "./byte-range.js";
6
6
  import { replyForFolder } from "./folder-location.js";
7
- import { onInvalid, problem, titleOf } from "./problem.js";
7
+ import { idString } from "./ids.js";
8
+ import { onInvalid, problem } from "./problem.js";
9
+ import { fromSlopifyPage } from "./same-page.js";
8
10
  import { waveformAnswer } from "./waveform.js";
9
- const id = z
10
- .string()
11
- .min(1)
12
- .max(64)
13
- .regex(/^[0-9A-Za-z_-]+$/);
11
+ const id = idString;
14
12
  const revisionParam = z.object({ projectId: id, revisionId: id });
15
13
  const recordParam = revisionParam.extend({ recordId: id });
16
14
  const folderParam = z.object({ id, revisionId: id, recordId: id });
@@ -66,11 +64,9 @@ export function revisionFileRoutes(deps) {
66
64
  }
67
65
  export function revisionFolderRoutes(deps) {
68
66
  return new Hono().post("/:id/revisions/:revisionId/:recordId/open-folder", zValidator("param", folderParam, onInvalid), async (c) => {
69
- const origin = c.req.header("origin");
70
- if (origin !== undefined && origin !== new URL(c.req.url).origin)
67
+ if (!fromSlopifyPage(c, deps.remoteAccess?.remoteHost))
71
68
  return problem(c, {
72
69
  status: 403,
73
- title: titleOf(403),
74
70
  detail: "For your safety, folders can only be opened from the Slopify page itself. Open Slopify and try again there.",
75
71
  });
76
72
  const { id: projectId, revisionId, recordId } = c.req.valid("param");
@@ -83,7 +79,6 @@ export function revisionFolderRoutes(deps) {
83
79
  function unavailable(c, reason) {
84
80
  return problem(c, {
85
81
  status: 404,
86
- title: titleOf(404),
87
82
  detail: reason === "missing-file"
88
83
  ? "This file was deleted or moved from the project folder. In the Edit tab, use Choose what to remake (in the project's More menu) to make it again."
89
84
  : reason === "no-images"
@@ -10,16 +10,14 @@ import { ensureBaseline, restoreRevision, saveRevision } from "../../slices/revi
10
10
  import { currentRevisionId, listRevisionHistory } from "../../slices/revisions/repo.js";
11
11
  import { restoreRevisionSchema, saveRevisionSchema } from "../../slices/revisions/schema.js";
12
12
  import { getRevisionView } from "../../slices/revisions/view.js";
13
- import { onInvalid, problem, titleOf } from "./problem.js";
14
- const id = z
15
- .string()
16
- .min(1)
17
- .max(64)
18
- .regex(/^[0-9A-Za-z_-]+$/);
19
- const projectParam = z.object({ id });
13
+ import { idParam, idString } from "./ids.js";
14
+ import { onInvalid, problem } from "./problem.js";
15
+ import { refusal } from "./refusal.js";
16
+ const id = idString;
17
+ const projectParam = idParam;
20
18
  const revisionParam = projectParam.extend({ revisionId: id });
21
- const saveBody = saveRevisionSchema.extend({ idempotencyKey: z.uuid() });
22
- const restoreBody = restoreRevisionSchema.extend({ idempotencyKey: z.uuid() });
19
+ export const saveBody = saveRevisionSchema.extend({ idempotencyKey: z.uuid() });
20
+ export const restoreBody = restoreRevisionSchema.extend({ idempotencyKey: z.uuid() });
23
21
  const statuses = {
24
22
  "no-project": 404,
25
23
  "no-revision": 404,
@@ -45,13 +43,11 @@ const details = {
45
43
  readiness: "Slopify cannot run this rebuild yet. Fix the problems listed, then try again.",
46
44
  };
47
45
  function refused(c, deps, projectId, value) {
48
- const status = statuses[value.reason];
49
- return problem(c, {
50
- status,
51
- title: titleOf(status),
46
+ return refusal(c, value.reason, {
47
+ statuses,
48
+ otherwise: 409,
52
49
  detail: details[value.reason],
53
50
  extensions: {
54
- reason: value.reason,
55
51
  currentRevisionId: currentRevisionId(deps.db, projectId) ?? null,
56
52
  fields: value.fields ?? [],
57
53
  },
@@ -60,7 +56,6 @@ function refused(c, deps, projectId, value) {
60
56
  function unavailable(c) {
61
57
  return problem(c, {
62
58
  status: 503,
63
- title: titleOf(503),
64
59
  detail: "Rebuilding is not ready yet because Slopify is still starting. Wait a moment, reload the page and try again.",
65
60
  });
66
61
  }
@@ -3,7 +3,7 @@ import { Hono } from "hono";
3
3
  import { z } from "zod";
4
4
  import { projectExists } from "../../slices/admission/repo.js";
5
5
  import { runCostOf } from "../../slices/run-cost/panel.js";
6
- import { onInvalid, problem, titleOf } from "./problem.js";
6
+ import { onInvalid, problem } from "./problem.js";
7
7
  const idParam = z.object({ id: z.string().min(1).max(64) });
8
8
  // The project's Run cost tab and its "Waiting for … limits" line: what the run's provider
9
9
  // calls used and cost, and any CLI plan limit a stage is waiting on. Read from the stored
@@ -17,7 +17,6 @@ export function runCostRoutes(deps) {
17
17
  if (!projectExists(deps.db, id))
18
18
  return problem(c, {
19
19
  status: 404,
20
- title: titleOf(404),
21
20
  detail: "This project no longer exists. Go back to Projects to pick another.",
22
21
  });
23
22
  return c.json(runCostOf(deps.db, id));