videobook 0.0.0-stage → 0.2.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 (565) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/LICENSE +21 -0
  3. package/README.md +195 -2
  4. package/bin/videobook.js +7 -0
  5. package/dist/acestep/client.js +203 -0
  6. package/dist/acestep/config.js +62 -0
  7. package/dist/acestep/errors.js +8 -0
  8. package/dist/acestep/process.js +175 -0
  9. package/dist/alive-captions/analyze.js +117 -0
  10. package/dist/alive-captions/audio.js +179 -0
  11. package/dist/alive-captions/frames.js +59 -0
  12. package/dist/alive-captions/model.js +37 -0
  13. package/dist/alive-captions/schema.js +49 -0
  14. package/dist/alive-captions/track.js +9 -0
  15. package/dist/api-key.js +33 -0
  16. package/dist/app-state.js +56 -0
  17. package/dist/app.js +257 -0
  18. package/dist/archive-b2.js +332 -0
  19. package/dist/archive-store.js +169 -0
  20. package/dist/artifact-label.js +36 -0
  21. package/dist/artifact-refs.js +49 -0
  22. package/dist/asset-tag-extraction.js +169 -0
  23. package/dist/asset-tags.js +228 -0
  24. package/dist/asset-usage.js +23 -0
  25. package/dist/audio-cue.js +71 -0
  26. package/dist/audio-encoding.js +32 -0
  27. package/dist/audio-sync-request.js +1 -0
  28. package/dist/audio-sync.js +223 -0
  29. package/dist/audio-trim-range.js +31 -0
  30. package/dist/book-context.js +31 -0
  31. package/dist/book-manager.js +374 -0
  32. package/dist/catalog-gc.js +26 -0
  33. package/dist/chain-executor.js +815 -0
  34. package/dist/chain-generation.js +115 -0
  35. package/dist/chain-plan.js +746 -0
  36. package/dist/chain-step-review.js +60 -0
  37. package/dist/character-name.js +25 -0
  38. package/dist/chat-generation-routing.js +73 -0
  39. package/dist/chat-generation-settings.js +78 -0
  40. package/dist/chat-models.js +44 -0
  41. package/dist/chat-prompt-rewrite.js +67 -0
  42. package/dist/chat-router.js +628 -0
  43. package/dist/chat-view-actions.js +49 -0
  44. package/dist/cli/args.js +242 -0
  45. package/dist/cli/config-command.js +103 -0
  46. package/dist/cli/doctor-command.js +289 -0
  47. package/dist/cli/entry.js +2 -0
  48. package/dist/cli/key-report.js +16 -0
  49. package/dist/cli/main.js +104 -0
  50. package/dist/cli/mcp-command.js +88 -0
  51. package/dist/cli/mcp-relay.js +51 -0
  52. package/dist/cli/open-browser.js +14 -0
  53. package/dist/cli/run-command.js +231 -0
  54. package/dist/cli/serve-command.js +62 -0
  55. package/dist/cli/setup-command.js +60 -0
  56. package/dist/cli/stdio-guard.js +8 -0
  57. package/dist/cold-open-model.js +7 -0
  58. package/dist/cold-open.js +79 -0
  59. package/dist/command-reference.js +95 -0
  60. package/dist/content-store.js +74 -0
  61. package/dist/cost.js +8 -0
  62. package/dist/crop-geometry.js +23 -0
  63. package/dist/dancing-workflow.js +447 -0
  64. package/dist/data-home.js +44 -0
  65. package/dist/diagnostics.js +109 -0
  66. package/dist/dialog-quality.js +34 -0
  67. package/dist/downloads-dest.js +56 -0
  68. package/dist/editing-moments.js +194 -0
  69. package/dist/elevenlabs-audio-extension.js +66 -0
  70. package/dist/engine.js +54 -0
  71. package/dist/env-file.js +91 -0
  72. package/dist/env-secrets.js +148 -0
  73. package/dist/ffmpeg-binary.js +12 -0
  74. package/dist/ffmpeg.js +425 -0
  75. package/dist/ffprobe-binary.js +77 -0
  76. package/dist/forget-asset.js +216 -0
  77. package/dist/frame-utils.js +27 -0
  78. package/dist/generation-credit-error.js +11 -0
  79. package/dist/generation-input-mentions.js +117 -0
  80. package/dist/generation-input-policy.js +68 -0
  81. package/dist/generation-reference-mentions.js +97 -0
  82. package/dist/goal-characters.js +49 -0
  83. package/dist/goal-plan-v3.js +273 -0
  84. package/dist/goal-plan.js +220 -0
  85. package/dist/goal-planner-prompt.js +28 -0
  86. package/dist/goal-prompt-budget.js +4 -0
  87. package/dist/goal-reference-images.js +25 -0
  88. package/dist/goal-state.js +261 -0
  89. package/dist/goal-stills.js +153 -0
  90. package/dist/goal-store.js +144 -0
  91. package/dist/goal-styles.js +144 -0
  92. package/dist/goal-v3-runtime.js +157 -0
  93. package/dist/guides/character_schema_guide.md +70 -0
  94. package/dist/guides/gpt_image_2_5_prompting_guide.md +21 -0
  95. package/dist/guides/grok_imagine_prompting_guide.md +72 -0
  96. package/dist/guides/image_reproduction_guide.md +84 -0
  97. package/dist/guides/kling_3_0_prompting_guide.md +136 -0
  98. package/dist/guides/nano_banana_pro_prompting_guide.md +28 -0
  99. package/dist/guides/seedance_2_prompting_guide.md +172 -0
  100. package/dist/http-access.js +49 -0
  101. package/dist/http-byte-range.js +31 -0
  102. package/dist/hydration.js +56 -0
  103. package/dist/image-compress.js +77 -0
  104. package/dist/image-edit-default.js +1 -0
  105. package/dist/image-edit-models.js +41 -0
  106. package/dist/image-edit-settings.js +11 -0
  107. package/dist/image-region-edit.js +32 -0
  108. package/dist/image-subject-map.js +523 -0
  109. package/dist/image.js +46 -0
  110. package/dist/index.js +12 -0
  111. package/dist/job-deadline.js +60 -0
  112. package/dist/jobs/_alibaba-upload.js +30 -0
  113. package/dist/jobs/_audio-chunks.js +31 -0
  114. package/dist/jobs/_download-into-asset.js +15 -0
  115. package/dist/jobs/_fal-shared.js +22 -0
  116. package/dist/jobs/_fal-upload.js +35 -0
  117. package/dist/jobs/_image-edit-adapter.js +213 -0
  118. package/dist/jobs/_kie-poll.js +119 -0
  119. package/dist/jobs/_kie-upload.js +32 -0
  120. package/dist/jobs/_lease.js +163 -0
  121. package/dist/jobs/_persist-output.js +19 -0
  122. package/dist/jobs/_storage-retry.js +21 -0
  123. package/dist/jobs/_transcribe.js +245 -0
  124. package/dist/jobs/_translate-transcript.js +270 -0
  125. package/dist/jobs/analyze-alive-captions.js +10 -0
  126. package/dist/jobs/analyze-editing-moments.js +104 -0
  127. package/dist/jobs/analyze-jokes.js +143 -0
  128. package/dist/jobs/analyze-keyword-placement.js +155 -0
  129. package/dist/jobs/analyze-music.js +14 -0
  130. package/dist/jobs/analyze-video.js +282 -0
  131. package/dist/jobs/apply-cuts.js +83 -0
  132. package/dist/jobs/apply-sfx.js +107 -0
  133. package/dist/jobs/archive-clip.js +458 -0
  134. package/dist/jobs/assemble-goal.js +98 -0
  135. package/dist/jobs/audio-boost.js +71 -0
  136. package/dist/jobs/audio-mix.js +161 -0
  137. package/dist/jobs/backfill-asset-tags.js +144 -0
  138. package/dist/jobs/burn-watermark.js +316 -0
  139. package/dist/jobs/change-video-speed.js +176 -0
  140. package/dist/jobs/clean-video-audio.js +207 -0
  141. package/dist/jobs/cold-open.js +166 -0
  142. package/dist/jobs/combine-goal.js +108 -0
  143. package/dist/jobs/complete-provider-task.js +185 -0
  144. package/dist/jobs/copy-library-asset.js +33 -0
  145. package/dist/jobs/cost-estimate-assessment.js +113 -0
  146. package/dist/jobs/crop-image.js +47 -0
  147. package/dist/jobs/crop-video.js +71 -0
  148. package/dist/jobs/describe-image.js +181 -0
  149. package/dist/jobs/detect-scenes.js +33 -0
  150. package/dist/jobs/doctor-fix.js +100 -0
  151. package/dist/jobs/download-video.js +427 -0
  152. package/dist/jobs/enqueue-search-index.js +24 -0
  153. package/dist/jobs/even-split.js +122 -0
  154. package/dist/jobs/export-sequence.js +365 -0
  155. package/dist/jobs/extend-audio.js +229 -0
  156. package/dist/jobs/extract-audio.js +72 -0
  157. package/dist/jobs/extract-frame.js +58 -0
  158. package/dist/jobs/extract-notebook-audio.js +82 -0
  159. package/dist/jobs/extract-notebook-frame.js +93 -0
  160. package/dist/jobs/extract-video-clip.js +8 -0
  161. package/dist/jobs/extract-video-poster.js +55 -0
  162. package/dist/jobs/extract-waveform.js +25 -0
  163. package/dist/jobs/finish-goal.js +255 -0
  164. package/dist/jobs/forget-asset.js +11 -0
  165. package/dist/jobs/generate-acestep-cover.js +219 -0
  166. package/dist/jobs/generate-media.js +70 -0
  167. package/dist/jobs/image-subjects.js +1059 -0
  168. package/dist/jobs/index.js +18 -0
  169. package/dist/jobs/inject-first-frame.js +175 -0
  170. package/dist/jobs/isolate-vocals.js +69 -0
  171. package/dist/jobs/kie-paygate.js +177 -0
  172. package/dist/jobs/label-music-sections.js +5 -0
  173. package/dist/jobs/osg-placement.js +135 -0
  174. package/dist/jobs/pad-video.js +72 -0
  175. package/dist/jobs/plan-goal.js +97 -0
  176. package/dist/jobs/post-record.js +21 -0
  177. package/dist/jobs/post-to-x.js +21 -0
  178. package/dist/jobs/prepare-search-reference.js +190 -0
  179. package/dist/jobs/prepare-temporal-search.js +16 -0
  180. package/dist/jobs/process-audio.js +67 -0
  181. package/dist/jobs/process-image.js +58 -0
  182. package/dist/jobs/process-upload.js +166 -0
  183. package/dist/jobs/process-video.js +438 -0
  184. package/dist/jobs/register-all.js +161 -0
  185. package/dist/jobs/relayout-notebook.js +78 -0
  186. package/dist/jobs/remove-sound.js +299 -0
  187. package/dist/jobs/render-project.js +380 -0
  188. package/dist/jobs/render-qr-outro.js +138 -0
  189. package/dist/jobs/render-timeline.js +283 -0
  190. package/dist/jobs/repair-search-indexes.js +42 -0
  191. package/dist/jobs/replace-video-audio.js +64 -0
  192. package/dist/jobs/restore-archive-clip.js +53 -0
  193. package/dist/jobs/reverse-video.js +53 -0
  194. package/dist/jobs/rewrite-script.js +173 -0
  195. package/dist/jobs/run-notebook.js +3335 -0
  196. package/dist/jobs/semantic-index.js +797 -0
  197. package/dist/jobs/smooth-scene-concat.js +257 -0
  198. package/dist/jobs/splice-video.js +280 -0
  199. package/dist/jobs/split-video.js +127 -0
  200. package/dist/jobs/suggest-cuts.js +149 -0
  201. package/dist/jobs/suggest-sfx.js +135 -0
  202. package/dist/jobs/sync-video-to-audio.js +329 -0
  203. package/dist/jobs/tag-asset.js +266 -0
  204. package/dist/jobs/task-failure.js +16 -0
  205. package/dist/jobs/timewarp-video.js +152 -0
  206. package/dist/jobs/transcribe-video-audio.js +117 -0
  207. package/dist/jobs/transcribe.js +35 -0
  208. package/dist/jobs/translate-transcript.js +55 -0
  209. package/dist/jobs/trim-audio.js +133 -0
  210. package/dist/jobs/trim-video.js +291 -0
  211. package/dist/jobs/xai-paygate.js +26 -0
  212. package/dist/keyword-placement-cache.js +74 -0
  213. package/dist/lazy-render.js +142 -0
  214. package/dist/library-browse.js +78 -0
  215. package/dist/lightning-defaults.js +206 -0
  216. package/dist/load-env.js +12 -0
  217. package/dist/local-search-models.js +206 -0
  218. package/dist/local-upload.js +318 -0
  219. package/dist/logger.js +69 -0
  220. package/dist/media/adapters/alibaba.js +20 -0
  221. package/dist/media/adapters/elevenlabs.js +58 -0
  222. package/dist/media/adapters/fal.js +45 -0
  223. package/dist/media/adapters/index.js +26 -0
  224. package/dist/media/adapters/kie.js +120 -0
  225. package/dist/media/adapters/pending.js +21 -0
  226. package/dist/media/adapters/types.js +5 -0
  227. package/dist/media/adapters/xai.js +18 -0
  228. package/dist/media/catalog.js +236 -0
  229. package/dist/media/complete.js +191 -0
  230. package/dist/media/cost.js +45 -0
  231. package/dist/media/descriptor.js +157 -0
  232. package/dist/media/endpoints.js +11 -0
  233. package/dist/media/frame-options.js +18 -0
  234. package/dist/media/insert-video-timing.js +17 -0
  235. package/dist/media/json-schema.js +75 -0
  236. package/dist/media/legacy-task-types.js +129 -0
  237. package/dist/media/listing.js +24 -0
  238. package/dist/media/model-id.js +14 -0
  239. package/dist/media/preview.js +86 -0
  240. package/dist/media/prompt-dialects.js +65 -0
  241. package/dist/media/ref-fields.js +106 -0
  242. package/dist/media/refs.js +131 -0
  243. package/dist/media/request.js +188 -0
  244. package/dist/media/types.js +6 -0
  245. package/dist/media/wire.js +161 -0
  246. package/dist/media-library.js +535 -0
  247. package/dist/media-output-path.js +33 -0
  248. package/dist/multyculty-workflow.js +757 -0
  249. package/dist/notebook-action-catalog.js +608 -0
  250. package/dist/notebook-cell-outputs.js +726 -0
  251. package/dist/notebook-credit-retry.js +45 -0
  252. package/dist/notebook-fixtures.js +8 -0
  253. package/dist/notebook-generation-contract.js +77 -0
  254. package/dist/notebook-generation-controls.js +145 -0
  255. package/dist/notebook-goal-layout.js +40 -0
  256. package/dist/notebook-graph.js +37 -0
  257. package/dist/notebook-image-edit.js +84 -0
  258. package/dist/notebook-initial-generation-prompt.js +222 -0
  259. package/dist/notebook-live-analysis.js +941 -0
  260. package/dist/notebook-media-ops.js +579 -0
  261. package/dist/notebook-mutations.js +20 -0
  262. package/dist/notebook-placement.js +41 -0
  263. package/dist/notebook-provider-failure.js +87 -0
  264. package/dist/notebook-published-post.js +26 -0
  265. package/dist/notebook-reference-resolver.js +227 -0
  266. package/dist/notebook-relayout.js +270 -0
  267. package/dist/notebook-run-plan.js +519 -0
  268. package/dist/notebook-shots.js +162 -0
  269. package/dist/notebook-workflow.js +856 -0
  270. package/dist/one-password.js +200 -0
  271. package/dist/openai-intelligence.js +99 -0
  272. package/dist/original-media-file.js +12 -0
  273. package/dist/osg-assets.js +36 -0
  274. package/dist/osg-placement-cache.js +64 -0
  275. package/dist/osg-settings.js +27 -0
  276. package/dist/paid-tools.js +22 -0
  277. package/dist/platform-guidance.js +26 -0
  278. package/dist/posts/dropbox-auth.js +91 -0
  279. package/dist/posts/dropbox-upload.js +132 -0
  280. package/dist/posts/icloud-drive.js +134 -0
  281. package/dist/posts/icloud-status.js +64 -0
  282. package/dist/posts/post-contract.js +2 -0
  283. package/dist/posts/post-copy-format.js +43 -0
  284. package/dist/posts/post-copy.js +193 -0
  285. package/dist/posts/post-folder.js +18 -0
  286. package/dist/posts/post-metrics.js +119 -0
  287. package/dist/posts/post-store.js +340 -0
  288. package/dist/posts/x-post-target.js +43 -0
  289. package/dist/preview-payload.js +90 -0
  290. package/dist/prompt-surfaces.js +22 -0
  291. package/dist/provider-prompt-transform.js +77 -0
  292. package/dist/provider-status-link.js +21 -0
  293. package/dist/provider-task-record.js +78 -0
  294. package/dist/provider-task-status.js +47 -0
  295. package/dist/provider-usage.js +294 -0
  296. package/dist/queue.js +810 -0
  297. package/dist/reference-video-duration.js +17 -0
  298. package/dist/remotion/BlurFillVideo.js +51 -0
  299. package/dist/remotion/KeywordGraphicsOverlay.js +68 -0
  300. package/dist/remotion/OsgOverlay.js +30 -0
  301. package/dist/remotion/QrOutroComposition.js +47 -0
  302. package/dist/remotion/RenderComposition.js +11 -0
  303. package/dist/remotion/browser.js +59 -0
  304. package/dist/remotion/constants.js +35 -0
  305. package/dist/remotion/index.js +66 -0
  306. package/dist/remotion/keyword-graphics.js +151 -0
  307. package/dist/remotion/keyword-placement.js +182 -0
  308. package/dist/remotion/osg-spec.js +198 -0
  309. package/dist/remotion/public/qr-outro-impact.wav +0 -0
  310. package/dist/remotion/qr-outro-audio.js +1 -0
  311. package/dist/remotion/qr-outro-spec.js +211 -0
  312. package/dist/remotion/shared-bundle.js +37 -0
  313. package/dist/remotion/subtitles/AliveCaptions.js +83 -0
  314. package/dist/remotion/subtitles/Highlight.js +58 -0
  315. package/dist/remotion/subtitles/Impact.js +67 -0
  316. package/dist/remotion/subtitles/SubtitleOverlay.js +9 -0
  317. package/dist/remotion/subtitles/alive-layout.js +321 -0
  318. package/dist/remotion/subtitles/alive-types.js +17 -0
  319. package/dist/remotion/subtitles/chunker.js +68 -0
  320. package/dist/remotion/subtitles/segments.js +42 -0
  321. package/dist/remotion/subtitles/styles.js +4 -0
  322. package/dist/remotion/subtitles/types.js +1 -0
  323. package/dist/remotion/util.js +18 -0
  324. package/dist/remotion-cancellation.js +15 -0
  325. package/dist/rename-helpers.js +204 -0
  326. package/dist/rhythm/analyze.js +128 -0
  327. package/dist/rhythm/contract.js +14 -0
  328. package/dist/rhythm/draft-model.js +19 -0
  329. package/dist/rhythm/energy.js +133 -0
  330. package/dist/rhythm/fft.js +44 -0
  331. package/dist/rhythm/model.js +71 -0
  332. package/dist/rhythm/music-analysis.js +124 -0
  333. package/dist/rhythm/music-draft.js +261 -0
  334. package/dist/rhythm/onset.js +101 -0
  335. package/dist/rhythm/run-worker.js +51 -0
  336. package/dist/rhythm/score.js +47 -0
  337. package/dist/rhythm/section-labels.js +168 -0
  338. package/dist/rhythm/sequence-rhythm.js +67 -0
  339. package/dist/rhythm/synthesize.js +103 -0
  340. package/dist/rhythm/timing.js +98 -0
  341. package/dist/rhythm/track.js +320 -0
  342. package/dist/rhythm/visual-fit.js +4 -0
  343. package/dist/rhythm/worker.js +25 -0
  344. package/dist/scene-detect.js +200 -0
  345. package/dist/scene-transition.js +12 -0
  346. package/dist/scheduled-posts.js +200 -0
  347. package/dist/search-index-coverage.js +13 -0
  348. package/dist/search-index-media.js +71 -0
  349. package/dist/search-reference-store.js +60 -0
  350. package/dist/search-scoring.js +17 -0
  351. package/dist/serve.js +125 -0
  352. package/dist/server-lock.js +143 -0
  353. package/dist/server.js +42 -0
  354. package/dist/srt.js +16 -0
  355. package/dist/tag-backfill-state.js +121 -0
  356. package/dist/tag-settings.js +30 -0
  357. package/dist/tag-vocabulary.js +237 -0
  358. package/dist/test-queue-cleanup.js +1 -0
  359. package/dist/time-parse.js +50 -0
  360. package/dist/timeline-helpers.js +554 -0
  361. package/dist/timewarp.js +87 -0
  362. package/dist/tools/_image-inputs.js +9 -0
  363. package/dist/tools/_media-refs.js +84 -0
  364. package/dist/tools/_shared.js +59 -0
  365. package/dist/tools/_suggest-shared.js +279 -0
  366. package/dist/tools/add-timeline-audio.js +73 -0
  367. package/dist/tools/alive-captions.js +157 -0
  368. package/dist/tools/analyze-jokes.js +56 -0
  369. package/dist/tools/analyze-video.js +66 -0
  370. package/dist/tools/animate-keywords.js +217 -0
  371. package/dist/tools/api-status.js +42 -0
  372. package/dist/tools/application-commands.js +34 -0
  373. package/dist/tools/apply-cuts.js +112 -0
  374. package/dist/tools/apply-sfx.js +152 -0
  375. package/dist/tools/apply-watermark.js +670 -0
  376. package/dist/tools/archive-clip.js +74 -0
  377. package/dist/tools/asset-history.js +61 -0
  378. package/dist/tools/asset-manifest.js +24 -0
  379. package/dist/tools/asset-tags.js +151 -0
  380. package/dist/tools/assign-slot.js +97 -0
  381. package/dist/tools/audio-boost.js +107 -0
  382. package/dist/tools/audio-mix.js +121 -0
  383. package/dist/tools/book-usage.js +16 -0
  384. package/dist/tools/chain.js +136 -0
  385. package/dist/tools/change-video-speed.js +105 -0
  386. package/dist/tools/chat-history.js +5 -0
  387. package/dist/tools/chat.js +848 -0
  388. package/dist/tools/clean-video-audio.js +97 -0
  389. package/dist/tools/clear-failed-tasks.js +66 -0
  390. package/dist/tools/cold-open.js +114 -0
  391. package/dist/tools/command-plan.js +1680 -0
  392. package/dist/tools/commands.js +1666 -0
  393. package/dist/tools/concat-images.js +236 -0
  394. package/dist/tools/connect-service.js +55 -0
  395. package/dist/tools/copy-file-to-clipboard.js +58 -0
  396. package/dist/tools/create-character.js +114 -0
  397. package/dist/tools/create-qart.js +253 -0
  398. package/dist/tools/create-qr-outro.js +149 -0
  399. package/dist/tools/create-qrbtf.js +215 -0
  400. package/dist/tools/create-watermark.js +97 -0
  401. package/dist/tools/crop-asset.js +18 -0
  402. package/dist/tools/crop-image.js +120 -0
  403. package/dist/tools/crop-video.js +97 -0
  404. package/dist/tools/delete-asset.js +104 -0
  405. package/dist/tools/describe-image.js +69 -0
  406. package/dist/tools/detect-scenes.js +44 -0
  407. package/dist/tools/doctor.js +126 -0
  408. package/dist/tools/download-video.js +114 -0
  409. package/dist/tools/duplicate-asset.js +184 -0
  410. package/dist/tools/edit-timeline.js +59 -0
  411. package/dist/tools/editing-moments.js +105 -0
  412. package/dist/tools/estimate-cost.js +171 -0
  413. package/dist/tools/even-split.js +295 -0
  414. package/dist/tools/export-asset-file.js +22 -0
  415. package/dist/tools/export-asset.js +140 -0
  416. package/dist/tools/export-sequence.js +250 -0
  417. package/dist/tools/export-to-downloads.js +54 -0
  418. package/dist/tools/export-transcript-srt.js +79 -0
  419. package/dist/tools/extend-audio.js +100 -0
  420. package/dist/tools/extract-audio.js +174 -0
  421. package/dist/tools/extract-frame.js +108 -0
  422. package/dist/tools/extract-notebook-audio.js +173 -0
  423. package/dist/tools/extract-notebook-frame.js +160 -0
  424. package/dist/tools/extract-video-clip.js +101 -0
  425. package/dist/tools/generate-media.js +157 -0
  426. package/dist/tools/generation-payload.js +48 -0
  427. package/dist/tools/get-timeline.js +12 -0
  428. package/dist/tools/get-watermark-config.js +38 -0
  429. package/dist/tools/goal.js +644 -0
  430. package/dist/tools/image-subjects.js +711 -0
  431. package/dist/tools/index.js +323 -0
  432. package/dist/tools/inject-first-frame.js +154 -0
  433. package/dist/tools/invert-image.js +70 -0
  434. package/dist/tools/isolate-vocals.js +66 -0
  435. package/dist/tools/list-assets.js +98 -0
  436. package/dist/tools/list-cell-generations.js +225 -0
  437. package/dist/tools/list-elevenlabs-voices.js +163 -0
  438. package/dist/tools/log-wrapper.js +29 -0
  439. package/dist/tools/make-cover-song.js +95 -0
  440. package/dist/tools/mark-asset-seen.js +23 -0
  441. package/dist/tools/media-library.js +245 -0
  442. package/dist/tools/move-timeline-audio.js +35 -0
  443. package/dist/tools/music-analysis.js +60 -0
  444. package/dist/tools/music-draft.js +29 -0
  445. package/dist/tools/pad-video.js +87 -0
  446. package/dist/tools/post-record.js +83 -0
  447. package/dist/tools/post.js +168 -0
  448. package/dist/tools/prompt-history.js +164 -0
  449. package/dist/tools/provider-key-status.js +248 -0
  450. package/dist/tools/qrbtf-render.js +849 -0
  451. package/dist/tools/read-asset-metadata.js +26 -0
  452. package/dist/tools/read-file-at-commit.js +29 -0
  453. package/dist/tools/read-transcript.js +41 -0
  454. package/dist/tools/reconcile-project-work.js +1452 -0
  455. package/dist/tools/recover-asset.js +28 -0
  456. package/dist/tools/remove-sound.js +72 -0
  457. package/dist/tools/remove-timeline-audio.js +34 -0
  458. package/dist/tools/remove-timeline-slot.js +40 -0
  459. package/dist/tools/rename-all.js +434 -0
  460. package/dist/tools/rename-asset.js +34 -0
  461. package/dist/tools/render-settings.js +47 -0
  462. package/dist/tools/render-timeline.js +120 -0
  463. package/dist/tools/render.js +384 -0
  464. package/dist/tools/reorder-character-images.js +45 -0
  465. package/dist/tools/replace-video-audio.js +133 -0
  466. package/dist/tools/restore-archive-clips.js +209 -0
  467. package/dist/tools/restore-asset-version.js +37 -0
  468. package/dist/tools/retry-generation.js +54 -0
  469. package/dist/tools/reverse-video.js +74 -0
  470. package/dist/tools/rewrite-prompt.js +388 -0
  471. package/dist/tools/rewrite-script.js +67 -0
  472. package/dist/tools/schema-from-apicity.js +66 -0
  473. package/dist/tools/search-archive.js +150 -0
  474. package/dist/tools/semantic-search.js +1214 -0
  475. package/dist/tools/sequence-rhythm.js +43 -0
  476. package/dist/tools/set-character-metadata.js +84 -0
  477. package/dist/tools/set-env-secret.js +40 -0
  478. package/dist/tools/set-osg.js +143 -0
  479. package/dist/tools/show-folder.js +37 -0
  480. package/dist/tools/smooth-scene-concat.js +122 -0
  481. package/dist/tools/splice-video.js +149 -0
  482. package/dist/tools/split-video.js +202 -0
  483. package/dist/tools/suggest-cuts.js +45 -0
  484. package/dist/tools/suggest-sfx.js +43 -0
  485. package/dist/tools/sync-video-to-audio.js +115 -0
  486. package/dist/tools/tag-backfill.js +177 -0
  487. package/dist/tools/timewarp-video.js +85 -0
  488. package/dist/tools/tool-def.js +1 -0
  489. package/dist/tools/tool-json-schema.js +116 -0
  490. package/dist/tools/transcribe.js +90 -0
  491. package/dist/tools/translate-subtitles.js +59 -0
  492. package/dist/tools/trim-audio.js +139 -0
  493. package/dist/tools/trim-video.js +168 -0
  494. package/dist/tools/update-script.js +25 -0
  495. package/dist/tools/update-timeline-audio.js +40 -0
  496. package/dist/tools/upload-file.js +41 -0
  497. package/dist/tools/verify-x-mentions.js +141 -0
  498. package/dist/tools/video-editing-command-contract.js +291 -0
  499. package/dist/tools/video-source-reference.js +30 -0
  500. package/dist/tools/video-time-input.js +18 -0
  501. package/dist/tools/videobook-domain.js +2021 -0
  502. package/dist/tools/wait-for-asset-ready.js +143 -0
  503. package/dist/tools/workspace-actions.js +54 -0
  504. package/dist/transcript-projection.js +202 -0
  505. package/dist/transcript-srt.js +33 -0
  506. package/dist/upload.js +76 -0
  507. package/dist/usage-providers.js +25 -0
  508. package/dist/video-analysis-projection.js +46 -0
  509. package/dist/video-poster.js +33 -0
  510. package/dist/video-proxy.js +99 -0
  511. package/dist/vision-regions.js +111 -0
  512. package/dist/voice-actions.js +90 -0
  513. package/dist/voice-audio.js +55 -0
  514. package/dist/voice-config.js +122 -0
  515. package/dist/voice-continuation.js +54 -0
  516. package/dist/voice-contract.js +1 -0
  517. package/dist/voice-http.js +145 -0
  518. package/dist/voice-reply-progress.js +55 -0
  519. package/dist/voice-session.js +992 -0
  520. package/dist/watermark-badge.js +259 -0
  521. package/dist/watermark-config.js +111 -0
  522. package/dist/watermark-handle-position.js +55 -0
  523. package/dist/watermark-platform.js +15 -0
  524. package/dist/watermark-timing.js +16 -0
  525. package/dist/workspace-action-catalog.js +315 -0
  526. package/dist/workspace-action-contract.js +21 -0
  527. package/dist/workspace-action-groups.js +98 -0
  528. package/dist/workspace-action-surfaces.js +7 -0
  529. package/dist/x/account.js +88 -0
  530. package/dist/x/authenticated-handle.js +30 -0
  531. package/dist/x/oauth-flow.js +191 -0
  532. package/dist/x/post-contract.js +1 -0
  533. package/dist/x/post-metadata.js +44 -0
  534. package/dist/x/post-to-x.js +456 -0
  535. package/dist/x/published-post-cell.js +90 -0
  536. package/dist/x/x-tokens.js +69 -0
  537. package/dist/ytdlp-binary.js +65 -0
  538. package/dist/zod-slots.js +334 -0
  539. package/package.json +84 -4
  540. package/ui/assets/alive-captions-BWb5nLKj.css +1 -0
  541. package/ui/assets/alive-captions-DPEr1nKM.js +1 -0
  542. package/ui/assets/confirm-bodies-D8ouHXKy.js +1 -0
  543. package/ui/assets/confirm-bodies-DrMhaXC6.css +1 -0
  544. package/ui/assets/fullscreen-B7nw4YbE.js +1 -0
  545. package/ui/assets/goal-ui-DK-xTCK0.js +2 -0
  546. package/ui/assets/goal-ui-DXdUM2KF.css +1 -0
  547. package/ui/assets/index-DzPAnCUj.css +1 -0
  548. package/ui/assets/index-kS1905s5.js +37 -0
  549. package/ui/assets/inspector-MlzQbTVo.js +1 -0
  550. package/ui/assets/jobs-ui-CoinOWx0.js +1 -0
  551. package/ui/assets/music-video-Cbe80kmQ.js +1 -0
  552. package/ui/assets/music-video-DPaiKKEq.css +1 -0
  553. package/ui/assets/ui-Oirr9VOf.css +1 -0
  554. package/ui/assets/ui-xo_Y1yQg.js +2 -0
  555. package/ui/assets/vendor-D2rfiZ5j.css +1 -0
  556. package/ui/assets/vendor-DfTCWgKm.js +19 -0
  557. package/ui/assets/vendor-flow-BXbeWxW7.js +7 -0
  558. package/ui/assets/vendor-flow-BnuhLJ6X.css +1 -0
  559. package/ui/assets/vendor-icons-CGqhCIXV.js +1 -0
  560. package/ui/assets/vendor-mcp-BTlEJcHV.js +67 -0
  561. package/ui/assets/vendor-react-Dj7D1fD4.js +9 -0
  562. package/ui/assets/vendor-remotion-CH1HXrlc.js +95 -0
  563. package/ui/assets/videobook-Qlp9HQFu.js +20 -0
  564. package/ui/favicon.svg +8 -0
  565. package/ui/index.html +126 -0
@@ -0,0 +1,119 @@
1
+ import { createX, XError } from "@apicity/x";
2
+ import { v7 as uuidv7 } from "uuid";
3
+ import { getValidAccessToken } from "../x/x-tokens.js";
4
+ import { listBookEngines, queue } from "../queue.js";
5
+ import { getPost, listPosts, patchPostRuntime, readPostRuntime } from "./post-store.js";
6
+ import { POST_METRIC_NAMES } from "./post-contract.js";
7
+ const MILESTONES = [1, 24, 72, 168, 672].map((hours) => hours * 3_600_000);
8
+ export function nextPostMetricAt(postedAt, observedAt) {
9
+ const start = new Date(postedAt).getTime();
10
+ const observed = new Date(observedAt).getTime();
11
+ const next = MILESTONES.map((offset) => start + offset).find((time) => time > observed);
12
+ return next === undefined ? null : new Date(next).toISOString();
13
+ }
14
+ export function recordPostMetrics(engine, id, snapshot) {
15
+ const post = getPost(engine, id);
16
+ if (!post.runtime.postedAt)
17
+ throw new Error("Link the published post before recording results");
18
+ const time = new Date(snapshot.observedAt).getTime();
19
+ if (!Number.isFinite(time) || time > Date.now() + 60_000 || time < new Date(post.runtime.postedAt).getTime())
20
+ throw new Error("Observation time must be after publication and no later than now");
21
+ for (const name of POST_METRIC_NAMES) {
22
+ const value = snapshot.values[name];
23
+ if (value !== undefined && value !== null && (!Number.isSafeInteger(value) || value < 0))
24
+ throw new Error(`Invalid ${name}: use a nonnegative whole number or leave it unknown`);
25
+ }
26
+ const runtime = readPostRuntime(engine, id);
27
+ const metrics = [...runtime.metrics, { ...snapshot, id: uuidv7(), observedAt: new Date(time).toISOString() }].sort((a, b) => a.observedAt.localeCompare(b.observedAt));
28
+ patchPostRuntime(engine, id, { metrics });
29
+ }
30
+ export async function refreshPostMetrics(engine, id, options = {}) {
31
+ const post = getPost(engine, id);
32
+ if (post.definition.platform !== "x" || !post.runtime.url || !post.runtime.postedAt)
33
+ throw new Error("Automatic metrics need a linked X publication");
34
+ const tweetId = post.runtime.url.match(/\/status\/(\d+)/)?.[1];
35
+ if (!tweetId)
36
+ throw new Error("Invalid X post link");
37
+ try {
38
+ const x = options.x ?? createX({ accessToken: await getValidAccessToken() });
39
+ const young = Date.now() - new Date(post.runtime.postedAt).getTime() < 30 * 86_400_000;
40
+ const lookup = (privateMetrics) => x.get.v2.tweets(tweetId, {
41
+ "tweet.fields": ["created_at", "author_id", "public_metrics", ...(privateMetrics ? ["non_public_metrics"] : [])],
42
+ expansions: ["attachments.media_keys"],
43
+ "media.fields": ["media_key", "type", "public_metrics"],
44
+ }, options.signal);
45
+ let result;
46
+ try {
47
+ result = await lookup(young);
48
+ }
49
+ catch (error) {
50
+ if (!young || !(error instanceof XError) || ![400, 403].includes(error.status))
51
+ throw error;
52
+ result = await lookup(false);
53
+ }
54
+ if (!result.data)
55
+ throw new Error("X post is unavailable, private or deleted");
56
+ const publicMetrics = result.data.public_metrics ?? {};
57
+ const privateMetrics = result.data.non_public_metrics ?? {};
58
+ const values = {};
59
+ const mapping = { impressions: "impression_count", likes: "like_count", replies: "reply_count", reposts: "retweet_count", quotes: "quote_count", bookmarks: "bookmark_count" };
60
+ for (const [name, source] of Object.entries(mapping)) {
61
+ const value = publicMetrics[source];
62
+ if (typeof value === "number")
63
+ values[name] = value;
64
+ }
65
+ if (typeof privateMetrics.url_link_clicks === "number")
66
+ values.clicks = privateMetrics.url_link_clicks;
67
+ const media = result.includes?.media?.filter((item) => item.type === "video");
68
+ const views = media?.map((item) => item.public_metrics?.view_count).filter((value) => typeof value === "number");
69
+ const snapshot = {
70
+ source: "x", observedAt: new Date().toISOString(), values,
71
+ ...(views?.length ? { mediaViews: views.reduce((sum, count) => sum + count, 0) } : {}),
72
+ note: "X video views belong to the media and may include other posts. Missing metrics are unknown.",
73
+ };
74
+ recordPostMetrics(engine, id, snapshot);
75
+ patchPostRuntime(engine, id, { nextMetricAt: nextPostMetricAt(post.runtime.postedAt, snapshot.observedAt), metricsError: null, metricsPaused: false });
76
+ return snapshot;
77
+ }
78
+ catch (error) {
79
+ const paused = (error instanceof XError && [401, 403, 404].includes(error.status))
80
+ || (error instanceof Error && /access token|token expired|unavailable, private or deleted/i.test(error.message));
81
+ patchPostRuntime(engine, id, {
82
+ metricsError: error instanceof XError ? `X metrics unavailable (${error.status}). Reconnect X or check the post link.` : error instanceof Error ? error.message : "Metrics unavailable",
83
+ metricsPaused: paused,
84
+ nextMetricAt: paused ? null : new Date(Date.now() + (error instanceof XError && error.status === 429 ? 15 : 5) * 60_000).toISOString(),
85
+ });
86
+ throw error;
87
+ }
88
+ }
89
+ let timer = null;
90
+ let polling = false;
91
+ async function collectDuePostMetrics() {
92
+ if (polling)
93
+ return;
94
+ polling = true;
95
+ try {
96
+ for (const engine of await listBookEngines()) {
97
+ for (const post of listPosts(engine)) {
98
+ if (post.definition.platform !== "x" || !post.runtime.url || !post.runtime.nextMetricAt || post.runtime.metricsPaused || new Date(post.runtime.nextMetricAt).getTime() > Date.now())
99
+ continue;
100
+ await queue.enqueue(`post-metrics-${post.id}-${new Date(post.runtime.nextMetricAt).getTime()}`, { type: "post_metrics", bookId: engine.book.get().bookId, postCellId: post.id });
101
+ }
102
+ }
103
+ }
104
+ finally {
105
+ polling = false;
106
+ }
107
+ }
108
+ export function startPostMetricsScheduler() {
109
+ if (timer)
110
+ return;
111
+ timer = setInterval(() => { void collectDuePostMetrics().catch(() => undefined); }, 60_000);
112
+ timer.unref();
113
+ void collectDuePostMetrics().catch(() => undefined);
114
+ }
115
+ export function stopPostMetricsScheduler() {
116
+ if (timer)
117
+ clearInterval(timer);
118
+ timer = null;
119
+ }
@@ -0,0 +1,340 @@
1
+ import { createHash } from "node:crypto";
2
+ import { extname } from "node:path";
3
+ import { v7 as uuidv7 } from "uuid";
4
+ import { findPrimaryMediaFile } from "videobook-engine";
5
+ import { nextHorizontalSlotFrom, notebookGridTag, notebookOccupiedSlots } from "videobook-engine/notebook-grid";
6
+ import { mutateNotebook, withNotebookMutation } from "../notebook-mutations.js";
7
+ import { slotRight } from "../notebook-placement.js";
8
+ const key = (id) => `application.post.v1.${id}`;
9
+ export function readPostRuntime(engine, id) {
10
+ return engine.settings.get(key(id)) ?? { status: "draft", metrics: [] };
11
+ }
12
+ export function patchPostRuntime(engine, id, patch) {
13
+ const next = { ...readPostRuntime(engine, id), ...patch };
14
+ const ids = engine.settings.get("application.posts.index.v1") ?? [];
15
+ if (!ids.includes(id)) {
16
+ const indexed = engine.settings.set("application.posts.index.v1", [...ids, id]);
17
+ if (!indexed.ok)
18
+ throw new Error(indexed.error.message);
19
+ }
20
+ const written = engine.settings.set(key(id), next);
21
+ if (!written.ok)
22
+ throw new Error(written.error.message);
23
+ return next;
24
+ }
25
+ function postDefinition(cell) {
26
+ const value = cell.inputs?.post;
27
+ if (!value || value.version !== 1)
28
+ throw new Error("Post needs migration before editing");
29
+ return value;
30
+ }
31
+ export function notebookPosts(engine, notebook) {
32
+ return notebook.cells.filter((cell) => cell.type === "post" && cell.inputs?.post).map((cell) => {
33
+ const definition = postDefinition(cell);
34
+ const runtime = readPostRuntime(engine, cell.id);
35
+ return {
36
+ id: cell.id, notebookId: notebook.id, address: notebookGridTag(cell.slot), label: cell.label ?? "Post",
37
+ definition: runtime.publication ? { ...runtime.publication, notes: definition.notes } : definition, runtime,
38
+ };
39
+ });
40
+ }
41
+ export function listPosts(engine) {
42
+ return engine.notebooks.list().flatMap((item) => {
43
+ const read = engine.notebooks.read(item.id);
44
+ return read.ok ? notebookPosts(engine, read.value) : [];
45
+ });
46
+ }
47
+ export function getPost(engine, id) {
48
+ const post = listPosts(engine).find((item) => item.id === id);
49
+ if (!post)
50
+ throw new Error(`Post cell ${id} not found`);
51
+ return post;
52
+ }
53
+ function captureLineage(notebook, source) {
54
+ const seen = new Set();
55
+ const visit = (cell) => {
56
+ if (seen.has(cell.id))
57
+ return;
58
+ seen.add(cell.id);
59
+ for (const edge of notebook.edges.filter((item) => item.target === cell.id)) {
60
+ const parent = notebook.cells.find((item) => item.id === edge.source);
61
+ if (parent)
62
+ visit(parent);
63
+ }
64
+ for (const name of ["producerCellId", "sourceCellId"]) {
65
+ const parent = notebook.cells.find((item) => item.id === cell.inputs?.[name]);
66
+ if (parent)
67
+ visit(parent);
68
+ }
69
+ if (typeof cell.inputs?.goalId === "string") {
70
+ const goal = notebook.cells.find((item) => item.type === "goal" && item.inputs?.goalId === cell.inputs?.goalId);
71
+ if (goal)
72
+ visit(goal);
73
+ }
74
+ const artifactIds = ["sourceArtifactId", "source_asset_id", "first_asset_id", "second_asset_id", "additional_asset_ids", "sourceArtifactIds", "image_asset_ids"].flatMap((name) => {
75
+ const value = cell.inputs?.[name];
76
+ return typeof value === "string" ? [value] : Array.isArray(value) ? value.filter((id) => typeof id === "string") : [];
77
+ });
78
+ for (const artifactId of artifactIds) {
79
+ const parent = notebook.cells.find((item) => item.outputArtifactId === artifactId && item.type !== "post");
80
+ if (parent)
81
+ visit(parent);
82
+ }
83
+ };
84
+ visit(source);
85
+ return notebook.cells.filter((cell) => seen.has(cell.id)).map((cell) => ({
86
+ cellId: cell.id, type: cell.type, label: cell.label, artifactId: cell.outputArtifactId,
87
+ prompt: cell.prompt, inputs: cell.inputs,
88
+ }));
89
+ }
90
+ async function freezeSource(engine, source) {
91
+ const artifactId = source.outputArtifactId;
92
+ if (!artifactId)
93
+ throw new Error("Choose a finished image or video cell");
94
+ const artifact = engine.artifacts.get(artifactId);
95
+ if (!artifact.ok || !["video", "image", "final"].includes(artifact.value.kind))
96
+ throw new Error("A Post needs an image or video source");
97
+ const revision = engine.head;
98
+ const manifest = await engine.files.manifest(artifactId);
99
+ if (!manifest.ok)
100
+ throw new Error(manifest.error.message);
101
+ const file = findPrimaryMediaFile(manifest.value.files, artifact.value.kind);
102
+ if (!file)
103
+ throw new Error("Source has no finished media file");
104
+ const bytes = await engine.files.readAtRevision(artifactId, file.name, revision);
105
+ if (!bytes.ok)
106
+ throw new Error(bytes.error.message);
107
+ const snapshot = await engine.artifacts.create(artifact.value.kind === "final" ? "video" : artifact.value.kind, `Post source · ${source.label ?? artifact.value.label ?? "clip"}`);
108
+ if (!snapshot.ok)
109
+ throw new Error(snapshot.error.message);
110
+ const snapshotArtifactId = snapshot.value.artifactId;
111
+ const filename = `original${extname(file.name)}`;
112
+ const written = await engine.files.write(snapshotArtifactId, filename, bytes.value);
113
+ if (!written.ok)
114
+ throw new Error(written.error.message);
115
+ const tagged = await engine.metadata.artifacts.write(snapshotArtifactId, "publication", { role: "source-snapshot", sourceArtifactId: artifactId, sourceRevision: revision });
116
+ if (!tagged.ok)
117
+ throw new Error(tagged.error.message);
118
+ return { cellId: source.id, artifactId, revision, objectHash: createHash("sha256").update(bytes.value).digest("hex"), filename, sourceFilename: file.name, snapshotArtifactId };
119
+ }
120
+ export async function createPost(engine, args) {
121
+ let createdId = "";
122
+ await withNotebookMutation(args.notebookId, async () => {
123
+ const read = engine.notebooks.read(args.notebookId);
124
+ if (!read.ok)
125
+ throw new Error(read.error.message);
126
+ const notebook = read.value;
127
+ const source = args.sourceCellId ? notebook.cells.find((cell) => cell.id === args.sourceCellId && cell.type !== "post") : undefined;
128
+ if (args.sourceCellId && !source)
129
+ throw new Error("Source cell not found");
130
+ const frozen = source ? await freezeSource(engine, source) : null;
131
+ const platform = args.platform ?? "tiktok";
132
+ const definition = {
133
+ version: 1, platform, caption: args.caption ?? "", credits: args.credits ?? "", account: args.account ?? "", notes: args.notes ?? "",
134
+ ...(platform === "tiktok" ? { delivery: args.delivery ?? "icloud" } : {}),
135
+ createdAt: new Date().toISOString(), source: frozen, lineage: source ? captureLineage(notebook, source) : [],
136
+ };
137
+ const cell = engine.notebooks.createCell({
138
+ type: "post", label: `Post · ${definition.platform}`,
139
+ slot: nextHorizontalSlotFrom(source ? slotRight(source.slot) : { row: 0, column: 0 }, notebookOccupiedSlots(notebook.cells)),
140
+ inputs: { post: definition },
141
+ ...(frozen?.snapshotArtifactId ? { outputArtifactId: frozen.snapshotArtifactId, references: [{
142
+ id: uuidv7(), kind: "artifact", targetId: frozen.artifactId,
143
+ snapshot: { objectHash: frozen.objectHash, revision: frozen.revision }, ordinal: 0,
144
+ }] } : {}),
145
+ });
146
+ createdId = cell.id;
147
+ const result = await engine.notebooks.write({ ...notebook, cells: [...notebook.cells, cell], edges: source ? [...notebook.edges, engine.notebooks.createEdge({ source: source.id, target: cell.id, targetInput: "post" })] : notebook.edges }, { operation: "create_post" });
148
+ if (!result.ok)
149
+ throw new Error(result.error.message);
150
+ });
151
+ return getPost(engine, createdId);
152
+ }
153
+ export async function updatePost(engine, id, patch) {
154
+ const post = getPost(engine, id);
155
+ const locked = Boolean(post.runtime.outbound || post.runtime.url || post.runtime.status === "scheduled");
156
+ if (locked && Object.keys(patch).some((name) => name !== "notes"))
157
+ throw new Error("This publication is frozen. Create a new Post for a different clip, caption or platform.");
158
+ let frozen = post.definition.source;
159
+ let lineage = post.definition.lineage;
160
+ if (patch.sourceCellId) {
161
+ if (frozen)
162
+ throw new Error("The source version is frozen. Create a new Post for another clip.");
163
+ const read = engine.notebooks.read(post.notebookId);
164
+ if (!read.ok)
165
+ throw new Error(read.error.message);
166
+ const source = read.value.cells.find((cell) => cell.id === patch.sourceCellId && cell.type !== "post");
167
+ if (!source)
168
+ throw new Error("Source cell not found");
169
+ frozen = await freezeSource(engine, source);
170
+ lineage = captureLineage(read.value, source);
171
+ }
172
+ const { sourceCellId: _source, ...fields } = patch;
173
+ const written = await mutateNotebook(engine, post.notebookId, "update_post", (document) => ({
174
+ ...document,
175
+ cells: document.cells.map((cell) => cell.id !== id ? cell : {
176
+ ...cell, label: `Post · ${fields.platform ?? post.definition.platform}`,
177
+ inputs: { post: { ...postDefinition(cell), ...fields, source: frozen, lineage } },
178
+ ...(frozen?.snapshotArtifactId ? { outputArtifactId: frozen.snapshotArtifactId } : {}),
179
+ }),
180
+ edges: patch.sourceCellId ? [...document.edges, engine.notebooks.createEdge({ source: patch.sourceCellId, target: id, targetInput: "post" })] : document.edges,
181
+ }));
182
+ if (!written.ok)
183
+ throw new Error(written.error.message);
184
+ return getPost(engine, id);
185
+ }
186
+ export async function freezePostOutbound(engine, id, media) {
187
+ const post = getPost(engine, id);
188
+ if (post.runtime.outbound)
189
+ return post.runtime.outbound;
190
+ const source = post.definition.source;
191
+ if (!source?.snapshotArtifactId || !source.filename)
192
+ throw new Error("Historical media version is unknown. Create a new Post to upload a clip.");
193
+ const artifact = engine.artifacts.get(source.snapshotArtifactId);
194
+ if (!artifact.ok)
195
+ throw new Error(artifact.error.message);
196
+ const created = await engine.artifacts.create(artifact.value.kind, `Publication · ${post.address}`);
197
+ if (!created.ok)
198
+ throw new Error(created.error.message);
199
+ const artifactId = created.value.artifactId;
200
+ const filename = media?.filename ?? source.filename;
201
+ const copied = media ? await engine.files.write(artifactId, filename, media.bytes) : await engine.files.copy(source.snapshotArtifactId, source.filename, artifactId, filename);
202
+ if (!copied.ok)
203
+ throw new Error(copied.error.message);
204
+ const caption = [post.definition.caption, post.definition.credits].filter(Boolean).join("\n\n");
205
+ const written = await engine.files.write(artifactId, "caption.txt", caption);
206
+ if (!written.ok)
207
+ throw new Error(written.error.message);
208
+ const tagged = await engine.metadata.artifacts.write(artifactId, "publication", { role: "outbound", postId: id });
209
+ if (!tagged.ok)
210
+ throw new Error(tagged.error.message);
211
+ const result = await mutateNotebook(engine, post.notebookId, "freeze_post", (document) => ({ ...document, cells: document.cells.map((cell) => cell.id === id ? { ...cell, outputArtifactId: artifactId } : cell) }));
212
+ if (!result.ok)
213
+ throw new Error(result.error.message);
214
+ const manifest = await engine.files.manifest(artifactId);
215
+ if (!manifest.ok)
216
+ throw new Error(manifest.error.message);
217
+ const outbound = { artifactId, filename, caption, frozenAt: new Date().toISOString(), revision: engine.head, objectHash: manifest.value.files.find(file => file.name === filename).objectHash };
218
+ patchPostRuntime(engine, id, { outbound, publication: post.definition });
219
+ return outbound;
220
+ }
221
+ function normalizePostUrl(value, platform) {
222
+ const url = new URL(value.trim());
223
+ if (url.protocol !== "https:" || url.username || url.password)
224
+ throw new Error("Use a public HTTPS post link");
225
+ const host = url.hostname.toLowerCase().replace(/^www\./, "");
226
+ const allowed = { x: ["x.com", "twitter.com"], instagram: ["instagram.com"], tiktok: ["tiktok.com", "vm.tiktok.com", "vt.tiktok.com"], youtube: ["youtube.com", "youtu.be"], other: [] };
227
+ if (platform !== "other" && !allowed[platform].includes(host))
228
+ throw new Error(`The URL does not match ${platform}`);
229
+ if (platform === "x") {
230
+ const id = url.pathname.match(/\/status\/(\d+)/)?.[1];
231
+ if (!id)
232
+ throw new Error("Use an X post URL containing /status/<id>");
233
+ return `https://x.com/i/web/status/${id}`;
234
+ }
235
+ url.hostname = host;
236
+ url.hash = "";
237
+ for (const name of [...url.searchParams.keys()])
238
+ if (name !== "v")
239
+ url.searchParams.delete(name);
240
+ return url.toString().replace(/\/$/, "");
241
+ }
242
+ export async function linkPost(engine, id, url, postedAt) {
243
+ const post = getPost(engine, id);
244
+ if (!post.definition.source)
245
+ throw new Error("Choose the source clip before linking a publication");
246
+ const normalized = normalizePostUrl(url, post.definition.platform);
247
+ const time = new Date(postedAt).getTime();
248
+ if (!Number.isFinite(time) || time > Date.now() + 60_000)
249
+ throw new Error("Enter the actual publication time");
250
+ const duplicate = () => (engine.settings.get("application.posts.index.v1") ?? []).some((otherId) => otherId !== id && readPostRuntime(engine, otherId).url === normalized);
251
+ if (duplicate())
252
+ throw new Error("That publication is already tracked by another Post cell");
253
+ if (post.runtime.url && post.runtime.url !== normalized)
254
+ throw new Error("This Post already represents a publication. Create a new Post for a repost.");
255
+ if (post.runtime.url)
256
+ return post;
257
+ await freezePostOutbound(engine, id);
258
+ if (duplicate())
259
+ throw new Error("That publication is already tracked by another Post cell");
260
+ patchPostRuntime(engine, id, { url: normalized, postedAt: new Date(time).toISOString(), status: "posted", error: null, nextMetricAt: post.definition.platform === "x" ? new Date().toISOString() : null, metricsPaused: false });
261
+ return getPost(engine, id);
262
+ }
263
+ function legacyXReceipt(inputs) {
264
+ const replies = (Array.isArray(inputs.replies) ? inputs.replies : []).flatMap((value) => {
265
+ if (!value || typeof value !== "object")
266
+ return [];
267
+ const reply = value;
268
+ const id = reply.tweet_id ?? reply.tweetId;
269
+ const url = reply.tweet_url ?? reply.tweetUrl;
270
+ if (typeof id !== "string" || typeof url !== "string")
271
+ return [];
272
+ return [{ tweet_id: id, tweet_url: url, media_ids: Array.isArray(reply.media_ids) ? reply.media_ids.filter((id) => typeof id === "string") : [], text: typeof reply.text === "string" ? reply.text : null, posted_at: typeof reply.posted_at === "string" ? reply.posted_at : "" }];
273
+ });
274
+ const error = inputs.followupError && typeof inputs.followupError === "object" ? inputs.followupError : null;
275
+ return { tweetId: String(inputs.tweetId ?? ""), mediaId: "", replies, followupError: error ? { reply_number: Number(error.reply_number ?? error.replyNumber), message: String(error.message ?? "Reply failed") } : null };
276
+ }
277
+ function migrateLegacyNotebook(engine, notebook) {
278
+ const legacy = notebook.cells.filter(cell => cell.inputs?.sourceOperation === "post_to_x" && !cell.inputs.post);
279
+ const ids = new Set(legacy.map(cell => cell.id));
280
+ const cells = [...notebook.cells];
281
+ const parents = new Map();
282
+ for (const cell of legacy) {
283
+ let source = cells.find(item => item.id === cell.inputs?.sourceCellId && !ids.has(item.id))
284
+ ?? cells.find(item => item.outputArtifactId === cell.outputArtifactId && !ids.has(item.id) && ["image", "video", "generate_image", "generate_video", "concat", "splice", "split_video"].includes(item.type));
285
+ if (!source && notebook.edges.some(edge => edge.source === cell.id)) {
286
+ source = engine.notebooks.createCell({ type: cell.type === "image" ? "image" : "video", label: "Historical post source", outputArtifactId: cell.outputArtifactId, slot: nextHorizontalSlotFrom(slotRight(cell.slot), notebookOccupiedSlots(cells)) });
287
+ cells.push(source);
288
+ }
289
+ if (source)
290
+ parents.set(cell.id, source);
291
+ }
292
+ const edges = notebook.edges.map(edge => ids.has(edge.source) ? { ...edge, source: parents.get(edge.source).id } : edge);
293
+ for (const cell of legacy) {
294
+ const source = parents.get(cell.id);
295
+ if (source && !edges.some(edge => edge.target === cell.id))
296
+ edges.push(engine.notebooks.createEdge({ source: source.id, target: cell.id, targetInput: "post" }));
297
+ }
298
+ return { ...notebook, edges, cells: cells.map(cell => {
299
+ if (!ids.has(cell.id))
300
+ return cell;
301
+ const source = parents.get(cell.id);
302
+ const definition = {
303
+ version: 1, platform: "x", account: "", caption: "", credits: "", notes: "Historical media version is unknown.",
304
+ createdAt: String(cell.inputs?.postedAt ?? new Date().toISOString()),
305
+ source: { cellId: source?.id ?? String(cell.inputs?.sourceCellId ?? ""), artifactId: cell.outputArtifactId ?? "", revision: null, objectHash: null, filename: null, snapshotArtifactId: null },
306
+ lineage: source ? captureLineage({ ...notebook, cells }, source) : [],
307
+ };
308
+ return { ...cell, type: "post", inputs: { post: definition } };
309
+ }) };
310
+ }
311
+ export async function migrateLegacyPosts(engine) {
312
+ for (const item of engine.notebooks.list()) {
313
+ const read = engine.notebooks.read(item.id);
314
+ if (!read.ok)
315
+ continue;
316
+ const legacy = read.value.cells.filter((cell) => cell.inputs?.sourceOperation === "post_to_x" && !cell.inputs.post);
317
+ if (!legacy.length)
318
+ continue;
319
+ for (const cell of legacy) {
320
+ const inputs = cell.inputs ?? {};
321
+ if (readPostRuntime(engine, cell.id).url)
322
+ continue;
323
+ patchPostRuntime(engine, cell.id, {
324
+ status: inputs.status === "partially_posted" ? "partially_posted" : "posted",
325
+ url: typeof inputs.tweetUrl === "string" ? inputs.tweetUrl : undefined,
326
+ postedAt: typeof inputs.postedAt === "string" ? inputs.postedAt : undefined,
327
+ x: legacyXReceipt(inputs),
328
+ nextMetricAt: new Date().toISOString(),
329
+ });
330
+ }
331
+ const result = await mutateNotebook(engine, item.id, "migrate_post_cells", (notebook) => migrateLegacyNotebook(engine, notebook));
332
+ if (!result.ok)
333
+ throw new Error(result.error.message);
334
+ for (const cell of legacy) {
335
+ const post = getPost(engine, cell.id);
336
+ if (!post.runtime.publication)
337
+ patchPostRuntime(engine, cell.id, { publication: post.definition });
338
+ }
339
+ }
340
+ }
@@ -0,0 +1,43 @@
1
+ import { createPost, getPost, updatePost } from "./post-store.js";
2
+ export async function ensureXPost(engine, assetId, text, postCellId) {
3
+ if (postCellId) {
4
+ let post = getPost(engine, postCellId);
5
+ if (post.definition.platform !== "x")
6
+ throw new Error("This Post targets another platform");
7
+ if (!post.definition.source?.snapshotArtifactId)
8
+ throw new Error("Choose a finished source clip first");
9
+ if (assetId && ![post.definition.source.artifactId, post.definition.source.snapshotArtifactId, post.runtime.outbound?.artifactId].includes(assetId))
10
+ throw new Error("Asset does not match the Post source");
11
+ if (text !== undefined && text !== post.definition.caption && text !== post.runtime.xRequest?.text)
12
+ post = await updatePost(engine, postCellId, { caption: text });
13
+ return post;
14
+ }
15
+ if (!assetId)
16
+ throw new Error("Provide asset_id or post_cell_id");
17
+ let source;
18
+ let notebookId;
19
+ for (const item of engine.notebooks.list()) {
20
+ const read = engine.notebooks.read(item.id);
21
+ if (!read.ok)
22
+ continue;
23
+ source = read.value.cells.find((cell) => cell.outputArtifactId === assetId && cell.type !== "post" && cell.inputs?.sourceOperation !== "post_to_x");
24
+ if (source) {
25
+ notebookId = item.id;
26
+ break;
27
+ }
28
+ }
29
+ if (!source || !notebookId) {
30
+ const artifact = engine.artifacts.get(assetId);
31
+ if (!artifact.ok || !["image", "video", "final"].includes(artifact.value.kind))
32
+ throw new Error("Post source must be an image or video");
33
+ const created = await engine.notebooks.create("Publications");
34
+ if (!created.ok)
35
+ throw new Error(created.error.message);
36
+ notebookId = created.value.id;
37
+ source = engine.notebooks.createCell({ type: artifact.value.kind === "image" ? "image" : "video", slot: { row: 0, column: 0 }, outputArtifactId: assetId, label: artifact.value.label });
38
+ const written = await engine.notebooks.write({ ...created.value, cells: [source], edges: [] }, { operation: "create_post_source" });
39
+ if (!written.ok)
40
+ throw new Error(written.error.message);
41
+ }
42
+ return createPost(engine, { notebookId, sourceCellId: source.id, platform: "x", caption: text });
43
+ }
@@ -0,0 +1,90 @@
1
+ import { isUuidV7 } from "videobook-engine";
2
+ import { isNotebookGridAddress, normalizeNotebookReference } from "videobook-engine/notebook-mentions";
3
+ export const PREVIEW_PLACEHOLDER_HOST = "url.resolved.later.for";
4
+ const PREVIEW_PLACEHOLDER_ORIGIN = `http://${PREVIEW_PLACEHOLDER_HOST}`;
5
+ const URL_FIELD_KEYS = new Set([
6
+ "image_urls",
7
+ "image_url",
8
+ "reference_image",
9
+ "reference_image_urls",
10
+ "reference_images",
11
+ "reference_video_urls",
12
+ "reference_audio_urls",
13
+ "video_url",
14
+ "video_urls",
15
+ "first_frame_url",
16
+ "last_frame_url",
17
+ "first_frame",
18
+ "last_frame",
19
+ "audio_url",
20
+ "audio_urls",
21
+ "character_image_urls",
22
+ "motion_video_urls",
23
+ "driving_audio_url",
24
+ "start_image",
25
+ "end_image",
26
+ ]);
27
+ export function previewPlaceholderUrl(reference) {
28
+ const bare = normalizeNotebookReference(reference);
29
+ return `${PREVIEW_PLACEHOLDER_ORIGIN}/${encodeURIComponent(bare)}`;
30
+ }
31
+ export function isPreviewPlaceholderUrl(value) {
32
+ try {
33
+ return new URL(value).host === PREVIEW_PLACEHOLDER_HOST;
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
39
+ function isUrlishKey(key) {
40
+ if (URL_FIELD_KEYS.has(key))
41
+ return true;
42
+ if (key.endsWith("_url") || key.endsWith("_urls"))
43
+ return true;
44
+ return key === "image"
45
+ || key === "images"
46
+ || key === "video"
47
+ || key === "videos"
48
+ || key === "audio"
49
+ || key === "audios";
50
+ }
51
+ // Pre-dispatch payloads reference media as artifact ids (the internal
52
+ // token) or, when still unresolved, grid addresses. Either form becomes a
53
+ // placeholder URL in previews; real URLs are substituted only at dispatch.
54
+ function localReference(value) {
55
+ if (/^https?:\/\//iu.test(value))
56
+ return null;
57
+ const reference = normalizeNotebookReference(value);
58
+ return isUuidV7(reference) || isNotebookGridAddress(reference)
59
+ ? reference
60
+ : null;
61
+ }
62
+ function transformValue(value, key, path, placeholders) {
63
+ if (typeof value === "string") {
64
+ if (!isUrlishKey(key))
65
+ return value;
66
+ const reference = localReference(value);
67
+ if (!reference)
68
+ return value;
69
+ const url = previewPlaceholderUrl(reference);
70
+ placeholders.push({ path, reference: `@${reference}`, url });
71
+ return url;
72
+ }
73
+ if (Array.isArray(value)) {
74
+ return value.map((item, index) => transformValue(item, key, `${path}[${index}]`, placeholders));
75
+ }
76
+ if (!value || typeof value !== "object")
77
+ return value;
78
+ return Object.fromEntries(Object.entries(value).map(([childKey, childValue]) => [
79
+ childKey,
80
+ transformValue(childValue, childKey, path ? `${path}.${childKey}` : childKey, placeholders),
81
+ ]));
82
+ }
83
+ export function applyPreviewPlaceholderUrls(payload) {
84
+ const placeholders = [];
85
+ const transformed = transformValue(payload, "", "", placeholders);
86
+ return {
87
+ payload: transformed,
88
+ placeholders,
89
+ };
90
+ }
@@ -0,0 +1,22 @@
1
+ export const PROMPT_SURFACES = [
2
+ "chat",
3
+ "image_rewrite",
4
+ "video_rewrite",
5
+ "script_rewrite",
6
+ "post",
7
+ "image_generate",
8
+ "video_generate",
9
+ "audio_generate",
10
+ ];
11
+ export function isPromptSurface(value) {
12
+ return (typeof value === "string" &&
13
+ PROMPT_SURFACES.includes(value));
14
+ }
15
+ const GENERATION_PROMPT_SURFACES = {
16
+ generate_image: "image_generate",
17
+ generate_video: "video_generate",
18
+ generate_audio: "audio_generate",
19
+ };
20
+ export function generationPromptSurface(cellType) {
21
+ return GENERATION_PROMPT_SURFACES[cellType] ?? null;
22
+ }