@gentbajko/slopify 3.25.9 → 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 (603) 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/4.0.0.md +51 -0
  195. package/dist/patch-notes/index.json +6 -0
  196. package/dist/slices/admission/heads.js +2 -1
  197. package/dist/slices/admission/repo.js +2 -1
  198. package/dist/slices/admission/rules.js +43 -8
  199. package/dist/slices/admission/schema.js +8 -0
  200. package/dist/slices/admission/stage-writes.js +0 -3
  201. package/dist/slices/admission/start.js +48 -31
  202. package/dist/slices/admission/substitute.js +7 -4
  203. package/dist/slices/agents/attention-posts.js +31 -0
  204. package/dist/slices/agents/attention-templates.js +33 -0
  205. package/dist/slices/agents/attention.js +10 -1
  206. package/dist/slices/agents/connection.js +2 -6
  207. package/dist/slices/agents/model.js +6 -3
  208. package/dist/slices/agents/repo.js +15 -7
  209. package/dist/slices/article/continuation.js +0 -63
  210. package/dist/slices/article/plain.js +3 -2
  211. package/dist/slices/article/segments.js +2 -62
  212. package/dist/slices/assistant/access.js +1 -0
  213. package/dist/slices/assistant/asks.js +80 -0
  214. package/dist/slices/assistant/cost.js +54 -0
  215. package/dist/slices/assistant/gate.js +222 -0
  216. package/dist/slices/assistant/model.js +79 -0
  217. package/dist/slices/assistant/names.js +23 -0
  218. package/dist/slices/assistant/prompt.js +74 -0
  219. package/dist/slices/assistant/repo.js +74 -0
  220. package/dist/slices/assistant/tokens.js +18 -0
  221. package/dist/slices/assistant/turn.js +342 -0
  222. package/dist/slices/autopilot/buffer.js +76 -0
  223. package/dist/slices/autopilot/model.js +14 -0
  224. package/dist/slices/autopilot/notify.js +27 -0
  225. package/dist/slices/autopilot/repo.js +34 -0
  226. package/dist/slices/autopilot/signals.js +194 -0
  227. package/dist/slices/autopilot/warnings.js +85 -0
  228. package/dist/slices/autopilot/weekly.js +100 -0
  229. package/dist/slices/backups/files.js +2 -6
  230. package/dist/slices/backups/repo.js +3 -15
  231. package/dist/slices/backups/service.js +4 -3
  232. package/dist/slices/channels/cast-images.js +2 -1
  233. package/dist/slices/channels/model.js +0 -1
  234. package/dist/slices/channels/rebrand.js +0 -3
  235. package/dist/slices/channels/repo.js +4 -0
  236. package/dist/slices/channels/schema.js +6 -0
  237. package/dist/slices/channels/service.js +10 -0
  238. package/dist/slices/channels/videos.js +2 -14
  239. package/dist/slices/checkpoints/index.js +0 -1
  240. package/dist/slices/control/providers.js +0 -26
  241. package/dist/slices/designs/artwork.js +66 -0
  242. package/dist/slices/designs/budget.js +23 -0
  243. package/dist/slices/designs/calls.js +124 -0
  244. package/dist/slices/designs/check-context.js +38 -0
  245. package/dist/slices/designs/check-text.js +43 -0
  246. package/dist/slices/designs/fields.js +88 -0
  247. package/dist/slices/designs/frames.js +34 -0
  248. package/dist/slices/designs/gate.js +128 -0
  249. package/dist/slices/designs/layout.js +293 -0
  250. package/dist/slices/designs/model.js +35 -0
  251. package/dist/slices/designs/pdf-job.js +23 -0
  252. package/dist/slices/designs/pdf-pages.js +30 -0
  253. package/dist/slices/designs/pdf-runner.js +68 -0
  254. package/dist/slices/designs/pdf-worker.js +24 -0
  255. package/dist/slices/designs/picture-size.js +79 -0
  256. package/dist/slices/designs/pictures.js +55 -0
  257. package/dist/slices/designs/quote.js +24 -0
  258. package/dist/slices/designs/render-args.js +36 -0
  259. package/dist/slices/designs/render-input.js +59 -0
  260. package/dist/slices/designs/render.js +215 -0
  261. package/dist/slices/designs/repo.js +101 -0
  262. package/dist/slices/designs/requests.js +80 -0
  263. package/dist/slices/designs/runner.js +239 -0
  264. package/dist/slices/designs/sample-repo.js +91 -0
  265. package/dist/slices/designs/sample.js +222 -0
  266. package/dist/slices/designs/schema.js +87 -0
  267. package/dist/slices/designs/sections.js +44 -0
  268. package/dist/slices/designs/service.js +233 -0
  269. package/dist/slices/designs/steps-repo.js +102 -0
  270. package/dist/slices/designs/studio-kind.js +39 -0
  271. package/dist/slices/designs/template-checks.js +44 -0
  272. package/dist/slices/designs/template-make.js +118 -0
  273. package/dist/slices/designs/templates.js +218 -0
  274. package/dist/slices/document/fonts.js +2 -1
  275. package/dist/slices/episodes/summarize.js +5 -1
  276. package/dist/slices/estimate/index.js +45 -7
  277. package/dist/slices/fonts/catalog.js +3 -2
  278. package/dist/slices/fonts/discovery.js +3 -2
  279. package/dist/slices/fonts/files.js +2 -6
  280. package/dist/slices/fonts/index.js +0 -1
  281. package/dist/slices/fonts/sfnt.js +45 -10
  282. package/dist/slices/images/appearance.js +6 -15
  283. package/dist/slices/images/scenes.js +100 -27
  284. package/dist/slices/keywords/digest.js +84 -0
  285. package/dist/slices/keywords/facts.js +92 -0
  286. package/dist/slices/keywords/fill.js +80 -0
  287. package/dist/slices/keywords/model.js +75 -0
  288. package/dist/slices/keywords/rules.js +187 -0
  289. package/dist/slices/keywords/schema.js +28 -0
  290. package/dist/slices/keywords/specs.js +260 -0
  291. package/dist/slices/keywords/values.js +74 -0
  292. package/dist/slices/library/lint.js +29 -12
  293. package/dist/slices/library/model.js +2 -0
  294. package/dist/slices/library/photorealistic.js +4 -12
  295. package/dist/slices/library/rename.js +9 -7
  296. package/dist/slices/library/save.js +14 -10
  297. package/dist/slices/library/slots.js +37 -11
  298. package/dist/slices/library/transfer.js +2 -1
  299. package/dist/slices/library/used-by.js +25 -10
  300. package/dist/slices/loudness/loudnorm.js +51 -11
  301. package/dist/slices/model-checks/cost.js +61 -0
  302. package/dist/slices/model-checks/listing.js +124 -0
  303. package/dist/slices/model-checks/model.js +20 -0
  304. package/dist/slices/model-checks/record.js +32 -0
  305. package/dist/slices/model-checks/repo.js +120 -0
  306. package/dist/slices/model-checks/rules.js +112 -0
  307. package/dist/slices/model-checks/run.js +143 -0
  308. package/dist/slices/model-checks/service.js +198 -0
  309. package/dist/slices/model-upkeep/switch.js +70 -25
  310. package/dist/slices/model-upkeep/usage.js +26 -11
  311. package/dist/slices/narration/breaths.js +2 -1
  312. package/dist/slices/narration/clean-pieces.js +2 -1
  313. package/dist/slices/narration/concat.js +2 -1
  314. package/dist/slices/narration/peaks.js +3 -4
  315. package/dist/slices/narration/pronunciation-chunks.js +4 -3
  316. package/dist/slices/narration/pronunciation.js +3 -2
  317. package/dist/slices/narration/steering.js +4 -3
  318. package/dist/slices/notifications/notifier.js +4 -3
  319. package/dist/slices/notifications/rules.js +5 -2
  320. package/dist/slices/notifications/settings.js +4 -12
  321. package/dist/slices/notifications/subject.js +2 -1
  322. package/dist/slices/onboarding/copy.js +2 -1
  323. package/dist/slices/onboarding/first-run.js +3 -2
  324. package/dist/slices/onboarding/full-video.js +3 -2
  325. package/dist/slices/onboarding/install.js +4 -3
  326. package/dist/slices/onboarding/model.js +0 -16
  327. package/dist/slices/onboarding/packs.js +22 -22
  328. package/dist/slices/onboarding/quick-short.js +2 -1
  329. package/dist/slices/onboarding/sample.js +0 -1
  330. package/dist/slices/onboarding/state.js +2 -11
  331. package/dist/slices/onboarding/template.js +2 -1
  332. package/dist/slices/patch-notes/library.js +3 -2
  333. package/dist/slices/play-drafts/convert.js +4 -0
  334. package/dist/slices/play-drafts/review-inputs.js +8 -2
  335. package/dist/slices/play-drafts/review.js +5 -4
  336. package/dist/slices/play-drafts/schema.js +4 -0
  337. package/dist/slices/project-templates/from-project.js +9 -2
  338. package/dist/slices/project-templates/repo.js +6 -1
  339. package/dist/slices/project-templates/setup.js +8 -0
  340. package/dist/slices/project-templates/stand-ins.js +22 -0
  341. package/dist/slices/rebuild/activity.js +2 -0
  342. package/dist/slices/rebuild/admission-repo.js +3 -2
  343. package/dist/slices/rebuild/narration-history.js +4 -5
  344. package/dist/slices/rebuild/narration-retry.js +2 -1
  345. package/dist/slices/rebuild/preview-details.js +27 -9
  346. package/dist/slices/rebuild/preview-plan.js +3 -8
  347. package/dist/slices/rebuild/recipe-build.js +8 -3
  348. package/dist/slices/rebuild/recipe-describe.js +3 -3
  349. package/dist/slices/rebuild/recipe-digest.js +58 -0
  350. package/dist/slices/rebuild/recipe-input-schema.js +10 -0
  351. package/dist/slices/rebuild/recipe-keywords.js +71 -0
  352. package/dist/slices/rebuild/recipe-legacy.js +5 -1
  353. package/dist/slices/rebuild/recipe-model.js +4 -0
  354. package/dist/slices/rebuild/recipe-narration-text.js +2 -1
  355. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  356. package/dist/slices/rebuild/recipe-reference.js +8 -4
  357. package/dist/slices/rebuild/recipe-reviews.js +27 -7
  358. package/dist/slices/rebuild/recipe-save.js +6 -3
  359. package/dist/slices/rebuild/recipe-scenes.js +40 -11
  360. package/dist/slices/rebuild/recipe-shorts.js +8 -8
  361. package/dist/slices/rebuild/recipe-social.js +46 -0
  362. package/dist/slices/rebuild/recipe-text.js +64 -20
  363. package/dist/slices/rebuild/recipe-validation.js +98 -28
  364. package/dist/slices/rebuild/recipe-visual.js +55 -16
  365. package/dist/slices/rebuild/recipe-work.js +15 -3
  366. package/dist/slices/rebuild/recipe-youtube.js +4 -3
  367. package/dist/slices/rebuild/recovery-selection.js +3 -1
  368. package/dist/slices/rebuild/recovery.js +3 -2
  369. package/dist/slices/rebuild/repo.js +4 -3
  370. package/dist/slices/rebuild/review-promise.js +25 -0
  371. package/dist/slices/rebuild/review-redo.js +2 -1
  372. package/dist/slices/rebuild/runtime-admission.js +4 -3
  373. package/dist/slices/rebuild/runtime-agent-review.js +11 -5
  374. package/dist/slices/rebuild/runtime-animate.js +9 -8
  375. package/dist/slices/rebuild/runtime-article.js +3 -2
  376. package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
  377. package/dist/slices/rebuild/runtime-document.js +8 -11
  378. package/dist/slices/rebuild/runtime-export-bed.js +6 -11
  379. package/dist/slices/rebuild/runtime-export-edit.js +6 -15
  380. package/dist/slices/rebuild/runtime-export-inputs.js +7 -7
  381. package/dist/slices/rebuild/runtime-export-short.js +8 -4
  382. package/dist/slices/rebuild/runtime-export.js +34 -12
  383. package/dist/slices/rebuild/runtime-figure-card.js +2 -1
  384. package/dist/slices/rebuild/runtime-image.js +4 -9
  385. package/dist/slices/rebuild/runtime-instructions.js +2 -0
  386. package/dist/slices/rebuild/runtime-keywords.js +59 -0
  387. package/dist/slices/rebuild/runtime-lines.js +2 -11
  388. package/dist/slices/rebuild/runtime-local.js +14 -20
  389. package/dist/slices/rebuild/runtime-narration-publication.js +2 -5
  390. package/dist/slices/rebuild/runtime-narration-reuse.js +9 -13
  391. package/dist/slices/rebuild/runtime-narration-text.js +10 -15
  392. package/dist/slices/rebuild/runtime-pictures.js +2 -4
  393. package/dist/slices/rebuild/runtime-piece-label.js +10 -6
  394. package/dist/slices/rebuild/runtime-plan.js +31 -5
  395. package/dist/slices/rebuild/runtime-provider.js +60 -19
  396. package/dist/slices/rebuild/runtime-publication.js +10 -9
  397. package/dist/slices/rebuild/runtime-review.js +33 -28
  398. package/dist/slices/rebuild/runtime-run.js +4 -1
  399. package/dist/slices/rebuild/runtime-shorts.js +25 -45
  400. package/dist/slices/rebuild/runtime-store.js +3 -8
  401. package/dist/slices/rebuild/runtime-subtitles.js +16 -14
  402. package/dist/slices/rebuild/runtime-voices.js +8 -15
  403. package/dist/slices/rebuild/runtime-youtube.js +11 -29
  404. package/dist/slices/rebuild/transition-repo.js +6 -5
  405. package/dist/slices/rebuild/work-records.js +6 -0
  406. package/dist/slices/reruns/cascade.js +1 -2
  407. package/dist/slices/reruns/index.js +6 -9
  408. package/dist/slices/reviews/external-model.js +3 -0
  409. package/dist/slices/reviews/external-repo.js +2 -1
  410. package/dist/slices/reviews/external-rows.js +2 -3
  411. package/dist/slices/reviews/model.js +26 -8
  412. package/dist/slices/reviews/repo.js +2 -1
  413. package/dist/slices/reviews/rules.js +1 -0
  414. package/dist/slices/reviews/view.js +5 -2
  415. package/dist/slices/revisions/downloads.js +4 -5
  416. package/dist/slices/revisions/mutation-assets.js +7 -4
  417. package/dist/slices/revisions/mutation-cues.js +2 -5
  418. package/dist/slices/revisions/mutations.js +0 -1
  419. package/dist/slices/revisions/repo.js +3 -1
  420. package/dist/slices/revisions/schema.js +1 -0
  421. package/dist/slices/revisions/view.js +20 -0
  422. package/dist/slices/run-cost/limits.js +5 -2
  423. package/dist/slices/run-cost/meter.js +12 -5
  424. package/dist/slices/run-cost/panel.js +8 -74
  425. package/dist/slices/run-cost/plan-readings.js +127 -0
  426. package/dist/slices/run-cost/plan-use.js +169 -0
  427. package/dist/slices/run-cost/pricing.js +3 -3
  428. package/dist/slices/run-cost/week.js +32 -4
  429. package/dist/slices/schedules/agenda.js +11 -2
  430. package/dist/slices/schedules/by-date.js +42 -0
  431. package/dist/slices/schedules/calendar.js +5 -0
  432. package/dist/slices/schedules/design-agenda.js +36 -0
  433. package/dist/slices/schedules/design-check.js +41 -0
  434. package/dist/slices/schedules/design-run.js +126 -0
  435. package/dist/slices/schedules/next-run.js +62 -0
  436. package/dist/slices/schedules/prepare.js +18 -2
  437. package/dist/slices/schedules/repo.js +24 -16
  438. package/dist/slices/schedules/rescore.js +35 -0
  439. package/dist/slices/schedules/scheduler.js +13 -3
  440. package/dist/slices/schedules/schema.js +54 -3
  441. package/dist/slices/schedules/score.js +107 -0
  442. package/dist/slices/schedules/service.js +57 -24
  443. package/dist/slices/schedules/topic-list.js +15 -0
  444. package/dist/slices/schedules/topic-scores.js +104 -0
  445. package/dist/slices/schedules/topics.js +48 -36
  446. package/dist/slices/settings/cli-paths.js +4 -6
  447. package/dist/slices/settings/first-run.js +2 -11
  448. package/dist/slices/settings/model-reach.js +3 -17
  449. package/dist/slices/settings/model.js +10 -0
  450. package/dist/slices/settings/playback.js +33 -22
  451. package/dist/slices/settings/probe-read.js +16 -0
  452. package/dist/slices/settings/repo.js +33 -0
  453. package/dist/slices/settings/tutorial.js +2 -2
  454. package/dist/slices/settings/voices.js +4 -12
  455. package/dist/slices/shorts/pick.js +2 -15
  456. package/dist/slices/shorts/prompts.js +2 -13
  457. package/dist/slices/shorts/render.js +3 -0
  458. package/dist/slices/social/channel.js +44 -0
  459. package/dist/slices/social/defaults.js +17 -0
  460. package/dist/slices/social/due.js +36 -0
  461. package/dist/slices/social/extension.js +18 -0
  462. package/dist/slices/social/fit.js +115 -0
  463. package/dist/slices/social/limits.js +186 -0
  464. package/dist/slices/social/links.js +21 -0
  465. package/dist/slices/social/media.js +131 -0
  466. package/dist/slices/social/model.js +75 -0
  467. package/dist/slices/social/notice.js +19 -0
  468. package/dist/slices/social/pack.js +176 -0
  469. package/dist/slices/social/post-prompt.js +55 -0
  470. package/dist/slices/social/queue.js +31 -0
  471. package/dist/slices/social/repo.js +133 -0
  472. package/dist/slices/social/reshape.js +97 -0
  473. package/dist/slices/social/rules.js +5 -0
  474. package/dist/slices/social/schema.js +70 -0
  475. package/dist/slices/social/serve.js +51 -0
  476. package/dist/slices/social/service.js +128 -0
  477. package/dist/slices/social/settings.js +21 -0
  478. package/dist/slices/social/sources.js +114 -0
  479. package/dist/slices/social/state.js +110 -0
  480. package/dist/slices/social/text.js +149 -0
  481. package/dist/slices/storage/arrange.js +3 -1
  482. package/dist/slices/storage/assets.js +8 -10
  483. package/dist/slices/storage/backup-designs.js +74 -0
  484. package/dist/slices/storage/backup-export.js +8 -3
  485. package/dist/slices/storage/backup-format.js +10 -0
  486. package/dist/slices/storage/backup-import.js +20 -2
  487. package/dist/slices/storage/delete-project.js +6 -5
  488. package/dist/slices/storage/files-location.js +6 -8
  489. package/dist/slices/storage/layout.js +13 -2
  490. package/dist/slices/storage/model.js +3 -0
  491. package/dist/slices/storage/portable.js +16 -11
  492. package/dist/slices/storage/schema.js +2 -0
  493. package/dist/slices/storage/staging-refs.js +2 -1
  494. package/dist/slices/storage/staging.js +6 -9
  495. package/dist/slices/storage/trim.js +10 -3
  496. package/dist/slices/studio/calendar.js +4 -1
  497. package/dist/slices/studio/pick.js +2 -11
  498. package/dist/slices/studio/plan.js +6 -15
  499. package/dist/slices/studio/queue.js +3 -14
  500. package/dist/slices/studio/releases.js +26 -0
  501. package/dist/slices/studio/settings.js +14 -51
  502. package/dist/slices/studio/videos.js +12 -0
  503. package/dist/slices/style-preview/render.js +34 -13
  504. package/dist/slices/style-preview/schema.js +13 -0
  505. package/dist/slices/style-preview/service.js +2 -1
  506. package/dist/slices/style-preview/settings.js +33 -5
  507. package/dist/slices/telemetry/collector-client.js +2 -4
  508. package/dist/slices/telemetry/flush.js +2 -4
  509. package/dist/slices/telemetry/model.js +0 -7
  510. package/dist/slices/telemetry/record.js +10 -3
  511. package/dist/slices/template-studio/anatomy-view.js +118 -0
  512. package/dist/slices/template-studio/anatomy.js +73 -0
  513. package/dist/slices/template-studio/bakeoff-make.js +225 -0
  514. package/dist/slices/template-studio/bakeoff-repo.js +83 -0
  515. package/dist/slices/template-studio/bakeoff.js +333 -0
  516. package/dist/slices/template-studio/budget.js +103 -0
  517. package/dist/slices/template-studio/checks.js +273 -0
  518. package/dist/slices/template-studio/document-sample.js +56 -0
  519. package/dist/slices/template-studio/edit.js +15 -0
  520. package/dist/slices/template-studio/for-all.js +161 -0
  521. package/dist/slices/template-studio/free-video.js +31 -0
  522. package/dist/slices/template-studio/free.js +176 -0
  523. package/dist/slices/template-studio/gate.js +40 -0
  524. package/dist/slices/template-studio/hidden.js +20 -0
  525. package/dist/slices/template-studio/jobs.js +79 -0
  526. package/dist/slices/template-studio/loudness.js +111 -0
  527. package/dist/slices/template-studio/make.js +40 -0
  528. package/dist/slices/template-studio/model.js +54 -0
  529. package/dist/slices/template-studio/pack-import.js +253 -0
  530. package/dist/slices/template-studio/pack-samples.js +60 -0
  531. package/dist/slices/template-studio/pack.js +205 -0
  532. package/dist/slices/template-studio/paid-view.js +66 -0
  533. package/dist/slices/template-studio/paid.js +348 -0
  534. package/dist/slices/template-studio/quote.js +148 -0
  535. package/dist/slices/template-studio/ready.js +104 -0
  536. package/dist/slices/template-studio/repo.js +134 -0
  537. package/dist/slices/template-studio/reviews-path.js +58 -0
  538. package/dist/slices/template-studio/runner.js +84 -0
  539. package/dist/slices/template-studio/save.js +236 -0
  540. package/dist/slices/template-studio/say-it-as.js +248 -0
  541. package/dist/slices/template-studio/schedule-gate.js +34 -0
  542. package/dist/slices/template-studio/schedule-price.js +30 -0
  543. package/dist/slices/template-studio/schedule-sample.js +87 -0
  544. package/dist/slices/template-studio/schema.js +218 -0
  545. package/dist/slices/template-studio/scope.js +124 -0
  546. package/dist/slices/template-studio/sections.js +176 -0
  547. package/dist/slices/template-studio/service.js +173 -0
  548. package/dist/slices/template-studio/status.js +174 -0
  549. package/dist/slices/template-studio/topic.js +223 -0
  550. package/dist/slices/trash/designs.js +76 -0
  551. package/dist/slices/trash/model.js +13 -4
  552. package/dist/slices/trash/service.js +52 -2
  553. package/dist/slices/tutorials/library.js +3 -2
  554. package/dist/slices/video/ass-text.js +17 -0
  555. package/dist/slices/video/audio-export.js +2 -64
  556. package/dist/slices/video/encoding.js +160 -0
  557. package/dist/slices/video/ffmpeg.js +23 -21
  558. package/dist/slices/video/figure-card.js +1 -15
  559. package/dist/slices/video/slideshow.js +43 -18
  560. package/dist/slices/voices/palette.js +0 -3
  561. package/dist/slices/youtube/answer.js +2 -15
  562. package/dist/slices/youtube/edits-repo.js +2 -11
  563. package/dist/tutorials/Agents-MCP.md +2 -1
  564. package/dist/tutorials/Assistant.md +42 -0
  565. package/dist/tutorials/Channels.md +2 -0
  566. package/dist/tutorials/Home-and-Projects.md +19 -0
  567. package/dist/tutorials/Install.md +1 -0
  568. package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
  569. package/dist/tutorials/Models.md +33 -1
  570. package/dist/tutorials/Notifications.md +6 -0
  571. package/dist/tutorials/Play-Images.md +4 -0
  572. package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
  573. package/dist/tutorials/Prompts.md +10 -6
  574. package/dist/tutorials/Remote-Access.md +63 -0
  575. package/dist/tutorials/Reviews-and-Checkpoints.md +13 -2
  576. package/dist/tutorials/Schedules.md +21 -1
  577. package/dist/tutorials/Settings-Reference.md +50 -8
  578. package/dist/tutorials/Template-Studio.md +125 -0
  579. package/dist/tutorials/Templates.md +3 -0
  580. package/dist/tutorials/_Sidebar.md +5 -0
  581. package/dist/updater/work-in-progress.js +42 -12
  582. package/dist/web/assets/index-CkAI_2E7.css +1 -0
  583. package/dist/web/assets/index-DcD0cdfo.js +334 -0
  584. package/dist/web/assets/{pdf-T7MCwjIv.js → pdf-TODRwF31.js} +1 -1
  585. package/dist/web/index.html +2 -2
  586. package/package.json +5 -1
  587. package/dist/slices/article/provided-entries.js +0 -66
  588. package/dist/slices/article/run.js +0 -145
  589. package/dist/slices/images/run.js +0 -234
  590. package/dist/slices/narration/legacy-plan.js +0 -58
  591. package/dist/slices/narration/run.js +0 -270
  592. package/dist/slices/rebuild/runtime-actions.js +0 -90
  593. package/dist/slices/research/run.js +0 -224
  594. package/dist/slices/subtitles/prepare.js +0 -194
  595. package/dist/slices/subtitles/transcript.js +0 -34
  596. package/dist/slices/thumbnail/run.js +0 -219
  597. package/dist/slices/video/audio-inputs.js +0 -33
  598. package/dist/slices/video/reuse-audio.js +0 -62
  599. package/dist/slices/video/run.js +0 -78
  600. package/dist/slices/video/write-export.js +0 -158
  601. package/dist/slices/video/write-subtitles.js +0 -59
  602. package/dist/web/assets/index-BadsoORk.css +0 -1
  603. package/dist/web/assets/index-D8LqNsCl.js +0 -326
@@ -0,0 +1,36 @@
1
+ import { fillBeforeMs, lateFillMs, nativeSchedule } from "./limits.js";
2
+ // Sites without a scheduler: Instagram, Bluesky and Reddit. A post asked to fill there before
3
+ // its release time waits for it; the extension fills it about two minutes before, and still
4
+ // fills one up to twelve hours late when the browser was closed (flagged late).
5
+ export function waitsForRelease(platform) {
6
+ return nativeSchedule[platform] === undefined;
7
+ }
8
+ // Whether a waiting post can be filled now: always on a site with a scheduler, and on one
9
+ // without when it has no release time or its time is (nearly) here.
10
+ export function readyNow(platform, releaseAt, now) {
11
+ if (!waitsForRelease(platform) || releaseAt === null)
12
+ return true;
13
+ const at = Date.parse(releaseAt);
14
+ return !Number.isFinite(at) || at - fillBeforeMs <= now.getTime();
15
+ }
16
+ // A waiting post's fill time within the next `horizonMs`, or a late one still worth filling;
17
+ // undefined for one that is not due, or too late.
18
+ export function dueAt(platform, releaseAt, now, horizonMs) {
19
+ if (!waitsForRelease(platform) || releaseAt === null)
20
+ return undefined;
21
+ const at = Date.parse(releaseAt);
22
+ if (!Number.isFinite(at))
23
+ return undefined;
24
+ const fill = at - fillBeforeMs;
25
+ const from = now.getTime();
26
+ if (at < from - lateFillMs || fill > from + horizonMs)
27
+ return undefined;
28
+ return { at: new Date(fill).toISOString(), late: at < from };
29
+ }
30
+ // Whether a post that waits for its release time has outlived it.
31
+ export function tooLate(platform, releaseAt, now) {
32
+ if (!waitsForRelease(platform) || releaseAt === null)
33
+ return false;
34
+ const at = Date.parse(releaseAt);
35
+ return Number.isFinite(at) && at < now.getTime() - lateFillMs;
36
+ }
@@ -0,0 +1,18 @@
1
+ import { z } from "zod";
2
+ import { readJsonSetting, writeSetting } from "../settings/repo.js";
3
+ import { studioPairing } from "../studio/settings.js";
4
+ import { socialPlatforms } from "./model.js";
5
+ const sitesKey = "social.extensionSites";
6
+ const stored = z.object({ sites: z.array(z.enum(socialPlatforms)), at: z.string() });
7
+ // The sites the extension says it may post on, as it last reported them.
8
+ export function writeExtensionSites(db, sites, now) {
9
+ writeSetting(db, sitesKey, JSON.stringify({ sites: [...new Set(sites)], at: now.toISOString() }));
10
+ }
11
+ export function postingExtension(db) {
12
+ const seen = readJsonSetting(db, sitesKey, stored, undefined);
13
+ return {
14
+ paired: studioPairing(db).origin !== null,
15
+ sites: seen?.sites ?? null,
16
+ seenAt: seen?.at ?? null,
17
+ };
18
+ }
@@ -0,0 +1,115 @@
1
+ // What doesn't fit a site, as plain sentences with the fix. A warning never blocks a fill: the
2
+ // site itself has the last word, and the person may know better. Pure.
3
+ import { limitsFor, nativeSchedule } from "./limits.js";
4
+ import { platformLabels, xTierLabels, } from "./model.js";
5
+ function duration(seconds) {
6
+ const whole = Math.round(seconds);
7
+ const hours = Math.floor(whole / 3600);
8
+ const minutes = Math.floor((whole % 3600) / 60);
9
+ const rest = whole % 60;
10
+ const parts = [
11
+ ...(hours > 0 ? [`${String(hours)} h`] : []),
12
+ ...(minutes > 0 ? [`${String(minutes)} min`] : []),
13
+ ...(rest > 0 || whole === 0 ? [`${String(rest)} s`] : []),
14
+ ];
15
+ return parts.join(" ");
16
+ }
17
+ export function mediaWarnings(platform, media, tier) {
18
+ const site = platformLabels[platform];
19
+ const limits = limitsFor(platform, tier);
20
+ const warnings = [];
21
+ const videos = media.filter((one) => one.kind === "video" || one.kind === "short");
22
+ const stills = media.length - videos.length;
23
+ if (videos.length > limits.videos)
24
+ warnings.push({
25
+ code: "too-many-videos",
26
+ sentence: `${site} takes one video per post. Keep the video or the short in the post's media, not both.`,
27
+ });
28
+ if (stills > limits.pictures)
29
+ warnings.push({
30
+ code: "too-many-pictures",
31
+ sentence: `${site} takes up to ${String(limits.pictures)} pictures per post; this one has ${String(stills)}. The extension puts in the first ${String(limits.pictures)}; pick fewer in the post's media to choose which.`,
32
+ });
33
+ if (!limits.mixes && videos.length > 0 && stills > 0)
34
+ warnings.push({
35
+ code: "video-and-pictures",
36
+ sentence: `${site} doesn't take a video and pictures in one post. Keep either the video or the pictures in the post's media.`,
37
+ });
38
+ for (const video of videos) {
39
+ if (video.seconds === undefined || video.seconds <= limits.videoSeconds)
40
+ continue;
41
+ const tierHint = platform === "x" && tier === "free"
42
+ ? ` If the channel's X account has Premium, set it in Channels → Brand → Posting.`
43
+ : "";
44
+ warnings.push({
45
+ code: "video-too-long",
46
+ sentence: `${platform === "x" ? `X ${xTierLabels[tier]}` : site} takes videos up to ${duration(limits.videoSeconds)}; this one is ${duration(video.seconds)}. Post a short instead, or the link to the video.${tierHint}`,
47
+ });
48
+ }
49
+ if ((platform === "tiktok" || platform === "instagram") && media.length === 0)
50
+ warnings.push({
51
+ code: "no-media",
52
+ sentence: `${site} posts need a video or pictures. Pick a short or pictures in the post's media.`,
53
+ });
54
+ if (platform === "tiktok" && videos.some((one) => one.kind === "video" && one.shape === "16:9"))
55
+ warnings.push({
56
+ code: "full-video",
57
+ sentence: "TikTok shows a 16:9 video small, between bars. Pick a short (9:16) in the post's media.",
58
+ });
59
+ return warnings;
60
+ }
61
+ export function redditWarnings(fields) {
62
+ const warnings = [];
63
+ if (fields.subreddit === "")
64
+ warnings.push({
65
+ code: "subreddit-missing",
66
+ sentence: "The subreddit isn't set, so Reddit will ask where to post. Type it in the post's Subreddit box (like videos, without r/).",
67
+ });
68
+ if (fields.title.trim() === "")
69
+ warnings.push({
70
+ code: "title-missing",
71
+ sentence: "Reddit posts need a title. Type one in the post's Title box.",
72
+ });
73
+ return warnings;
74
+ }
75
+ // When the post goes out, from the YouTube item it carries: the site's scheduler at the release
76
+ // time, the extension filling it at release time on a site without one, or now.
77
+ export function scheduleFor(platform, releaseAt, now) {
78
+ const site = platformLabels[platform];
79
+ if (releaseAt === null)
80
+ return {
81
+ schedule: { mode: "now", at: null },
82
+ warnings: [
83
+ {
84
+ code: "no-release",
85
+ sentence: `This project has no release time, so the post goes out when you press Post on ${site}. Give it a time in the project's Release block to post with the video.`,
86
+ },
87
+ ],
88
+ };
89
+ const at = Date.parse(releaseAt);
90
+ const ahead = at - now.getTime();
91
+ if (!Number.isFinite(at) || ahead <= 0)
92
+ return {
93
+ schedule: { mode: "now", at: releaseAt },
94
+ warnings: [
95
+ {
96
+ code: "release-past",
97
+ sentence: `The release time has passed, so the post goes out when you press Post on ${site}.`,
98
+ },
99
+ ],
100
+ };
101
+ const window = nativeSchedule[platform];
102
+ if (window === undefined)
103
+ return { schedule: { mode: "at-release", at: releaseAt }, warnings: [] };
104
+ if (ahead < window.minMs || ahead > window.maxMs)
105
+ return {
106
+ schedule: { mode: "now", at: releaseAt },
107
+ warnings: [
108
+ {
109
+ code: "release-outside-window",
110
+ sentence: `${site} schedules posts only ${duration(window.minMs / 1000)} to ${String(Math.round(window.maxMs / 86_400_000))} days ahead, so the extension leaves it to post now. Fill it in again when the release time is within that window, or press Post at the release time.`,
111
+ },
112
+ ],
113
+ };
114
+ return { schedule: { mode: "native", at: releaseAt }, warnings: [] };
115
+ }
@@ -0,0 +1,186 @@
1
+ // What each site takes, as observed on the site (`checked` is the date, null where it was
2
+ // not seen yet and comes from the site's own help pages). Browser-safe.
3
+ const free = {
4
+ text: 280,
5
+ unit: "weighted",
6
+ pictures: 4,
7
+ videos: 1,
8
+ videoSeconds: 140,
9
+ mixes: true,
10
+ pictureShape: "original",
11
+ hashtags: 10,
12
+ aiLabel: true,
13
+ checked: null,
14
+ source: "help.x.com: posting limits, video length",
15
+ };
16
+ const xLimits = {
17
+ free,
18
+ premium: { ...free, text: 25_000, videoSeconds: 3 * 3600 },
19
+ premium_plus: { ...free, text: 25_000, videoSeconds: 3 * 3600 },
20
+ };
21
+ const others = {
22
+ linkedin: {
23
+ text: 3000,
24
+ unit: "characters",
25
+ pictures: 20,
26
+ videos: 1,
27
+ videoSeconds: 15 * 60,
28
+ mixes: false,
29
+ pictureShape: "original",
30
+ hashtags: 5,
31
+ aiLabel: false,
32
+ checked: null,
33
+ source: "linkedin.com/help: share an update, video specifications",
34
+ },
35
+ instagram: {
36
+ text: 2200,
37
+ unit: "characters",
38
+ pictures: 20,
39
+ videos: 1,
40
+ videoSeconds: 15 * 60,
41
+ mixes: true,
42
+ pictureShape: "4:5",
43
+ hashtags: 30,
44
+ aiLabel: true,
45
+ checked: "2026-10-18",
46
+ source: "Instagram's Create new post dialog (caption counter 0/2,200, crop 4:5)",
47
+ },
48
+ tiktok: {
49
+ text: 4000,
50
+ unit: "characters",
51
+ pictures: 35,
52
+ videos: 1,
53
+ videoSeconds: 60 * 60,
54
+ mixes: false,
55
+ pictureShape: "original",
56
+ hashtags: 10,
57
+ aiLabel: true,
58
+ checked: "2026-10-18",
59
+ source: "TikTok Studio's upload page (60 min videos, 35 photos, 4000 characters)",
60
+ },
61
+ reddit: {
62
+ text: 40_000,
63
+ unit: "characters",
64
+ pictures: 20,
65
+ videos: 1,
66
+ videoSeconds: 15 * 60,
67
+ mixes: false,
68
+ pictureShape: "original",
69
+ hashtags: 0,
70
+ aiLabel: false,
71
+ checked: null,
72
+ source: "reddithelp.com: posting images and videos",
73
+ },
74
+ bluesky: {
75
+ text: 300,
76
+ unit: "graphemes",
77
+ pictures: 4,
78
+ videos: 1,
79
+ videoSeconds: 3 * 60,
80
+ mixes: false,
81
+ pictureShape: "original",
82
+ hashtags: 3,
83
+ aiLabel: false,
84
+ checked: "2026-10-18",
85
+ source: "Bluesky's composer (counter starts at 300); 4 images or one video",
86
+ },
87
+ };
88
+ export function limitsFor(platform, tier = "free") {
89
+ return platform === "x" ? xLimits[tier] : others[platform];
90
+ }
91
+ // Reddit's title limit.
92
+ export const redditTitleMax = 300;
93
+ export const subredditPattern = /^[A-Za-z0-9][A-Za-z0-9_]{1,20}$/;
94
+ const minute = 60_000;
95
+ const day = 24 * 60 * minute;
96
+ export const nativeSchedule = {
97
+ x: { minMs: 5 * minute, maxMs: 540 * day, checked: null },
98
+ linkedin: { minMs: 60 * minute, maxMs: 90 * day, checked: null },
99
+ tiktok: { minMs: 15 * minute, maxMs: 10 * day, checked: null },
100
+ };
101
+ // How long before the release time the extension fills a post on a site without a scheduler,
102
+ // and how late it still fills one when the browser was closed.
103
+ export const fillBeforeMs = 2 * minute;
104
+ export const lateFillMs = 12 * 60 * minute;
105
+ // Where each site's post form opens.
106
+ export const composeUrls = {
107
+ x: "https://x.com/compose/post",
108
+ linkedin: "https://www.linkedin.com/feed/?shareActive=true",
109
+ instagram: "https://www.instagram.com/",
110
+ tiktok: "https://www.tiktok.com/tiktokstudio/upload",
111
+ reddit: "https://www.reddit.com/submit",
112
+ bluesky: "https://bsky.app/",
113
+ };
114
+ // The address Fill in <site> opens: the post form with "#slopify-post=<projectId>:<platform>".
115
+ // The extension fills only a post form that carries this mark (or the one its alarm opens at
116
+ // release time, `packages/extension/src/social/messages.ts`), and only that project's post; a
117
+ // post form the person opens themselves is never touched.
118
+ export const fillMark = "slopify-post";
119
+ export function fillUrl(composeUrl, projectId, platform) {
120
+ return `${composeUrl.replace(/#.*$/, "")}#${fillMark}=${encodeURIComponent(projectId)}:${platform}`;
121
+ }
122
+ // Reddit's submit page takes the title, text and link in its address.
123
+ export function redditSubmitUrl(fields) {
124
+ const query = new URLSearchParams({ title: fields.title });
125
+ if (fields.text !== undefined && fields.text !== "")
126
+ query.set("text", fields.text);
127
+ if (fields.url !== undefined && fields.url !== "")
128
+ query.set("url", fields.url);
129
+ const sub = fields.subreddit === "" ? "" : `/r/${encodeURIComponent(fields.subreddit)}`;
130
+ return `https://www.reddit.com${sub}/submit?${query.toString()}`;
131
+ }
132
+ // A published post's link on each site, as the person pastes it or the extension reads it.
133
+ export const linkPatterns = {
134
+ x: /^https:\/\/(?:www\.)?(?:x|twitter)\.com\/[A-Za-z0-9_]{1,15}\/status\/\d+(?:[/?#].*)?$/,
135
+ linkedin: /^https:\/\/(?:www\.)?linkedin\.com\/(?:feed\/update\/urn:li:[A-Za-z]+:\d+|posts\/[^\s]+)\/?(?:[?#].*)?$/,
136
+ instagram: /^https:\/\/(?:www\.)?instagram\.com\/(?:[A-Za-z0-9_.]+\/)?(?:p|reel)\/[A-Za-z0-9_-]+\/?(?:[?#].*)?$/,
137
+ tiktok: /^https:\/\/(?:www\.)?tiktok\.com\/@[A-Za-z0-9_.]+\/(?:video|photo)\/\d+\/?(?:[?#].*)?$/,
138
+ reddit: /^https:\/\/(?:www\.|old\.|new\.)?reddit\.com\/r\/[A-Za-z0-9_]+\/comments\/[a-z0-9]+(?:\/[^\s]*)?$/,
139
+ bluesky: /^https:\/\/bsky\.app\/profile\/[^\s/]+\/post\/[a-z0-9]+\/?(?:[?#].*)?$/,
140
+ };
141
+ export const linkExamples = {
142
+ x: "https://x.com/you/status/…",
143
+ linkedin: "https://www.linkedin.com/feed/update/urn:li:activity:…",
144
+ instagram: "https://www.instagram.com/p/…",
145
+ tiktok: "https://www.tiktok.com/@you/video/…",
146
+ reddit: "https://www.reddit.com/r/…/comments/…",
147
+ bluesky: "https://bsky.app/profile/you.bsky.social/post/…",
148
+ };
149
+ // The account names as the channel stores them, each site's own way of writing one.
150
+ export const accountPatterns = {
151
+ x: /^@?[A-Za-z0-9_]{1,15}$/,
152
+ linkedin: /^(?:in|company)\/[A-Za-z0-9-_%]{2,100}$/,
153
+ instagram: /^@?[A-Za-z0-9_.]{1,30}$/,
154
+ tiktok: /^@?[A-Za-z0-9_.]{2,24}$/,
155
+ reddit: /^(?:u\/)?[A-Za-z0-9_-]{3,20}$/,
156
+ bluesky: /^@?[A-Za-z0-9.-]{3,253}$/,
157
+ };
158
+ export const accountExamples = {
159
+ x: "@yourname",
160
+ linkedin: "in/your-name",
161
+ instagram: "yourname",
162
+ tiktok: "@yourname",
163
+ reddit: "u/yourname",
164
+ bluesky: "yourname.bsky.social",
165
+ };
166
+ // The page listing the account's own posts, where the extension finds a post's link.
167
+ export function profileUrl(platform, accounts) {
168
+ const raw = accounts[platform]?.trim() ?? "";
169
+ if (raw === "")
170
+ return null;
171
+ const bare = raw.replace(/^@/, "").replace(/^u\//, "");
172
+ switch (platform) {
173
+ case "x":
174
+ return `https://x.com/${bare}`;
175
+ case "linkedin":
176
+ return `https://www.linkedin.com/${bare}/recent-activity/all/`;
177
+ case "instagram":
178
+ return `https://www.instagram.com/${bare}/`;
179
+ case "tiktok":
180
+ return `https://www.tiktok.com/@${bare}`;
181
+ case "reddit":
182
+ return `https://www.reddit.com/user/${bare}/submitted/`;
183
+ case "bluesky":
184
+ return `https://bsky.app/profile/${bare}`;
185
+ }
186
+ }
@@ -0,0 +1,21 @@
1
+ // How long after a post went out the extension looks it up on the person's profile, and for
2
+ // how long it keeps looking.
3
+ export const lookupAfterMs = 10 * 60 * 1000;
4
+ export const lookupForMs = 7 * 24 * 60 * 60 * 1000;
5
+ export const lookupStartLength = 40;
6
+ // A filled or scheduled post without its link, once it should be on the site.
7
+ export function lookupDue(progress, releaseAt, now) {
8
+ if (progress.url !== null)
9
+ return false;
10
+ if (progress.state !== "filled" && progress.state !== "scheduled")
11
+ return false;
12
+ const from = Date.parse(progress.state === "scheduled" && releaseAt !== null ? releaseAt : (progress.filledAt ?? ""));
13
+ if (!Number.isFinite(from))
14
+ return false;
15
+ const since = now.getTime() - from;
16
+ return since >= lookupAfterMs && since <= lookupForMs;
17
+ }
18
+ // The post's first characters, as the profile page shows them, to find it among its posts.
19
+ export function lookupStart(text) {
20
+ return [...text.replace(/\s+/gu, " ").trim()].slice(0, lookupStartLength).join("");
21
+ }
@@ -0,0 +1,131 @@
1
+ // Which files a post carries, in the shape its site wants them. Pure: the sources come from
2
+ // the project's outputs (`sources.ts`); a reshaped picture or a design's render is made the
3
+ // first time it is asked for (`reshape.ts`), so listing a pack never draws anything.
4
+ import { limitsFor } from "./limits.js";
5
+ import { platformLabels, } from "./model.js";
6
+ const shapeSlugs = {
7
+ "1:1": "1x1",
8
+ "4:5": "4x5",
9
+ "9:16": "9x16",
10
+ "16:9": "16x9",
11
+ };
12
+ // A design's format on each site: portrait on Instagram, a story on TikTok, square elsewhere.
13
+ export function designFormatFor(platform) {
14
+ return platform === "instagram" ? "portrait" : platform === "tiktok" ? "story" : "square";
15
+ }
16
+ const designShapes = {
17
+ portrait: "4:5",
18
+ story: "9:16",
19
+ square: "1:1",
20
+ };
21
+ // The file names the media routes take: the asset itself, `<asset>--4x5` for a reshaped copy,
22
+ // and `design--<id>--<format>` for a design's render.
23
+ export function mediaFile(asset, shape) {
24
+ return shape === "original" ? asset : `${asset}--${shapeSlugs[shape]}`;
25
+ }
26
+ export function parseMediaFile(file) {
27
+ const design = /^design--([A-Za-z0-9_-]{1,64})--([a-z0-9]+)$/u.exec(file);
28
+ if (design !== null) {
29
+ const format = Object.keys(designShapes).find((one) => one === design[2]);
30
+ return format === undefined || design[1] === undefined
31
+ ? undefined
32
+ : { kind: "design", id: design[1], format: format };
33
+ }
34
+ const match = /^([a-z0-9]+(?:-[a-z0-9]+)*)(?:--([0-9]+x[0-9]+))?$/u.exec(file);
35
+ if (match === null || match[1] === undefined)
36
+ return undefined;
37
+ if (match[2] === undefined)
38
+ return { kind: "asset", asset: match[1], shape: "original" };
39
+ const shape = Object.keys(shapeSlugs).find((one) => shapeSlugs[one] === match[2]);
40
+ return shape === undefined ? undefined : { kind: "asset", asset: match[1], shape };
41
+ }
42
+ const url = (projectId, file) => `/api/social/media/${projectId}/${file}`;
43
+ function still(sources, kind, source, shape) {
44
+ const file = mediaFile(source.asset, shape);
45
+ const reshaped = shape !== "original";
46
+ return {
47
+ kind,
48
+ file,
49
+ url: url(sources.projectId, file),
50
+ filename: reshaped
51
+ ? `${source.filename.replace(/\.[^.]+$/u, "")}-${shapeSlugs[shape]}.jpg`
52
+ : source.filename,
53
+ contentType: reshaped ? "image/jpeg" : source.contentType,
54
+ bytes: reshaped ? null : source.bytes,
55
+ shape,
56
+ ...(source.alt === undefined ? {} : { alt: source.alt }),
57
+ };
58
+ }
59
+ // The post's files in the order the site shows them: the video first, then the pictures.
60
+ export function mediaFor(platform, choice, sources) {
61
+ const media = [];
62
+ const warnings = [];
63
+ const site = platformLabels[platform];
64
+ const missing = (sentence) => warnings.push({ code: "media-missing", sentence });
65
+ const shape = limitsFor(platform).pictureShape;
66
+ if (choice.video === "long") {
67
+ if (sources.long === null)
68
+ missing("The video isn't made yet. Let the Video stage finish (or use Continue the run on the project page), then open Prepare upload again.");
69
+ else
70
+ media.push({
71
+ kind: "video",
72
+ file: sources.long.asset,
73
+ url: url(sources.projectId, sources.long.asset),
74
+ filename: sources.long.filename,
75
+ contentType: sources.long.contentType,
76
+ bytes: sources.long.bytes,
77
+ shape: sources.format,
78
+ ...(sources.long.seconds === null ? {} : { seconds: sources.long.seconds }),
79
+ });
80
+ }
81
+ else if (choice.video === "short") {
82
+ const number = choice.short ?? 1;
83
+ const short = sources.shorts.find((one) => one.number === number);
84
+ if (short?.file == null)
85
+ missing(short === undefined
86
+ ? `This project has no short ${String(number)}. Pick another short for ${site} in the post's media, or turn on Shorts in Edit project.`
87
+ : `Short ${String(number)} isn't rendered yet. Let the Video stage finish, then open Prepare upload again.`);
88
+ else
89
+ media.push({
90
+ kind: "short",
91
+ file: short.file.asset,
92
+ url: url(sources.projectId, short.file.asset),
93
+ filename: short.file.filename,
94
+ contentType: short.file.contentType,
95
+ bytes: short.file.bytes,
96
+ shape: "9:16",
97
+ ...(short.file.seconds === null ? {} : { seconds: short.file.seconds }),
98
+ });
99
+ }
100
+ if (choice.thumbnail === true) {
101
+ if (sources.thumbnail === null)
102
+ missing("The thumbnail isn't made yet. Let the Thumbnail stage finish, or set Thumbnail in Edit project.");
103
+ else
104
+ media.push(still(sources, "thumbnail", sources.thumbnail, shape));
105
+ }
106
+ const wanted = choice.pictures ?? 0;
107
+ if (wanted > 0) {
108
+ if (sources.pictures.length === 0)
109
+ missing("This project has no pictures yet. Let the Images stage finish, or pick other media for this post.");
110
+ for (const picture of sources.pictures.slice(0, wanted))
111
+ media.push(still(sources, "picture", picture, shape));
112
+ }
113
+ const format = designFormatFor(platform);
114
+ for (const id of choice.designs ?? []) {
115
+ if (!sources.designs.some((design) => design.id === id)) {
116
+ missing("A design picked for this post is gone or belongs to another project. Pick the post's designs again.");
117
+ continue;
118
+ }
119
+ const file = `design--${id}--${format}`;
120
+ media.push({
121
+ kind: "design",
122
+ file,
123
+ url: url(sources.projectId, file),
124
+ filename: `design-${id}-${format}.png`,
125
+ contentType: "image/png",
126
+ bytes: null,
127
+ shape: designShapes[format] ?? "original",
128
+ });
129
+ }
130
+ return { media, warnings };
131
+ }
@@ -0,0 +1,75 @@
1
+ // Posting a project's work to X, LinkedIn, Instagram, TikTok, Reddit and Bluesky. Slopify never
2
+ // posts: the Slopify Studio extension fills each site's own post form and the person presses
3
+ // Post. Browser-safe: the project page, Template Studio and the extension read these names.
4
+ export const socialPlatforms = [
5
+ "x",
6
+ "linkedin",
7
+ "instagram",
8
+ "tiktok",
9
+ "reddit",
10
+ "bluesky",
11
+ ];
12
+ export const platformLabels = {
13
+ x: "X",
14
+ linkedin: "LinkedIn",
15
+ instagram: "Instagram",
16
+ tiktok: "TikTok",
17
+ reddit: "Reddit",
18
+ bluesky: "Bluesky",
19
+ };
20
+ // Where a post's words come from. "description" is the YouTube description without its
21
+ // chapters, "summary" the article's digest summary, "prompt" a Library prompt of kind "post"
22
+ // (a paid AI call), "custom" a short text with {{Slopify_…}} keywords, "none" no words.
23
+ export const postTextSources = [
24
+ "description",
25
+ "summary",
26
+ "title",
27
+ "prompt",
28
+ "custom",
29
+ "none",
30
+ ];
31
+ // The X subscription of the channel's account: it only changes which warnings a post gets.
32
+ export const xTiers = ["free", "premium", "premium_plus"];
33
+ export const xTierLabels = {
34
+ free: "Free",
35
+ premium: "Premium",
36
+ premium_plus: "Premium+",
37
+ };
38
+ export const redditPostKinds = ["link", "text", "media"];
39
+ // Where a post stands. "waiting": the person pressed Fill in <site>; "filling": the extension
40
+ // took it; "filled": the form is filled and waits for the person's Post; "scheduled": the site
41
+ // will post it at the release time; "posted": it is on the site; "failed": the fill stopped.
42
+ export const postStates = [
43
+ "none",
44
+ "waiting",
45
+ "filling",
46
+ "filled",
47
+ "scheduled",
48
+ "posted",
49
+ "failed",
50
+ ];
51
+ // What the extension reports after a fill (`POST /api/social/ext/result`).
52
+ export const reportedStates = ["filling", "filled", "scheduled", "posted", "failed"];
53
+ export const postMediaKinds = ["video", "short", "thumbnail", "picture", "design"];
54
+ export const mediaShapes = ["original", "1:1", "4:5", "9:16", "16:9"];
55
+ // How the post meets the release time: the site's own scheduler set to it, the extension
56
+ // filling it when it is due (the person presses Post), or posted when the person presses it.
57
+ export const scheduleModes = ["native", "at-release", "now"];
58
+ export const warningCodes = [
59
+ "text-cut",
60
+ "too-many-pictures",
61
+ "video-too-long",
62
+ "video-and-pictures",
63
+ "no-media",
64
+ "subreddit-missing",
65
+ "title-missing",
66
+ "release-past",
67
+ "release-outside-window",
68
+ "no-release",
69
+ "media-missing",
70
+ "text-missing",
71
+ "full-video",
72
+ "too-many-videos",
73
+ "text-too-long",
74
+ "link-missing",
75
+ ];
@@ -0,0 +1,19 @@
1
+ import { platformLabels } from "./model.js";
2
+ // The button the person presses on each site to post.
3
+ const postButton = {
4
+ x: "Post",
5
+ linkedin: "Post",
6
+ instagram: "Share",
7
+ tiktok: "Post",
8
+ reddit: "Post",
9
+ bluesky: "Publish",
10
+ };
11
+ // The Notification URL's message when the extension filled a post at its release time on a site
12
+ // without a scheduler: the person presses the button, Slopify never does.
13
+ export function postReadyNotice(pack) {
14
+ const site = platformLabels[pack.platform];
15
+ return {
16
+ headline: `Ready to post on ${site}: ${pack.projectTitle}`,
17
+ detail: `The Slopify Studio extension filled the post in your browser. Open the ${site} tab and press ${postButton[pack.platform]}; Slopify never posts by itself.`,
18
+ };
19
+ }