@gentbajko/slopify 3.25.9 → 4.1.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 (718) hide show
  1. package/dist/adapters/account-limits.js +30 -0
  2. package/dist/adapters/alignment/audio.js +1 -1
  3. package/dist/adapters/alignment/cache.js +4 -6
  4. package/dist/adapters/alignment/ctc.js +1 -2
  5. package/dist/adapters/alignment/lock.js +4 -3
  6. package/dist/adapters/alignment/multilingual.js +2 -4
  7. package/dist/adapters/alignment/prefetch.js +2 -1
  8. package/dist/adapters/alignment/runner.js +1 -1
  9. package/dist/adapters/alignment/worker.js +3 -2
  10. package/dist/adapters/explain.js +10 -12
  11. package/dist/adapters/ffmpeg.js +19 -1
  12. package/dist/adapters/host-cli/index.js +21 -4
  13. package/dist/adapters/host-cli/transport.js +2 -1
  14. package/dist/adapters/http-failure.js +53 -0
  15. package/dist/adapters/image/bytes.js +2 -1
  16. package/dist/adapters/image/codex.js +42 -9
  17. package/dist/adapters/image/fal.js +21 -53
  18. package/dist/adapters/image/google.js +33 -56
  19. package/dist/adapters/image/models.js +2 -2
  20. package/dist/adapters/image/openai.js +22 -48
  21. package/dist/adapters/image/reference.js +36 -0
  22. package/dist/adapters/image/replicate.js +21 -49
  23. package/dist/adapters/image/video.js +2 -1
  24. package/dist/adapters/interest/search-interest.js +113 -0
  25. package/dist/adapters/key-probes.js +54 -3
  26. package/dist/adapters/llm/antigravity-models.js +1 -1
  27. package/dist/adapters/llm/antigravity.js +11 -6
  28. package/dist/adapters/llm/assistant-cli.js +388 -0
  29. package/dist/adapters/llm/child-session.js +83 -0
  30. package/dist/adapters/llm/claude-code-models.js +52 -99
  31. package/dist/adapters/llm/claude-code.js +16 -4
  32. package/dist/adapters/llm/codex-limits.js +27 -68
  33. package/dist/adapters/llm/codex-models.js +1 -1
  34. package/dist/adapters/llm/codex-server-models.js +61 -109
  35. package/dist/adapters/llm/codex.js +12 -1
  36. package/dist/adapters/llm/document-workspace.js +3 -2
  37. package/dist/adapters/llm/image-workspace.js +3 -2
  38. package/dist/adapters/llm/openrouter.js +12 -48
  39. package/dist/adapters/llm/run-cli.js +9 -24
  40. package/dist/adapters/llm/sse-lines.js +5 -1
  41. package/dist/adapters/model-access.js +37 -0
  42. package/dist/adapters/tts/cartesia.js +15 -46
  43. package/dist/adapters/tts/elevenlabs.js +20 -52
  44. package/dist/adapters/tts/gemini.js +21 -41
  45. package/dist/adapters/tts/inworld-async.js +6 -2
  46. package/dist/adapters/tts/inworld.js +6 -2
  47. package/dist/adapters/tts/openai.js +20 -50
  48. package/dist/adapters/tts/system.js +2 -1
  49. package/dist/assets/models-lifecycle.yaml +6 -0
  50. package/dist/assistant-wiring.js +60 -0
  51. package/dist/autopilot-timers.js +59 -0
  52. package/dist/catalog/lifecycle.js +52 -0
  53. package/dist/catalog/propose.js +204 -0
  54. package/dist/catalog/registry.js +23 -14
  55. package/dist/catalog/schema.js +4 -1
  56. package/dist/catalog/store.js +39 -0
  57. package/dist/catalog/validate.js +5 -2
  58. package/dist/edge/autostart/native.js +3 -5
  59. package/dist/edge/autostart/prompt.js +2 -1
  60. package/dist/edge/autostart/service.js +3 -4
  61. package/dist/edge/cli-args.js +3 -4
  62. package/dist/edge/cli.js +2 -1
  63. package/dist/edge/docker-install/apply.js +11 -11
  64. package/dist/edge/docker-install/run.js +2 -1
  65. package/dist/edge/docker-install/state.js +4 -3
  66. package/dist/edge/docker-install/tree.js +2 -4
  67. package/dist/edge/docker-install/volume.js +3 -2
  68. package/dist/edge/docker.js +5 -4
  69. package/dist/edge/events/hub.js +5 -5
  70. package/dist/edge/http/actions.js +11 -32
  71. package/dist/edge/http/actor.js +5 -3
  72. package/dist/edge/http/app.js +86 -11
  73. package/dist/edge/http/assistant.js +129 -0
  74. package/dist/edge/http/audio-preview.js +4 -11
  75. package/dist/edge/http/auditions.js +1 -1
  76. package/dist/edge/http/autostart.js +5 -5
  77. package/dist/edge/http/backups.js +2 -4
  78. package/dist/edge/http/channel-memory.js +9 -22
  79. package/dist/edge/http/channels.js +14 -27
  80. package/dist/edge/http/checkpoints.js +39 -21
  81. package/dist/edge/http/design-templates.js +167 -0
  82. package/dist/edge/http/designs.js +178 -0
  83. package/dist/edge/http/diagnostics.js +5 -1
  84. package/dist/edge/http/document-themes.js +5 -14
  85. package/dist/edge/http/draft-files.js +1 -3
  86. package/dist/edge/http/draft-problem.js +5 -10
  87. package/dist/edge/http/drafts.js +3 -3
  88. package/dist/edge/http/entries.js +6 -9
  89. package/dist/edge/http/extension-auth.js +27 -0
  90. package/dist/edge/http/external-reviews.js +12 -11
  91. package/dist/edge/http/files.js +5 -11
  92. package/dist/edge/http/folder-location.js +1 -4
  93. package/dist/edge/http/fonts.js +0 -7
  94. package/dist/edge/http/home.js +47 -14
  95. package/dist/edge/http/host-cli.js +18 -8
  96. package/dist/edge/http/ids.js +8 -0
  97. package/dist/edge/http/keywords.js +35 -0
  98. package/dist/edge/http/model-checks.js +132 -0
  99. package/dist/edge/http/narration-peaks.js +5 -13
  100. package/dist/edge/http/onboarding.js +30 -26
  101. package/dist/edge/http/open-folder.js +5 -10
  102. package/dist/edge/http/outputs.js +7 -14
  103. package/dist/edge/http/patch-notes.js +6 -12
  104. package/dist/edge/http/planning.js +3 -7
  105. package/dist/edge/http/problem.js +2 -4
  106. package/dist/edge/http/project-create.js +2 -1
  107. package/dist/edge/http/project-templates.js +31 -19
  108. package/dist/edge/http/projects.js +24 -16
  109. package/dist/edge/http/prompts.js +9 -16
  110. package/dist/edge/http/pronunciations.js +1 -2
  111. package/dist/edge/http/providers.js +48 -34
  112. package/dist/edge/http/refusal.js +29 -0
  113. package/dist/edge/http/remote-access.js +40 -0
  114. package/dist/edge/http/remote-guard.js +71 -0
  115. package/dist/edge/http/request-guard.js +91 -0
  116. package/dist/edge/http/reviews.js +12 -25
  117. package/dist/edge/http/revision-files.js +6 -11
  118. package/dist/edge/http/revisions.js +14 -19
  119. package/dist/edge/http/run-cost.js +1 -2
  120. package/dist/edge/http/same-page.js +80 -0
  121. package/dist/edge/http/schedules.js +69 -40
  122. package/dist/edge/http/settings.js +27 -14
  123. package/dist/edge/http/social.js +283 -0
  124. package/dist/edge/http/staging.js +3 -5
  125. package/dist/edge/http/storage-files.js +5 -7
  126. package/dist/edge/http/storage.js +33 -25
  127. package/dist/edge/http/studio.js +94 -69
  128. package/dist/edge/http/style-preview.js +3 -6
  129. package/dist/edge/http/subtitles.js +3 -17
  130. package/dist/edge/http/template-studio.js +436 -0
  131. package/dist/edge/http/trash.js +11 -12
  132. package/dist/edge/http/tutorial.js +1 -2
  133. package/dist/edge/http/tutorials.js +5 -11
  134. package/dist/edge/http/update.js +7 -13
  135. package/dist/edge/http/waveform.js +3 -6
  136. package/dist/edge/http/youtube-edits.js +3 -4
  137. package/dist/edge/mcp/assistant.js +279 -0
  138. package/dist/edge/mcp/attribution.js +8 -2
  139. package/dist/edge/mcp/call-route.js +11 -2
  140. package/dist/edge/mcp/excluded-routes.js +29 -3
  141. package/dist/edge/mcp/http.js +11 -2
  142. package/dist/edge/mcp/instructions.js +6 -4
  143. package/dist/edge/mcp/library-tools.js +9 -5
  144. package/dist/edge/mcp/remake-tools.js +7 -4
  145. package/dist/edge/mcp/route-tool.js +35 -15
  146. package/dist/edge/mcp/server.js +13 -8
  147. package/dist/edge/mcp/tools-channels.js +11 -9
  148. package/dist/edge/mcp/tools-designs.js +165 -0
  149. package/dist/edge/mcp/tools-library.js +19 -7
  150. package/dist/edge/mcp/tools-planning.js +40 -21
  151. package/dist/edge/mcp/tools-projects.js +38 -11
  152. package/dist/edge/mcp/tools-publishing.js +9 -9
  153. package/dist/edge/mcp/tools-runs.js +23 -10
  154. package/dist/edge/mcp/tools-schedules.js +32 -8
  155. package/dist/edge/mcp/tools-setup.js +75 -11
  156. package/dist/edge/mcp/tools-social.js +47 -0
  157. package/dist/edge/mcp/tools-storage.js +11 -3
  158. package/dist/edge/mcp/tools-template-studio.js +166 -0
  159. package/dist/edge/mcp/tools.js +6 -0
  160. package/dist/edge/remote-access/cli.js +71 -0
  161. package/dist/edge/remote-access/model.js +32 -0
  162. package/dist/edge/remote-access/rules.js +207 -0
  163. package/dist/edge/remote-access/service.js +203 -0
  164. package/dist/edge/remote-access/store.js +44 -0
  165. package/dist/edge/remote-access/views.js +43 -0
  166. package/dist/extension/slopify-studio-chrome.zip +0 -0
  167. package/dist/extension/slopify-studio-firefox.zip +0 -0
  168. package/dist/host-cli/install.js +2 -2
  169. package/dist/host-cli/open-folder.js +2 -2
  170. package/dist/host-cli/paths.js +2 -4
  171. package/dist/host-cli/server.js +4 -4
  172. package/dist/host-cli/service.js +3 -3
  173. package/dist/host-cli/status.js +7 -7
  174. package/dist/kernel/db/migrations/0063-model-checks.sql +20 -0
  175. package/dist/kernel/db/migrations/0064-template-studio.sql +46 -0
  176. package/dist/kernel/db/migrations/0065-promise-and-assistant-reviews.sql +58 -0
  177. package/dist/kernel/db/migrations/0066-assistant.sql +44 -0
  178. package/dist/kernel/db/migrations/0067-autopilot.sql +6 -0
  179. package/dist/kernel/db/migrations/0068-topic-scores.sql +17 -0
  180. package/dist/kernel/db/migrations/0070-designs.sql +53 -0
  181. package/dist/kernel/db/migrations/0071-design-usage.sql +33 -0
  182. package/dist/kernel/db/migrations/0072-design-templates.sql +33 -0
  183. package/dist/kernel/db/migrations/0073-schedule-design-kind.sql +5 -0
  184. package/dist/kernel/db/migrations/0074-template-bakeoffs.sql +44 -0
  185. package/dist/kernel/db/migrations/0075-social-posts.sql +25 -0
  186. package/dist/kernel/db/migrations/0076-channel-social.sql +5 -0
  187. package/dist/kernel/db/migrations/0077-post-prompts.sql +17 -0
  188. package/dist/kernel/db/migrations/0078-uploaded-from-links.sql +7 -0
  189. package/dist/kernel/db/tx.js +2 -4
  190. package/dist/kernel/errors.js +27 -1
  191. package/dist/kernel/json-answer.js +15 -0
  192. package/dist/kernel/lock.js +4 -9
  193. package/dist/kernel/paths.js +8 -0
  194. package/dist/kernel/pipeline.js +12 -0
  195. package/dist/kernel/ports/assistant-cli.js +4 -0
  196. package/dist/kernel/ports/host-cli.js +3 -0
  197. package/dist/kernel/ports/interest.js +4 -0
  198. package/dist/kernel/ports/languages.js +0 -3
  199. package/dist/kernel/ports/model.js +2 -0
  200. package/dist/kernel/ports/settings-places.js +31 -0
  201. package/dist/kernel/ports/system-speech.js +31 -3
  202. package/dist/kernel/runner/attempt.js +14 -4
  203. package/dist/kernel/runner/audition.js +3 -1
  204. package/dist/kernel/runner/checkpoint-authority.js +2 -1
  205. package/dist/kernel/runner/index.js +8 -10
  206. package/dist/kernel/runner/model-access.js +40 -0
  207. package/dist/kernel/runner/model-check.js +153 -0
  208. package/dist/kernel/runner/providers.js +18 -12
  209. package/dist/kernel/runner/standalone.js +11 -7
  210. package/dist/main.js +338 -21
  211. package/dist/model-catalog.js +20 -0
  212. package/dist/model-checks.js +166 -0
  213. package/dist/patch-notes/4.0.0.md +51 -0
  214. package/dist/patch-notes/4.1.0.md +42 -0
  215. package/dist/patch-notes/index.json +12 -0
  216. package/dist/slices/admission/heads.js +2 -1
  217. package/dist/slices/admission/listing-facts.js +95 -0
  218. package/dist/slices/admission/repo.js +11 -7
  219. package/dist/slices/admission/rules.js +50 -15
  220. package/dist/slices/admission/schema.js +8 -0
  221. package/dist/slices/admission/stage-writes.js +0 -3
  222. package/dist/slices/admission/start.js +48 -31
  223. package/dist/slices/admission/state-words.js +34 -5
  224. package/dist/slices/admission/substitute.js +7 -4
  225. package/dist/slices/agents/attention-posts.js +31 -0
  226. package/dist/slices/agents/attention-templates.js +33 -0
  227. package/dist/slices/agents/attention.js +10 -1
  228. package/dist/slices/agents/connection.js +2 -6
  229. package/dist/slices/agents/model.js +6 -3
  230. package/dist/slices/agents/repo.js +15 -7
  231. package/dist/slices/article/continuation.js +0 -63
  232. package/dist/slices/article/plain.js +3 -2
  233. package/dist/slices/article/segments.js +2 -62
  234. package/dist/slices/assistant/access.js +1 -0
  235. package/dist/slices/assistant/asks.js +80 -0
  236. package/dist/slices/assistant/cost.js +54 -0
  237. package/dist/slices/assistant/gate.js +222 -0
  238. package/dist/slices/assistant/model.js +86 -0
  239. package/dist/slices/assistant/names.js +23 -0
  240. package/dist/slices/assistant/prompt.js +74 -0
  241. package/dist/slices/assistant/repo.js +74 -0
  242. package/dist/slices/assistant/tokens.js +18 -0
  243. package/dist/slices/assistant/turn.js +344 -0
  244. package/dist/slices/autopilot/buffer.js +76 -0
  245. package/dist/slices/autopilot/model.js +14 -0
  246. package/dist/slices/autopilot/notify.js +27 -0
  247. package/dist/slices/autopilot/repo.js +34 -0
  248. package/dist/slices/autopilot/signals.js +194 -0
  249. package/dist/slices/autopilot/warnings.js +85 -0
  250. package/dist/slices/autopilot/weekly.js +100 -0
  251. package/dist/slices/backups/files.js +2 -6
  252. package/dist/slices/backups/repo.js +3 -15
  253. package/dist/slices/backups/service.js +8 -7
  254. package/dist/slices/channels/cast-images.js +3 -2
  255. package/dist/slices/channels/model.js +0 -1
  256. package/dist/slices/channels/rebrand.js +0 -3
  257. package/dist/slices/channels/repo.js +4 -0
  258. package/dist/slices/channels/schema.js +7 -1
  259. package/dist/slices/channels/service.js +10 -0
  260. package/dist/slices/channels/videos.js +3 -15
  261. package/dist/slices/checkpoints/index.js +0 -1
  262. package/dist/slices/control/providers.js +2 -28
  263. package/dist/slices/designs/artwork.js +66 -0
  264. package/dist/slices/designs/budget.js +23 -0
  265. package/dist/slices/designs/calls.js +124 -0
  266. package/dist/slices/designs/check-context.js +38 -0
  267. package/dist/slices/designs/check-text.js +43 -0
  268. package/dist/slices/designs/fields.js +88 -0
  269. package/dist/slices/designs/frames.js +34 -0
  270. package/dist/slices/designs/gate.js +130 -0
  271. package/dist/slices/designs/layout.js +293 -0
  272. package/dist/slices/designs/model.js +35 -0
  273. package/dist/slices/designs/pdf-job.js +23 -0
  274. package/dist/slices/designs/pdf-pages.js +30 -0
  275. package/dist/slices/designs/pdf-runner.js +68 -0
  276. package/dist/slices/designs/pdf-worker.js +24 -0
  277. package/dist/slices/designs/picture-size.js +79 -0
  278. package/dist/slices/designs/pictures.js +55 -0
  279. package/dist/slices/designs/quote.js +24 -0
  280. package/dist/slices/designs/render-args.js +36 -0
  281. package/dist/slices/designs/render-input.js +59 -0
  282. package/dist/slices/designs/render.js +215 -0
  283. package/dist/slices/designs/repo.js +101 -0
  284. package/dist/slices/designs/requests.js +80 -0
  285. package/dist/slices/designs/runner.js +239 -0
  286. package/dist/slices/designs/sample-repo.js +91 -0
  287. package/dist/slices/designs/sample.js +223 -0
  288. package/dist/slices/designs/schema.js +87 -0
  289. package/dist/slices/designs/sections.js +44 -0
  290. package/dist/slices/designs/service.js +233 -0
  291. package/dist/slices/designs/steps-repo.js +102 -0
  292. package/dist/slices/designs/studio-kind.js +39 -0
  293. package/dist/slices/designs/template-checks.js +44 -0
  294. package/dist/slices/designs/template-make.js +118 -0
  295. package/dist/slices/designs/templates.js +218 -0
  296. package/dist/slices/document/fonts.js +2 -1
  297. package/dist/slices/document/model.js +7 -4
  298. package/dist/slices/document/render.js +2 -1
  299. package/dist/slices/document/theme-schema.js +1 -1
  300. package/dist/slices/document/theme.js +49 -3
  301. package/dist/slices/episodes/summarize.js +6 -2
  302. package/dist/slices/estimate/index.js +72 -28
  303. package/dist/slices/estimate/requests.js +4 -4
  304. package/dist/slices/fixes/rules.js +3 -1
  305. package/dist/slices/fonts/catalog.js +5 -3
  306. package/dist/slices/fonts/coverage.js +2 -1
  307. package/dist/slices/fonts/discovery.js +3 -2
  308. package/dist/slices/fonts/files.js +2 -6
  309. package/dist/slices/fonts/index.js +0 -1
  310. package/dist/slices/fonts/sfnt.js +45 -10
  311. package/dist/slices/images/appearance.js +8 -16
  312. package/dist/slices/images/scenes.js +102 -28
  313. package/dist/slices/keywords/digest.js +85 -0
  314. package/dist/slices/keywords/facts.js +92 -0
  315. package/dist/slices/keywords/fill.js +80 -0
  316. package/dist/slices/keywords/model.js +75 -0
  317. package/dist/slices/keywords/rules.js +187 -0
  318. package/dist/slices/keywords/schema.js +28 -0
  319. package/dist/slices/keywords/specs.js +260 -0
  320. package/dist/slices/keywords/values.js +74 -0
  321. package/dist/slices/library/lint.js +29 -12
  322. package/dist/slices/library/model.js +2 -0
  323. package/dist/slices/library/photorealistic.js +4 -12
  324. package/dist/slices/library/rename.js +9 -7
  325. package/dist/slices/library/save.js +14 -10
  326. package/dist/slices/library/slots.js +37 -11
  327. package/dist/slices/library/transfer.js +5 -4
  328. package/dist/slices/library/used-by.js +25 -10
  329. package/dist/slices/loudness/loudnorm.js +53 -12
  330. package/dist/slices/loudness/model.js +3 -2
  331. package/dist/slices/loudness/sentence-level.js +2 -1
  332. package/dist/slices/model-checks/cost.js +61 -0
  333. package/dist/slices/model-checks/listing.js +124 -0
  334. package/dist/slices/model-checks/model.js +20 -0
  335. package/dist/slices/model-checks/record.js +32 -0
  336. package/dist/slices/model-checks/repo.js +120 -0
  337. package/dist/slices/model-checks/rules.js +125 -0
  338. package/dist/slices/model-checks/run.js +143 -0
  339. package/dist/slices/model-checks/service.js +198 -0
  340. package/dist/slices/model-upkeep/model.js +1 -1
  341. package/dist/slices/model-upkeep/switch.js +72 -27
  342. package/dist/slices/model-upkeep/usage.js +39 -13
  343. package/dist/slices/narration/breaths.js +3 -1
  344. package/dist/slices/narration/clean-pieces.js +3 -1
  345. package/dist/slices/narration/concat.js +2 -1
  346. package/dist/slices/narration/pauses-model.js +3 -2
  347. package/dist/slices/narration/peaks.js +3 -4
  348. package/dist/slices/narration/pronunciation-chunks.js +4 -3
  349. package/dist/slices/narration/pronunciation.js +6 -4
  350. package/dist/slices/narration/steering.js +4 -3
  351. package/dist/slices/notifications/notifier.js +4 -3
  352. package/dist/slices/notifications/rules.js +5 -2
  353. package/dist/slices/notifications/settings.js +4 -12
  354. package/dist/slices/notifications/subject.js +4 -3
  355. package/dist/slices/onboarding/copy.js +21 -3
  356. package/dist/slices/onboarding/first-run.js +16 -40
  357. package/dist/slices/onboarding/full-video.js +3 -2
  358. package/dist/slices/onboarding/install.js +6 -4
  359. package/dist/slices/onboarding/model.js +26 -37
  360. package/dist/slices/onboarding/packs.js +22 -22
  361. package/dist/slices/onboarding/preferred-model.js +14 -0
  362. package/dist/slices/onboarding/quick-short.js +105 -65
  363. package/dist/slices/onboarding/sample.js +4 -2
  364. package/dist/slices/onboarding/state.js +9 -11
  365. package/dist/slices/onboarding/template.js +8 -6
  366. package/dist/slices/outputs/add.js +3 -2
  367. package/dist/slices/outputs/service.js +2 -2
  368. package/dist/slices/patch-notes/library.js +4 -3
  369. package/dist/slices/play-drafts/convert.js +4 -0
  370. package/dist/slices/play-drafts/readiness.js +3 -3
  371. package/dist/slices/play-drafts/review-inputs.js +9 -3
  372. package/dist/slices/play-drafts/review.js +5 -4
  373. package/dist/slices/play-drafts/schema.js +4 -0
  374. package/dist/slices/play-drafts/service.js +17 -7
  375. package/dist/slices/project-templates/from-project.js +9 -2
  376. package/dist/slices/project-templates/next-chapter.js +2 -1
  377. package/dist/slices/project-templates/provider-defaults.js +22 -0
  378. package/dist/slices/project-templates/repo.js +6 -1
  379. package/dist/slices/project-templates/setup-sections.js +26 -0
  380. package/dist/slices/project-templates/setup.js +14 -1
  381. package/dist/slices/project-templates/stand-ins.js +22 -0
  382. package/dist/slices/rebuild/activity.js +2 -0
  383. package/dist/slices/rebuild/admission-repo.js +3 -2
  384. package/dist/slices/rebuild/narration-history.js +4 -5
  385. package/dist/slices/rebuild/narration-retry.js +2 -1
  386. package/dist/slices/rebuild/preview-details.js +32 -14
  387. package/dist/slices/rebuild/preview-plan.js +3 -8
  388. package/dist/slices/rebuild/recipe-build.js +8 -3
  389. package/dist/slices/rebuild/recipe-describe.js +3 -3
  390. package/dist/slices/rebuild/recipe-digest.js +58 -0
  391. package/dist/slices/rebuild/recipe-input-schema.js +10 -0
  392. package/dist/slices/rebuild/recipe-keywords.js +71 -0
  393. package/dist/slices/rebuild/recipe-legacy.js +5 -1
  394. package/dist/slices/rebuild/recipe-model.js +4 -0
  395. package/dist/slices/rebuild/recipe-narration-text.js +2 -1
  396. package/dist/slices/rebuild/recipe-preparation.js +4 -4
  397. package/dist/slices/rebuild/recipe-reference.js +8 -4
  398. package/dist/slices/rebuild/recipe-reviews.js +27 -7
  399. package/dist/slices/rebuild/recipe-save.js +8 -5
  400. package/dist/slices/rebuild/recipe-scenes.js +40 -11
  401. package/dist/slices/rebuild/recipe-shorts.js +8 -8
  402. package/dist/slices/rebuild/recipe-social.js +46 -0
  403. package/dist/slices/rebuild/recipe-text.js +64 -20
  404. package/dist/slices/rebuild/recipe-validation.js +102 -31
  405. package/dist/slices/rebuild/recipe-visual.js +55 -16
  406. package/dist/slices/rebuild/recipe-voices.js +3 -2
  407. package/dist/slices/rebuild/recipe-work.js +19 -7
  408. package/dist/slices/rebuild/recipe-youtube.js +4 -3
  409. package/dist/slices/rebuild/recovery-selection.js +3 -1
  410. package/dist/slices/rebuild/recovery.js +9 -8
  411. package/dist/slices/rebuild/remake-outdated.js +5 -5
  412. package/dist/slices/rebuild/repo.js +4 -3
  413. package/dist/slices/rebuild/review-promise.js +25 -0
  414. package/dist/slices/rebuild/review-redo.js +3 -2
  415. package/dist/slices/rebuild/runtime-admission.js +4 -3
  416. package/dist/slices/rebuild/runtime-agent-review.js +11 -5
  417. package/dist/slices/rebuild/runtime-animate.js +10 -9
  418. package/dist/slices/rebuild/runtime-article.js +7 -5
  419. package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
  420. package/dist/slices/rebuild/runtime-document.js +11 -13
  421. package/dist/slices/rebuild/runtime-export-bed.js +8 -12
  422. package/dist/slices/rebuild/runtime-export-edit.js +8 -16
  423. package/dist/slices/rebuild/runtime-export-inputs.js +12 -11
  424. package/dist/slices/rebuild/runtime-export-short.js +9 -5
  425. package/dist/slices/rebuild/runtime-export.js +38 -15
  426. package/dist/slices/rebuild/runtime-figure-card.js +2 -1
  427. package/dist/slices/rebuild/runtime-image.js +8 -12
  428. package/dist/slices/rebuild/runtime-instructions.js +2 -0
  429. package/dist/slices/rebuild/runtime-keywords.js +59 -0
  430. package/dist/slices/rebuild/runtime-lines.js +2 -11
  431. package/dist/slices/rebuild/runtime-local.js +17 -22
  432. package/dist/slices/rebuild/runtime-narration-publication.js +2 -5
  433. package/dist/slices/rebuild/runtime-narration-reuse.js +9 -13
  434. package/dist/slices/rebuild/runtime-narration-text.js +13 -17
  435. package/dist/slices/rebuild/runtime-pictures.js +2 -4
  436. package/dist/slices/rebuild/runtime-piece-label.js +10 -6
  437. package/dist/slices/rebuild/runtime-plan.js +31 -5
  438. package/dist/slices/rebuild/runtime-provider.js +67 -25
  439. package/dist/slices/rebuild/runtime-publication.js +10 -9
  440. package/dist/slices/rebuild/runtime-review.js +39 -33
  441. package/dist/slices/rebuild/runtime-run.js +4 -1
  442. package/dist/slices/rebuild/runtime-shorts.js +31 -50
  443. package/dist/slices/rebuild/runtime-store.js +20 -10
  444. package/dist/slices/rebuild/runtime-subtitles.js +28 -25
  445. package/dist/slices/rebuild/runtime-voices.js +10 -16
  446. package/dist/slices/rebuild/runtime-youtube.js +14 -31
  447. package/dist/slices/rebuild/service-readiness.js +11 -3
  448. package/dist/slices/rebuild/transition-repo.js +6 -5
  449. package/dist/slices/rebuild/work-records.js +6 -0
  450. package/dist/slices/reruns/cascade.js +1 -2
  451. package/dist/slices/reruns/index.js +6 -9
  452. package/dist/slices/research/synthesis.js +3 -2
  453. package/dist/slices/reviews/external-model.js +4 -1
  454. package/dist/slices/reviews/external-repo.js +2 -1
  455. package/dist/slices/reviews/external-rows.js +2 -3
  456. package/dist/slices/reviews/holds.js +5 -4
  457. package/dist/slices/reviews/model.js +32 -11
  458. package/dist/slices/reviews/repo.js +2 -1
  459. package/dist/slices/reviews/rules.js +4 -3
  460. package/dist/slices/reviews/verdict.js +2 -1
  461. package/dist/slices/reviews/view.js +5 -2
  462. package/dist/slices/revisions/downloads.js +4 -5
  463. package/dist/slices/revisions/image-plan.js +1 -1
  464. package/dist/slices/revisions/mutation-assets.js +9 -5
  465. package/dist/slices/revisions/mutation-cues.js +2 -5
  466. package/dist/slices/revisions/mutations.js +3 -3
  467. package/dist/slices/revisions/repo.js +3 -1
  468. package/dist/slices/revisions/schema.js +1 -0
  469. package/dist/slices/revisions/view.js +20 -0
  470. package/dist/slices/run-cost/limits.js +5 -2
  471. package/dist/slices/run-cost/meter.js +12 -5
  472. package/dist/slices/run-cost/panel.js +8 -74
  473. package/dist/slices/run-cost/plan-readings.js +127 -0
  474. package/dist/slices/run-cost/plan-use.js +169 -0
  475. package/dist/slices/run-cost/pricing.js +3 -3
  476. package/dist/slices/run-cost/week.js +32 -4
  477. package/dist/slices/schedules/agenda.js +18 -7
  478. package/dist/slices/schedules/by-date.js +42 -0
  479. package/dist/slices/schedules/calendar.js +5 -0
  480. package/dist/slices/schedules/design-agenda.js +36 -0
  481. package/dist/slices/schedules/design-check.js +41 -0
  482. package/dist/slices/schedules/design-run.js +126 -0
  483. package/dist/slices/schedules/next-run.js +62 -0
  484. package/dist/slices/schedules/prepare.js +18 -2
  485. package/dist/slices/schedules/repo.js +30 -21
  486. package/dist/slices/schedules/rescore.js +35 -0
  487. package/dist/slices/schedules/scheduler.js +13 -3
  488. package/dist/slices/schedules/schema.js +57 -3
  489. package/dist/slices/schedules/score.js +107 -0
  490. package/dist/slices/schedules/service.js +57 -24
  491. package/dist/slices/schedules/topic-list.js +15 -0
  492. package/dist/slices/schedules/topic-scores.js +104 -0
  493. package/dist/slices/schedules/topics.js +53 -41
  494. package/dist/slices/settings/cli-paths.js +4 -6
  495. package/dist/slices/settings/first-run.js +8 -43
  496. package/dist/slices/settings/health.js +30 -15
  497. package/dist/slices/settings/key-test.js +1 -1
  498. package/dist/slices/settings/model-reach.js +3 -17
  499. package/dist/slices/settings/model.js +14 -2
  500. package/dist/slices/settings/playback.js +34 -23
  501. package/dist/slices/settings/probe-read.js +16 -0
  502. package/dist/slices/settings/repo.js +33 -0
  503. package/dist/slices/settings/tutorial.js +2 -2
  504. package/dist/slices/settings/voices.js +4 -12
  505. package/dist/slices/shorts/keep.js +2 -1
  506. package/dist/slices/shorts/pick.js +6 -18
  507. package/dist/slices/shorts/prompts.js +4 -14
  508. package/dist/slices/shorts/render.js +6 -2
  509. package/dist/slices/social/channel.js +44 -0
  510. package/dist/slices/social/defaults.js +17 -0
  511. package/dist/slices/social/due.js +36 -0
  512. package/dist/slices/social/extension.js +18 -0
  513. package/dist/slices/social/fit.js +115 -0
  514. package/dist/slices/social/limits.js +186 -0
  515. package/dist/slices/social/links.js +21 -0
  516. package/dist/slices/social/media.js +132 -0
  517. package/dist/slices/social/model.js +75 -0
  518. package/dist/slices/social/notice.js +19 -0
  519. package/dist/slices/social/pack.js +176 -0
  520. package/dist/slices/social/post-prompt.js +56 -0
  521. package/dist/slices/social/queue.js +31 -0
  522. package/dist/slices/social/repo.js +133 -0
  523. package/dist/slices/social/reshape.js +97 -0
  524. package/dist/slices/social/rules.js +5 -0
  525. package/dist/slices/social/schema.js +70 -0
  526. package/dist/slices/social/serve.js +51 -0
  527. package/dist/slices/social/service.js +129 -0
  528. package/dist/slices/social/settings.js +21 -0
  529. package/dist/slices/social/sources.js +114 -0
  530. package/dist/slices/social/state.js +110 -0
  531. package/dist/slices/social/text.js +149 -0
  532. package/dist/slices/storage/arrange.js +3 -1
  533. package/dist/slices/storage/assets.js +8 -10
  534. package/dist/slices/storage/backup-designs.js +74 -0
  535. package/dist/slices/storage/backup-export.js +8 -3
  536. package/dist/slices/storage/backup-format.js +10 -0
  537. package/dist/slices/storage/backup-import.js +22 -4
  538. package/dist/slices/storage/delete-project.js +6 -5
  539. package/dist/slices/storage/files-location.js +6 -8
  540. package/dist/slices/storage/layout.js +13 -2
  541. package/dist/slices/storage/model.js +3 -0
  542. package/dist/slices/storage/portable.js +17 -12
  543. package/dist/slices/storage/schema.js +2 -0
  544. package/dist/slices/storage/staging-refs.js +2 -1
  545. package/dist/slices/storage/staging.js +6 -9
  546. package/dist/slices/storage/trim.js +26 -9
  547. package/dist/slices/studio/calendar.js +7 -2
  548. package/dist/slices/studio/disclosure.js +4 -4
  549. package/dist/slices/studio/model.js +1 -1
  550. package/dist/slices/studio/pack.js +9 -8
  551. package/dist/slices/studio/pick.js +2 -11
  552. package/dist/slices/studio/plan.js +7 -16
  553. package/dist/slices/studio/queue.js +3 -14
  554. package/dist/slices/studio/releases.js +26 -0
  555. package/dist/slices/studio/settings.js +20 -57
  556. package/dist/slices/studio/videos.js +35 -4
  557. package/dist/slices/style-preview/render.js +37 -16
  558. package/dist/slices/style-preview/schema.js +14 -1
  559. package/dist/slices/style-preview/service.js +2 -1
  560. package/dist/slices/style-preview/settings.js +33 -5
  561. package/dist/slices/subtitles/captions.js +1 -1
  562. package/dist/slices/telemetry/collector-client.js +2 -4
  563. package/dist/slices/telemetry/flush.js +2 -4
  564. package/dist/slices/telemetry/model.js +0 -7
  565. package/dist/slices/telemetry/record.js +10 -3
  566. package/dist/slices/template-studio/anatomy-view.js +161 -0
  567. package/dist/slices/template-studio/anatomy.js +73 -0
  568. package/dist/slices/template-studio/bakeoff-make.js +225 -0
  569. package/dist/slices/template-studio/bakeoff-repo.js +83 -0
  570. package/dist/slices/template-studio/bakeoff.js +333 -0
  571. package/dist/slices/template-studio/budget.js +106 -0
  572. package/dist/slices/template-studio/cards.js +78 -0
  573. package/dist/slices/template-studio/checks.js +322 -0
  574. package/dist/slices/template-studio/document-sample.js +56 -0
  575. package/dist/slices/template-studio/edit.js +15 -0
  576. package/dist/slices/template-studio/for-all.js +161 -0
  577. package/dist/slices/template-studio/free-video.js +31 -0
  578. package/dist/slices/template-studio/free.js +176 -0
  579. package/dist/slices/template-studio/gate.js +40 -0
  580. package/dist/slices/template-studio/hidden.js +20 -0
  581. package/dist/slices/template-studio/jobs.js +79 -0
  582. package/dist/slices/template-studio/loudness.js +111 -0
  583. package/dist/slices/template-studio/make.js +40 -0
  584. package/dist/slices/template-studio/model.js +58 -0
  585. package/dist/slices/template-studio/pack-import.js +253 -0
  586. package/dist/slices/template-studio/pack-samples.js +60 -0
  587. package/dist/slices/template-studio/pack.js +207 -0
  588. package/dist/slices/template-studio/paid-view.js +66 -0
  589. package/dist/slices/template-studio/paid.js +355 -0
  590. package/dist/slices/template-studio/quote.js +155 -0
  591. package/dist/slices/template-studio/ready.js +104 -0
  592. package/dist/slices/template-studio/repo.js +134 -0
  593. package/dist/slices/template-studio/reviews-path.js +58 -0
  594. package/dist/slices/template-studio/runner.js +84 -0
  595. package/dist/slices/template-studio/save.js +236 -0
  596. package/dist/slices/template-studio/say-it-as.js +248 -0
  597. package/dist/slices/template-studio/schedule-gate.js +34 -0
  598. package/dist/slices/template-studio/schedule-price.js +32 -0
  599. package/dist/slices/template-studio/schedule-sample.js +91 -0
  600. package/dist/slices/template-studio/schema.js +243 -0
  601. package/dist/slices/template-studio/scope.js +124 -0
  602. package/dist/slices/template-studio/sections.js +176 -0
  603. package/dist/slices/template-studio/service.js +175 -0
  604. package/dist/slices/template-studio/status.js +191 -0
  605. package/dist/slices/template-studio/topic.js +223 -0
  606. package/dist/slices/thumbnail/by-llm.js +1 -1
  607. package/dist/slices/trash/designs.js +76 -0
  608. package/dist/slices/trash/model.js +13 -4
  609. package/dist/slices/trash/service.js +52 -2
  610. package/dist/slices/tutorials/library.js +3 -2
  611. package/dist/slices/uploads/repo.js +37 -1
  612. package/dist/slices/uploads/rules.js +3 -2
  613. package/dist/slices/video/ass-text.js +17 -0
  614. package/dist/slices/video/audio-export.js +2 -64
  615. package/dist/slices/video/edit-list.js +2 -2
  616. package/dist/slices/video/edit-settings.js +1 -1
  617. package/dist/slices/video/encoding.js +169 -0
  618. package/dist/slices/video/ffmpeg.js +23 -21
  619. package/dist/slices/video/figure-card.js +1 -15
  620. package/dist/slices/video/plan.js +2 -1
  621. package/dist/slices/video/slideshow.js +43 -18
  622. package/dist/slices/voices/attribution.js +3 -2
  623. package/dist/slices/voices/model.js +9 -8
  624. package/dist/slices/voices/palette.js +0 -3
  625. package/dist/slices/voices/script.js +2 -1
  626. package/dist/slices/youtube/answer.js +4 -16
  627. package/dist/slices/youtube/edits-repo.js +3 -12
  628. package/dist/tutorials/AI-CLIs.md +25 -25
  629. package/dist/tutorials/Agents-MCP.md +34 -33
  630. package/dist/tutorials/Assistant.md +42 -0
  631. package/dist/tutorials/Backups.md +7 -7
  632. package/dist/tutorials/Calendar.md +17 -25
  633. package/dist/tutorials/Cast-Library.md +19 -19
  634. package/dist/tutorials/Channels.md +42 -35
  635. package/dist/tutorials/Costs-and-Run-Cost.md +38 -40
  636. package/dist/tutorials/Create-Article.md +114 -0
  637. package/dist/tutorials/Create-Extras.md +112 -0
  638. package/dist/tutorials/Create-Images.md +165 -0
  639. package/dist/tutorials/Create-Narration.md +178 -0
  640. package/dist/tutorials/Create-Overview.md +244 -0
  641. package/dist/tutorials/Create-Video-and-Style.md +144 -0
  642. package/dist/tutorials/Docker.md +3 -3
  643. package/dist/tutorials/Document-Themes.md +32 -32
  644. package/dist/tutorials/Editing-a-Project.md +94 -94
  645. package/dist/tutorials/FAQ.md +18 -18
  646. package/dist/tutorials/First-Launch-and-Welcome.md +50 -64
  647. package/dist/tutorials/Home-and-Projects.md +115 -71
  648. package/dist/tutorials/Home.md +33 -33
  649. package/dist/tutorials/Install.md +14 -13
  650. package/dist/tutorials/Intros-and-Outros.md +33 -34
  651. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +62 -58
  652. package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
  653. package/dist/tutorials/Models.md +53 -21
  654. package/dist/tutorials/Multiple-Voices.md +41 -40
  655. package/dist/tutorials/Narration-Aliases-and-Glossary.md +14 -14
  656. package/dist/tutorials/Notifications.md +27 -17
  657. package/dist/tutorials/Other-Languages.md +15 -15
  658. package/dist/tutorials/PDF-Documents.md +38 -29
  659. package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
  660. package/dist/tutorials/Project-Page.md +118 -120
  661. package/dist/tutorials/Prompts-and-Templates.md +84 -0
  662. package/dist/tutorials/Prompts.md +48 -44
  663. package/dist/tutorials/Providers-and-Keys.md +105 -98
  664. package/dist/tutorials/Publishing-to-YouTube.md +31 -31
  665. package/dist/tutorials/Recovery-and-Retries.md +31 -31
  666. package/dist/tutorials/Remote-Access.md +63 -0
  667. package/dist/tutorials/Reviews-and-Checkpoints.md +98 -90
  668. package/dist/tutorials/Schedules.md +46 -27
  669. package/dist/tutorials/Settings-Reference.md +194 -174
  670. package/dist/tutorials/Shorts.md +34 -33
  671. package/dist/tutorials/Start-at-Login.md +3 -3
  672. package/dist/tutorials/Studio-Extension.md +22 -24
  673. package/dist/tutorials/Template-Studio.md +128 -0
  674. package/dist/tutorials/Templates.md +51 -77
  675. package/dist/tutorials/Trash.md +4 -4
  676. package/dist/tutorials/Troubleshooting.md +44 -44
  677. package/dist/tutorials/Uninstalling-and-Moving.md +5 -5
  678. package/dist/tutorials/Updating-and-Patch-Notes.md +18 -18
  679. package/dist/tutorials/Video-Editing.md +34 -34
  680. package/dist/tutorials/Where-Your-Files-Live.md +10 -10
  681. package/dist/tutorials/YouTube-Description.md +26 -26
  682. package/dist/tutorials/Your-First-Short.md +35 -35
  683. package/dist/tutorials/_Sidebar.md +19 -14
  684. package/dist/updater/work-in-progress.js +42 -12
  685. package/dist/web/assets/index-BNftKQrA.css +1 -0
  686. package/dist/web/assets/index-Cpa58woP.js +459 -0
  687. package/dist/web/assets/pdf-BKh8f7R6.js +56 -0
  688. package/dist/web/assets/pdf.worker.min-BmVo14Nb.mjs +28 -0
  689. package/dist/web/assets/pdf.worker.min-D7i8o3wR.js +1 -0
  690. package/dist/web/index.html +2 -2
  691. package/package.json +6 -2
  692. package/dist/slices/article/provided-entries.js +0 -66
  693. package/dist/slices/article/run.js +0 -145
  694. package/dist/slices/images/run.js +0 -234
  695. package/dist/slices/narration/legacy-plan.js +0 -58
  696. package/dist/slices/narration/run.js +0 -270
  697. package/dist/slices/rebuild/runtime-actions.js +0 -90
  698. package/dist/slices/research/run.js +0 -224
  699. package/dist/slices/subtitles/prepare.js +0 -194
  700. package/dist/slices/subtitles/transcript.js +0 -34
  701. package/dist/slices/thumbnail/run.js +0 -219
  702. package/dist/slices/video/audio-inputs.js +0 -33
  703. package/dist/slices/video/reuse-audio.js +0 -62
  704. package/dist/slices/video/run.js +0 -78
  705. package/dist/slices/video/write-export.js +0 -158
  706. package/dist/slices/video/write-subtitles.js +0 -59
  707. package/dist/tutorials/Library-Overview.md +0 -76
  708. package/dist/tutorials/Play-Images.md +0 -161
  709. package/dist/tutorials/Play-Narration.md +0 -167
  710. package/dist/tutorials/Play-Outputs.md +0 -109
  711. package/dist/tutorials/Play-Overview.md +0 -227
  712. package/dist/tutorials/Play-Title-and-Article.md +0 -136
  713. package/dist/tutorials/Play-Video-and-Style.md +0 -143
  714. package/dist/web/assets/index-BadsoORk.css +0 -1
  715. package/dist/web/assets/index-D8LqNsCl.js +0 -326
  716. package/dist/web/assets/pdf-T7MCwjIv.js +0 -54
  717. package/dist/web/assets/pdf.worker.min-Dswkl-cV.mjs +0 -29
  718. package/dist/web/assets/pdf.worker.min-zC6nfbxG.js +0 -1
@@ -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
  }
@@ -0,0 +1,74 @@
1
+ import { rowsOf } from "./backup-export.js";
2
+ // Designs and design templates in a backup's library part. Their pictures travel with every
3
+ // other picture in image_blobs; the files drawn from them are a cache and never travel.
4
+ export const designTables = [
5
+ "designs",
6
+ "design_steps",
7
+ "design_templates",
8
+ "design_template_revisions",
9
+ "design_template_samples",
10
+ ];
11
+ export function exportDesigns(db, put) {
12
+ put("designs", rowsOf(db, "SELECT * FROM designs ORDER BY rowid"));
13
+ put("design_steps", rowsOf(db, "SELECT * FROM design_steps ORDER BY design_id, n"));
14
+ put("design_templates", rowsOf(db, "SELECT * FROM design_templates ORDER BY rowid"));
15
+ put("design_template_revisions", rowsOf(db, "SELECT * FROM design_template_revisions ORDER BY template_id, version"));
16
+ put("design_template_samples", rowsOf(db, "SELECT * FROM design_template_samples ORDER BY rowid"));
17
+ }
18
+ // The project a design links to, when the one here is the backup's own (it came in with this
19
+ // import, or with an earlier one: same id, same creation time); a different project that only
20
+ // shares the id is not linked.
21
+ function sameProject(db, scratch, id) {
22
+ if (typeof id !== "string")
23
+ return null;
24
+ const here = db.prepare("SELECT created_at FROM projects WHERE id=?").get(id);
25
+ const there = scratch.prepare("SELECT created_at FROM projects WHERE id=?").get(id);
26
+ return here !== undefined && there !== undefined && here.created_at === there.created_at
27
+ ? id
28
+ : null;
29
+ }
30
+ // Added beside what is here: a design or template this install already has (same id) is kept
31
+ // as it is. A picture the other install was still making never lands here; a sample job it was
32
+ // running starts over.
33
+ export function mergeDesigns(db, scratch, insert) {
34
+ const has = (sql, id) => db.prepare(sql).get(id) !== undefined;
35
+ const templates = { added: 0, skipped: 0 };
36
+ for (const row of rowsOf(scratch, "SELECT * FROM design_templates ORDER BY rowid")) {
37
+ const id = String(row.id);
38
+ if (has("SELECT 1 FROM design_templates WHERE id=?", id)) {
39
+ templates.skipped += 1;
40
+ continue;
41
+ }
42
+ insert("design_templates", row);
43
+ for (const revision of rowsOf(scratch, "SELECT * FROM design_template_revisions WHERE template_id=? ORDER BY version", id))
44
+ insert("design_template_revisions", revision);
45
+ templates.added += 1;
46
+ }
47
+ const designs = { added: 0, skipped: 0 };
48
+ for (const row of rowsOf(scratch, "SELECT * FROM designs ORDER BY rowid")) {
49
+ const id = String(row.id);
50
+ // A template's hidden sample is not one of the person's designs, so it isn't counted.
51
+ if (has("SELECT 1 FROM designs WHERE id=?", id)) {
52
+ if (row.sample_of === null)
53
+ designs.skipped += 1;
54
+ continue;
55
+ }
56
+ insert("designs", { ...row, project_id: sameProject(db, scratch, row.project_id) });
57
+ for (const step of rowsOf(scratch, "SELECT * FROM design_steps WHERE design_id=? ORDER BY n", id))
58
+ insert("design_steps", step.state === "making"
59
+ ? {
60
+ ...step,
61
+ state: "failed",
62
+ error: "Stopped by the backup. Press Try again.",
63
+ settled_at: step.created_at ?? null,
64
+ }
65
+ : step);
66
+ if (row.sample_of === null)
67
+ designs.added += 1;
68
+ }
69
+ for (const row of rowsOf(scratch, "SELECT * FROM design_template_samples ORDER BY rowid"))
70
+ if (has("SELECT 1 FROM design_templates WHERE id=?", String(row.template_id)) &&
71
+ !has("SELECT 1 FROM design_template_samples WHERE template_id=?", String(row.template_id)))
72
+ insert("design_template_samples", row.state === "making" ? { ...row, state: "queued" } : row);
73
+ return { designs, templates };
74
+ }
@@ -3,7 +3,10 @@ import { constants, lstatSync, readdirSync } from "node:fs";
3
3
  import { open } from "node:fs/promises";
4
4
  import { join } from "node:path";
5
5
  import { transact } from "../../kernel/db/tx.js";
6
+ import { hasErrnoCode } from "../../kernel/errors.js";
7
+ import { notTemplateSample } from "../template-studio/repo.js";
6
8
  import { projectMarker } from "./arrange.js";
9
+ import { exportDesigns } from "./backup-designs.js";
7
10
  import { backupFormat, backupSchemaVersion, checksumsMember, fontMember, imageMember, libraryMember, manifestMember, projectFileMember, projectMember, projectTables, safeRelativePath, stagedMember, usageMember, } from "./backup-format.js";
8
11
  import { outputPath, projectDir, stagingPath } from "./layout.js";
9
12
  import { exportableSettings, installedUploadedFontFiles } from "./portable.js";
@@ -36,7 +39,7 @@ export function busyProjects(deps) {
36
39
  OR EXISTS(SELECT 1 FROM revision_work w WHERE w.project_id=p.id AND w.state='running')
37
40
  OR EXISTS(SELECT 1 FROM project_queue q WHERE q.project_id=p.id AND q.state='queued')
38
41
  AS busy
39
- FROM projects p ORDER BY p.created_at, p.id`)
42
+ FROM projects p WHERE ${notTemplateSample("p")} ORDER BY p.created_at, p.id`)
40
43
  .all()
41
44
  .flatMap((row) => {
42
45
  const id = String(row.id);
@@ -60,7 +63,7 @@ export function planBackup(deps) {
60
63
  },
61
64
  },
62
65
  projects: deps.db
63
- .prepare("SELECT id,title FROM projects ORDER BY created_at,id")
66
+ .prepare(`SELECT id,title FROM projects WHERE ${notTemplateSample()} ORDER BY created_at,id`)
64
67
  .all()
65
68
  .map((row) => projectSnapshot(deps, String(row.id), String(row.title))),
66
69
  }));
@@ -238,7 +241,7 @@ function projectFiles(root) {
238
241
  entries = readdirSync(join(root, relative), { withFileTypes: true });
239
242
  }
240
243
  catch (error) {
241
- if (error instanceof Error && "code" in error && error.code === "ENOENT")
244
+ if (hasErrnoCode(error, "ENOENT"))
242
245
  return;
243
246
  throw error;
244
247
  }
@@ -271,6 +274,7 @@ function librarySnapshot(deps) {
271
274
  put("narration_aliases", rowsOf(db, "SELECT * FROM narration_aliases ORDER BY position,rowid"));
272
275
  put("project_templates", rowsOf(db, "SELECT * FROM project_templates ORDER BY rowid"));
273
276
  put("project_template_revisions", rowsOf(db, "SELECT * FROM project_template_revisions ORDER BY template_id,version"));
277
+ put("template_gates", rowsOf(db, "SELECT * FROM template_gates ORDER BY template_id,version"));
274
278
  put("schedules", rowsOf(db, "SELECT * FROM schedules ORDER BY rowid"));
275
279
  put("schedule_runs", rowsOf(db, "SELECT * FROM schedule_runs ORDER BY rowid"));
276
280
  put("schedule_topics", rowsOf(db, "SELECT * FROM schedule_topics ORDER BY rowid"));
@@ -281,6 +285,7 @@ function librarySnapshot(deps) {
281
285
  put("episode_memories", rowsOf(db, "SELECT * FROM episode_memories ORDER BY rowid"));
282
286
  put("channel_videos", rowsOf(db, "SELECT * FROM channel_videos ORDER BY rowid"));
283
287
  put("standalone_usage", rowsOf(db, "SELECT * FROM standalone_usage ORDER BY rowid"));
288
+ exportDesigns(db, put);
284
289
  // Every picture, not only the cast's: a project's config names the pictures it was started
285
290
  // with by hash, and a Rebuild reads them from here.
286
291
  const images = db
@@ -75,6 +75,8 @@ export const projectTables = [
75
75
  "plan_limit_readings",
76
76
  "project_channels",
77
77
  "project_trash",
78
+ // Since 4.0.0: each project's posts to other sites, their hand edits and links.
79
+ "social_posts",
78
80
  ];
79
81
  export const libraryTables = [
80
82
  "settings",
@@ -103,6 +105,14 @@ export const libraryTables = [
103
105
  // Since 3.0.0: what topic generation, episode summaries and cast pictures cost, which
104
106
  // belongs to a schedule or channel rather than a project.
105
107
  "standalone_usage",
108
+ // Since 4.0.0: which template versions the template studio marked ready.
109
+ "template_gates",
110
+ // Since 4.0.0: designs with every version of their picture, and design templates.
111
+ "designs",
112
+ "design_steps",
113
+ "design_templates",
114
+ "design_template_revisions",
115
+ "design_template_samples",
106
116
  ];
107
117
  export const usageTables = ["telemetry_events"];
108
118
  // Ceilings a well-formed backup of a real install stays far below. They bound what a hostile
@@ -16,6 +16,7 @@ import { projectTemplateSchema } from "../project-templates/schema.js";
16
16
  import { revisionById } from "../revisions/repo.js";
17
17
  import { runsForSchedule, scheduleRows } from "../schedules/repo.js";
18
18
  import { allTelemetryEvents } from "../telemetry/repo.js";
19
+ import { mergeDesigns } from "./backup-designs.js";
19
20
  import { projectRows, rowsOf } from "./backup-export.js";
20
21
  import { backupMaxJsonBytes, backupMaxManifestBytes, backupSchemaVersion, checksumsMember, checksumsSchema, fontMember, imageMember, libraryMember, libraryPartSchema, libraryTables, manifestHead, manifestMember, manifestSchema, projectFileMember, projectMember, projectPartSchema, projectTables, stagedMember, usageMember, usagePartSchema, usageTables, } from "./backup-format.js";
21
22
  import { outputPath, projectDir, stagingPath } from "./layout.js";
@@ -382,6 +383,8 @@ function loadScratch(clock, manifest, library, usage, parts) {
382
383
  // broken fails there, and the upgrade's own message would talk about downgrading.
383
384
  throw damaged("some of its records point at records it doesn't hold.");
384
385
  }
386
+ // A design only links to a project; one whose project didn't travel stays, unlinked.
387
+ scratch.exec("UPDATE designs SET project_id=NULL WHERE project_id NOT IN (SELECT id FROM projects)");
385
388
  if (scratch.prepare("PRAGMA foreign_key_check").all().length > 0)
386
389
  throw damaged("some of its records point at records it doesn't hold.");
387
390
  return scratch;
@@ -433,7 +436,7 @@ function checkScratch(deps, scratch, library, parts) {
433
436
  }
434
437
  }
435
438
  catch {
436
- throw unreadable("a PDF theme");
439
+ throw unreadable("a PDF look");
437
440
  }
438
441
  try {
439
442
  for (const row of rowsOf(scratch, "SELECT id FROM channels")) {
@@ -462,7 +465,7 @@ function checkScratch(deps, scratch, library, parts) {
462
465
  throw new Error("staged files");
463
466
  }
464
467
  catch {
465
- throw unreadable("a Play draft");
468
+ throw unreadable("a draft on Create");
466
469
  }
467
470
  try {
468
471
  allTelemetryEvents(scratch);
@@ -633,6 +636,8 @@ function commit(deps, work, manifest, prepared) {
633
636
  }
634
637
  insertRow(db, "project_template_revisions", next);
635
638
  }
639
+ for (const gate of rowsOf(scratch, "SELECT * FROM template_gates WHERE template_id=? ORDER BY version", id))
640
+ insertRow(db, "template_gates", gate);
636
641
  if (renamed)
637
642
  templates.renamed += 1;
638
643
  else
@@ -640,8 +645,15 @@ function commit(deps, work, manifest, prepared) {
640
645
  }
641
646
  const schedules = { added: 0, renamed: 0, skipped: 0, paused: 0 };
642
647
  for (const schedule of rowsOf(scratch, "SELECT * FROM schedules ORDER BY rowid")) {
643
- if (has("SELECT 1 FROM schedules WHERE id=?", String(schedule.id)) ||
644
- !has("SELECT 1 FROM project_template_revisions WHERE template_id=? AND version=?", String(schedule.template_id), Number(schedule.template_version))) {
648
+ // A design schedule's template comes in with the designs, further down.
649
+ const design = schedule.template_kind === "design";
650
+ const templateHere = design
651
+ ? scratch
652
+ .prepare("SELECT 1 FROM design_templates WHERE id=?")
653
+ .get(String(schedule.template_id)) !== undefined ||
654
+ has("SELECT 1 FROM design_templates WHERE id=?", String(schedule.template_id))
655
+ : has("SELECT 1 FROM project_template_revisions WHERE template_id=? AND version=?", String(schedule.template_id), Number(schedule.template_version));
656
+ if (has("SELECT 1 FROM schedules WHERE id=?", String(schedule.id)) || !templateHere) {
645
657
  schedules.skipped += 1;
646
658
  continue;
647
659
  }
@@ -732,6 +744,10 @@ function commit(deps, work, manifest, prepared) {
732
744
  move(folder, projectDir(paths, listed.id));
733
745
  imported.push({ id: listed.id, title: listed.title });
734
746
  }
747
+ // After the projects, so a design keeps its link to one that came in with it.
748
+ const merged = mergeDesigns(db, scratch, (table, row) => {
749
+ insertRow(db, table, row);
750
+ });
735
751
  const summary = {
736
752
  backup: {
737
753
  id: manifest.backupId,
@@ -748,6 +764,8 @@ function commit(deps, work, manifest, prepared) {
748
764
  schedules,
749
765
  voices: { added: voicesAdded, renamed: 0, skipped: voicesSkipped },
750
766
  drafts,
767
+ designs: { ...merged.designs, renamed: 0 },
768
+ designTemplates: { ...merged.templates, renamed: 0 },
751
769
  settings: { added: settingsAdded, kept: settingsKept },
752
770
  fonts,
753
771
  usage: { events, alreadyImported },
@@ -1,6 +1,8 @@
1
1
  import { rmSync } from "node:fs";
2
+ import { errorText } from "../../kernel/errors.js";
2
3
  import { derive } from "../../kernel/runner/graph.js";
3
4
  import { stagesOf } from "../admission/repo.js";
5
+ import { dropReshaped } from "../social/reshape.js";
4
6
  import { projectDir, renderCacheDir } from "./layout.js";
5
7
  // A run in flight: a call the runner holds, or a stage running or waiting out a retry.
6
8
  export function projectBusy(deps, projectId) {
@@ -19,13 +21,15 @@ export function deleteProject(deps, projectId) {
19
21
  try {
20
22
  rmSync(dir, { recursive: true, force: true });
21
23
  rmSync(renderCacheDir(deps.paths, projectId), { recursive: true, force: true });
24
+ // The pictures cut for its posts (`slices/social/reshape.ts`).
25
+ dropReshaped(deps.paths, projectId);
22
26
  }
23
27
  catch (error) {
24
28
  deps.log.write("warn", "project.delete", {
25
29
  projectId,
26
- detail: messageOf(error),
30
+ detail: errorText(error),
27
31
  });
28
- return { ok: false, reason: "files", detail: messageOf(error) };
32
+ return { ok: false, reason: "files", detail: errorText(error) };
29
33
  }
30
34
  // Stages, attempts, pieces and outputs go with it: every one of those tables declares
31
35
  // ON DELETE CASCADE on the project, and the connection runs with foreign keys on
@@ -36,6 +40,3 @@ export function deleteProject(deps, projectId) {
36
40
  places.forget(projectId);
37
41
  return { ok: true };
38
42
  }
39
- function messageOf(error) {
40
- return error instanceof Error ? error.message : String(error);
41
- }
@@ -3,8 +3,9 @@ import { createReadStream, lstatSync, readdirSync } from "node:fs";
3
3
  import { access, constants, copyFile, lstat, mkdir, readdir, rename, statfs, utimes, } from "node:fs/promises";
4
4
  import { dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
5
5
  import { z } from "zod";
6
+ import { errorText, hasErrnoCode } from "../../kernel/errors.js";
6
7
  import { repoint } from "../../kernel/paths.js";
7
- import { readSetting, writeSetting } from "../settings/repo.js";
8
+ import { deleteSetting, readSetting, writeSetting } from "../settings/repo.js";
8
9
  import { backupsFolderName, renderCacheFolder } from "./layout.js";
9
10
  // Where the files a user looks at live: project folders, scheduled backups and anything else
10
11
  // Slopify saves for them. New installs keep them in <Documents>/Slopify; installs from before
@@ -73,7 +74,7 @@ export async function ensureFilesFolders(layout) {
73
74
  }
74
75
  async function emptyOrMissing(path) {
75
76
  const entries = await readdir(path).catch((error) => {
76
- if (hasCode(error, "ENOENT"))
77
+ if (hasErrnoCode(error, "ENOENT"))
77
78
  return [];
78
79
  throw error;
79
80
  });
@@ -172,7 +173,7 @@ export function createFilesService(deps) {
172
173
  finished = {
173
174
  ...current.progress,
174
175
  phase: "failed",
175
- error: error instanceof Error ? error.message : String(error),
176
+ error: errorText(error),
176
177
  };
177
178
  })
178
179
  .finally(() => {
@@ -294,7 +295,7 @@ function sourceFiles(pair) {
294
295
  entries = readdirSync(dir, { withFileTypes: true });
295
296
  }
296
297
  catch (error) {
297
- if (hasCode(error, "ENOENT"))
298
+ if (hasErrnoCode(error, "ENOENT"))
298
299
  return;
299
300
  throw error;
300
301
  }
@@ -383,7 +384,7 @@ async function runMove(deps, from, target, pairs, update) {
383
384
  }
384
385
  const to = { kind: "root", root: target };
385
386
  writeSetting(deps.db, filesLocationKey, JSON.stringify(to));
386
- deps.db.prepare("DELETE FROM settings WHERE key=?").run(filesMoveKey);
387
+ deleteSetting(deps.db, filesMoveKey);
387
388
  const old = from.kind === "root" ? from.root : deps.paths.projects;
388
389
  repoint(deps.paths, filesLayoutOf(to, deps.paths.dataDir));
389
390
  return old;
@@ -514,9 +515,6 @@ function within(root, path) {
514
515
  function samePath(a, b) {
515
516
  return relative(resolve(a), resolve(b)) === "";
516
517
  }
517
- function hasCode(error, code) {
518
- return typeof error === "object" && error !== null && "code" in error && error.code === code;
519
- }
520
518
  function formatBytes(bytes) {
521
519
  if (!Number.isFinite(bytes))
522
520
  return "unknown";
@@ -1,6 +1,11 @@
1
1
  import { extname, isAbsolute, relative, resolve } from "node:path";
2
- // A project's folder: its readable name once it has one (`places.ts`), its id until then.
2
+ import { internalError } from "../../kernel/errors.js";
3
+ import { templateSamplesDir } from "../../kernel/paths.js";
4
+ // A project's folder: its readable name once it has one (`places.ts`), its id until then. A
5
+ // template's studio sample is kept in the data dir by its id, out of the user's Projects folder.
3
6
  export function projectDir(paths, projectId) {
7
+ if (paths.samples?.has(projectId) === true)
8
+ return contained(templateSamplesDir(paths), projectId);
4
9
  return contained(paths.projects, paths.places?.folderOf(projectId) ?? projectId);
5
10
  }
6
11
  // The clips of each project's last render (`slices/video/clip-cache.ts`). Hidden, so
@@ -8,10 +13,14 @@ export function projectDir(paths, projectId) {
8
13
  // files leaves it behind and a render simply encodes its clips again.
9
14
  export const renderCacheFolder = ".render-cache";
10
15
  export function renderCacheDir(paths, projectId) {
16
+ if (paths.samples?.has(projectId) === true)
17
+ return contained(templateSamplesDir(paths), `${renderCacheFolder}/${projectId}`);
11
18
  return contained(paths.projects, `${renderCacheFolder}/${projectId}`);
12
19
  }
13
20
  // A stored path on disk: where it was moved to (`places.ts`), or the stored path itself.
14
21
  export function outputPath(paths, projectId, relativePath) {
22
+ if (paths.samples?.has(projectId) === true)
23
+ return contained(projectDir(paths, projectId), relativePath);
15
24
  return contained(projectDir(paths, projectId), paths.places?.placeOf(projectId, relativePath) ?? relativePath);
16
25
  }
17
26
  // Scheduled backups (slices/backups) land in paths.backups unless the user picks another
@@ -31,7 +40,7 @@ function contained(root, path) {
31
40
  const target = resolve(root, path);
32
41
  const inside = relative(root, target);
33
42
  if (inside === "" || inside.startsWith("..") || isAbsolute(inside)) {
34
- throw new Error(`Slopify hit an internal error (the saved file path ${path} resolves outside ${root}). Try again; if it happens again, use Download diagnostics in Settings and report it.`);
43
+ throw new Error(internalError(`the saved file path ${path} resolves outside ${root}`));
35
44
  }
36
45
  return target;
37
46
  }
@@ -111,6 +120,8 @@ export function outputFileName(role, index, extension, stageKind) {
111
120
  return `audio-levelled${extension}`;
112
121
  case "reference":
113
122
  return `reference${extension}`;
123
+ case "social_post":
124
+ return `social/post-${String(index).padStart(2, "0")}.txt`;
114
125
  case "image":
115
126
  return `images/${String(index).padStart(3, "0")}${extension}`;
116
127
  }
@@ -52,5 +52,8 @@ export const outputRoles = [
52
52
  // Level the volume: a narration segment joined again from its pieces, each brought to one
53
53
  // loudness first (`meta.segment` says which). The exports play it in place of the plain join.
54
54
  "audio_levelled",
55
+ // A post for another site written by a Library prompt of kind "post" (`slices/social`);
56
+ // `meta.platform` says which site.
57
+ "social_post",
55
58
  ];
56
59
  export const stagedFileStates = ["copying", "staged"];