@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,128 @@
1
+ import { projectById } from "../admission/repo.js";
2
+ import { projectChannelId } from "../channels/repo.js";
3
+ import { channelSocial } from "./channel.js";
4
+ import { dueAt, readyNow, tooLate, waitsForRelease } from "./due.js";
5
+ import { profileUrl } from "./limits.js";
6
+ import { lookupDue, lookupStart } from "./links.js";
7
+ import { platformLabels, } from "./model.js";
8
+ import { packFor } from "./pack.js";
9
+ import { expireWaiting, takeNext } from "./queue.js";
10
+ import { postsIn, readPost, writeProgress } from "./repo.js";
11
+ import { askFill, pasteLink, reportFill } from "./state.js";
12
+ // Edit project has no posting editor: a project's sites come from its template (or Play).
13
+ export function notPostingSentence(platform) {
14
+ const site = platformLabels[platform];
15
+ return `This project doesn't post to ${site}. A project's sites come from its template: turn ${site} on under Post to in Template Studio → Upload pack (Library → Templates → Open studio), or on Play under Outputs, for the projects it makes next.`;
16
+ }
17
+ const notPosting = (platform) => ({
18
+ ok: false,
19
+ status: 404,
20
+ sentence: notPostingSentence(platform),
21
+ });
22
+ // The release time a waiting post fills at, read from its pack.
23
+ function releaseOf(deps, projectId, platform, now) {
24
+ return packFor(deps, projectId, platform, now)?.schedule.at ?? null;
25
+ }
26
+ // Fill in <site>: the post waits for the extension (on a site without a scheduler, for its
27
+ // release time when that is still ahead).
28
+ export function fillPost(deps, projectId, platform, now) {
29
+ const before = packFor(deps, projectId, platform, now);
30
+ if (before === undefined)
31
+ return notPosting(platform);
32
+ const asked = askFill(readPost(deps.db, { projectId, platform }), platform, now);
33
+ if (!asked.ok)
34
+ return { ok: false, status: 409, sentence: asked.sentence };
35
+ writeProgress(deps.db, { projectId, platform }, asked.progress, now);
36
+ const pack = packFor(deps, projectId, platform, now) ?? before;
37
+ return {
38
+ ok: true,
39
+ pack,
40
+ waitsForRelease: !readyNow(platform, pack.schedule.at, now),
41
+ };
42
+ }
43
+ // The person pasted the post's link, or removed it.
44
+ export function setPostLink(deps, projectId, platform, url, now) {
45
+ if (packFor(deps, projectId, platform, now) === undefined)
46
+ return notPosting(platform);
47
+ const pasted = pasteLink(readPost(deps.db, { projectId, platform }), platform, url, now);
48
+ if (!pasted.ok)
49
+ return { ok: false, status: 400, sentence: pasted.sentence };
50
+ writeProgress(deps.db, { projectId, platform }, pasted.progress, now);
51
+ return { ok: true };
52
+ }
53
+ const waits = (deps, now) => (post) => waitsForRelease(post.platform) &&
54
+ !tooLate(post.platform, releaseOf(deps, post.projectId, post.platform, now), now);
55
+ // The extension's next post for a site: the oldest waiting one that can be filled now, of
56
+ // `projectId` when the tab Slopify opened names it.
57
+ export function nextPost(deps, platform, now, projectId) {
58
+ expireWaiting(deps.db, now, waits(deps, now));
59
+ for (;;) {
60
+ const taken = takeNext(deps.db, platform, now, (post) => readyNow(platform, releaseOf(deps, post.projectId, platform, now), now), projectId);
61
+ if (taken === undefined)
62
+ return undefined;
63
+ const pack = packFor(deps, taken.post.projectId, platform, now);
64
+ if (pack !== undefined)
65
+ return { pack, waiting: taken.waiting };
66
+ // The project or its post is gone: it stops waiting, and the next one is taken.
67
+ writeProgress(deps.db, taken.post, { ...taken.post, state: "none", waitingAt: null }, now);
68
+ }
69
+ }
70
+ export function reportPost(deps, report, now) {
71
+ const { projectId, platform } = report;
72
+ if (projectById(deps.db, projectId) === undefined)
73
+ return {
74
+ ok: false,
75
+ status: 404,
76
+ sentence: "This project no longer exists in Slopify, so nothing was recorded.",
77
+ };
78
+ const reported = reportFill(readPost(deps.db, { projectId, platform }), platform, report, now);
79
+ if (!reported.ok)
80
+ return {
81
+ ok: false,
82
+ status: reported.reason === "bad-link" ? 400 : 409,
83
+ sentence: reported.sentence,
84
+ };
85
+ writeProgress(deps.db, { projectId, platform }, reported.progress, now);
86
+ return { ok: true, pack: packFor(deps, projectId, platform, now) };
87
+ }
88
+ // Waiting posts on sites without a scheduler whose release is within `horizonMs` (or passed
89
+ // at most 12 hours ago), for the extension's alarms.
90
+ export function duePosts(deps, now, horizonMs) {
91
+ return postsIn(deps.db, ["waiting"]).flatMap((post) => {
92
+ const pack = packFor(deps, post.projectId, post.platform, now);
93
+ const due = pack === undefined ? undefined : dueAt(post.platform, pack.schedule.at, now, horizonMs);
94
+ return pack === undefined || due === undefined
95
+ ? []
96
+ : [
97
+ {
98
+ projectId: post.projectId,
99
+ projectTitle: pack.projectTitle,
100
+ platform: post.platform,
101
+ at: due.at,
102
+ late: due.late,
103
+ composeUrl: pack.composeUrl,
104
+ },
105
+ ];
106
+ });
107
+ }
108
+ // Filled or scheduled posts without their link, once they should be on the site, with the
109
+ // profile page to find them on (the channel's account there).
110
+ export function linksToFind(deps, now) {
111
+ return postsIn(deps.db, ["filled", "scheduled"]).flatMap((post) => {
112
+ const pack = packFor(deps, post.projectId, post.platform, now);
113
+ if (pack === undefined || !lookupDue(post, pack.schedule.at, now))
114
+ return [];
115
+ const { accounts } = channelSocial(deps.db, projectChannelId(deps.db, post.projectId));
116
+ const profile = profileUrl(post.platform, accounts);
117
+ return profile === null || pack.text.trim() === ""
118
+ ? []
119
+ : [
120
+ {
121
+ projectId: post.projectId,
122
+ platform: post.platform,
123
+ profileUrl: profile,
124
+ start: lookupStart(pack.text),
125
+ },
126
+ ];
127
+ });
128
+ }
@@ -0,0 +1,21 @@
1
+ import { socialPlatforms } from "./model.js";
2
+ // The settings as a run keeps them: only the sites turned on, each without a prompt name its
3
+ // text source doesn't use or a custom text it doesn't post. None turned on is undefined, so a
4
+ // draft without posting converts to the run it always did.
5
+ export function socialOfForm(form) {
6
+ if (form === undefined)
7
+ return undefined;
8
+ const kept = {};
9
+ for (const platform of socialPlatforms) {
10
+ const choice = form[platform];
11
+ if (choice === undefined)
12
+ continue;
13
+ const { prompt, custom, ...rest } = choice;
14
+ kept[platform] = {
15
+ ...rest,
16
+ ...(choice.text === "prompt" && prompt?.trim() ? { prompt: prompt.trim() } : {}),
17
+ ...(choice.text === "custom" && custom !== undefined ? { custom } : {}),
18
+ };
19
+ }
20
+ return Object.keys(kept).length === 0 ? undefined : kept;
21
+ }
@@ -0,0 +1,114 @@
1
+ import { readFileSync, statSync } from "node:fs";
2
+ import { projectById } from "../admission/repo.js";
3
+ import { projectChannelId } from "../channels/repo.js";
4
+ import { listDesigns } from "../designs/repo.js";
5
+ import { digestKey } from "../rebuild/recipe-digest.js";
6
+ import { isClipPath } from "../rebuild/runtime-export-edit.js";
7
+ import { piecesForRevision } from "../revisions/manifest-repo.js";
8
+ import { currentRevisionId } from "../revisions/repo.js";
9
+ import { assetOf } from "../storage/asset-name.js";
10
+ import { contentTypeOf, downloadName, slugOf } from "../storage/downloads.js";
11
+ import { outputPath } from "../storage/layout.js";
12
+ import { outputsOf } from "../storage/repo.js";
13
+ import { uploadPack } from "../studio/pack.js";
14
+ import { videoOf } from "../studio/videos.js";
15
+ import { channelSocial } from "./channel.js";
16
+ import { stripChapters } from "./text.js";
17
+ export function postSources(deps, projectId) {
18
+ const project = projectById(deps.db, projectId);
19
+ const pack = uploadPack(deps, projectId);
20
+ if (project === undefined || !pack.ok)
21
+ return undefined;
22
+ const outputs = outputsOf(deps.db, projectId).filter((output) => onDisk(deps, projectId, output));
23
+ const slug = slugOf(project.title);
24
+ const source = (output) => ({
25
+ asset: assetOf(output),
26
+ filename: downloadName(slug, output),
27
+ contentType: contentTypeOf(output.path),
28
+ bytes: output.bytes,
29
+ });
30
+ const seconds = (output) => output.durationMs === null ? null : output.durationMs / 1000;
31
+ const video = outputs.find((output) => output.role === "video");
32
+ const long = pack.pack.items[0];
33
+ const shorts = pack.pack.items.filter((item) => item.kind === "short");
34
+ const shortVideo = (asset) => outputs.find((output) => output.role === "short_video" && assetOf(output) === asset);
35
+ const thumbnail = long?.thumbnails[0];
36
+ const thumbnailOutput = outputs.find((output) => output.role === "thumbnail" && assetOf(output) === thumbnail?.asset);
37
+ const pictures = outputs
38
+ .filter((output) => output.role === "image" && !isClipPath(output.path))
39
+ .sort((a, b) => (a.meta.index ?? 0) - (b.meta.index ?? 0))
40
+ .map((output) => {
41
+ const alt = altOf(output.meta.prompt);
42
+ return { ...source(output), ...(alt === undefined ? {} : { alt }) };
43
+ });
44
+ const description = stripChapters(long?.description ?? "");
45
+ const written = {};
46
+ for (const output of outputs)
47
+ if (output.role === "social_post" && output.meta.platform !== undefined)
48
+ written[output.meta.platform] = readFileSync(outputPath(deps.paths, projectId, output.path), "utf8").trim();
49
+ const longOnYoutube = videoOf(deps.db, projectId, null);
50
+ return {
51
+ projectId,
52
+ title: project.title,
53
+ config: project.config,
54
+ youtube: pack.pack,
55
+ media: {
56
+ projectId,
57
+ format: project.config.format,
58
+ long: video === undefined ? null : { ...source(video), seconds: seconds(video) },
59
+ shorts: shorts.map((item) => {
60
+ const output = shortVideo(item.video?.asset);
61
+ return {
62
+ number: item.short ?? 0,
63
+ file: output === undefined ? null : { ...source(output), seconds: seconds(output) },
64
+ };
65
+ }),
66
+ thumbnail: thumbnailOutput === undefined ? null : source(thumbnailOutput),
67
+ pictures,
68
+ designs: listDesigns(deps.db, { projectId }).map((design) => ({
69
+ id: design.id,
70
+ name: design.name,
71
+ })),
72
+ },
73
+ description,
74
+ summary: digestSummary(deps.db, projectId) ?? firstParagraph(description),
75
+ written,
76
+ tags: long?.tags ?? [],
77
+ shortHashtags: shorts.flatMap((item) => item.tags),
78
+ youtubeLink: longOnYoutube?.uploadState === "done" ? `https://youtu.be/${longOnYoutube.videoId}` : null,
79
+ channel: channelSocial(deps.db, projectChannelId(deps.db, projectId)),
80
+ };
81
+ }
82
+ function onDisk(deps, projectId, output) {
83
+ return (statSync(outputPath(deps.paths, projectId, output.path), {
84
+ throwIfNoEntry: false,
85
+ })?.isFile() === true);
86
+ }
87
+ // A picture's alt text: the first sentence of the prompt that drew it, kept short.
88
+ function altOf(prompt) {
89
+ const text = prompt?.replace(/\s+/gu, " ").trim() ?? "";
90
+ if (text === "")
91
+ return undefined;
92
+ const sentence = /^.*?[.!?](?=\s|$)/u.exec(text)?.[0] ?? text;
93
+ return sentence.length <= 300 ? sentence : `${sentence.slice(0, 299)}…`;
94
+ }
95
+ function firstParagraph(text) {
96
+ return text.split(/\n\s*\n/u)[0]?.trim() ?? "";
97
+ }
98
+ // The summary the current version's keyword digest wrote, when it ran.
99
+ function digestSummary(db, projectId) {
100
+ const revision = currentRevisionId(db, projectId);
101
+ if (revision === undefined)
102
+ return undefined;
103
+ const piece = piecesForRevision(db, projectId, revision).find((row) => row.key === digestKey && row.selected && row.piece.state === "done");
104
+ if (piece?.piece.payload == null)
105
+ return undefined;
106
+ try {
107
+ const payload = JSON.parse(piece.piece.payload);
108
+ const summary = payload.digest?.Slopify_Summary;
109
+ return typeof summary === "string" && summary.trim() !== "" ? summary.trim() : undefined;
110
+ }
111
+ catch {
112
+ return undefined;
113
+ }
114
+ }
@@ -0,0 +1,110 @@
1
+ import { linkExamples, linkPatterns } from "./limits.js";
2
+ import { platformLabels, } from "./model.js";
3
+ export const noProgress = {
4
+ state: "none",
5
+ waitingAt: null,
6
+ filledAt: null,
7
+ scheduledAt: null,
8
+ postedAt: null,
9
+ url: null,
10
+ message: null,
11
+ by: null,
12
+ };
13
+ // Fill in <site>: the post waits for the extension. A post already on the site isn't filled
14
+ // again until its link is removed.
15
+ export function askFill(current, platform, now) {
16
+ if (current.state === "posted")
17
+ return {
18
+ ok: false,
19
+ reason: "posted",
20
+ sentence: `This post is already on ${platformLabels[platform]}. To post it again, remove its link under the post first, then press Fill in ${platformLabels[platform]}.`,
21
+ };
22
+ return {
23
+ ok: true,
24
+ progress: { ...noProgress, state: "waiting", waitingAt: now.toISOString() },
25
+ };
26
+ }
27
+ // The extension took the post, filled it, saw the site schedule or publish it, or stopped.
28
+ export function reportFill(current, platform, report, now) {
29
+ const site = platformLabels[platform];
30
+ if (current.state === "none")
31
+ return {
32
+ ok: false,
33
+ reason: "not-asked",
34
+ sentence: `Nothing was waiting to be filled in ${site} for this project. In Slopify, open the project, press Prepare upload, then Fill in ${site}.`,
35
+ };
36
+ const url = report.url === undefined || report.url === "" ? null : report.url;
37
+ if (url !== null && !linkPatterns[platform].test(url))
38
+ return { ok: false, reason: "bad-link", sentence: badLink(platform) };
39
+ if (current.state === "posted") {
40
+ if (report.state !== "posted")
41
+ return {
42
+ ok: false,
43
+ reason: "posted",
44
+ sentence: `This post is already on ${site}; Slopify keeps it as posted.`,
45
+ };
46
+ return { ok: true, progress: { ...current, url: current.url ?? url } };
47
+ }
48
+ const at = now.toISOString();
49
+ const message = report.message === undefined || report.message === "" ? null : report.message;
50
+ switch (report.state) {
51
+ case "filling":
52
+ return { ok: true, progress: { ...current, state: "filling", message: null } };
53
+ case "filled":
54
+ return {
55
+ ok: true,
56
+ progress: { ...current, state: "filled", filledAt: at, message, by: "extension" },
57
+ };
58
+ case "scheduled":
59
+ return {
60
+ ok: true,
61
+ progress: {
62
+ ...current,
63
+ state: "scheduled",
64
+ filledAt: current.filledAt ?? at,
65
+ scheduledAt: at,
66
+ message,
67
+ by: "extension",
68
+ },
69
+ };
70
+ case "posted":
71
+ return {
72
+ ok: true,
73
+ progress: { ...current, state: "posted", postedAt: at, url, message, by: "extension" },
74
+ };
75
+ case "failed":
76
+ return {
77
+ ok: true,
78
+ progress: {
79
+ ...current,
80
+ state: "failed",
81
+ message: message ??
82
+ `The fill in ${site} stopped without saying why. Press Fill in ${site} again.`,
83
+ by: "extension",
84
+ },
85
+ };
86
+ }
87
+ }
88
+ // The person pasted the post's link (posted by hand or the extension missed it), or removed it.
89
+ export function pasteLink(current, platform, url, now) {
90
+ if (url === null || url.trim() === "")
91
+ return { ok: true, progress: noProgress };
92
+ const link = url.trim();
93
+ if (!linkPatterns[platform].test(link))
94
+ return { ok: false, reason: "bad-link", sentence: badLink(platform) };
95
+ return {
96
+ ok: true,
97
+ progress: {
98
+ ...current,
99
+ state: "posted",
100
+ postedAt: current.postedAt ?? now.toISOString(),
101
+ url: link,
102
+ message: null,
103
+ by: "person",
104
+ },
105
+ };
106
+ }
107
+ function badLink(platform) {
108
+ const site = platformLabels[platform];
109
+ return `That isn't a link to a post on ${site}. Open the post on ${site}, copy its address (like ${linkExamples[platform]}) and paste it again.`;
110
+ }
@@ -0,0 +1,149 @@
1
+ // A post's words: counted the way each site counts them, cut to fit at a sentence, with the
2
+ // hashtags and link at the end. Pure and browser-safe: Prepare upload's live counter uses it.
3
+ import { parseTimestamp } from "../youtube/timestamps.js";
4
+ const urlPattern = /https?:\/\/[^\s]+/gu;
5
+ // X counts a link as 23 characters, whatever its length.
6
+ const xUrlWeight = 23;
7
+ // X counts these code points once; every other (CJK, emoji) twice.
8
+ const xLightRanges = [
9
+ [0, 4351],
10
+ [8192, 8205],
11
+ [8208, 8223],
12
+ [8242, 8247],
13
+ ];
14
+ const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
15
+ const pictographic = /\p{Extended_Pictographic}/u;
16
+ function xWeight(text) {
17
+ let total = 0;
18
+ for (const { segment } of segmenter.segment(text)) {
19
+ if (pictographic.test(segment)) {
20
+ total += 2;
21
+ continue;
22
+ }
23
+ for (const char of segment) {
24
+ const code = char.codePointAt(0) ?? 0;
25
+ total += xLightRanges.some(([low, high]) => code >= low && code <= high) ? 1 : 2;
26
+ }
27
+ }
28
+ return total;
29
+ }
30
+ export function countFor(text, unit) {
31
+ switch (unit) {
32
+ case "weighted": {
33
+ const links = text.match(urlPattern) ?? [];
34
+ return xWeight(text.replace(urlPattern, "")) + links.length * xUrlWeight;
35
+ }
36
+ case "graphemes":
37
+ return [...segmenter.segment(text)].length;
38
+ case "characters":
39
+ return text.length;
40
+ }
41
+ }
42
+ // The body, then the link and hashtags, each part on its own paragraph. A body too long is cut
43
+ // at the last sentence that still fits, else at the last word with "…"; the link and hashtags
44
+ // are never cut, but dropped (hashtags first) when even they don't fit.
45
+ export function fitText(input, limits, siteName) {
46
+ const body = input.body.trim();
47
+ const tagLine = input.hashtags.join(" ");
48
+ const join = (parts) => parts.filter((part) => part !== "").join("\n\n");
49
+ const count = (text) => countFor(text, limits.unit);
50
+ const whole = join([body, input.link ?? "", tagLine]);
51
+ if (count(whole) <= limits.text)
52
+ return { text: whole, used: count(whole) };
53
+ for (const tail of [[input.link ?? "", tagLine], [input.link ?? ""], []]) {
54
+ const tailText = join(tail);
55
+ const room = limits.text - (tailText === "" ? 0 : count(`\n\n${tailText}`));
56
+ if (room <= 0)
57
+ continue;
58
+ const cut = cutBody(body, room, count);
59
+ if (cut === "" && body !== "")
60
+ continue;
61
+ const text = join([cut, ...tail]);
62
+ const dropped = tail.length === 2
63
+ ? ""
64
+ : tail.length === 1
65
+ ? " and its hashtags"
66
+ : ", its hashtags and the link";
67
+ return {
68
+ text,
69
+ used: count(text),
70
+ notice: `The text was cut to fit ${siteName}'s ${String(limits.text)} characters${dropped}. Shorten it in the post's text box, or pick a shorter text source.`,
71
+ };
72
+ }
73
+ const text = cutBody(body, limits.text, count);
74
+ return {
75
+ text,
76
+ used: count(text),
77
+ notice: `The text was cut to fit ${siteName}'s ${String(limits.text)} characters. Shorten it in the post's text box.`,
78
+ };
79
+ }
80
+ function cutBody(body, room, count) {
81
+ if (count(body) <= room)
82
+ return body;
83
+ const sentences = body.match(/[^.!?\n]+(?:[.!?]+["')\]]*|\n+|$)/gu) ?? [body];
84
+ let kept = "";
85
+ for (const sentence of sentences) {
86
+ const next = kept + sentence;
87
+ if (count(next.trimEnd()) > room)
88
+ break;
89
+ kept = next;
90
+ }
91
+ if (kept.trim() !== "")
92
+ return kept.trim();
93
+ const words = body.split(/\s+/u);
94
+ let line = "";
95
+ for (const word of words) {
96
+ const next = line === "" ? word : `${line} ${word}`;
97
+ if (count(`${next}…`) > room)
98
+ break;
99
+ line = next;
100
+ }
101
+ return line === "" ? "" : `${line}…`;
102
+ }
103
+ // Hashtags for a post: the description's tags, then the shorts' hashtags, each written once as
104
+ // a #Word without spaces or punctuation, at most `count`.
105
+ export function hashtagsFor(sources, count) {
106
+ if (count <= 0)
107
+ return [];
108
+ const seen = new Set();
109
+ const tags = [];
110
+ for (const raw of sources.flat()) {
111
+ const word = raw
112
+ .replace(/^#+/u, "")
113
+ .split(/[^\p{L}\p{N}_]+/u)
114
+ .map((part, index) => (index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)))
115
+ .join("");
116
+ if (word === "" || /^\d+$/u.test(word))
117
+ continue;
118
+ const key = word.toLowerCase();
119
+ if (seen.has(key))
120
+ continue;
121
+ seen.add(key);
122
+ tags.push(`#${word}`);
123
+ if (tags.length >= count)
124
+ break;
125
+ }
126
+ return tags;
127
+ }
128
+ // The YouTube description without what only YouTube uses: its chapter lines and their heading,
129
+ // and the hashtag lines at the end (the post gets its own).
130
+ export function stripChapters(description) {
131
+ const lines = description.replace(/\r\n?/gu, "\n").split("\n");
132
+ const isChapter = (line) => {
133
+ const match = /^(\S+)\s+\S/u.exec(line.trim());
134
+ return match !== null && parseTimestamp(match[1] ?? "") !== undefined;
135
+ };
136
+ const kept = lines.filter((line, index) => {
137
+ if (isChapter(line))
138
+ return false;
139
+ const heading = /^(?:chapters|timestamps|contents)\s*:?\s*$/iu.test(line.trim());
140
+ return !(heading && isChapter(lines[index + 1] ?? ""));
141
+ });
142
+ const trailing = (line) => line.trim() === "" || /^(?:\s*#[\p{L}\p{N}_]+\s*)+$/u.test(line);
143
+ while (kept.length > 0 && trailing(kept.at(-1) ?? ""))
144
+ kept.pop();
145
+ return kept
146
+ .join("\n")
147
+ .replace(/\n{3,}/gu, "\n\n")
148
+ .trim();
149
+ }
@@ -1,6 +1,7 @@
1
1
  import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmdirSync, statSync, writeFileSync, } from "node:fs";
2
2
  import { basename, dirname, extname, join } from "node:path";
3
3
  import { pickedShortsOf } from "../shorts/clips.js";
4
+ import { notTemplateSample } from "../template-studio/repo.js";
4
5
  import { backupsFolderName, projectDir, renderCacheFolder } from "./layout.js";
5
6
  // The file in each project folder that says which project it is, for when the database and
6
7
  // the folders disagree (a restored database, a folder renamed by hand). Reconcile keeps it.
@@ -20,7 +21,8 @@ export function arrangeKeys(db) {
20
21
  (SELECT count(*)||':'||COALESCE(max(a.id),'') FROM project_assets a WHERE a.project_id=p.id) AS assets,
21
22
  (SELECT count(*)||':'||COALESCE(max(o.id),'') FROM outputs o WHERE o.project_id=p.id) AS legacy,
22
23
  (SELECT count(*)||':'||COALESCE(max(r.id),'') FROM revision_outputs r WHERE r.project_id=p.id AND r.selected=1 AND r.revision_id=h.revision_id) AS current
23
- FROM projects p LEFT JOIN project_heads h ON h.project_id=p.id`)
24
+ FROM projects p LEFT JOIN project_heads h ON h.project_id=p.id
25
+ WHERE ${notTemplateSample("p")}`)
24
26
  .all())
25
27
  keys.set(String(row.id), [
26
28
  `layout ${layoutVersion}`,
@@ -1,5 +1,6 @@
1
1
  import { closeSync, fchmodSync, fstatSync, fsyncSync, mkdirSync, openSync, rmSync, writeFileSync, } from "node:fs";
2
2
  import { basename, dirname, isAbsolute, relative, sep } from "node:path";
3
+ import { errorText, internalError } from "../../kernel/errors.js";
3
4
  import { redact } from "../../kernel/log.js";
4
5
  import { outputPath } from "./layout.js";
5
6
  export function allocateAsset(deps, projectId, filename) {
@@ -10,7 +11,7 @@ export function allocateAsset(deps, projectId, filename) {
10
11
  const absolutePath = outputPath(deps.paths, projectId, path);
11
12
  const directory = outputPath(deps.paths, projectId, `assets/${id}`);
12
13
  if (dirname(absolutePath) !== directory)
13
- throw new Error("Slopify hit an internal error (a file name to save is not a plain file name). Try again; if it happens again, use Download diagnostics in Settings and report it.");
14
+ throw new Error(internalError("a file name to save is not a plain file name"));
14
15
  mkdirSync(outputPath(deps.paths, projectId, "assets"), { recursive: true, mode: 0o700 });
15
16
  mkdirSync(directory, { mode: 0o700 });
16
17
  return { id, projectId, path, absolutePath };
@@ -20,7 +21,7 @@ export function sealAsset(deps, pending) {
20
21
  try {
21
22
  const stats = fstatSync(file);
22
23
  if (!stats.isFile())
23
- throw new Error("Slopify hit an internal error (a saved file is not a regular file). Try again; if it happens again, use Download diagnostics in Settings and report it.");
24
+ throw new Error(internalError("a saved file is not a regular file"));
24
25
  fchmodSync(file, 0o600);
25
26
  fsyncSync(file);
26
27
  return {
@@ -58,7 +59,7 @@ export function discardPreparedAssets(deps, assets) {
58
59
  const filename = basename(asset.path);
59
60
  requireBasename(filename);
60
61
  if (asset.path !== `assets/${asset.id}/${filename}`) {
61
- throw new Error("Slopify hit an internal error (a prepared file is not in its allocated directory). Try again; if it happens again, use Download diagnostics in Settings and report it.");
62
+ throw new Error(internalError("a prepared file is not in its allocated directory"));
62
63
  }
63
64
  const directory = outputPath(deps.paths, asset.projectId, `assets/${asset.id}`);
64
65
  const registered = deps.db
@@ -68,7 +69,7 @@ export function discardPreparedAssets(deps, assets) {
68
69
  if (row.id === asset.id)
69
70
  return true;
70
71
  if (typeof row.path !== "string")
71
- throw new Error("Slopify hit an internal error (a saved file has no path). Try again; if it happens again, use Download diagnostics in Settings and report it.");
72
+ throw new Error(internalError("a saved file has no path"));
72
73
  const inside = relative(directory, outputPath(deps.paths, asset.projectId, row.path));
73
74
  return (inside === "" ||
74
75
  (inside !== ".." && !inside.startsWith(`..${sep}`) && !isAbsolute(inside)));
@@ -92,17 +93,14 @@ function requireBasename(filename) {
92
93
  }) ||
93
94
  /[. ]$/.test(filename) ||
94
95
  /^(?:con|prn|aux|nul|conin\$|conout\$|com[1-9¹²³]|lpt[1-9¹²³])(?:\.|$)/i.test(filename))
95
- throw new Error("Slopify hit an internal error (a file name to save is not allowed on every system). Try again; if it happens again, use Download diagnostics in Settings and report it.");
96
- }
97
- function messageOf(error) {
98
- return error instanceof Error ? error.message : String(error);
96
+ throw new Error(internalError("a file name to save is not allowed on every system"));
99
97
  }
100
98
  function warnCleanup(deps, projectId, error) {
101
99
  try {
102
- deps.log.write("warn", "asset.cleanup", { projectId, detail: messageOf(error) });
100
+ deps.log.write("warn", "asset.cleanup", { projectId, detail: errorText(error) });
103
101
  }
104
102
  catch (loggingError) {
105
103
  // A full disk can prevent both cleanup and file logging; preserve the write error.
106
- process.emitWarning(redact(`asset.cleanup ${projectId}: ${messageOf(error)}; logging failed: ${messageOf(loggingError)}`));
104
+ process.emitWarning(redact(`asset.cleanup ${projectId}: ${errorText(error)}; logging failed: ${errorText(loggingError)}`));
107
105
  }
108
106
  }