@crievo/omni 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (709) hide show
  1. package/.env.example +41 -0
  2. package/README.md +2 -2
  3. package/bin/omni.mjs +14 -0
  4. package/docs/onboarding/clone-voice.png +0 -0
  5. package/docs/onboarding/getting-started.png +0 -0
  6. package/docs/onboarding/new-video.png +0 -0
  7. package/docs/onboarding/providers.png +0 -0
  8. package/docs/onboarding/publish.png +0 -0
  9. package/docs/onboarding/references.png +0 -0
  10. package/docs/onboarding.md +212 -0
  11. package/omnivideo.config.example.json +437 -0
  12. package/package.json +40 -4
  13. package/reference/captions.md +156 -0
  14. package/reference/carousel-contract.md +91 -0
  15. package/reference/cloud.md +96 -0
  16. package/reference/learnings.md +792 -0
  17. package/reference/omnichat.md +305 -0
  18. package/reference/premiere-mcp.md +91 -0
  19. package/reference/publish.md +305 -0
  20. package/reference/scenes-contract.md +109 -0
  21. package/reference/studio.md +666 -0
  22. package/reference/voicestudio.md +81 -0
  23. package/reference/writing-for-voice.md +67 -0
  24. package/requirements.txt +3 -0
  25. package/studio/api/app.ts +87 -0
  26. package/studio/api/lib/errors.ts +33 -0
  27. package/studio/api/lib/files.ts +41 -0
  28. package/studio/api/lib/hono.ts +9 -0
  29. package/studio/api/lib/result.ts +14 -0
  30. package/studio/api/lib/validate.ts +31 -0
  31. package/studio/api/lib/video.ts +10 -0
  32. package/studio/api/middleware/json-charset.ts +11 -0
  33. package/studio/api/middleware/local-only.ts +32 -0
  34. package/studio/api/middleware/no-timeout.ts +10 -0
  35. package/studio/api/routes/admin.ts +18 -0
  36. package/studio/api/routes/agent-cascade.ts +100 -0
  37. package/studio/api/routes/agent-history.ts +23 -0
  38. package/studio/api/routes/agent.ts +162 -0
  39. package/studio/api/routes/analytics.ts +31 -0
  40. package/studio/api/routes/approval.ts +60 -0
  41. package/studio/api/routes/archive.ts +12 -0
  42. package/studio/api/routes/captions.ts +28 -0
  43. package/studio/api/routes/carousel.ts +38 -0
  44. package/studio/api/routes/clone.ts +27 -0
  45. package/studio/api/routes/create.ts +19 -0
  46. package/studio/api/routes/events.ts +11 -0
  47. package/studio/api/routes/export.ts +14 -0
  48. package/studio/api/routes/media.ts +30 -0
  49. package/studio/api/routes/omnichat.ts +113 -0
  50. package/studio/api/routes/onboarding.ts +14 -0
  51. package/studio/api/routes/publish.ts +40 -0
  52. package/studio/api/routes/queue.ts +14 -0
  53. package/studio/api/routes/references.ts +40 -0
  54. package/studio/api/routes/runner.ts +17 -0
  55. package/studio/api/routes/schedule.ts +47 -0
  56. package/studio/api/routes/services.ts +11 -0
  57. package/studio/api/routes/sessions.ts +27 -0
  58. package/studio/api/routes/settings.ts +102 -0
  59. package/studio/api/routes/sound.ts +45 -0
  60. package/studio/api/routes/thumbnail.ts +18 -0
  61. package/studio/api/routes/usage.ts +13 -0
  62. package/studio/api/routes/videos.ts +41 -0
  63. package/studio/api/routes/voices.ts +71 -0
  64. package/studio/api/services/admin.ts +113 -0
  65. package/studio/api/services/agent-cascade.ts +975 -0
  66. package/studio/api/services/agent-claude.ts +47 -0
  67. package/studio/api/services/agent-history.ts +103 -0
  68. package/studio/api/services/agent-persona.ts +77 -0
  69. package/studio/api/services/agent-tools.ts +2237 -0
  70. package/studio/api/services/agent-ui-tools.ts +259 -0
  71. package/studio/api/services/agent-wait.ts +32 -0
  72. package/studio/api/services/agent.ts +553 -0
  73. package/studio/api/services/analytics.ts +92 -0
  74. package/studio/api/services/approval.ts +134 -0
  75. package/studio/api/services/archive.ts +38 -0
  76. package/studio/api/services/captions.ts +268 -0
  77. package/studio/api/services/carousel.ts +206 -0
  78. package/studio/api/services/classify.ts +196 -0
  79. package/studio/api/services/clone-phrases.ts +75 -0
  80. package/studio/api/services/clone.ts +722 -0
  81. package/studio/api/services/create.ts +421 -0
  82. package/studio/api/services/events.ts +215 -0
  83. package/studio/api/services/media.ts +32 -0
  84. package/studio/api/services/omnichat.ts +624 -0
  85. package/studio/api/services/onboarding.ts +77 -0
  86. package/studio/api/services/publish.ts +287 -0
  87. package/studio/api/services/queue.ts +150 -0
  88. package/studio/api/services/references.ts +269 -0
  89. package/studio/api/services/root.ts +60 -0
  90. package/studio/api/services/services.ts +114 -0
  91. package/studio/api/services/sessions.ts +1007 -0
  92. package/studio/api/services/settings.ts +983 -0
  93. package/studio/api/services/sound.ts +311 -0
  94. package/studio/api/services/thumbnail.ts +133 -0
  95. package/studio/api/services/usage.ts +27 -0
  96. package/studio/api/services/videos.ts +418 -0
  97. package/studio/api/services/voice-pause.ts +98 -0
  98. package/studio/api/services/voices.ts +324 -0
  99. package/studio/runner/runner.ts +244 -0
  100. package/studio/shared/omni-actions.ts +68 -0
  101. package/studio/shared/omni-admin.ts +52 -0
  102. package/studio/shared/omni-farewell.ts +48 -0
  103. package/studio/shared/omni-hotword.ts +20 -0
  104. package/studio/shared/omni-screens.ts +228 -0
  105. package/studio/shared/schemas/admin.ts +15 -0
  106. package/studio/shared/schemas/agent-cascade.ts +58 -0
  107. package/studio/shared/schemas/agent-history.ts +73 -0
  108. package/studio/shared/schemas/agent.ts +50 -0
  109. package/studio/shared/schemas/analytics.ts +6 -0
  110. package/studio/shared/schemas/approval.ts +22 -0
  111. package/studio/shared/schemas/captions.ts +19 -0
  112. package/studio/shared/schemas/carousel.ts +26 -0
  113. package/studio/shared/schemas/clone.ts +40 -0
  114. package/studio/shared/schemas/common.ts +16 -0
  115. package/studio/shared/schemas/omnichat.ts +54 -0
  116. package/studio/shared/schemas/onboarding.ts +5 -0
  117. package/studio/shared/schemas/publish.ts +45 -0
  118. package/studio/shared/schemas/references.ts +20 -0
  119. package/studio/shared/schemas/settings.ts +82 -0
  120. package/studio/shared/schemas/sound.ts +22 -0
  121. package/studio/shared/schemas/videos.ts +38 -0
  122. package/studio/shared/schemas/voices.ts +23 -0
  123. package/studio/shared/types/api.ts +1290 -0
  124. package/studio/studio.ts +148 -0
  125. package/studio/tsconfig.json +15 -0
  126. package/studio/web/assets/logos/cartesia.svg +4 -0
  127. package/studio/web/assets/logos/elevenlabs.svg +12 -0
  128. package/studio/web/assets/logos/fish.svg +24 -0
  129. package/studio/web/assets/logos/omni-black.png +0 -0
  130. package/studio/web/assets/logos/omni-white.png +0 -0
  131. package/studio/web/assets/logos/voicestudio.svg +21 -0
  132. package/studio/web/components/AgentMark.tsx +7 -0
  133. package/studio/web/components/AgentWalking.tsx +11 -0
  134. package/studio/web/components/ApiKeyField.tsx +104 -0
  135. package/studio/web/components/AppShell.tsx +64 -0
  136. package/studio/web/components/ApprovalNotice.tsx +65 -0
  137. package/studio/web/components/AvatarGroupIcon.tsx +22 -0
  138. package/studio/web/components/AvatarNameField.tsx +86 -0
  139. package/studio/web/components/AvatarPicker.tsx +84 -0
  140. package/studio/web/components/BrandLogo.tsx +16 -0
  141. package/studio/web/components/CartesiaPlanSetting.tsx +90 -0
  142. package/studio/web/components/ClaudeWalking.tsx +32 -0
  143. package/studio/web/components/CloneVoiceDialog/GuidedScript.tsx +39 -0
  144. package/studio/web/components/CloneVoiceDialog/LanguageBar.tsx +37 -0
  145. package/studio/web/components/CloneVoiceDialog/LevelMeter.tsx +31 -0
  146. package/studio/web/components/CloneVoiceDialog/ProviderChoice.tsx +81 -0
  147. package/studio/web/components/CloneVoiceDialog/QualityPanel.tsx +62 -0
  148. package/studio/web/components/CloneVoiceDialog/RecordStep.tsx +50 -0
  149. package/studio/web/components/CloneVoiceDialog/RecorderBar.tsx +79 -0
  150. package/studio/web/components/CloneVoiceDialog/RedoSummary.tsx +54 -0
  151. package/studio/web/components/CloneVoiceDialog/SaveStep.tsx +91 -0
  152. package/studio/web/components/CloneVoiceDialog/SourceBar.tsx +30 -0
  153. package/studio/web/components/CloneVoiceDialog/index.tsx +141 -0
  154. package/studio/web/components/CloneVoiceDialog/types.ts +28 -0
  155. package/studio/web/components/CloneVoiceDialog/useCloneVoice.ts +528 -0
  156. package/studio/web/components/ConnectionSignals.tsx +51 -0
  157. package/studio/web/components/EditVoiceDialog/EditVoiceFooter.tsx +72 -0
  158. package/studio/web/components/EditVoiceDialog/ProviderCards.tsx +81 -0
  159. package/studio/web/components/EditVoiceDialog/index.tsx +256 -0
  160. package/studio/web/components/ErrorBanner.tsx +115 -0
  161. package/studio/web/components/FinishedTask.tsx +41 -0
  162. package/studio/web/components/FishModelSetting.tsx +69 -0
  163. package/studio/web/components/FishPriceSetting.tsx +44 -0
  164. package/studio/web/components/HumanBanner.tsx +64 -0
  165. package/studio/web/components/Lightbox.tsx +41 -0
  166. package/studio/web/components/LogStream.tsx +42 -0
  167. package/studio/web/components/Markdown.tsx +21 -0
  168. package/studio/web/components/MissingDmsPanel/BatchSend.tsx +137 -0
  169. package/studio/web/components/MissingDmsPanel/CommentCard.tsx +168 -0
  170. package/studio/web/components/MissingDmsPanel/CommentFeed.tsx +45 -0
  171. package/studio/web/components/MissingDmsPanel/DmBubble.tsx +38 -0
  172. package/studio/web/components/MissingDmsPanel/ReplyComposer.tsx +63 -0
  173. package/studio/web/components/MissingDmsPanel/index.tsx +98 -0
  174. package/studio/web/components/NotFound.tsx +48 -0
  175. package/studio/web/components/Omni/OmniAvatarPicker.tsx +92 -0
  176. package/studio/web/components/Omni/OmniCards.tsx +202 -0
  177. package/studio/web/components/Omni/OmniGlow.tsx +183 -0
  178. package/studio/web/components/Omni/OmniHistory.tsx +119 -0
  179. package/studio/web/components/Omni/OmniHotword.tsx +40 -0
  180. package/studio/web/components/Omni/OmniMicSelect.tsx +92 -0
  181. package/studio/web/components/Omni/OmniVoiceSample.tsx +81 -0
  182. package/studio/web/components/Omni/OmniWidget.tsx +743 -0
  183. package/studio/web/components/Omni/PendingActions.tsx +200 -0
  184. package/studio/web/components/Omni/Transcript.tsx +127 -0
  185. package/studio/web/components/OmnichatAccountStatus.tsx +87 -0
  186. package/studio/web/components/OmnichatRulePanel/RuleConfig.tsx +150 -0
  187. package/studio/web/components/OmnichatRulePanel/RuleDryRun.tsx +15 -0
  188. package/studio/web/components/OmnichatRulePanel/RuleEnableConfirm.tsx +42 -0
  189. package/studio/web/components/OmnichatRulePanel/RuleSkeleton.tsx +18 -0
  190. package/studio/web/components/OmnichatRulePanel/RuleStatus.tsx +55 -0
  191. package/studio/web/components/OmnichatRulePanel/index.tsx +115 -0
  192. package/studio/web/components/OmnichatRulePanel/rule-steps.ts +67 -0
  193. package/studio/web/components/OmnichatRulePanel/steps/MessageStep.tsx +91 -0
  194. package/studio/web/components/OmnichatRulePanel/steps/RepliesStep.tsx +201 -0
  195. package/studio/web/components/OmnichatRulePanel/steps/TriggerStep.tsx +76 -0
  196. package/studio/web/components/OmnichatRulePanel/steps/types.ts +11 -0
  197. package/studio/web/components/Palette.tsx +17 -0
  198. package/studio/web/components/PlatformCard.tsx +109 -0
  199. package/studio/web/components/PlatformIcon.tsx +19 -0
  200. package/studio/web/components/ProviderCard.tsx +80 -0
  201. package/studio/web/components/ProviderDetail.tsx +367 -0
  202. package/studio/web/components/ProviderLogo.tsx +20 -0
  203. package/studio/web/components/QueuePanel.tsx +77 -0
  204. package/studio/web/components/ReferenceActions.tsx +89 -0
  205. package/studio/web/components/ReferencePicker.tsx +136 -0
  206. package/studio/web/components/ResultMessage.tsx +7 -0
  207. package/studio/web/components/RuleForm.tsx +138 -0
  208. package/studio/web/components/RuleLog.tsx +151 -0
  209. package/studio/web/components/RunningTask.tsx +23 -0
  210. package/studio/web/components/SampleList.tsx +55 -0
  211. package/studio/web/components/SessionPanel.tsx +352 -0
  212. package/studio/web/components/SettingRow.tsx +14 -0
  213. package/studio/web/components/SettingsCategorySkeleton.tsx +25 -0
  214. package/studio/web/components/SidePanel.tsx +77 -0
  215. package/studio/web/components/Sidebar.tsx +289 -0
  216. package/studio/web/components/StageStepper.tsx +87 -0
  217. package/studio/web/components/StudioOffline.tsx +80 -0
  218. package/studio/web/components/TaskName.tsx +17 -0
  219. package/studio/web/components/TopBanners.tsx +30 -0
  220. package/studio/web/components/VideoOmnichatPanel.tsx +287 -0
  221. package/studio/web/components/VoiceAvatar.tsx +33 -0
  222. package/studio/web/components/VoiceCarousel.tsx +196 -0
  223. package/studio/web/components/VoiceCategory.tsx +9 -0
  224. package/studio/web/components/VoiceModal.tsx +39 -0
  225. package/studio/web/components/VoiceProfileCard.tsx +75 -0
  226. package/studio/web/components/VoiceProfilePicker.tsx +107 -0
  227. package/studio/web/components/VoiceProfilesSettings.tsx +150 -0
  228. package/studio/web/components/VoiceProviderCard.tsx +118 -0
  229. package/studio/web/components/VoiceProvidersCard.tsx +26 -0
  230. package/studio/web/components/WaitList.tsx +21 -0
  231. package/studio/web/components/icons.tsx +247 -0
  232. package/studio/web/components/ui/accordion.tsx +47 -0
  233. package/studio/web/components/ui/badge.tsx +18 -0
  234. package/studio/web/components/ui/calendar.tsx +43 -0
  235. package/studio/web/components/ui/chart.tsx +229 -0
  236. package/studio/web/components/ui/checkbox.tsx +26 -0
  237. package/studio/web/components/ui/cx.ts +2 -0
  238. package/studio/web/components/ui/date-time-picker.tsx +84 -0
  239. package/studio/web/components/ui/panel.tsx +23 -0
  240. package/studio/web/components/ui/picker.tsx +58 -0
  241. package/studio/web/components/ui/popover.tsx +24 -0
  242. package/studio/web/components/ui/select.tsx +89 -0
  243. package/studio/web/components/ui/skeleton.tsx +10 -0
  244. package/studio/web/components/ui/slider.tsx +68 -0
  245. package/studio/web/components/ui/switch.tsx +13 -0
  246. package/studio/web/components/ui/tooltip.test.tsx +30 -0
  247. package/studio/web/components/ui/tooltip.tsx +59 -0
  248. package/studio/web/hooks/useAccounts.ts +27 -0
  249. package/studio/web/hooks/useAdmin.ts +12 -0
  250. package/studio/web/hooks/useAnalytics.ts +21 -0
  251. package/studio/web/hooks/useAvatarAudioReactive.ts +87 -0
  252. package/studio/web/hooks/useBrowserConnect.ts +48 -0
  253. package/studio/web/hooks/useClonePlan.ts +14 -0
  254. package/studio/web/hooks/useConnected.ts +7 -0
  255. package/studio/web/hooks/useLiveQuery.ts +29 -0
  256. package/studio/web/hooks/useMenuCollapsed.ts +32 -0
  257. package/studio/web/hooks/useMicDevices.ts +53 -0
  258. package/studio/web/hooks/useMissingDms.ts +211 -0
  259. package/studio/web/hooks/useMissingSetupSteps.ts +26 -0
  260. package/studio/web/hooks/useNow.ts +11 -0
  261. package/studio/web/hooks/useOmniAgent.ts +278 -0
  262. package/studio/web/hooks/useOmniClaude.ts +124 -0
  263. package/studio/web/hooks/useOmniDock.ts +142 -0
  264. package/studio/web/hooks/useOmniGreeting.ts +60 -0
  265. package/studio/web/hooks/useOmniHotword.ts +128 -0
  266. package/studio/web/hooks/useOmnichat.ts +26 -0
  267. package/studio/web/hooks/useOnboarding.ts +27 -0
  268. package/studio/web/hooks/usePost.ts +23 -0
  269. package/studio/web/hooks/useQueue.ts +10 -0
  270. package/studio/web/hooks/useReferences.ts +20 -0
  271. package/studio/web/hooks/useRuleEditor.ts +101 -0
  272. package/studio/web/hooks/useRunner.ts +24 -0
  273. package/studio/web/hooks/useSaveAction.ts +23 -0
  274. package/studio/web/hooks/useSchedule.ts +11 -0
  275. package/studio/web/hooks/useServices.ts +10 -0
  276. package/studio/web/hooks/useSessions.ts +15 -0
  277. package/studio/web/hooks/useSettings.ts +11 -0
  278. package/studio/web/hooks/useSettingsSection.ts +30 -0
  279. package/studio/web/hooks/useSidePanel.ts +23 -0
  280. package/studio/web/hooks/useSound.ts +30 -0
  281. package/studio/web/hooks/useUsage.ts +11 -0
  282. package/studio/web/hooks/useVideoVoice.ts +36 -0
  283. package/studio/web/hooks/useVideos.ts +21 -0
  284. package/studio/web/hooks/useVoiceProfilePreview.ts +52 -0
  285. package/studio/web/hooks/useVoiceProfiles.ts +7 -0
  286. package/studio/web/hooks/useVoiceRunning.ts +10 -0
  287. package/studio/web/hooks/useVoiceStudioClosed.ts +8 -0
  288. package/studio/web/index.html +19 -0
  289. package/studio/web/lib/agent.ts +7 -0
  290. package/studio/web/lib/alerts.ts +210 -0
  291. package/studio/web/lib/analytics-metrics.ts +351 -0
  292. package/studio/web/lib/api.ts +70 -0
  293. package/studio/web/lib/audio.ts +39 -0
  294. package/studio/web/lib/avatars.ts +41 -0
  295. package/studio/web/lib/calendar.ts +218 -0
  296. package/studio/web/lib/caption-position.ts +249 -0
  297. package/studio/web/lib/carousel.ts +24 -0
  298. package/studio/web/lib/chime.ts +42 -0
  299. package/studio/web/lib/clone-voice.ts +55 -0
  300. package/studio/web/lib/export.ts +11 -0
  301. package/studio/web/lib/files.ts +8 -0
  302. package/studio/web/lib/format.ts +93 -0
  303. package/studio/web/lib/media.ts +29 -0
  304. package/studio/web/lib/mic-device.ts +72 -0
  305. package/studio/web/lib/missing-dms.ts +70 -0
  306. package/studio/web/lib/omni-cards.ts +42 -0
  307. package/studio/web/lib/omni-cascade.ts +1067 -0
  308. package/studio/web/lib/omni-greeting-audio.ts +24 -0
  309. package/studio/web/lib/omni-hotword.ts +231 -0
  310. package/studio/web/lib/omni-panel.ts +67 -0
  311. package/studio/web/lib/omni-realtime.ts +811 -0
  312. package/studio/web/lib/omni-ui.ts +402 -0
  313. package/studio/web/lib/omni-wait.ts +174 -0
  314. package/studio/web/lib/omnichat-api.ts +44 -0
  315. package/studio/web/lib/orb.ts +270 -0
  316. package/studio/web/lib/pcm-recorder.ts +115 -0
  317. package/studio/web/lib/platforms.ts +17 -0
  318. package/studio/web/lib/providers.ts +27 -0
  319. package/studio/web/lib/publish-draft.ts +52 -0
  320. package/studio/web/lib/publish-settings.ts +81 -0
  321. package/studio/web/lib/query-client.ts +122 -0
  322. package/studio/web/lib/references.ts +13 -0
  323. package/studio/web/lib/services.ts +40 -0
  324. package/studio/web/lib/sessions.ts +103 -0
  325. package/studio/web/lib/settings-categories.ts +58 -0
  326. package/studio/web/lib/sound-audio.ts +33 -0
  327. package/studio/web/lib/sound-timeline.ts +291 -0
  328. package/studio/web/lib/sse.ts +86 -0
  329. package/studio/web/lib/stage-history.ts +141 -0
  330. package/studio/web/lib/stages.ts +344 -0
  331. package/studio/web/lib/studio-offline.ts +60 -0
  332. package/studio/web/lib/upload.ts +24 -0
  333. package/studio/web/lib/video-tabs.ts +118 -0
  334. package/studio/web/lib/voice-profile-draft.ts +34 -0
  335. package/studio/web/lib/voice-profiles.ts +3 -0
  336. package/studio/web/main.tsx +14 -0
  337. package/studio/web/pages/AdminPage/index.tsx +123 -0
  338. package/studio/web/pages/AgentPage/components/Measurements.tsx +102 -0
  339. package/studio/web/pages/AgentPage/index.tsx +28 -0
  340. package/studio/web/pages/AnalyticsPage/components/About.tsx +52 -0
  341. package/studio/web/pages/AnalyticsPage/components/ChannelCards.tsx +106 -0
  342. package/studio/web/pages/AnalyticsPage/components/ChannelView.tsx +137 -0
  343. package/studio/web/pages/AnalyticsPage/components/CollectionStatus.tsx +46 -0
  344. package/studio/web/pages/AnalyticsPage/components/Comparison.tsx +68 -0
  345. package/studio/web/pages/AnalyticsPage/components/Diagnosis.tsx +50 -0
  346. package/studio/web/pages/AnalyticsPage/components/Highlights.tsx +49 -0
  347. package/studio/web/pages/AnalyticsPage/components/Hint.tsx +11 -0
  348. package/studio/web/pages/AnalyticsPage/components/HookVsBody.tsx +100 -0
  349. package/studio/web/pages/AnalyticsPage/components/HourlyWave.tsx +133 -0
  350. package/studio/web/pages/AnalyticsPage/components/Insights.tsx +70 -0
  351. package/studio/web/pages/AnalyticsPage/components/MetricCard.tsx +46 -0
  352. package/studio/web/pages/AnalyticsPage/components/PlanSection.tsx +40 -0
  353. package/studio/web/pages/AnalyticsPage/components/ProgressionChart.tsx +155 -0
  354. package/studio/web/pages/AnalyticsPage/components/ReelBarsChart.tsx +154 -0
  355. package/studio/web/pages/AnalyticsPage/components/ReelThumb.tsx +19 -0
  356. package/studio/web/pages/AnalyticsPage/components/ReelView.tsx +290 -0
  357. package/studio/web/pages/AnalyticsPage/components/ReelsTable.tsx +269 -0
  358. package/studio/web/pages/AnalyticsPage/components/RequestWithFocus.tsx +38 -0
  359. package/studio/web/pages/AnalyticsPage/components/SegmentedControl.tsx +35 -0
  360. package/studio/web/pages/AnalyticsPage/components/VsMedian.tsx +31 -0
  361. package/studio/web/pages/AnalyticsPage/index.tsx +17 -0
  362. package/studio/web/pages/GettingStartedPage/index.tsx +174 -0
  363. package/studio/web/pages/NewVideoPage/components/DeliverablesPicker.tsx +62 -0
  364. package/studio/web/pages/NewVideoPage/components/FormatPicker.tsx +33 -0
  365. package/studio/web/pages/NewVideoPage/index.tsx +133 -0
  366. package/studio/web/pages/NotFoundPage/index.tsx +9 -0
  367. package/studio/web/pages/OmnichatPage/components/NewFromLink.tsx +59 -0
  368. package/studio/web/pages/OmnichatPage/components/RuleListItem.tsx +64 -0
  369. package/studio/web/pages/OmnichatPage/components/RulePreview.tsx +14 -0
  370. package/studio/web/pages/OmnichatPage/components/RuleView.tsx +88 -0
  371. package/studio/web/pages/OmnichatPage/index.tsx +153 -0
  372. package/studio/web/pages/QueuePage/index.tsx +22 -0
  373. package/studio/web/pages/ReferencePage/components/ReferenceTexts.tsx +131 -0
  374. package/studio/web/pages/ReferencePage/index.tsx +216 -0
  375. package/studio/web/pages/ReferencesPage/components/NewReferenceForm.tsx +150 -0
  376. package/studio/web/pages/ReferencesPage/components/ReferenceCard.tsx +42 -0
  377. package/studio/web/pages/ReferencesPage/components/ReferenceGridSkeleton.tsx +31 -0
  378. package/studio/web/pages/ReferencesPage/index.tsx +67 -0
  379. package/studio/web/pages/SchedulePage/components/CalendarSkeleton.tsx +18 -0
  380. package/studio/web/pages/SchedulePage/components/DayView.tsx +177 -0
  381. package/studio/web/pages/SchedulePage/components/HourPicker.tsx +103 -0
  382. package/studio/web/pages/SchedulePage/components/MonthDayCell.tsx +111 -0
  383. package/studio/web/pages/SchedulePage/components/MonthView.tsx +47 -0
  384. package/studio/web/pages/SchedulePage/components/PlatformIcons.tsx +12 -0
  385. package/studio/web/pages/SchedulePage/components/PostThumb.tsx +15 -0
  386. package/studio/web/pages/SchedulePage/components/WeekColumn.tsx +75 -0
  387. package/studio/web/pages/SchedulePage/components/WeekView.tsx +35 -0
  388. package/studio/web/pages/SchedulePage/index.tsx +285 -0
  389. package/studio/web/pages/SettingsPage/components/AgentAccounts.tsx +263 -0
  390. package/studio/web/pages/SettingsPage/components/AgentCategory.tsx +20 -0
  391. package/studio/web/pages/SettingsPage/components/ModelPicker.tsx +49 -0
  392. package/studio/web/pages/SettingsPage/components/ModelsSettings.tsx +138 -0
  393. package/studio/web/pages/SettingsPage/components/PlatformsCategory.tsx +23 -0
  394. package/studio/web/pages/SettingsPage/components/PublishingCategory/AccountStatus.tsx +29 -0
  395. package/studio/web/pages/SettingsPage/components/PublishingCategory/AutoPublishPanel.tsx +90 -0
  396. package/studio/web/pages/SettingsPage/components/PublishingCategory/BrowserConnect.tsx +62 -0
  397. package/studio/web/pages/SettingsPage/components/PublishingCategory/CheckField.tsx +19 -0
  398. package/studio/web/pages/SettingsPage/components/PublishingCategory/InstagramPanel.tsx +113 -0
  399. package/studio/web/pages/SettingsPage/components/PublishingCategory/OmnichatSettingsPanel.tsx +146 -0
  400. package/studio/web/pages/SettingsPage/components/PublishingCategory/SaveBar.tsx +33 -0
  401. package/studio/web/pages/SettingsPage/components/PublishingCategory/SchedulePanel.tsx +132 -0
  402. package/studio/web/pages/SettingsPage/components/PublishingCategory/SettingsCard.tsx +47 -0
  403. package/studio/web/pages/SettingsPage/components/PublishingCategory/SettingsCardGroup.tsx +19 -0
  404. package/studio/web/pages/SettingsPage/components/PublishingCategory/TextPanel.tsx +55 -0
  405. package/studio/web/pages/SettingsPage/components/PublishingCategory/TiktokPanel.tsx +123 -0
  406. package/studio/web/pages/SettingsPage/components/PublishingCategory/YoutubePanel.tsx +100 -0
  407. package/studio/web/pages/SettingsPage/components/PublishingCategory/badges.tsx +15 -0
  408. package/studio/web/pages/SettingsPage/components/PublishingCategory/index.tsx +40 -0
  409. package/studio/web/pages/SettingsPage/components/PublishingCategory/types.ts +10 -0
  410. package/studio/web/pages/SettingsPage/components/UsageCategory.tsx +389 -0
  411. package/studio/web/pages/SettingsPage/components/VoiceProvidersCategory.tsx +97 -0
  412. package/studio/web/pages/SettingsPage/index.tsx +85 -0
  413. package/studio/web/pages/VideoPage/components/AudioPlayer.tsx +113 -0
  414. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/AxisField.tsx +50 -0
  415. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionList.tsx +56 -0
  416. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionListItem.tsx +92 -0
  417. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionMargins.tsx +57 -0
  418. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionStyleFields.tsx +70 -0
  419. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionTextForm.tsx +87 -0
  420. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/ColorField.tsx +37 -0
  421. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/MarginField.tsx +59 -0
  422. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/NumberField.tsx +57 -0
  423. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/PositionControls.tsx +140 -0
  424. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/captionEditor.ts +215 -0
  425. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/useCaptionPosition.ts +154 -0
  426. package/studio/web/pages/VideoPage/components/CarouselChangeRequest.tsx +72 -0
  427. package/studio/web/pages/VideoPage/components/CarouselCompositionStage.tsx +80 -0
  428. package/studio/web/pages/VideoPage/components/CarouselCreate.tsx +63 -0
  429. package/studio/web/pages/VideoPage/components/CarouselMissing.tsx +19 -0
  430. package/studio/web/pages/VideoPage/components/CarouselPublishStage.tsx +173 -0
  431. package/studio/web/pages/VideoPage/components/CarouselScriptStage.tsx +82 -0
  432. package/studio/web/pages/VideoPage/components/CarouselSlideCard.tsx +49 -0
  433. package/studio/web/pages/VideoPage/components/CarouselViewer.tsx +55 -0
  434. package/studio/web/pages/VideoPage/components/ChangedVoices.tsx +53 -0
  435. package/studio/web/pages/VideoPage/components/CompositionStage.tsx +78 -0
  436. package/studio/web/pages/VideoPage/components/CoverPicker.tsx +32 -0
  437. package/studio/web/pages/VideoPage/components/EditorStage.tsx +53 -0
  438. package/studio/web/pages/VideoPage/components/LineTakes.tsx +160 -0
  439. package/studio/web/pages/VideoPage/components/LineTextEditor.tsx +124 -0
  440. package/studio/web/pages/VideoPage/components/LineVoicePicker.tsx +37 -0
  441. package/studio/web/pages/VideoPage/components/MediaImage.tsx +25 -0
  442. package/studio/web/pages/VideoPage/components/MotionScriptStage.tsx +39 -0
  443. package/studio/web/pages/VideoPage/components/NarrationStage.tsx +70 -0
  444. package/studio/web/pages/VideoPage/components/PublishForm.tsx +387 -0
  445. package/studio/web/pages/VideoPage/components/PublishStage.tsx +76 -0
  446. package/studio/web/pages/VideoPage/components/PublishText.tsx +149 -0
  447. package/studio/web/pages/VideoPage/components/ScenesApprovalPanel.tsx +278 -0
  448. package/studio/web/pages/VideoPage/components/ScheduledInfo.tsx +72 -0
  449. package/studio/web/pages/VideoPage/components/ScriptApprovalPanel.tsx +189 -0
  450. package/studio/web/pages/VideoPage/components/ScriptStage.tsx +17 -0
  451. package/studio/web/pages/VideoPage/components/SequencePlayer.tsx +146 -0
  452. package/studio/web/pages/VideoPage/components/SoundTimeline/AddSound.tsx +117 -0
  453. package/studio/web/pages/VideoPage/components/SoundTimeline/AreaSelection.tsx +74 -0
  454. package/studio/web/pages/VideoPage/components/SoundTimeline/BedPanel.tsx +94 -0
  455. package/studio/web/pages/VideoPage/components/SoundTimeline/BedRow.tsx +45 -0
  456. package/studio/web/pages/VideoPage/components/SoundTimeline/CaptionOverlay.tsx +44 -0
  457. package/studio/web/pages/VideoPage/components/SoundTimeline/CaptionSaveBar.tsx +32 -0
  458. package/studio/web/pages/VideoPage/components/SoundTimeline/CaptionsPanel.tsx +22 -0
  459. package/studio/web/pages/VideoPage/components/SoundTimeline/ClipBlock.tsx +111 -0
  460. package/studio/web/pages/VideoPage/components/SoundTimeline/ClipPanel.tsx +76 -0
  461. package/studio/web/pages/VideoPage/components/SoundTimeline/CutsPanel.tsx +44 -0
  462. package/studio/web/pages/VideoPage/components/SoundTimeline/EditorTabBar.tsx +73 -0
  463. package/studio/web/pages/VideoPage/components/SoundTimeline/EffectChoice.tsx +46 -0
  464. package/studio/web/pages/VideoPage/components/SoundTimeline/EffectItem.tsx +89 -0
  465. package/studio/web/pages/VideoPage/components/SoundTimeline/EffectPanel.tsx +232 -0
  466. package/studio/web/pages/VideoPage/components/SoundTimeline/EffectsRow.tsx +21 -0
  467. package/studio/web/pages/VideoPage/components/SoundTimeline/Lane.tsx +39 -0
  468. package/studio/web/pages/VideoPage/components/SoundTimeline/MarkerRow.tsx +40 -0
  469. package/studio/web/pages/VideoPage/components/SoundTimeline/MuteButton.tsx +36 -0
  470. package/studio/web/pages/VideoPage/components/SoundTimeline/NumberInput.tsx +37 -0
  471. package/studio/web/pages/VideoPage/components/SoundTimeline/PlanWarnings.tsx +19 -0
  472. package/studio/web/pages/VideoPage/components/SoundTimeline/PlayerResizeHandle.tsx +50 -0
  473. package/studio/web/pages/VideoPage/components/SoundTimeline/PreviewControls.tsx +51 -0
  474. package/studio/web/pages/VideoPage/components/SoundTimeline/SceneRow.tsx +54 -0
  475. package/studio/web/pages/VideoPage/components/SoundTimeline/SettingsPanel.tsx +65 -0
  476. package/studio/web/pages/VideoPage/components/SoundTimeline/SoundLibrary.tsx +99 -0
  477. package/studio/web/pages/VideoPage/components/SoundTimeline/SoundPreview.tsx +82 -0
  478. package/studio/web/pages/VideoPage/components/SoundTimeline/SpeedPanel.tsx +128 -0
  479. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineDock.tsx +56 -0
  480. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineFooter.tsx +26 -0
  481. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineSkeleton.tsx +21 -0
  482. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineToolbar.tsx +160 -0
  483. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineTracks.tsx +121 -0
  484. package/studio/web/pages/VideoPage/components/SoundTimeline/TrackSidebar.tsx +30 -0
  485. package/studio/web/pages/VideoPage/components/SoundTimeline/VoiceRow.tsx +53 -0
  486. package/studio/web/pages/VideoPage/components/SoundTimeline/Waveform.tsx +97 -0
  487. package/studio/web/pages/VideoPage/components/SoundTimeline/clipActions.ts +50 -0
  488. package/studio/web/pages/VideoPage/components/SoundTimeline/effectActions.ts +161 -0
  489. package/studio/web/pages/VideoPage/components/SoundTimeline/index.tsx +161 -0
  490. package/studio/web/pages/VideoPage/components/SoundTimeline/timelineGeometry.ts +45 -0
  491. package/studio/web/pages/VideoPage/components/SoundTimeline/types.ts +43 -0
  492. package/studio/web/pages/VideoPage/components/SoundTimeline/useAutosave.ts +107 -0
  493. package/studio/web/pages/VideoPage/components/SoundTimeline/useEditorShortcuts.ts +168 -0
  494. package/studio/web/pages/VideoPage/components/SoundTimeline/useFitHeight.ts +40 -0
  495. package/studio/web/pages/VideoPage/components/SoundTimeline/useFollowPlayhead.ts +21 -0
  496. package/studio/web/pages/VideoPage/components/SoundTimeline/useListenMix.ts +43 -0
  497. package/studio/web/pages/VideoPage/components/SoundTimeline/usePlayback.ts +231 -0
  498. package/studio/web/pages/VideoPage/components/SoundTimeline/usePlayerWidth.ts +27 -0
  499. package/studio/web/pages/VideoPage/components/SoundTimeline/useSoundData.ts +124 -0
  500. package/studio/web/pages/VideoPage/components/SoundTimeline/useSoundTimeline.ts +511 -0
  501. package/studio/web/pages/VideoPage/components/StageDrawer/CompareVersions.tsx +111 -0
  502. package/studio/web/pages/VideoPage/components/StageDrawer/VersionCard.tsx +81 -0
  503. package/studio/web/pages/VideoPage/components/StageDrawer/VersionMedia.tsx +24 -0
  504. package/studio/web/pages/VideoPage/components/StageDrawer/VersionText.tsx +15 -0
  505. package/studio/web/pages/VideoPage/components/StageDrawer/index.tsx +109 -0
  506. package/studio/web/pages/VideoPage/components/StageProcess.tsx +126 -0
  507. package/studio/web/pages/VideoPage/components/StageResult.tsx +69 -0
  508. package/studio/web/pages/VideoPage/components/SwapReference.tsx +92 -0
  509. package/studio/web/pages/VideoPage/components/ThumbnailPanel.tsx +252 -0
  510. package/studio/web/pages/VideoPage/components/TimingPanel.tsx +89 -0
  511. package/studio/web/pages/VideoPage/components/VideoPlayer.tsx +18 -0
  512. package/studio/web/pages/VideoPage/components/VideoSkeleton.tsx +50 -0
  513. package/studio/web/pages/VideoPage/components/VideoVoiceInfo.tsx +33 -0
  514. package/studio/web/pages/VideoPage/components/VideoVoicePicker.tsx +165 -0
  515. package/studio/web/pages/VideoPage/components/VoicePauseBanner.tsx +56 -0
  516. package/studio/web/pages/VideoPage/components/WithLogo.tsx +10 -0
  517. package/studio/web/pages/VideoPage/index.tsx +295 -0
  518. package/studio/web/pages/VideosPage/components/PublishedOn.tsx +85 -0
  519. package/studio/web/pages/VideosPage/components/ServicesPanel.tsx +81 -0
  520. package/studio/web/pages/VideosPage/components/SessionsPanel.tsx +62 -0
  521. package/studio/web/pages/VideosPage/components/VideoListSkeleton.tsx +25 -0
  522. package/studio/web/pages/VideosPage/components/VideoRow.tsx +122 -0
  523. package/studio/web/pages/VideosPage/index.tsx +134 -0
  524. package/studio/web/pages/VoicePage/index.tsx +28 -0
  525. package/studio/web/router.tsx +201 -0
  526. package/studio/web/styles/base/elements.css +55 -0
  527. package/studio/web/styles/base/utilities.css +25 -0
  528. package/studio/web/styles/components/badge.css +33 -0
  529. package/studio/web/styles/components/claude-running.css +27 -0
  530. package/studio/web/styles/components/claude-session.css +212 -0
  531. package/studio/web/styles/components/claude-walking.css +92 -0
  532. package/studio/web/styles/components/clone-voice-save.css +136 -0
  533. package/studio/web/styles/components/clone-voice.css +379 -0
  534. package/studio/web/styles/components/controls.css +70 -0
  535. package/studio/web/styles/components/data-and-image.css +54 -0
  536. package/studio/web/styles/components/forms.css +132 -0
  537. package/studio/web/styles/components/human-banner.css +99 -0
  538. package/studio/web/styles/components/lightbox.css +50 -0
  539. package/studio/web/styles/components/markdown.css +68 -0
  540. package/studio/web/styles/components/modal.css +70 -0
  541. package/studio/web/styles/components/omni-cards.css +105 -0
  542. package/studio/web/styles/components/omni.css +859 -0
  543. package/studio/web/styles/components/omnichat.css +538 -0
  544. package/studio/web/styles/components/panel.css +115 -0
  545. package/studio/web/styles/components/platform-card.css +140 -0
  546. package/studio/web/styles/components/provider-card.css +132 -0
  547. package/studio/web/styles/components/queue.css +185 -0
  548. package/studio/web/styles/components/reference-actions.css +133 -0
  549. package/studio/web/styles/components/reference-picker.css +146 -0
  550. package/studio/web/styles/components/results-drawer.css +224 -0
  551. package/studio/web/styles/components/segmented.css +115 -0
  552. package/studio/web/styles/components/side-panel-tab.css +23 -0
  553. package/studio/web/styles/components/status-signals.css +66 -0
  554. package/studio/web/styles/components/stepper.css +208 -0
  555. package/studio/web/styles/components/table.css +19 -0
  556. package/studio/web/styles/components/tabs.css +24 -0
  557. package/studio/web/styles/components/ui/accordion.css +71 -0
  558. package/studio/web/styles/components/ui/chart.css +162 -0
  559. package/studio/web/styles/components/ui/checkbox.css +37 -0
  560. package/studio/web/styles/components/ui/date-picker.css +153 -0
  561. package/studio/web/styles/components/ui/select.css +110 -0
  562. package/studio/web/styles/components/ui/skeleton.css +25 -0
  563. package/studio/web/styles/components/ui/slider.css +81 -0
  564. package/studio/web/styles/components/ui/switch.css +41 -0
  565. package/studio/web/styles/components/ui/tooltip.css +24 -0
  566. package/studio/web/styles/components/voice-carousel.css +255 -0
  567. package/studio/web/styles/components/voice-editor.css +179 -0
  568. package/studio/web/styles/components/voice-grid.css +56 -0
  569. package/studio/web/styles/components/voice-profiles.css +192 -0
  570. package/studio/web/styles/design-system.md +136 -0
  571. package/studio/web/styles/index.css +116 -0
  572. package/studio/web/styles/layout/page.css +41 -0
  573. package/studio/web/styles/layout/sidebar-groups.css +39 -0
  574. package/studio/web/styles/layout/sidebar.css +111 -0
  575. package/studio/web/styles/pages/agent.css +26 -0
  576. package/studio/web/styles/pages/analytics/dashboard.css +614 -0
  577. package/studio/web/styles/pages/analytics/insights.css +48 -0
  578. package/studio/web/styles/pages/getting-started.css +166 -0
  579. package/studio/web/styles/pages/new-video/form.css +32 -0
  580. package/studio/web/styles/pages/not-found.css +59 -0
  581. package/studio/web/styles/pages/references.css +204 -0
  582. package/studio/web/styles/pages/schedule/calendar.css +541 -0
  583. package/studio/web/styles/pages/schedule/chip-states.css +60 -0
  584. package/studio/web/styles/pages/settings/models.css +72 -0
  585. package/studio/web/styles/pages/settings/shell.css +361 -0
  586. package/studio/web/styles/pages/settings/usage.css +63 -0
  587. package/studio/web/styles/pages/studio-offline.css +105 -0
  588. package/studio/web/styles/pages/video/caption-controls.css +194 -0
  589. package/studio/web/styles/pages/video/captions-grid.css +70 -0
  590. package/studio/web/styles/pages/video/captions.css +79 -0
  591. package/studio/web/styles/pages/video/editor-chat.css +103 -0
  592. package/studio/web/styles/pages/video/export.css +50 -0
  593. package/studio/web/styles/pages/video/frames.css +29 -0
  594. package/studio/web/styles/pages/video/fullscreen-stage.css +400 -0
  595. package/studio/web/styles/pages/video/instagram-carousel.css +309 -0
  596. package/studio/web/styles/pages/video/layout.css +285 -0
  597. package/studio/web/styles/pages/video/narration.css +138 -0
  598. package/studio/web/styles/pages/video/preview-overview.css +11 -0
  599. package/studio/web/styles/pages/video/processing-notice.css +34 -0
  600. package/studio/web/styles/pages/video/publish-when.css +76 -0
  601. package/studio/web/styles/pages/video/publish.css +97 -0
  602. package/studio/web/styles/pages/video/result.css +96 -0
  603. package/studio/web/styles/pages/video/scenes.css +167 -0
  604. package/studio/web/styles/pages/video/sound-timeline.css +672 -0
  605. package/studio/web/styles/pages/video/sound-toolbar.css +149 -0
  606. package/studio/web/styles/pages/video/take-player.css +100 -0
  607. package/studio/web/styles/pages/video/thumbnail.css +157 -0
  608. package/studio/web/styles/pages/video/timeline.css +117 -0
  609. package/studio/web/styles/pages/video/voice-picker.css +14 -0
  610. package/studio/web/styles/pages/video-list/active-row.css +27 -0
  611. package/studio/web/styles/pages/video-list/card-link.css +49 -0
  612. package/studio/web/styles/pages/video-list/list.css +86 -0
  613. package/studio/web/styles/theme/identity/details.css +204 -0
  614. package/studio/web/styles/theme/identity/shell.css +249 -0
  615. package/studio/web/styles/theme/identity/surfaces.css +183 -0
  616. package/studio/web/styles/theme/identity/titles-and-buttons.css +120 -0
  617. package/studio/web/styles/theme/no-borders/blocks.css +208 -0
  618. package/studio/web/styles/theme/no-borders/controls.css +256 -0
  619. package/studio/web/styles/tokens.css +245 -0
  620. package/studio/web/types/api.ts +2 -0
  621. package/studio/web/types/assets.d.ts +9 -0
  622. package/templates/brief.md +27 -0
  623. package/templates/captions.html +204 -0
  624. package/templates/carousel-composition.md +47 -0
  625. package/templates/carousel-script.md +60 -0
  626. package/templates/components/voiceavatar/README.md +87 -0
  627. package/templates/components/voiceavatar/demo.html +50 -0
  628. package/templates/components/voiceavatar/voiceavatar.js +415 -0
  629. package/templates/motion.md +80 -0
  630. package/templates/playbook.md +43 -0
  631. package/templates/reference.md +123 -0
  632. package/templates/scene.html +112 -0
  633. package/templates/scenes.example.json +96 -0
  634. package/templates/script.md +63 -0
  635. package/templates/slide.html +71 -0
  636. package/templates/wireframe.html +91 -0
  637. package/tools/analysis.mjs +300 -0
  638. package/tools/approval.mjs +442 -0
  639. package/tools/assemble.mjs +942 -0
  640. package/tools/build-motion.mjs +437 -0
  641. package/tools/captions.mjs +246 -0
  642. package/tools/carousel.mjs +558 -0
  643. package/tools/compose.mjs +307 -0
  644. package/tools/cuts.mjs +74 -0
  645. package/tools/editor.mjs +419 -0
  646. package/tools/epidemic-capture.js +54 -0
  647. package/tools/fanout.mjs +69 -0
  648. package/tools/history.mjs +70 -0
  649. package/tools/history.py +61 -0
  650. package/tools/job-queue.mjs +180 -0
  651. package/tools/job_queue.py +430 -0
  652. package/tools/lib/agents.mjs +412 -0
  653. package/tools/lib/channel.mjs +399 -0
  654. package/tools/lib/claude-settings.mjs +67 -0
  655. package/tools/lib/cli.mjs +9 -0
  656. package/tools/lib/fanout.mjs +234 -0
  657. package/tools/lib/http.mjs +94 -0
  658. package/tools/lib/index.js +14 -0
  659. package/tools/lib/mcp-server.mjs +69 -0
  660. package/tools/lib/metrics/metrics.mjs +204 -0
  661. package/tools/lib/narration.mjs +15 -0
  662. package/tools/lib/oauth-client.mjs +295 -0
  663. package/tools/lib/omnichat/config.mjs +139 -0
  664. package/tools/lib/omnichat/errors.mjs +42 -0
  665. package/tools/lib/omnichat/extract.mjs +82 -0
  666. package/tools/lib/omnichat/gate.mjs +151 -0
  667. package/tools/lib/omnichat/manual.mjs +166 -0
  668. package/tools/lib/omnichat/match.mjs +78 -0
  669. package/tools/lib/omnichat/pass.mjs +393 -0
  670. package/tools/lib/omnichat/record.mjs +208 -0
  671. package/tools/lib/omnichat/text.mjs +315 -0
  672. package/tools/lib/omnichat/webhook.mjs +166 -0
  673. package/tools/lib/pairing.mjs +99 -0
  674. package/tools/lib/paths.mjs +41 -0
  675. package/tools/lib/platforms/instagram.mjs +469 -0
  676. package/tools/lib/platforms/tiktok.mjs +343 -0
  677. package/tools/lib/platforms/youtube.mjs +228 -0
  678. package/tools/lib/publish/publish.mjs +244 -0
  679. package/tools/lib/schedule/schedule.mjs +191 -0
  680. package/tools/lib/service.mjs +134 -0
  681. package/tools/lib/skill-client.mjs +126 -0
  682. package/tools/lib/skills-release.mjs +196 -0
  683. package/tools/lib/testing.mjs +52 -0
  684. package/tools/lib/up.mjs +187 -0
  685. package/tools/metrics.mjs +415 -0
  686. package/tools/models.mjs +134 -0
  687. package/tools/narrate.mjs +257 -0
  688. package/tools/omni.mjs +298 -0
  689. package/tools/omni_paths.py +12 -0
  690. package/tools/omnichat.mjs +1577 -0
  691. package/tools/omnivideo.py +1122 -0
  692. package/tools/package.mjs +130 -0
  693. package/tools/platforms.mjs +236 -0
  694. package/tools/premiere.mjs +533 -0
  695. package/tools/publish.mjs +1496 -0
  696. package/tools/reference.mjs +1408 -0
  697. package/tools/render.mjs +426 -0
  698. package/tools/runner.mjs +156 -0
  699. package/tools/setup.mjs +466 -0
  700. package/tools/skills-release.mjs +93 -0
  701. package/tools/sound.mjs +1323 -0
  702. package/tools/soundtrack.mjs +217 -0
  703. package/tools/thumbnail.mjs +271 -0
  704. package/tools/timing.mjs +108 -0
  705. package/tools/usage.py +628 -0
  706. package/tools/voice-envelope.mjs +185 -0
  707. package/tools/voice.py +448 -0
  708. package/tools/voice_sample.py +75 -0
  709. package/tools/wireframe.mjs +230 -0
@@ -0,0 +1,315 @@
1
+ /* The rule's texts: the DM, the public reply, the rule checks and the
2
+ classifier prompt for uncertain comments. Pure. */
3
+
4
+ import { buttonMessage } from "../platforms/instagram.mjs";
5
+ import { BUTTON_TEXT_LIMIT, BUTTON_TITLE_LIMIT, DM_BYTE_LIMIT } from "./config.mjs";
6
+
7
+ /** @typedef {import("./match.mjs").Rule} Rule */
8
+ /** @typedef {import("./config.mjs").OmnichatConfig} OmnichatConfig */
9
+
10
+ /**
11
+ * {username} (legacy {usuario}); without the name (the API did not send it) the comma and @
12
+ * before it go too: "Oi, @{username}!" becomes "Oi!".
13
+ * @param {string} t @param {string | null | undefined} username
14
+ */
15
+ export const withUsername = (t, username) =>
16
+ username
17
+ ? t.replaceAll("{username}", username).replaceAll("{usuario}", username)
18
+ : t.replace(/[,\s]*@?\{(?:username|usuario)\}/g, "").replace(/^[,\s]+/, "");
19
+
20
+ /**
21
+ * The shortcode of a post link.
22
+ * @param {string | null | undefined} url
23
+ */
24
+ export const postCode = url => /\/(?:reels?|p|tv)\/([\w-]+)/.exec(url ?? "")?.[1] ?? null;
25
+
26
+ /** The sample Reel link (terminal previews, and the worst case of the byte limit). */
27
+ export const SAMPLE_POST = "https://www.instagram.com/reel/XXXXXXXXXXX/";
28
+ /** The sample carousel link. */
29
+ export const SAMPLE_CAROUSEL = "https://www.instagram.com/p/XXXXXXXXXXX/";
30
+
31
+ /**
32
+ * The DM. The post is not a message in the conversation, so it is quoted by its link,
33
+ * which Instagram shows as the post card: {post} puts it wherever the DM wants; without
34
+ * {post}, quote_post puts it on the first line. A DM that already has the link does not repeat it.
35
+ * @param {Rule} rule @param {string | null | undefined} username @param {string | null | undefined} post
36
+ * @param {{ default_dm: string }} C
37
+ */
38
+ export function dmText(rule, username, post, C) {
39
+ let t = withUsername(rule.dm || C.default_dm, username);
40
+ if (rule.link) t = t.includes("{link}") ? t.replaceAll("{link}", rule.link) : `${t.trimEnd()}\n${rule.link}`;
41
+ t = t.replaceAll("{link}", "");
42
+ if (t.includes("{post}")) t = post ? t.replaceAll("{post}", post) : t.replace(/[ \t]*\{post\}[ \t]*/g, " ");
43
+ else if (rule.quote_post && post && !t.includes(`/${postCode(post)}/`)) t = `${post}\n${t.trim()}`;
44
+ return t.trim();
45
+ }
46
+
47
+ /** @param {Rule} rule @param {{ default_link_label: string }} C */
48
+ export const linkLabel = (rule, C) => (rule.link_label || C.default_link_label).trim();
49
+
50
+ /** Does the link go in a button? Only with a link to open. @param {Rule} rule */
51
+ export const linkInButton = rule => !!(rule.link_button && rule.link);
52
+
53
+ /**
54
+ * The DM's text when the link goes in a button: {link} comes out, and so does what was left
55
+ * hanging before it ("Aqui está o link: {link}" becomes "Aqui está o link"). quote_post does not apply: inside
56
+ * a button message the post's link is plain text, without the card ({post} written in the DM still goes).
57
+ * @param {Rule} rule @param {string | null | undefined} username @param {string | null | undefined} post
58
+ * @param {{ default_dm: string }} C
59
+ */
60
+ export function dmTextWithoutLink(rule, username, post, C) {
61
+ const dm = (rule.dm || C.default_dm).replace(/[\s:–-]*\{link\}/g, "");
62
+ /* a DM that was only {link} has nothing left: empty, not the default DM */
63
+ return dm.trim() ? dmText({ ...rule, link: "", quote_post: false, dm }, username, post, C) : "";
64
+ }
65
+
66
+ /**
67
+ * What the delivery sends: the DM as text, or with link_button the text and a button that opens the link.
68
+ * @param {Rule} rule @param {string | null | undefined} username @param {string | null | undefined} post
69
+ * @param {{ default_dm: string, default_link_label: string }} C
70
+ * @returns {string | Record<string, any>}
71
+ */
72
+ export const deliveryMessage = (rule, username, post, C) =>
73
+ linkInButton(rule)
74
+ ? buttonMessage(dmTextWithoutLink(rule, username, post, C), [{ title: linkLabel(rule, C), url: rule.link }])
75
+ : dmText(rule, username, post, C);
76
+
77
+ /**
78
+ * The delivery as one line, for previews: the text and [label] when the link goes in a button.
79
+ * @param {Rule} rule @param {string | null | undefined} username @param {string | null | undefined} post
80
+ * @param {{ default_dm: string, default_link_label: string }} C
81
+ */
82
+ export const deliveryPreview = (rule, username, post, C) =>
83
+ linkInButton(rule)
84
+ ? `${dmTextWithoutLink(rule, username, post, C)} [${linkLabel(rule, C)} → ${rule.link}]`
85
+ : dmText(rule, username, post, C);
86
+
87
+ /**
88
+ * Does the rule gate the delivery behind a button tap (and, optionally, a follow)?
89
+ * @param {Rule} rule
90
+ */
91
+ export const gateOn = rule => !!(rule.button || rule.require_follow);
92
+
93
+ /**
94
+ * The DM that carries the button, before the tap.
95
+ * @param {Rule} rule @param {string | null | undefined} username @param {{ default_opening_dm: string }} C
96
+ */
97
+ export const openingText = (rule, username, C) =>
98
+ withUsername(rule.opening_dm || C.default_opening_dm, username).trim();
99
+
100
+ /**
101
+ * What goes out when the person taps without following the account.
102
+ * @param {Rule} rule @param {string | null | undefined} username @param {{ default_not_following_dm: string }} C
103
+ */
104
+ export const notFollowingText = (rule, username, C) =>
105
+ withUsername(rule.not_following_dm || C.default_not_following_dm, username).trim();
106
+
107
+ /** @param {Rule} rule @param {{ default_button_label: string }} C */
108
+ export const buttonLabel = (rule, C) => (rule.button_label || C.default_button_label).trim();
109
+
110
+ /** @param {Rule} rule @param {{ default_retry_label: string }} C */
111
+ export const retryLabel = (rule, C) => (rule.retry_label || C.default_retry_label).trim();
112
+
113
+ /**
114
+ * Whoever taps "já sigo" without following gets, the second time, a button that delivers anyway: on unless
115
+ * the rule says follow_skip false.
116
+ * @param {Rule} rule
117
+ */
118
+ export const followSkipOn = rule => rule.follow_skip !== false;
119
+
120
+ /** @param {Rule} rule @param {{ default_skip_label: string }} C */
121
+ export const skipLabel = (rule, C) => (rule.skip_label || C.default_skip_label).trim();
122
+
123
+ /**
124
+ * What "Receber sem seguir" delivers: the rule's delivery (link, link button, quote_post) with the skip DM as text.
125
+ * @param {Rule} rule @param {string | null | undefined} username @param {string | null | undefined} post
126
+ * @param {{ default_dm: string, default_skip_dm: string, default_link_label: string }} C
127
+ * @returns {string | Record<string, any>}
128
+ */
129
+ export const skipDeliveryMessage = (rule, username, post, C) =>
130
+ deliveryMessage({ ...rule, dm: rule.skip_dm || C.default_skip_dm }, username, post, C);
131
+
132
+ /**
133
+ * The skip delivery as one line, for previews.
134
+ * @param {Rule} rule @param {string | null | undefined} username @param {string | null | undefined} post
135
+ * @param {{ default_dm: string, default_skip_dm: string, default_link_label: string }} C
136
+ */
137
+ export const skipDeliveryPreview = (rule, username, post, C) =>
138
+ deliveryPreview({ ...rule, dm: rule.skip_dm || C.default_skip_dm }, username, post, C);
139
+
140
+ /**
141
+ * One public reply picked at random, or null (DM only).
142
+ * @param {Rule} rule @param {string | null | undefined} username
143
+ * @param {{ default_public_replies: string[] }} C @param {() => number} [random]
144
+ */
145
+ export function publicReplyText(rule, username, C, random = Math.random) {
146
+ const list = rule.public_replies ?? C.default_public_replies;
147
+ return list.length ? withUsername(list[Math.floor(random() * list.length)], username).trim() : null;
148
+ }
149
+
150
+ /**
151
+ * A new rule: born disabled.
152
+ * @param {OmnichatConfig} C
153
+ * @returns {Rule}
154
+ */
155
+ export const newRule = C => ({
156
+ enabled: false,
157
+ keywords: [],
158
+ any_comment: false,
159
+ link: "",
160
+ dm: C.default_dm,
161
+ public_replies: C.default_public_replies,
162
+ quote_post: !!C.quote_post,
163
+ button: false,
164
+ require_follow: false,
165
+ });
166
+
167
+ /**
168
+ * What doesn't add up in a rule (empty: it can be enabled).
169
+ * @param {Rule} r @param {OmnichatConfig} C
170
+ * @returns {string[]}
171
+ */
172
+ export function ruleProblems(r, C) {
173
+ const p = [];
174
+ if (!r.any_comment && !(r.keywords ?? []).some(x => x.trim()))
175
+ p.push("diga a palavra-chave (--keyword QUERO) ou --any");
176
+ if (r.link && !/^https?:\/\/\S+$/.test(r.link))
177
+ p.push(`link precisa começar com http:// ou https:// (veio "${r.link}")`);
178
+ if (
179
+ !r.link &&
180
+ !/\S/.test((r.dm || C.default_dm).replaceAll("{link}", "").replaceAll("{username}", "").replaceAll("{usuario}", ""))
181
+ )
182
+ p.push("sem link, a DM precisa de texto");
183
+ /* a placeholder left to fill in, like the default DM's <artefato>: never goes out as is */
184
+ const blank = /<[^<>\s]+>/.exec(r.dm || C.default_dm)?.[0];
185
+ if (blank) p.push(`a DM ainda tem ${blank}: troque pelo que ela entrega`);
186
+ if (!r.link && (r.dm || C.default_dm).includes("{link}"))
187
+ p.push("a DM usa {link}, mas a regra não tem link (--link https://…)");
188
+ /* the worst case: a username with Instagram's maximum length (30) */
189
+ const bytes = new TextEncoder().encode(dmText(r, "x".repeat(30), SAMPLE_POST, C)).byteLength;
190
+ if (bytes > DM_BYTE_LIMIT) p.push(`a DM passa do limite do Instagram: ${bytes} de ${DM_BYTE_LIMIT} bytes`);
191
+ if (r.link_button) {
192
+ if (!r.link) p.push("o botão de link precisa do link (--link https://…)");
193
+ if (linkLabel(r, C).length > BUTTON_TITLE_LIMIT)
194
+ p.push(`o rótulo do botão de link passa de ${BUTTON_TITLE_LIMIT} caracteres`);
195
+ if (r.link && dmTextWithoutLink(r, "x".repeat(30), SAMPLE_POST, C).length > BUTTON_TEXT_LIMIT)
196
+ p.push(`com o botão de link, a DM passa de ${BUTTON_TEXT_LIMIT} caracteres`);
197
+ if (r.link && !dmTextWithoutLink(r, "x".repeat(30), SAMPLE_POST, C))
198
+ p.push("com o botão de link, a DM precisa de texto além do {link}");
199
+ }
200
+ if (gateOn(r)) {
201
+ const who = "x".repeat(30);
202
+ if (openingText(r, who, C).length > BUTTON_TEXT_LIMIT)
203
+ p.push(`o texto acima do botão passa de ${BUTTON_TEXT_LIMIT} caracteres`);
204
+ if (buttonLabel(r, C).length > BUTTON_TITLE_LIMIT)
205
+ p.push(`o rótulo do botão passa de ${BUTTON_TITLE_LIMIT} caracteres`);
206
+ if (r.require_follow) {
207
+ if (notFollowingText(r, who, C).length > BUTTON_TEXT_LIMIT)
208
+ p.push(`a mensagem de quem não segue passa de ${BUTTON_TEXT_LIMIT} caracteres`);
209
+ if (retryLabel(r, C).length > BUTTON_TITLE_LIMIT)
210
+ p.push(`o rótulo do botão de quem não segue passa de ${BUTTON_TITLE_LIMIT} caracteres`);
211
+ if (followSkipOn(r)) {
212
+ if (skipLabel(r, C).length > BUTTON_TITLE_LIMIT)
213
+ p.push(`o rótulo do botão de receber sem seguir passa de ${BUTTON_TITLE_LIMIT} caracteres`);
214
+ const skipDm = { ...r, dm: r.skip_dm || C.default_skip_dm };
215
+ const skipBytes = new TextEncoder().encode(dmText(skipDm, who, SAMPLE_POST, C)).byteLength;
216
+ if (skipBytes > DM_BYTE_LIMIT)
217
+ p.push(
218
+ `a mensagem de receber sem seguir passa do limite do Instagram: ${skipBytes} de ${DM_BYTE_LIMIT} bytes`,
219
+ );
220
+ if (r.link_button && r.link && dmTextWithoutLink(skipDm, who, SAMPLE_POST, C).length > BUTTON_TEXT_LIMIT)
221
+ p.push(`com o botão de link, a mensagem de receber sem seguir passa de ${BUTTON_TEXT_LIMIT} caracteres`);
222
+ }
223
+ }
224
+ }
225
+ for (const t of r.public_replies ?? []) if (t.length > 2200) p.push("resposta pública com mais de 2.200 caracteres");
226
+ return p;
227
+ }
228
+
229
+ /**
230
+ * A list from an array or a comma/newline separated text.
231
+ * @param {unknown} v
232
+ * @returns {string[]}
233
+ */
234
+ export const toList = v =>
235
+ (Array.isArray(v) ? v : String(v ?? "").split(/[,\n]/)).map(s => String(s).trim()).filter(Boolean);
236
+
237
+ /**
238
+ * Applies the studio's JSON edit to a rule (only the fields present). Mutates and returns `r`.
239
+ * @param {Rule} r @param {Record<string, any>} j
240
+ */
241
+ export function applyRuleJson(r, j) {
242
+ if (j.keywords !== undefined) r.keywords = toList(j.keywords);
243
+ if (j.any_comment !== undefined) r.any_comment = !!j.any_comment;
244
+ if (j.link !== undefined) r.link = String(j.link ?? "").trim();
245
+ if (j.dm !== undefined) r.dm = String(j.dm ?? "").trim();
246
+ if (j.public_replies !== undefined)
247
+ r.public_replies = toList(
248
+ Array.isArray(j.public_replies) ? j.public_replies : String(j.public_replies ?? "").split("\n"),
249
+ );
250
+ if (j.generic_phrases !== undefined) r.generic_phrases = j.generic_phrases !== false;
251
+ if (j.offer !== undefined) r.offer = String(j.offer ?? "").trim();
252
+ if (j.quote_post !== undefined) r.quote_post = j.quote_post === true;
253
+ if (j.button !== undefined) r.button = j.button === true;
254
+ if (j.require_follow !== undefined) r.require_follow = j.require_follow === true;
255
+ if (j.link_button !== undefined) r.link_button = j.link_button === true;
256
+ if (j.follow_skip !== undefined) r.follow_skip = j.follow_skip !== false;
257
+ for (const k of [
258
+ "opening_dm",
259
+ "button_label",
260
+ "not_following_dm",
261
+ "retry_label",
262
+ "link_label",
263
+ "skip_label",
264
+ "skip_dm",
265
+ ])
266
+ if (j[k] !== undefined) r[k] = String(j[k] ?? "").trim();
267
+ if (j.already_replied !== undefined) r.already_replied = j.already_replied === "send" ? "send" : "skip";
268
+ if (j.already_replied_if !== undefined)
269
+ r.already_replied_if = toList(
270
+ Array.isArray(j.already_replied_if) ? j.already_replied_if : String(j.already_replied_if ?? "").split("\n"),
271
+ );
272
+ return r;
273
+ }
274
+
275
+ /**
276
+ * What the post offers, for the classifier: the rule's offer, the caption, the link and the keyword.
277
+ * @param {Rule} rule @param {Record<string, any> | null} pub publication.json
278
+ */
279
+ export function offerText(rule, pub) {
280
+ const p = pub ?? {};
281
+ const caption = String(p.caption || [p.title, p.description].filter(Boolean).join("\n") || "").slice(0, 600);
282
+ return [
283
+ rule.offer,
284
+ caption && `Legenda do post: ${caption}`,
285
+ rule.link && `O que a DM manda: ${rule.link}`,
286
+ (rule.keywords ?? []).length && `Palavra-chave pedida no post: ${rule.keywords.join(", ")}`,
287
+ ]
288
+ .filter(Boolean)
289
+ .join("\n");
290
+ }
291
+
292
+ /**
293
+ * The prompt that asks a model which uncertain comments request the delivery.
294
+ * The input is `classifyInput(items)`; the answer is parsed by `parseClassification`.
295
+ * @param {string} offer
296
+ */
297
+ export const classifyPrompt =
298
+ offer => `Você lê comentários de um post do Instagram. Quem comenta pedindo recebe por mensagem direta o que o post oferece:
299
+
300
+ ${offer}
301
+
302
+ Para cada comentário da entrada (um objeto {id: texto}), diga se a pessoa está pedindo para receber isso (true) ou se "quero", "manda", "link" e afins aparecem por outro motivo, como um desejo qualquer, um elogio ou uma conversa (false). "eu quero que faça sol hoje" é false; "quero muito testar isso" é true. Na dúvida, false.
303
+ Responda só com um objeto JSON {"<id>": true|false}, sem markdown nem comentário.`;
304
+
305
+ /**
306
+ * @param {Array<[string, { text: string }]>} items
307
+ */
308
+ export const classifyInput = items => JSON.stringify(Object.fromEntries(items.map(([id, c]) => [id, c.text])));
309
+
310
+ /**
311
+ * The model's answer as { id: boolean }, or throws when it is not JSON.
312
+ * @param {string} out
313
+ * @returns {Record<string, boolean>}
314
+ */
315
+ export const parseClassification = out => JSON.parse(/** @type {string} */ (/\{[\s\S]*\}/.exec(out)?.[0]));
@@ -0,0 +1,166 @@
1
+ /* ============================================================================
2
+ The Instagram messaging webhook, as a pure request handler: the verification handshake, the
3
+ signature, the taps in the payload and one `handleTap` per tap. The CLI serves it on a local port
4
+ (behind a tunnel); a cloud function can call the same `handleWebhook` with its own storage.
5
+ ============================================================================ */
6
+
7
+ import { createHmac, timingSafeEqual } from "node:crypto";
8
+ import { withDefaults } from "../http.mjs";
9
+ import { handleTap, parseTapPayload } from "./gate.mjs";
10
+
11
+ /** @typedef {import("../http.mjs").Deps} Deps */
12
+ /** @typedef {import("./config.mjs").OmnichatConfig} OmnichatConfig */
13
+ /** @typedef {import("./match.mjs").Rule} Rule */
14
+ /** @typedef {import("./gate.mjs").Tap} Tap */
15
+ /** @typedef {import("../platforms/instagram.mjs").InstagramClient} InstagramClient */
16
+
17
+ /** The webhook fields the gate needs: the tap (`messaging_postbacks`) and the quick reply (`messages`). */
18
+ export const WEBHOOK_FIELDS = ["messages", "messaging_postbacks"];
19
+
20
+ /**
21
+ * The handshake Meta does when the callback URL is saved: echoes `hub.challenge` if the token matches.
22
+ * @param {Record<string, string | undefined>} query @param {string} verifyToken
23
+ * @returns {string | null}
24
+ */
25
+ export function verifyChallenge(query, verifyToken) {
26
+ if (!verifyToken || query["hub.mode"] !== "subscribe" || query["hub.verify_token"] !== verifyToken) return null;
27
+ return query["hub.challenge"] ?? "";
28
+ }
29
+
30
+ /**
31
+ * `X-Hub-Signature-256` is `sha256=` + the HMAC-SHA256 of the raw body with the app secret.
32
+ * @param {string | Uint8Array} raw @param {string | null | undefined} header @param {string} secret
33
+ */
34
+ export function validSignature(raw, header, secret) {
35
+ if (!secret || !header?.startsWith("sha256=")) return false;
36
+ const want = createHmac("sha256", secret).update(raw).digest();
37
+ const got = Buffer.from(header.slice(7), "hex");
38
+ return got.length === want.length && timingSafeEqual(got, want);
39
+ }
40
+
41
+ /**
42
+ * The taps of a webhook payload: button postbacks and quick replies whose payload is ours.
43
+ * Echoes (the account's own messages) and everything else are left out.
44
+ * @param {any} body
45
+ * @returns {Array<Tap & { recipientId: string | null }>}
46
+ */
47
+ export function tapsIn(body) {
48
+ /** @type {Array<Tap & { recipientId: string | null }>} */
49
+ const taps = [];
50
+ for (const entry of body?.entry ?? []) {
51
+ for (const m of entry.messaging ?? []) {
52
+ if (m.message?.is_echo) continue;
53
+ const mid = m.postback?.mid ?? m.message?.mid ?? null;
54
+ const parsed = parseTapPayload(m.postback?.payload ?? m.message?.quick_reply?.payload);
55
+ if (!parsed || !m.sender?.id) continue;
56
+ taps.push({ ...parsed, senderId: String(m.sender.id), mid, recipientId: m.recipient?.id ?? entry.id ?? null });
57
+ }
58
+ }
59
+ return taps;
60
+ }
61
+
62
+ /**
63
+ * The posts with a new comment from someone else (the `comments` field): each one deserves a pass now,
64
+ * instead of waiting for the next check. The account's own comments (its public replies) are left out.
65
+ * @param {any} body
66
+ * @returns {string[]} media ids, without repeats
67
+ */
68
+ export function commentedMedia(body) {
69
+ const media = new Set();
70
+ for (const entry of body?.entry ?? []) {
71
+ for (const ch of entry.changes ?? []) {
72
+ const v = ch.value ?? {};
73
+ if (ch.field !== "comments" || !v.media?.id) continue;
74
+ if (v.from?.id && String(v.from.id) === String(entry.id)) continue;
75
+ media.add(String(v.media.id));
76
+ }
77
+ }
78
+ return [...media];
79
+ }
80
+
81
+ /**
82
+ * What the handler needs to work on one post: its rule and record, behind the caller's lock/storage.
83
+ * `withRecord` loads the rule and the record, runs `fn`, saves the record when `fn` returns true.
84
+ * @typedef {object} Target
85
+ * @property {string} name for the log
86
+ * @property {string | null} post the post's link
87
+ * @property {(fn: (s: { rule: Rule, record: any }) => Promise<boolean>) => Promise<void>} withRecord
88
+ */
89
+
90
+ /**
91
+ * One webhook request. GET is the handshake; POST is the events: signature checked on the raw body,
92
+ * then each tap handled. Answers 200 only when every tap was handled (or is not ours), 500 otherwise,
93
+ * so Meta delivers the failed event again (handled events are not repeated: see `mids`).
94
+ * @param {{ method: string, query?: Record<string, string | undefined>, raw?: string | Uint8Array,
95
+ * signature?: string | null, secret: string, verifyToken: string,
96
+ * resolveTarget: (media: string) => Promise<Target | null>,
97
+ * account: () => Promise<{ client: InstagramClient, id: string }>,
98
+ * onComments?: (media: string[]) => void, C: OmnichatConfig } & Deps} p
99
+ * `onComments` gets the posts with a new comment (see commentedMedia); it should not hold the answer
100
+ * @returns {Promise<{ status: number, body: string }>}
101
+ */
102
+ export async function handleWebhook({
103
+ method,
104
+ query = {},
105
+ raw = "",
106
+ signature,
107
+ secret,
108
+ verifyToken,
109
+ resolveTarget,
110
+ account,
111
+ onComments,
112
+ C,
113
+ ...deps
114
+ }) {
115
+ const d = withDefaults(deps);
116
+ if (method === "GET") {
117
+ const challenge = verifyChallenge(query, verifyToken);
118
+ return challenge === null
119
+ ? { status: 403, body: "token de verificação não confere" }
120
+ : { status: 200, body: challenge };
121
+ }
122
+ if (method !== "POST") return { status: 405, body: "método não aceito" };
123
+ if (!validSignature(raw, signature, secret)) return { status: 401, body: "assinatura inválida" };
124
+ let body;
125
+ try {
126
+ body = JSON.parse(typeof raw === "string" ? raw : new TextDecoder().decode(raw));
127
+ } catch {
128
+ return { status: 400, body: "corpo não é JSON" };
129
+ }
130
+ const commented = commentedMedia(body);
131
+ if (commented.length) onComments?.(commented);
132
+ const taps = tapsIn(body);
133
+ if (!taps.length) return { status: 200, body: "ok" };
134
+ let failed = 0;
135
+ let acct;
136
+ for (const tap of taps) {
137
+ try {
138
+ acct ??= await account();
139
+ if (tap.recipientId && tap.recipientId !== acct.id) continue;
140
+ const target = await resolveTarget(tap.media);
141
+ if (!target) {
142
+ d.log(`${new Date(d.now()).toISOString()} webhook: toque em um post sem regra (${tap.media}), ignorado`);
143
+ continue;
144
+ }
145
+ const { client, id } = acct;
146
+ await target.withRecord(async ({ rule, record }) => {
147
+ const r = await handleTap({ tap, rule, record, account: { client, id }, post: target.post, C, ...d });
148
+ d.log(
149
+ `${new Date(d.now()).toISOString()} ${target.name}: toque de @${r.username ?? "?"} → ${
150
+ {
151
+ unknown: "comentário desconhecido",
152
+ duplicate: "evento repetido",
153
+ delivered: "enviado",
154
+ not_following: "não segue, pedi para seguir",
155
+ }[r.status]
156
+ }`,
157
+ );
158
+ return r.status !== "unknown";
159
+ });
160
+ } catch (e) {
161
+ failed++;
162
+ d.warn(`${new Date(d.now()).toISOString()} webhook: ${/** @type {Error} */ (e).message}`);
163
+ }
164
+ }
165
+ return failed ? { status: 500, body: "falhou, tente de novo" } : { status: 200, body: "ok" };
166
+ }
@@ -0,0 +1,99 @@
1
+ /* Pairing this Mac with the hosted studio (cloud/studio): the device authorization flow (RFC 8628) of the
2
+ studio's Better Auth, then the trade of the short session for the Mac's own key at /runner/register.
3
+ Used by `tools/runner.mjs login` and by the runner itself when the cloud refuses its key
4
+ (studio/runner/runner.ts). Pure: fetch, sleep and the clock are injected, so the tests run without a
5
+ network. Messages in Portuguese: they reach the user as they are. */
6
+
7
+ export const RUNNER_CLIENT_ID = "omnivideo-runner";
8
+
9
+ /**
10
+ * @typedef {{ fetch?: typeof fetch, sleep?: (ms: number) => Promise<void>, now?: () => number }} Deps
11
+ * @typedef {{ device_code: string, user_code: string, verification_uri_complete: string,
12
+ * interval?: number, expires_in: number }} DeviceCode
13
+ */
14
+
15
+ /** @param {string} url @param {string} path @param {{ body?: unknown, token?: string, method?: string }} o @param {typeof fetch} f */
16
+ async function call(url, path, { body, token, method = "POST" }, f) {
17
+ const r = await f(`${url}${path}`, {
18
+ method,
19
+ headers: { "content-type": "application/json", ...(token ? { authorization: `Bearer ${token}` } : {}) },
20
+ body: body === undefined ? undefined : JSON.stringify(body),
21
+ });
22
+ const j = await r.json().catch(() => ({}));
23
+ return { status: r.status, ok: r.ok, body: /** @type {any} */ (j) };
24
+ }
25
+
26
+ /** Asks the studio for a pairing code. @param {string} url @param {Deps} [d] @returns {Promise<DeviceCode>} */
27
+ export async function startPairing(url, d = {}) {
28
+ const r = await call(url, "/auth/device/code", { body: { client_id: RUNNER_CLIENT_ID } }, d.fetch ?? fetch);
29
+ if (!r.ok)
30
+ throw new Error(
31
+ `o studio não abriu o pareamento (${r.status}: ${r.body?.error_description ?? r.body?.error ?? "?"})`,
32
+ );
33
+ return r.body;
34
+ }
35
+
36
+ /**
37
+ * Waits for the code to be approved at <studio>/runner/pair and trades it for this Mac's key.
38
+ * @param {string} url @param {DeviceCode} code @param {string} name the Mac's name in the list of Macs
39
+ * @param {Deps} [d]
40
+ * @returns {Promise<{ key: string, name: string }>}
41
+ */
42
+ export async function finishPairing(url, code, name, d = {}) {
43
+ const f = d.fetch ?? fetch;
44
+ const sleep = d.sleep ?? (ms => new Promise(r => setTimeout(r, ms)));
45
+ const now = d.now ?? Date.now;
46
+ let wait = (code.interval || 3) * 1000;
47
+ const deadline = now() + code.expires_in * 1000;
48
+ let session = "";
49
+ while (now() < deadline) {
50
+ await sleep(wait);
51
+ const t = await call(
52
+ url,
53
+ "/auth/device/token",
54
+ {
55
+ body: {
56
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
57
+ device_code: code.device_code,
58
+ client_id: RUNNER_CLIENT_ID,
59
+ },
60
+ },
61
+ f,
62
+ );
63
+ if (t.ok && t.body?.access_token) {
64
+ session = t.body.access_token;
65
+ break;
66
+ }
67
+ const e = t.body?.error;
68
+ if (e === "authorization_pending") continue;
69
+ if (e === "slow_down") {
70
+ wait += 5000;
71
+ continue;
72
+ }
73
+ if (e === "access_denied") throw new Error("o pareamento foi recusado na página");
74
+ if (e === "expired_token") throw new Error("o código do pareamento expirou");
75
+ throw new Error(`o studio recusou o pareamento (${t.status}: ${t.body?.error_description ?? e ?? "?"})`);
76
+ }
77
+ if (!session) throw new Error("o código do pareamento expirou");
78
+ const reg = await call(url, "/runner/register", { body: { name }, token: session }, f);
79
+ if (!reg.ok || !reg.body?.key)
80
+ throw new Error(`o studio não entregou a chave do Mac (${reg.status}: ${reg.body?.error ?? "?"})`);
81
+ return { key: reg.body.key, name: reg.body.name };
82
+ }
83
+
84
+ /**
85
+ * Whether the studio still takes this key: `valid` false only when it answered that the key is refused
86
+ * (an unreachable studio is not a verdict on the key).
87
+ * @param {string} url @param {string} token @param {Deps} [d]
88
+ * @returns {Promise<{ valid: boolean | null, name?: string, online?: boolean, error?: string }>}
89
+ */
90
+ export async function checkKey(url, token, d = {}) {
91
+ try {
92
+ const r = await call(url, "/runner/whoami", { method: "GET", token }, d.fetch ?? fetch);
93
+ if (r.ok) return { valid: true, name: r.body.name, online: !!r.body.online };
94
+ if (r.status === 401) return { valid: false, error: r.body?.error ?? "chave recusada" };
95
+ return { valid: null, error: r.body?.error ?? `HTTP ${r.status}` };
96
+ } catch (e) {
97
+ return { valid: null, error: String(/** @type {Error} */ (e).message) };
98
+ }
99
+ }
@@ -0,0 +1,41 @@
1
+ /* The two roots every tool works from (docs/specs/phase-2-plugin-cli.md, step 1).
2
+
3
+ CODE_ROOT where tools/, templates/, studio/, docs/ and the .venv live: this repo, or the folder
4
+ the `omni` CLI extracts its tools into (~/.omni/bin/<version>/). OMNI_CODE_ROOT points it
5
+ elsewhere, for code bundled into the compiled studio, whose own path is /$bunfs/….
6
+ DATA_ROOT where videos/, omnivideo.config.json, .env, channel/, kit/ and the dot folders live: the
7
+ workspace. OMNI_HOME points it elsewhere; without it, it is the code root, so this repo
8
+ works as it always has.
9
+
10
+ isMainModule(import.meta.url) replaces `process.argv[1] === fileURLToPath(import.meta.url)`: inside
11
+ a compiled binary every bundled module's path equals argv[1], so that check fires on import. */
12
+
13
+ import { realpathSync } from "node:fs";
14
+ import { dirname, join, resolve } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+
17
+ const OWN_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
18
+
19
+ export const CODE_ROOT = resolve(process.env.OMNI_CODE_ROOT || OWN_ROOT);
20
+ export const DATA_ROOT = resolve(process.env.OMNI_HOME || CODE_ROOT);
21
+
22
+ /* a path inside a Bun compiled binary (/$bunfs/… on macOS and Linux, B:/~BUN/… on Windows) */
23
+ const bundled = (/** @type {string} */ p) => p.startsWith("/$bunfs/") || /^[A-Za-z]:[\\/]~BUN[\\/]/.test(p);
24
+
25
+ /** true when the module at `url` is the script node was started with, false when it was imported
26
+ * @param {string} url */
27
+ export function isMainModule(url) {
28
+ const self = fileURLToPath(url);
29
+ const arg = process.argv[1];
30
+ if (!arg || bundled(self)) return false;
31
+ try {
32
+ return realpathSync(arg) === realpathSync(self);
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+
38
+ /** the launchd plist fragment that carries OMNI_HOME to an agent, or nothing when the data root is
39
+ * the code root @param {(s: string) => string} esc */
40
+ export const homeEnvPlist = esc =>
41
+ DATA_ROOT === CODE_ROOT ? "" : `<key>OMNI_HOME</key><string>${esc(DATA_ROOT)}</string>`;