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,259 @@
1
+ import sharp from "sharp";
2
+ import QRCode from "qrcode";
3
+ import { WATERMARK_LOGO_PATHS, watermarkProfileUrl } from "./watermark-platform.js";
4
+ import { writeImageVariants } from "./tools/concat-images.js";
5
+ export const DEFAULT_HANDLE = "justinwtanner";
6
+ // Layout identifier persisted in artifact metadata so apply_watermark can
7
+ // reuse an existing badge and skip regenerating an identical one.
8
+ export const WATERMARK_BADGE_LAYOUT = "badge-v6";
9
+ const WATERMARK_HANDLE_LAYOUT = "handle-v5";
10
+ const WATERMARK_QR_LAYOUT = "qr-v1";
11
+ export const BADGE_WIDTH = 480;
12
+ const QR_SIZE = 384;
13
+ const TILE_PAD = 48; // white frame around the QR — ≥4 quiet-zone modules up to QR v5
14
+ const TILE_SIZE = QR_SIZE + TILE_PAD * 2; // 480
15
+ const PILL_GAP = 12;
16
+ const PILL_HEIGHT = 88;
17
+ const HANDLE_PILL_CONTENT = 26;
18
+ export const BADGE_HEIGHT = TILE_SIZE + PILL_GAP + PILL_HEIGHT; // 580
19
+ export const BADGE_ASPECT = BADGE_WIDTH / BADGE_HEIGHT;
20
+ export const QR_ONLY_BADGE_HEIGHT = TILE_SIZE;
21
+ export const QR_ONLY_BADGE_ASPECT = BADGE_WIDTH / QR_ONLY_BADGE_HEIGHT;
22
+ export const HANDLE_ONLY_BADGE_HEIGHT = PILL_HEIGHT;
23
+ export const DEFAULT_HANDLE_PADDING = 32;
24
+ export const MIN_HANDLE_PADDING = 0;
25
+ export const MAX_HANDLE_PADDING = 96;
26
+ export const DEFAULT_HANDLE_ROUNDING = 100;
27
+ export const MIN_HANDLE_ROUNDING = 0;
28
+ export const MAX_HANDLE_ROUNDING = 100;
29
+ export function defaultUrl(handle, platform = "x") {
30
+ return watermarkProfileUrl(handle, platform);
31
+ }
32
+ export function watermarkHandleLabel(handle) {
33
+ return handle.replace(/^@/, "");
34
+ }
35
+ function clampHandlePadding(value) {
36
+ if (!Number.isFinite(value))
37
+ return DEFAULT_HANDLE_PADDING;
38
+ return Math.round(Math.min(MAX_HANDLE_PADDING, Math.max(MIN_HANDLE_PADDING, value)));
39
+ }
40
+ export function handlePillHeight(paddingY = DEFAULT_HANDLE_PADDING) {
41
+ const clamped = clampHandlePadding(paddingY);
42
+ return Math.max(HANDLE_PILL_CONTENT + 4, PILL_HEIGHT + 2 * (clamped - DEFAULT_HANDLE_PADDING));
43
+ }
44
+ function watermarkCombinedBadgeHeight(paddingY = DEFAULT_HANDLE_PADDING) {
45
+ return TILE_SIZE + PILL_GAP + handlePillHeight(paddingY);
46
+ }
47
+ function clampHandleRounding(value) {
48
+ if (!Number.isFinite(value))
49
+ return DEFAULT_HANDLE_ROUNDING;
50
+ return Math.round(Math.min(MAX_HANDLE_ROUNDING, Math.max(MIN_HANDLE_ROUNDING, value)));
51
+ }
52
+ function escapeXml(value) {
53
+ return value
54
+ .replace(/&/g, "&")
55
+ .replace(/</g, "&lt;")
56
+ .replace(/>/g, "&gt;")
57
+ .replace(/"/g, "&quot;")
58
+ .replace(/'/g, "&apos;");
59
+ }
60
+ export function watermarkBadgeAspect(includeQr = true, paddingY = DEFAULT_HANDLE_PADDING) {
61
+ return BADGE_WIDTH / (includeQr ? watermarkCombinedBadgeHeight(paddingY) : handlePillHeight(paddingY));
62
+ }
63
+ function watermarkBadgeVariant(options) {
64
+ return options.variant ?? (options.includeQr === false ? "handle" : "combined");
65
+ }
66
+ function watermarkBadgeLayout(variant) {
67
+ if (variant === "qr")
68
+ return WATERMARK_QR_LAYOUT;
69
+ return variant === "combined" ? WATERMARK_BADGE_LAYOUT : WATERMARK_HANDLE_LAYOUT;
70
+ }
71
+ function watermarkBadgeHeight(variant, paddingY) {
72
+ if (variant === "qr")
73
+ return QR_ONLY_BADGE_HEIGHT;
74
+ return variant === "combined"
75
+ ? watermarkCombinedBadgeHeight(paddingY)
76
+ : handlePillHeight(paddingY);
77
+ }
78
+ export async function buildWatermarkBadgePng(url, handle, options = {}) {
79
+ const platform = options.platform ?? "x";
80
+ const variant = watermarkBadgeVariant(options);
81
+ const includeQr = variant !== "handle";
82
+ const includeHandle = variant !== "qr";
83
+ const includePlatformIcon = includeHandle && (options.includePlatformIcon ?? options.includeXIcon ?? true);
84
+ const includeHandleBorder = includeHandle && (options.includeHandleBorder ?? true);
85
+ const handlePadding = clampHandlePadding(options.handlePadding ?? DEFAULT_HANDLE_PADDING);
86
+ const handlePaddingY = clampHandlePadding(options.handlePaddingY ?? DEFAULT_HANDLE_PADDING);
87
+ const handleRounding = clampHandleRounding(options.handleRounding ?? DEFAULT_HANDLE_ROUNDING);
88
+ const qrBuffer = includeQr
89
+ ? await QRCode.toBuffer(url, {
90
+ type: "png",
91
+ margin: 0,
92
+ color: { dark: "#0B0B0F", light: "#FFFFFF" },
93
+ errorCorrectionLevel: "H",
94
+ width: QR_SIZE,
95
+ })
96
+ : null;
97
+ const pillHeight = handlePillHeight(handlePaddingY);
98
+ const badgeHeight = watermarkBadgeHeight(variant, handlePaddingY);
99
+ const pillY = variant === "combined" ? TILE_SIZE + PILL_GAP : 0;
100
+ const label = watermarkHandleLabel(handle);
101
+ const iconSize = HANDLE_PILL_CONTENT;
102
+ const iconGap = 14;
103
+ const iconSpace = includePlatformIcon ? iconSize + iconGap : 0;
104
+ const availableWidth = BADGE_WIDTH - 2 - handlePadding * 2 - iconSpace;
105
+ const fontSize = Math.max(12, Math.min(28, Math.floor(availableWidth / (0.6 * Math.max(1, Array.from(label).length)))));
106
+ // Font fallback changes SVG baseline metrics between macOS and Linux.
107
+ // Place the painted glyph bounds in the pill, independent of those metrics.
108
+ const labelSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="${BADGE_WIDTH}" height="${fontSize * 3}">
109
+ <text x="${BADGE_WIDTH / 2}" y="${fontSize * 2}" text-anchor="middle" font-family="Helvetica, Arial, sans-serif" font-size="${fontSize}" font-weight="700" letter-spacing="0.1" fill="#FFFFFF">${escapeXml(label)}</text>
110
+ </svg>`;
111
+ const labelPixels = includeHandle && label.trim()
112
+ ? await sharp(Buffer.from(labelSvg)).trim({ threshold: 1, lineArt: true }).png().toBuffer()
113
+ : null;
114
+ const labelImage = labelPixels
115
+ ? await sharp(labelPixels).resize({
116
+ width: Math.floor(availableWidth),
117
+ height: HANDLE_PILL_CONTENT,
118
+ fit: "inside",
119
+ withoutEnlargement: true,
120
+ }).png().toBuffer({ resolveWithObject: true })
121
+ : null;
122
+ const labelWidth = labelImage?.info.width ?? 0;
123
+ const contentWidth = iconSpace + labelWidth;
124
+ const contentX = (BADGE_WIDTH - contentWidth) / 2;
125
+ const pillWidth = Math.min(BADGE_WIDTH - 2, Math.ceil(contentWidth + handlePadding * 2));
126
+ const pillX = (BADGE_WIDTH - pillWidth) / 2;
127
+ const logoY = pillY + (pillHeight - iconSize) / 2;
128
+ const pillRadius = ((pillHeight - 2) / 2) * (handleRounding / 100);
129
+ const border = includeHandleBorder
130
+ ? ` stroke="#FFFFFF" stroke-opacity="0.55" stroke-width="2"`
131
+ : "";
132
+ const tile = includeQr
133
+ ? `<rect x="0" y="0" width="${TILE_SIZE}" height="${TILE_SIZE}" rx="40" fill="#FFFFFF"/>`
134
+ : "";
135
+ const pill = includeHandle
136
+ ? `<rect x="${pillX}" y="${pillY + 1}" width="${pillWidth}" height="${pillHeight - 2}" rx="${pillRadius}" fill="#0B0B0F" fill-opacity="0.92"${border}/>
137
+ ${includePlatformIcon ? `<svg x="${contentX}" y="${logoY}" width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24"><path d="${WATERMARK_LOGO_PATHS[platform]}" fill="#FFFFFF"/></svg>` : ""}`
138
+ : "";
139
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${BADGE_WIDTH}" height="${badgeHeight}" viewBox="0 0 ${BADGE_WIDTH} ${badgeHeight}">
140
+ ${tile}
141
+ ${pill}
142
+ </svg>`;
143
+ const background = await sharp(Buffer.from(svg)).png().toBuffer();
144
+ const layers = [];
145
+ if (qrBuffer)
146
+ layers.push({ input: qrBuffer, top: TILE_PAD, left: TILE_PAD });
147
+ if (labelImage)
148
+ layers.push({
149
+ input: labelImage.data,
150
+ left: Math.round(contentX + iconSpace),
151
+ top: pillY + Math.round((pillHeight - labelImage.info.height) / 2),
152
+ });
153
+ if (layers.length === 0)
154
+ return background;
155
+ return sharp(background)
156
+ .composite(layers)
157
+ .png()
158
+ .toBuffer();
159
+ }
160
+ /** Build the badge and persist it as an image artifact with preview variants. */
161
+ export async function createWatermarkAsset(engine, handle, url, options = {}) {
162
+ const platform = options.platform ?? "x";
163
+ const variant = watermarkBadgeVariant(options);
164
+ const includePlatformIcon = options.includePlatformIcon ?? options.includeXIcon ?? true;
165
+ const includeHandleBorder = options.includeHandleBorder ?? true;
166
+ const handlePadding = clampHandlePadding(options.handlePadding ?? DEFAULT_HANDLE_PADDING);
167
+ const handlePaddingY = clampHandlePadding(options.handlePaddingY ?? DEFAULT_HANDLE_PADDING);
168
+ const handleRounding = clampHandleRounding(options.handleRounding ?? DEFAULT_HANDLE_ROUNDING);
169
+ let buffer;
170
+ try {
171
+ buffer = await buildWatermarkBadgePng(url, handle, {
172
+ variant,
173
+ platform,
174
+ includePlatformIcon,
175
+ includeHandleBorder,
176
+ handlePadding,
177
+ handlePaddingY,
178
+ handleRounding,
179
+ });
180
+ }
181
+ catch (err) {
182
+ return { ok: false, error: `Failed to build watermark card: ${err instanceof Error ? err.message : String(err)}` };
183
+ }
184
+ const assetIdentity = handle.toLowerCase().replace(/[^a-z0-9]+/g, "-") || "link";
185
+ const assetName = `watermark-${variant}-${assetIdentity}-${Date.now().toString(36)}`;
186
+ const created = await engine.artifacts.create("img", assetName);
187
+ if (!created.ok) {
188
+ return { ok: false, error: `Failed to create image asset: ${created.error.message}` };
189
+ }
190
+ const assetId = created.value.artifactId;
191
+ const writeResult = await engine.files.write(assetId, "original.png", buffer);
192
+ if (!writeResult.ok) {
193
+ await engine.artifacts.delete(assetId).catch(() => undefined);
194
+ return { ok: false, error: writeResult.error.message };
195
+ }
196
+ const metadataResult = await engine.metadata.artifacts.write(assetId, "original", {
197
+ origin: "watermark",
198
+ layout: watermarkBadgeLayout(variant),
199
+ created_at: new Date().toISOString(),
200
+ handle,
201
+ url,
202
+ platform,
203
+ include_qr: variant !== "handle",
204
+ include_handle: variant !== "qr",
205
+ include_platform_icon: variant !== "qr" && includePlatformIcon,
206
+ include_x_icon: variant !== "qr" && platform === "x" && includePlatformIcon,
207
+ include_handle_border: variant !== "qr" && includeHandleBorder,
208
+ handle_padding: variant !== "qr" ? handlePadding : null,
209
+ handle_padding_y: variant !== "qr" ? handlePaddingY : null,
210
+ handle_rounding: variant !== "qr" ? handleRounding : null,
211
+ width: BADGE_WIDTH,
212
+ height: watermarkBadgeHeight(variant, handlePaddingY),
213
+ });
214
+ if (!metadataResult.ok) {
215
+ await engine.artifacts.delete(assetId).catch(() => undefined);
216
+ return { ok: false, error: metadataResult.error.message };
217
+ }
218
+ const variantsResult = await writeImageVariants(engine, assetId, buffer);
219
+ if (!variantsResult.ok) {
220
+ await engine.artifacts.delete(assetId).catch(() => undefined);
221
+ return { ok: false, error: variantsResult.error };
222
+ }
223
+ return { ok: true, assetId };
224
+ }
225
+ /** Find an existing badge artifact for this handle + url (same layout), so
226
+ * repeated applies share one card instead of minting duplicates. */
227
+ export async function findWatermarkAsset(engine, handle, url, options = {}) {
228
+ const platform = options.platform ?? "x";
229
+ const variant = watermarkBadgeVariant(options);
230
+ const layout = watermarkBadgeLayout(variant);
231
+ const includePlatformIcon = options.includePlatformIcon ?? options.includeXIcon ?? true;
232
+ const includeHandleBorder = options.includeHandleBorder ?? true;
233
+ const handlePadding = clampHandlePadding(options.handlePadding ?? DEFAULT_HANDLE_PADDING);
234
+ const handlePaddingY = clampHandlePadding(options.handlePaddingY ?? DEFAULT_HANDLE_PADDING);
235
+ const handleRounding = clampHandleRounding(options.handleRounding ?? DEFAULT_HANDLE_ROUNDING);
236
+ for (const artifact of engine.artifacts.list()) {
237
+ if (artifact.kind !== "image")
238
+ continue;
239
+ const meta = await engine.metadata.artifacts.read(artifact.artifactId, "original");
240
+ if (!meta.ok)
241
+ continue;
242
+ const storedPaddingY = typeof meta.value.handle_padding_y === "number"
243
+ ? meta.value.handle_padding_y
244
+ : DEFAULT_HANDLE_PADDING;
245
+ if (meta.value.origin === "watermark"
246
+ && meta.value.layout === layout
247
+ && (meta.value.platform ?? "x") === platform
248
+ && meta.value.handle === handle
249
+ && (variant === "handle" || meta.value.url === url)
250
+ && (variant === "qr" || (meta.value.include_platform_icon ?? meta.value.include_x_icon) === includePlatformIcon)
251
+ && (variant === "qr" || meta.value.include_handle_border === includeHandleBorder)
252
+ && (variant === "qr" || meta.value.handle_padding === handlePadding)
253
+ && (variant === "qr" || storedPaddingY === handlePaddingY)
254
+ && (variant === "qr" || meta.value.handle_rounding === handleRounding)) {
255
+ return artifact.artifactId;
256
+ }
257
+ }
258
+ return null;
259
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Persisted watermark handle preferences.
3
+ *
4
+ * The chat watermark review remembers the X handles you've used so it can default to
5
+ * your last-used (or explicitly saved) handle instead of asking every time. The
6
+ * authenticated user's handle is read live via @apicity/x's /2/users/me endpoint
7
+ * (see x/authenticated-handle.ts); `authenticatedHandle` below is a read-through
8
+ * cache so the review still pre-fills when the API is unreachable.
9
+ *
10
+ * Stored as unversioned application state in the main Dolt database.
11
+ */
12
+ import { getActiveEngine } from "./queue.js";
13
+ import { watermarkProfileFor } from "./lightning-defaults.js";
14
+ const RECENT_LIMIT = 8;
15
+ const EMPTY_CONFIG = { savedHandle: null, recent: [], authenticatedHandle: null };
16
+ const SETTINGS_KEY = "application.watermark.v1";
17
+ /** Trim surrounding whitespace then strip one leading @; empty string ⇒ null. */
18
+ export function normalizeHandle(handle) {
19
+ const cleaned = handle.trim().replace(/^@/, "").trim();
20
+ return cleaned.length > 0 ? cleaned : null;
21
+ }
22
+ let writeChain = Promise.resolve();
23
+ function serialize(fn) {
24
+ const next = writeChain.then(fn, fn);
25
+ writeChain = next.catch(() => { });
26
+ return next;
27
+ }
28
+ function settingsKey(platform) {
29
+ return platform === "x" ? SETTINGS_KEY : "application.watermark.tiktok.v1";
30
+ }
31
+ function readConfig(engine, platform = "x") {
32
+ const parsed = engine.settings.get(settingsKey(platform));
33
+ if (!parsed)
34
+ return { ...EMPTY_CONFIG };
35
+ const recent = Array.isArray(parsed.recent)
36
+ ? parsed.recent.filter((handle) => typeof handle === "string")
37
+ : [];
38
+ const savedHandle = typeof parsed.savedHandle === "string" ? parsed.savedHandle : null;
39
+ const authenticatedHandle = typeof parsed.authenticatedHandle === "string" ? parsed.authenticatedHandle : null;
40
+ const profile = parsed.profile && typeof parsed.profile === "object" && !Array.isArray(parsed.profile)
41
+ ? parsed.profile
42
+ : undefined;
43
+ return { savedHandle, recent, authenticatedHandle, ...(profile ? { profile } : {}) };
44
+ }
45
+ export function effectiveWatermarkProfile(config, platform) {
46
+ return watermarkProfileFor(platform, {
47
+ ...config.profile,
48
+ ...(config.savedHandle ? { handle: config.savedHandle } : {}),
49
+ });
50
+ }
51
+ export async function readWatermarkConfig(engine = getActiveEngine(), platform = "x") {
52
+ return readConfig(engine, platform);
53
+ }
54
+ function writeWatermarkConfig(engine, config, platform = "x") {
55
+ const written = engine.settings.set(settingsKey(platform), config);
56
+ if (!written.ok)
57
+ throw new Error(written.error.message);
58
+ }
59
+ /** Record a freshly-used handle: unshift into `recent` (case-insensitive dedup,
60
+ * capped), and pin it as `savedHandle` when `saveDefault` is set. No-op for
61
+ * blank handles. Returns the updated config. */
62
+ export async function recordHandle(handle, opts = {}, engine = getActiveEngine()) {
63
+ const normalized = normalizeHandle(handle);
64
+ const platform = opts.platform ?? "x";
65
+ if (!normalized)
66
+ return readConfig(engine, platform);
67
+ return serialize(async () => {
68
+ const current = readConfig(engine, platform);
69
+ const deduped = current.recent.filter((h) => h.toLowerCase() !== normalized.toLowerCase());
70
+ const next = {
71
+ savedHandle: opts.saveDefault ? normalized : current.savedHandle,
72
+ recent: [normalized, ...deduped].slice(0, RECENT_LIMIT),
73
+ authenticatedHandle: current.authenticatedHandle,
74
+ ...(current.profile ? { profile: current.profile } : {}),
75
+ };
76
+ writeWatermarkConfig(engine, next, platform);
77
+ return next;
78
+ });
79
+ }
80
+ /** Cache the authenticated X handle (read-through fallback when the API is
81
+ * unreachable). Pass null to clear. No-op when the value is unchanged. */
82
+ export async function setAuthenticatedHandle(handle, engine = getActiveEngine()) {
83
+ const normalized = handle === null ? null : normalizeHandle(handle);
84
+ return serialize(async () => {
85
+ const current = readConfig(engine);
86
+ if (current.authenticatedHandle === normalized)
87
+ return current;
88
+ const next = {
89
+ savedHandle: current.savedHandle,
90
+ recent: current.recent,
91
+ authenticatedHandle: normalized,
92
+ ...(current.profile ? { profile: current.profile } : {}),
93
+ };
94
+ writeWatermarkConfig(engine, next);
95
+ return next;
96
+ });
97
+ }
98
+ export async function saveWatermarkProfile(profile, platform = "tiktok", engine = getActiveEngine()) {
99
+ return serialize(async () => {
100
+ const current = readConfig(engine, platform);
101
+ const handle = typeof profile.handle === "string" ? normalizeHandle(profile.handle) : null;
102
+ const next = {
103
+ savedHandle: handle ?? current.savedHandle,
104
+ recent: current.recent,
105
+ authenticatedHandle: current.authenticatedHandle,
106
+ profile: { ...current.profile, ...profile, ...(handle ? { handle } : {}) },
107
+ };
108
+ writeWatermarkConfig(engine, next, platform);
109
+ return next;
110
+ });
111
+ }
@@ -0,0 +1,55 @@
1
+ export const DEFAULT_HANDLE_VERTICAL_PERCENT = 33;
2
+ export const MIN_HANDLE_VERTICAL_PERCENT = 10;
3
+ export const MAX_HANDLE_VERTICAL_PERCENT = 90;
4
+ export const WATERMARK_HANDLE_PLACEMENTS = [
5
+ "center",
6
+ "above-qr",
7
+ "on-qr",
8
+ "below-qr",
9
+ ];
10
+ function clampHandleVerticalPercent(value) {
11
+ if (!Number.isFinite(value))
12
+ return DEFAULT_HANDLE_VERTICAL_PERCENT;
13
+ return Math.min(MAX_HANDLE_VERTICAL_PERCENT, Math.max(MIN_HANDLE_VERTICAL_PERCENT, value));
14
+ }
15
+ function watermarkHandleOverlayY(verticalPercent) {
16
+ const boundedPercent = clampHandleVerticalPercent(verticalPercent);
17
+ return `main_h*${boundedPercent}/100-overlay_h/2`;
18
+ }
19
+ function axisPosition(align, main, overlay, padding) {
20
+ if (align === "start")
21
+ return padding;
22
+ if (align === "end")
23
+ return main - overlay - padding;
24
+ return Math.round((main - overlay) / 2);
25
+ }
26
+ function watermarkQrOverlayPixels(corner, canvasWidth, canvasHeight, qrSize, padding) {
27
+ const [vertical, horizontal] = corner.split("-");
28
+ return {
29
+ x: axisPosition(horizontal === "left" ? "start" : horizontal === "right" ? "end" : "center", canvasWidth, qrSize, padding),
30
+ y: axisPosition(vertical === "top" ? "start" : vertical === "bottom" ? "end" : "center", canvasHeight, qrSize, padding),
31
+ };
32
+ }
33
+ function clampOverlayOrigin(value, main, overlay, padding) {
34
+ return Math.round(Math.min(main - overlay - padding, Math.max(padding, value)));
35
+ }
36
+ export function watermarkHandleOverlayPosition(options) {
37
+ if (options.placement === "center") {
38
+ return {
39
+ x: "(main_w-overlay_w)/2",
40
+ y: watermarkHandleOverlayY(options.handleVerticalPercent ?? DEFAULT_HANDLE_VERTICAL_PERCENT),
41
+ };
42
+ }
43
+ const qr = watermarkQrOverlayPixels(options.corner, options.canvasWidth, options.canvasHeight, options.qrSize, options.padding);
44
+ const gap = Math.max(2, Math.round(options.qrSize * 0.06));
45
+ const x = qr.x + (options.qrSize - options.handleWidth) / 2;
46
+ const y = options.placement === "above-qr"
47
+ ? qr.y - options.handleHeight - gap
48
+ : options.placement === "below-qr"
49
+ ? qr.y + options.qrSize + gap
50
+ : qr.y + (options.qrSize - options.handleHeight) / 2;
51
+ return {
52
+ x: String(clampOverlayOrigin(x, options.canvasWidth, options.handleWidth, options.padding)),
53
+ y: String(clampOverlayOrigin(y, options.canvasHeight, options.handleHeight, options.padding)),
54
+ };
55
+ }
@@ -0,0 +1,15 @@
1
+ export const WATERMARK_PLATFORMS = ["x", "tiktok"];
2
+ export const WATERMARK_PLATFORM_LABELS = {
3
+ x: "X",
4
+ tiktok: "TikTok",
5
+ };
6
+ export const WATERMARK_LOGO_PATHS = {
7
+ x: "M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z",
8
+ tiktok: "M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z",
9
+ };
10
+ export function watermarkProfileUrl(handle, platform = "x") {
11
+ return platform === "tiktok" ? `https://www.tiktok.com/@${handle}` : `https://x.com/${handle}`;
12
+ }
13
+ export function isWatermarkPlatform(value) {
14
+ return value === "x" || value === "tiktok";
15
+ }
@@ -0,0 +1,16 @@
1
+ export function effectiveWatermarkTiming(label, duration, startSeconds, endSeconds) {
2
+ if (!Number.isFinite(duration) || duration <= 0)
3
+ throw new Error("Video has no usable duration");
4
+ const effectiveStart = startSeconds ?? 0;
5
+ if (effectiveStart < 0 || effectiveStart >= duration) {
6
+ throw new Error(`${label} start must be before the ${duration.toFixed(2)}s clip end`);
7
+ }
8
+ const effectiveEnd = endSeconds === undefined ? undefined : Math.min(endSeconds, duration);
9
+ if (effectiveEnd !== undefined && effectiveEnd <= effectiveStart) {
10
+ throw new Error(`${label} end must be after its start`);
11
+ }
12
+ return {
13
+ ...(startSeconds !== undefined ? { startSeconds } : {}),
14
+ ...(effectiveEnd !== undefined ? { endSeconds: effectiveEnd } : {}),
15
+ };
16
+ }