@crievo/omni 0.0.0-stage → 0.1.1

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 (708) hide show
  1. package/.env.example +41 -0
  2. package/README.md +2 -2
  3. package/bin/omni.mjs +18 -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.tsx +59 -0
  247. package/studio/web/hooks/useAccounts.ts +27 -0
  248. package/studio/web/hooks/useAdmin.ts +12 -0
  249. package/studio/web/hooks/useAnalytics.ts +21 -0
  250. package/studio/web/hooks/useAvatarAudioReactive.ts +87 -0
  251. package/studio/web/hooks/useBrowserConnect.ts +48 -0
  252. package/studio/web/hooks/useClonePlan.ts +14 -0
  253. package/studio/web/hooks/useConnected.ts +7 -0
  254. package/studio/web/hooks/useLiveQuery.ts +29 -0
  255. package/studio/web/hooks/useMenuCollapsed.ts +32 -0
  256. package/studio/web/hooks/useMicDevices.ts +53 -0
  257. package/studio/web/hooks/useMissingDms.ts +211 -0
  258. package/studio/web/hooks/useMissingSetupSteps.ts +26 -0
  259. package/studio/web/hooks/useNow.ts +11 -0
  260. package/studio/web/hooks/useOmniAgent.ts +278 -0
  261. package/studio/web/hooks/useOmniClaude.ts +124 -0
  262. package/studio/web/hooks/useOmniDock.ts +142 -0
  263. package/studio/web/hooks/useOmniGreeting.ts +60 -0
  264. package/studio/web/hooks/useOmniHotword.ts +128 -0
  265. package/studio/web/hooks/useOmnichat.ts +26 -0
  266. package/studio/web/hooks/useOnboarding.ts +27 -0
  267. package/studio/web/hooks/usePost.ts +23 -0
  268. package/studio/web/hooks/useQueue.ts +10 -0
  269. package/studio/web/hooks/useReferences.ts +20 -0
  270. package/studio/web/hooks/useRuleEditor.ts +101 -0
  271. package/studio/web/hooks/useRunner.ts +24 -0
  272. package/studio/web/hooks/useSaveAction.ts +23 -0
  273. package/studio/web/hooks/useSchedule.ts +11 -0
  274. package/studio/web/hooks/useServices.ts +10 -0
  275. package/studio/web/hooks/useSessions.ts +15 -0
  276. package/studio/web/hooks/useSettings.ts +11 -0
  277. package/studio/web/hooks/useSettingsSection.ts +30 -0
  278. package/studio/web/hooks/useSidePanel.ts +23 -0
  279. package/studio/web/hooks/useSound.ts +30 -0
  280. package/studio/web/hooks/useUsage.ts +11 -0
  281. package/studio/web/hooks/useVideoVoice.ts +36 -0
  282. package/studio/web/hooks/useVideos.ts +21 -0
  283. package/studio/web/hooks/useVoiceProfilePreview.ts +52 -0
  284. package/studio/web/hooks/useVoiceProfiles.ts +7 -0
  285. package/studio/web/hooks/useVoiceRunning.ts +10 -0
  286. package/studio/web/hooks/useVoiceStudioClosed.ts +8 -0
  287. package/studio/web/index.html +19 -0
  288. package/studio/web/lib/agent.ts +7 -0
  289. package/studio/web/lib/alerts.ts +210 -0
  290. package/studio/web/lib/analytics-metrics.ts +351 -0
  291. package/studio/web/lib/api.ts +70 -0
  292. package/studio/web/lib/audio.ts +39 -0
  293. package/studio/web/lib/avatars.ts +41 -0
  294. package/studio/web/lib/calendar.ts +218 -0
  295. package/studio/web/lib/caption-position.ts +249 -0
  296. package/studio/web/lib/carousel.ts +24 -0
  297. package/studio/web/lib/chime.ts +42 -0
  298. package/studio/web/lib/clone-voice.ts +55 -0
  299. package/studio/web/lib/export.ts +11 -0
  300. package/studio/web/lib/files.ts +8 -0
  301. package/studio/web/lib/format.ts +93 -0
  302. package/studio/web/lib/media.ts +29 -0
  303. package/studio/web/lib/mic-device.ts +72 -0
  304. package/studio/web/lib/missing-dms.ts +70 -0
  305. package/studio/web/lib/omni-cards.ts +42 -0
  306. package/studio/web/lib/omni-cascade.ts +1067 -0
  307. package/studio/web/lib/omni-greeting-audio.ts +24 -0
  308. package/studio/web/lib/omni-hotword.ts +231 -0
  309. package/studio/web/lib/omni-panel.ts +67 -0
  310. package/studio/web/lib/omni-realtime.ts +811 -0
  311. package/studio/web/lib/omni-ui.ts +402 -0
  312. package/studio/web/lib/omni-wait.ts +174 -0
  313. package/studio/web/lib/omnichat-api.ts +44 -0
  314. package/studio/web/lib/orb.ts +270 -0
  315. package/studio/web/lib/pcm-recorder.ts +115 -0
  316. package/studio/web/lib/platforms.ts +17 -0
  317. package/studio/web/lib/providers.ts +27 -0
  318. package/studio/web/lib/publish-draft.ts +52 -0
  319. package/studio/web/lib/publish-settings.ts +81 -0
  320. package/studio/web/lib/query-client.ts +122 -0
  321. package/studio/web/lib/references.ts +13 -0
  322. package/studio/web/lib/services.ts +40 -0
  323. package/studio/web/lib/sessions.ts +103 -0
  324. package/studio/web/lib/settings-categories.ts +58 -0
  325. package/studio/web/lib/sound-audio.ts +33 -0
  326. package/studio/web/lib/sound-timeline.ts +291 -0
  327. package/studio/web/lib/sse.ts +86 -0
  328. package/studio/web/lib/stage-history.ts +141 -0
  329. package/studio/web/lib/stages.ts +344 -0
  330. package/studio/web/lib/studio-offline.ts +60 -0
  331. package/studio/web/lib/upload.ts +24 -0
  332. package/studio/web/lib/video-tabs.ts +118 -0
  333. package/studio/web/lib/voice-profile-draft.ts +34 -0
  334. package/studio/web/lib/voice-profiles.ts +3 -0
  335. package/studio/web/main.tsx +14 -0
  336. package/studio/web/pages/AdminPage/index.tsx +123 -0
  337. package/studio/web/pages/AgentPage/components/Measurements.tsx +102 -0
  338. package/studio/web/pages/AgentPage/index.tsx +28 -0
  339. package/studio/web/pages/AnalyticsPage/components/About.tsx +52 -0
  340. package/studio/web/pages/AnalyticsPage/components/ChannelCards.tsx +106 -0
  341. package/studio/web/pages/AnalyticsPage/components/ChannelView.tsx +137 -0
  342. package/studio/web/pages/AnalyticsPage/components/CollectionStatus.tsx +46 -0
  343. package/studio/web/pages/AnalyticsPage/components/Comparison.tsx +68 -0
  344. package/studio/web/pages/AnalyticsPage/components/Diagnosis.tsx +50 -0
  345. package/studio/web/pages/AnalyticsPage/components/Highlights.tsx +49 -0
  346. package/studio/web/pages/AnalyticsPage/components/Hint.tsx +11 -0
  347. package/studio/web/pages/AnalyticsPage/components/HookVsBody.tsx +100 -0
  348. package/studio/web/pages/AnalyticsPage/components/HourlyWave.tsx +133 -0
  349. package/studio/web/pages/AnalyticsPage/components/Insights.tsx +70 -0
  350. package/studio/web/pages/AnalyticsPage/components/MetricCard.tsx +46 -0
  351. package/studio/web/pages/AnalyticsPage/components/PlanSection.tsx +40 -0
  352. package/studio/web/pages/AnalyticsPage/components/ProgressionChart.tsx +155 -0
  353. package/studio/web/pages/AnalyticsPage/components/ReelBarsChart.tsx +154 -0
  354. package/studio/web/pages/AnalyticsPage/components/ReelThumb.tsx +19 -0
  355. package/studio/web/pages/AnalyticsPage/components/ReelView.tsx +290 -0
  356. package/studio/web/pages/AnalyticsPage/components/ReelsTable.tsx +269 -0
  357. package/studio/web/pages/AnalyticsPage/components/RequestWithFocus.tsx +38 -0
  358. package/studio/web/pages/AnalyticsPage/components/SegmentedControl.tsx +35 -0
  359. package/studio/web/pages/AnalyticsPage/components/VsMedian.tsx +31 -0
  360. package/studio/web/pages/AnalyticsPage/index.tsx +17 -0
  361. package/studio/web/pages/GettingStartedPage/index.tsx +174 -0
  362. package/studio/web/pages/NewVideoPage/components/DeliverablesPicker.tsx +62 -0
  363. package/studio/web/pages/NewVideoPage/components/FormatPicker.tsx +33 -0
  364. package/studio/web/pages/NewVideoPage/index.tsx +133 -0
  365. package/studio/web/pages/NotFoundPage/index.tsx +9 -0
  366. package/studio/web/pages/OmnichatPage/components/NewFromLink.tsx +59 -0
  367. package/studio/web/pages/OmnichatPage/components/RuleListItem.tsx +64 -0
  368. package/studio/web/pages/OmnichatPage/components/RulePreview.tsx +14 -0
  369. package/studio/web/pages/OmnichatPage/components/RuleView.tsx +88 -0
  370. package/studio/web/pages/OmnichatPage/index.tsx +153 -0
  371. package/studio/web/pages/QueuePage/index.tsx +22 -0
  372. package/studio/web/pages/ReferencePage/components/ReferenceTexts.tsx +131 -0
  373. package/studio/web/pages/ReferencePage/index.tsx +216 -0
  374. package/studio/web/pages/ReferencesPage/components/NewReferenceForm.tsx +150 -0
  375. package/studio/web/pages/ReferencesPage/components/ReferenceCard.tsx +42 -0
  376. package/studio/web/pages/ReferencesPage/components/ReferenceGridSkeleton.tsx +31 -0
  377. package/studio/web/pages/ReferencesPage/index.tsx +67 -0
  378. package/studio/web/pages/SchedulePage/components/CalendarSkeleton.tsx +18 -0
  379. package/studio/web/pages/SchedulePage/components/DayView.tsx +177 -0
  380. package/studio/web/pages/SchedulePage/components/HourPicker.tsx +103 -0
  381. package/studio/web/pages/SchedulePage/components/MonthDayCell.tsx +111 -0
  382. package/studio/web/pages/SchedulePage/components/MonthView.tsx +47 -0
  383. package/studio/web/pages/SchedulePage/components/PlatformIcons.tsx +12 -0
  384. package/studio/web/pages/SchedulePage/components/PostThumb.tsx +15 -0
  385. package/studio/web/pages/SchedulePage/components/WeekColumn.tsx +75 -0
  386. package/studio/web/pages/SchedulePage/components/WeekView.tsx +35 -0
  387. package/studio/web/pages/SchedulePage/index.tsx +285 -0
  388. package/studio/web/pages/SettingsPage/components/AgentAccounts.tsx +263 -0
  389. package/studio/web/pages/SettingsPage/components/AgentCategory.tsx +20 -0
  390. package/studio/web/pages/SettingsPage/components/ModelPicker.tsx +49 -0
  391. package/studio/web/pages/SettingsPage/components/ModelsSettings.tsx +138 -0
  392. package/studio/web/pages/SettingsPage/components/PlatformsCategory.tsx +23 -0
  393. package/studio/web/pages/SettingsPage/components/PublishingCategory/AccountStatus.tsx +29 -0
  394. package/studio/web/pages/SettingsPage/components/PublishingCategory/AutoPublishPanel.tsx +90 -0
  395. package/studio/web/pages/SettingsPage/components/PublishingCategory/BrowserConnect.tsx +62 -0
  396. package/studio/web/pages/SettingsPage/components/PublishingCategory/CheckField.tsx +19 -0
  397. package/studio/web/pages/SettingsPage/components/PublishingCategory/InstagramPanel.tsx +113 -0
  398. package/studio/web/pages/SettingsPage/components/PublishingCategory/OmnichatSettingsPanel.tsx +146 -0
  399. package/studio/web/pages/SettingsPage/components/PublishingCategory/SaveBar.tsx +33 -0
  400. package/studio/web/pages/SettingsPage/components/PublishingCategory/SchedulePanel.tsx +132 -0
  401. package/studio/web/pages/SettingsPage/components/PublishingCategory/SettingsCard.tsx +47 -0
  402. package/studio/web/pages/SettingsPage/components/PublishingCategory/SettingsCardGroup.tsx +19 -0
  403. package/studio/web/pages/SettingsPage/components/PublishingCategory/TextPanel.tsx +55 -0
  404. package/studio/web/pages/SettingsPage/components/PublishingCategory/TiktokPanel.tsx +123 -0
  405. package/studio/web/pages/SettingsPage/components/PublishingCategory/YoutubePanel.tsx +100 -0
  406. package/studio/web/pages/SettingsPage/components/PublishingCategory/badges.tsx +15 -0
  407. package/studio/web/pages/SettingsPage/components/PublishingCategory/index.tsx +40 -0
  408. package/studio/web/pages/SettingsPage/components/PublishingCategory/types.ts +10 -0
  409. package/studio/web/pages/SettingsPage/components/UsageCategory.tsx +389 -0
  410. package/studio/web/pages/SettingsPage/components/VoiceProvidersCategory.tsx +97 -0
  411. package/studio/web/pages/SettingsPage/index.tsx +85 -0
  412. package/studio/web/pages/VideoPage/components/AudioPlayer.tsx +113 -0
  413. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/AxisField.tsx +50 -0
  414. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionList.tsx +56 -0
  415. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionListItem.tsx +92 -0
  416. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionMargins.tsx +57 -0
  417. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionStyleFields.tsx +70 -0
  418. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionTextForm.tsx +87 -0
  419. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/ColorField.tsx +37 -0
  420. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/MarginField.tsx +59 -0
  421. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/NumberField.tsx +57 -0
  422. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/PositionControls.tsx +140 -0
  423. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/captionEditor.ts +215 -0
  424. package/studio/web/pages/VideoPage/components/CaptionPositionEditor/useCaptionPosition.ts +154 -0
  425. package/studio/web/pages/VideoPage/components/CarouselChangeRequest.tsx +72 -0
  426. package/studio/web/pages/VideoPage/components/CarouselCompositionStage.tsx +80 -0
  427. package/studio/web/pages/VideoPage/components/CarouselCreate.tsx +63 -0
  428. package/studio/web/pages/VideoPage/components/CarouselMissing.tsx +19 -0
  429. package/studio/web/pages/VideoPage/components/CarouselPublishStage.tsx +173 -0
  430. package/studio/web/pages/VideoPage/components/CarouselScriptStage.tsx +82 -0
  431. package/studio/web/pages/VideoPage/components/CarouselSlideCard.tsx +49 -0
  432. package/studio/web/pages/VideoPage/components/CarouselViewer.tsx +55 -0
  433. package/studio/web/pages/VideoPage/components/ChangedVoices.tsx +53 -0
  434. package/studio/web/pages/VideoPage/components/CompositionStage.tsx +78 -0
  435. package/studio/web/pages/VideoPage/components/CoverPicker.tsx +32 -0
  436. package/studio/web/pages/VideoPage/components/EditorStage.tsx +53 -0
  437. package/studio/web/pages/VideoPage/components/LineTakes.tsx +160 -0
  438. package/studio/web/pages/VideoPage/components/LineTextEditor.tsx +124 -0
  439. package/studio/web/pages/VideoPage/components/LineVoicePicker.tsx +37 -0
  440. package/studio/web/pages/VideoPage/components/MediaImage.tsx +25 -0
  441. package/studio/web/pages/VideoPage/components/MotionScriptStage.tsx +39 -0
  442. package/studio/web/pages/VideoPage/components/NarrationStage.tsx +70 -0
  443. package/studio/web/pages/VideoPage/components/PublishForm.tsx +387 -0
  444. package/studio/web/pages/VideoPage/components/PublishStage.tsx +76 -0
  445. package/studio/web/pages/VideoPage/components/PublishText.tsx +149 -0
  446. package/studio/web/pages/VideoPage/components/ScenesApprovalPanel.tsx +278 -0
  447. package/studio/web/pages/VideoPage/components/ScheduledInfo.tsx +72 -0
  448. package/studio/web/pages/VideoPage/components/ScriptApprovalPanel.tsx +189 -0
  449. package/studio/web/pages/VideoPage/components/ScriptStage.tsx +17 -0
  450. package/studio/web/pages/VideoPage/components/SequencePlayer.tsx +146 -0
  451. package/studio/web/pages/VideoPage/components/SoundTimeline/AddSound.tsx +117 -0
  452. package/studio/web/pages/VideoPage/components/SoundTimeline/AreaSelection.tsx +74 -0
  453. package/studio/web/pages/VideoPage/components/SoundTimeline/BedPanel.tsx +94 -0
  454. package/studio/web/pages/VideoPage/components/SoundTimeline/BedRow.tsx +45 -0
  455. package/studio/web/pages/VideoPage/components/SoundTimeline/CaptionOverlay.tsx +44 -0
  456. package/studio/web/pages/VideoPage/components/SoundTimeline/CaptionSaveBar.tsx +32 -0
  457. package/studio/web/pages/VideoPage/components/SoundTimeline/CaptionsPanel.tsx +22 -0
  458. package/studio/web/pages/VideoPage/components/SoundTimeline/ClipBlock.tsx +111 -0
  459. package/studio/web/pages/VideoPage/components/SoundTimeline/ClipPanel.tsx +76 -0
  460. package/studio/web/pages/VideoPage/components/SoundTimeline/CutsPanel.tsx +44 -0
  461. package/studio/web/pages/VideoPage/components/SoundTimeline/EditorTabBar.tsx +73 -0
  462. package/studio/web/pages/VideoPage/components/SoundTimeline/EffectChoice.tsx +46 -0
  463. package/studio/web/pages/VideoPage/components/SoundTimeline/EffectItem.tsx +89 -0
  464. package/studio/web/pages/VideoPage/components/SoundTimeline/EffectPanel.tsx +232 -0
  465. package/studio/web/pages/VideoPage/components/SoundTimeline/EffectsRow.tsx +21 -0
  466. package/studio/web/pages/VideoPage/components/SoundTimeline/Lane.tsx +39 -0
  467. package/studio/web/pages/VideoPage/components/SoundTimeline/MarkerRow.tsx +40 -0
  468. package/studio/web/pages/VideoPage/components/SoundTimeline/MuteButton.tsx +36 -0
  469. package/studio/web/pages/VideoPage/components/SoundTimeline/NumberInput.tsx +37 -0
  470. package/studio/web/pages/VideoPage/components/SoundTimeline/PlanWarnings.tsx +19 -0
  471. package/studio/web/pages/VideoPage/components/SoundTimeline/PlayerResizeHandle.tsx +50 -0
  472. package/studio/web/pages/VideoPage/components/SoundTimeline/PreviewControls.tsx +51 -0
  473. package/studio/web/pages/VideoPage/components/SoundTimeline/SceneRow.tsx +54 -0
  474. package/studio/web/pages/VideoPage/components/SoundTimeline/SettingsPanel.tsx +65 -0
  475. package/studio/web/pages/VideoPage/components/SoundTimeline/SoundLibrary.tsx +99 -0
  476. package/studio/web/pages/VideoPage/components/SoundTimeline/SoundPreview.tsx +82 -0
  477. package/studio/web/pages/VideoPage/components/SoundTimeline/SpeedPanel.tsx +128 -0
  478. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineDock.tsx +56 -0
  479. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineFooter.tsx +26 -0
  480. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineSkeleton.tsx +21 -0
  481. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineToolbar.tsx +160 -0
  482. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineTracks.tsx +121 -0
  483. package/studio/web/pages/VideoPage/components/SoundTimeline/TrackSidebar.tsx +30 -0
  484. package/studio/web/pages/VideoPage/components/SoundTimeline/VoiceRow.tsx +53 -0
  485. package/studio/web/pages/VideoPage/components/SoundTimeline/Waveform.tsx +97 -0
  486. package/studio/web/pages/VideoPage/components/SoundTimeline/clipActions.ts +50 -0
  487. package/studio/web/pages/VideoPage/components/SoundTimeline/effectActions.ts +161 -0
  488. package/studio/web/pages/VideoPage/components/SoundTimeline/index.tsx +161 -0
  489. package/studio/web/pages/VideoPage/components/SoundTimeline/timelineGeometry.ts +45 -0
  490. package/studio/web/pages/VideoPage/components/SoundTimeline/types.ts +43 -0
  491. package/studio/web/pages/VideoPage/components/SoundTimeline/useAutosave.ts +107 -0
  492. package/studio/web/pages/VideoPage/components/SoundTimeline/useEditorShortcuts.ts +168 -0
  493. package/studio/web/pages/VideoPage/components/SoundTimeline/useFitHeight.ts +40 -0
  494. package/studio/web/pages/VideoPage/components/SoundTimeline/useFollowPlayhead.ts +21 -0
  495. package/studio/web/pages/VideoPage/components/SoundTimeline/useListenMix.ts +43 -0
  496. package/studio/web/pages/VideoPage/components/SoundTimeline/usePlayback.ts +231 -0
  497. package/studio/web/pages/VideoPage/components/SoundTimeline/usePlayerWidth.ts +27 -0
  498. package/studio/web/pages/VideoPage/components/SoundTimeline/useSoundData.ts +124 -0
  499. package/studio/web/pages/VideoPage/components/SoundTimeline/useSoundTimeline.ts +511 -0
  500. package/studio/web/pages/VideoPage/components/StageDrawer/CompareVersions.tsx +111 -0
  501. package/studio/web/pages/VideoPage/components/StageDrawer/VersionCard.tsx +81 -0
  502. package/studio/web/pages/VideoPage/components/StageDrawer/VersionMedia.tsx +24 -0
  503. package/studio/web/pages/VideoPage/components/StageDrawer/VersionText.tsx +15 -0
  504. package/studio/web/pages/VideoPage/components/StageDrawer/index.tsx +109 -0
  505. package/studio/web/pages/VideoPage/components/StageProcess.tsx +126 -0
  506. package/studio/web/pages/VideoPage/components/StageResult.tsx +69 -0
  507. package/studio/web/pages/VideoPage/components/SwapReference.tsx +92 -0
  508. package/studio/web/pages/VideoPage/components/ThumbnailPanel.tsx +252 -0
  509. package/studio/web/pages/VideoPage/components/TimingPanel.tsx +89 -0
  510. package/studio/web/pages/VideoPage/components/VideoPlayer.tsx +18 -0
  511. package/studio/web/pages/VideoPage/components/VideoSkeleton.tsx +50 -0
  512. package/studio/web/pages/VideoPage/components/VideoVoiceInfo.tsx +33 -0
  513. package/studio/web/pages/VideoPage/components/VideoVoicePicker.tsx +165 -0
  514. package/studio/web/pages/VideoPage/components/VoicePauseBanner.tsx +56 -0
  515. package/studio/web/pages/VideoPage/components/WithLogo.tsx +10 -0
  516. package/studio/web/pages/VideoPage/index.tsx +295 -0
  517. package/studio/web/pages/VideosPage/components/PublishedOn.tsx +85 -0
  518. package/studio/web/pages/VideosPage/components/ServicesPanel.tsx +81 -0
  519. package/studio/web/pages/VideosPage/components/SessionsPanel.tsx +62 -0
  520. package/studio/web/pages/VideosPage/components/VideoListSkeleton.tsx +25 -0
  521. package/studio/web/pages/VideosPage/components/VideoRow.tsx +122 -0
  522. package/studio/web/pages/VideosPage/index.tsx +134 -0
  523. package/studio/web/pages/VoicePage/index.tsx +28 -0
  524. package/studio/web/router.tsx +201 -0
  525. package/studio/web/styles/base/elements.css +55 -0
  526. package/studio/web/styles/base/utilities.css +25 -0
  527. package/studio/web/styles/components/badge.css +33 -0
  528. package/studio/web/styles/components/claude-running.css +27 -0
  529. package/studio/web/styles/components/claude-session.css +212 -0
  530. package/studio/web/styles/components/claude-walking.css +92 -0
  531. package/studio/web/styles/components/clone-voice-save.css +136 -0
  532. package/studio/web/styles/components/clone-voice.css +379 -0
  533. package/studio/web/styles/components/controls.css +70 -0
  534. package/studio/web/styles/components/data-and-image.css +54 -0
  535. package/studio/web/styles/components/forms.css +132 -0
  536. package/studio/web/styles/components/human-banner.css +99 -0
  537. package/studio/web/styles/components/lightbox.css +50 -0
  538. package/studio/web/styles/components/markdown.css +68 -0
  539. package/studio/web/styles/components/modal.css +70 -0
  540. package/studio/web/styles/components/omni-cards.css +105 -0
  541. package/studio/web/styles/components/omni.css +859 -0
  542. package/studio/web/styles/components/omnichat.css +538 -0
  543. package/studio/web/styles/components/panel.css +115 -0
  544. package/studio/web/styles/components/platform-card.css +140 -0
  545. package/studio/web/styles/components/provider-card.css +132 -0
  546. package/studio/web/styles/components/queue.css +185 -0
  547. package/studio/web/styles/components/reference-actions.css +133 -0
  548. package/studio/web/styles/components/reference-picker.css +146 -0
  549. package/studio/web/styles/components/results-drawer.css +224 -0
  550. package/studio/web/styles/components/segmented.css +115 -0
  551. package/studio/web/styles/components/side-panel-tab.css +23 -0
  552. package/studio/web/styles/components/status-signals.css +66 -0
  553. package/studio/web/styles/components/stepper.css +208 -0
  554. package/studio/web/styles/components/table.css +19 -0
  555. package/studio/web/styles/components/tabs.css +24 -0
  556. package/studio/web/styles/components/ui/accordion.css +71 -0
  557. package/studio/web/styles/components/ui/chart.css +162 -0
  558. package/studio/web/styles/components/ui/checkbox.css +37 -0
  559. package/studio/web/styles/components/ui/date-picker.css +153 -0
  560. package/studio/web/styles/components/ui/select.css +110 -0
  561. package/studio/web/styles/components/ui/skeleton.css +25 -0
  562. package/studio/web/styles/components/ui/slider.css +81 -0
  563. package/studio/web/styles/components/ui/switch.css +41 -0
  564. package/studio/web/styles/components/ui/tooltip.css +24 -0
  565. package/studio/web/styles/components/voice-carousel.css +255 -0
  566. package/studio/web/styles/components/voice-editor.css +179 -0
  567. package/studio/web/styles/components/voice-grid.css +56 -0
  568. package/studio/web/styles/components/voice-profiles.css +192 -0
  569. package/studio/web/styles/design-system.md +136 -0
  570. package/studio/web/styles/index.css +116 -0
  571. package/studio/web/styles/layout/page.css +41 -0
  572. package/studio/web/styles/layout/sidebar-groups.css +39 -0
  573. package/studio/web/styles/layout/sidebar.css +111 -0
  574. package/studio/web/styles/pages/agent.css +26 -0
  575. package/studio/web/styles/pages/analytics/dashboard.css +614 -0
  576. package/studio/web/styles/pages/analytics/insights.css +48 -0
  577. package/studio/web/styles/pages/getting-started.css +166 -0
  578. package/studio/web/styles/pages/new-video/form.css +32 -0
  579. package/studio/web/styles/pages/not-found.css +59 -0
  580. package/studio/web/styles/pages/references.css +204 -0
  581. package/studio/web/styles/pages/schedule/calendar.css +541 -0
  582. package/studio/web/styles/pages/schedule/chip-states.css +60 -0
  583. package/studio/web/styles/pages/settings/models.css +72 -0
  584. package/studio/web/styles/pages/settings/shell.css +361 -0
  585. package/studio/web/styles/pages/settings/usage.css +63 -0
  586. package/studio/web/styles/pages/studio-offline.css +105 -0
  587. package/studio/web/styles/pages/video/caption-controls.css +194 -0
  588. package/studio/web/styles/pages/video/captions-grid.css +70 -0
  589. package/studio/web/styles/pages/video/captions.css +79 -0
  590. package/studio/web/styles/pages/video/editor-chat.css +103 -0
  591. package/studio/web/styles/pages/video/export.css +50 -0
  592. package/studio/web/styles/pages/video/frames.css +29 -0
  593. package/studio/web/styles/pages/video/fullscreen-stage.css +400 -0
  594. package/studio/web/styles/pages/video/instagram-carousel.css +309 -0
  595. package/studio/web/styles/pages/video/layout.css +285 -0
  596. package/studio/web/styles/pages/video/narration.css +138 -0
  597. package/studio/web/styles/pages/video/preview-overview.css +11 -0
  598. package/studio/web/styles/pages/video/processing-notice.css +34 -0
  599. package/studio/web/styles/pages/video/publish-when.css +76 -0
  600. package/studio/web/styles/pages/video/publish.css +97 -0
  601. package/studio/web/styles/pages/video/result.css +96 -0
  602. package/studio/web/styles/pages/video/scenes.css +167 -0
  603. package/studio/web/styles/pages/video/sound-timeline.css +672 -0
  604. package/studio/web/styles/pages/video/sound-toolbar.css +149 -0
  605. package/studio/web/styles/pages/video/take-player.css +100 -0
  606. package/studio/web/styles/pages/video/thumbnail.css +157 -0
  607. package/studio/web/styles/pages/video/timeline.css +117 -0
  608. package/studio/web/styles/pages/video/voice-picker.css +14 -0
  609. package/studio/web/styles/pages/video-list/active-row.css +27 -0
  610. package/studio/web/styles/pages/video-list/card-link.css +49 -0
  611. package/studio/web/styles/pages/video-list/list.css +86 -0
  612. package/studio/web/styles/theme/identity/details.css +204 -0
  613. package/studio/web/styles/theme/identity/shell.css +249 -0
  614. package/studio/web/styles/theme/identity/surfaces.css +183 -0
  615. package/studio/web/styles/theme/identity/titles-and-buttons.css +120 -0
  616. package/studio/web/styles/theme/no-borders/blocks.css +208 -0
  617. package/studio/web/styles/theme/no-borders/controls.css +256 -0
  618. package/studio/web/styles/tokens.css +245 -0
  619. package/studio/web/types/api.ts +2 -0
  620. package/studio/web/types/assets.d.ts +9 -0
  621. package/templates/brief.md +27 -0
  622. package/templates/captions.html +204 -0
  623. package/templates/carousel-composition.md +47 -0
  624. package/templates/carousel-script.md +60 -0
  625. package/templates/components/voiceavatar/README.md +87 -0
  626. package/templates/components/voiceavatar/demo.html +50 -0
  627. package/templates/components/voiceavatar/voiceavatar.js +415 -0
  628. package/templates/motion.md +80 -0
  629. package/templates/playbook.md +43 -0
  630. package/templates/reference.md +123 -0
  631. package/templates/scene.html +112 -0
  632. package/templates/scenes.example.json +96 -0
  633. package/templates/script.md +63 -0
  634. package/templates/slide.html +71 -0
  635. package/templates/wireframe.html +91 -0
  636. package/tools/analysis.mjs +300 -0
  637. package/tools/approval.mjs +442 -0
  638. package/tools/assemble.mjs +942 -0
  639. package/tools/build-motion.mjs +437 -0
  640. package/tools/captions.mjs +246 -0
  641. package/tools/carousel.mjs +558 -0
  642. package/tools/compose.mjs +307 -0
  643. package/tools/cuts.mjs +74 -0
  644. package/tools/editor.mjs +419 -0
  645. package/tools/epidemic-capture.js +54 -0
  646. package/tools/fanout.mjs +69 -0
  647. package/tools/history.mjs +70 -0
  648. package/tools/history.py +61 -0
  649. package/tools/job-queue.mjs +180 -0
  650. package/tools/job_queue.py +430 -0
  651. package/tools/lib/agents.mjs +412 -0
  652. package/tools/lib/channel.mjs +399 -0
  653. package/tools/lib/claude-settings.mjs +67 -0
  654. package/tools/lib/cli.mjs +9 -0
  655. package/tools/lib/fanout.mjs +234 -0
  656. package/tools/lib/http.mjs +94 -0
  657. package/tools/lib/index.js +14 -0
  658. package/tools/lib/mcp-server.mjs +69 -0
  659. package/tools/lib/metrics/metrics.mjs +204 -0
  660. package/tools/lib/narration.mjs +15 -0
  661. package/tools/lib/oauth-client.mjs +295 -0
  662. package/tools/lib/omnichat/config.mjs +139 -0
  663. package/tools/lib/omnichat/errors.mjs +42 -0
  664. package/tools/lib/omnichat/extract.mjs +82 -0
  665. package/tools/lib/omnichat/gate.mjs +151 -0
  666. package/tools/lib/omnichat/manual.mjs +166 -0
  667. package/tools/lib/omnichat/match.mjs +78 -0
  668. package/tools/lib/omnichat/pass.mjs +393 -0
  669. package/tools/lib/omnichat/record.mjs +208 -0
  670. package/tools/lib/omnichat/text.mjs +315 -0
  671. package/tools/lib/omnichat/webhook.mjs +166 -0
  672. package/tools/lib/pairing.mjs +99 -0
  673. package/tools/lib/paths.mjs +41 -0
  674. package/tools/lib/platforms/instagram.mjs +469 -0
  675. package/tools/lib/platforms/tiktok.mjs +343 -0
  676. package/tools/lib/platforms/youtube.mjs +228 -0
  677. package/tools/lib/publish/publish.mjs +244 -0
  678. package/tools/lib/schedule/schedule.mjs +191 -0
  679. package/tools/lib/service.mjs +134 -0
  680. package/tools/lib/skill-client.mjs +126 -0
  681. package/tools/lib/skills-release.mjs +196 -0
  682. package/tools/lib/testing.mjs +52 -0
  683. package/tools/lib/up.mjs +187 -0
  684. package/tools/metrics.mjs +415 -0
  685. package/tools/models.mjs +134 -0
  686. package/tools/narrate.mjs +257 -0
  687. package/tools/omni.mjs +298 -0
  688. package/tools/omni_paths.py +12 -0
  689. package/tools/omnichat.mjs +1577 -0
  690. package/tools/omnivideo.py +1122 -0
  691. package/tools/package.mjs +130 -0
  692. package/tools/platforms.mjs +236 -0
  693. package/tools/premiere.mjs +533 -0
  694. package/tools/publish.mjs +1496 -0
  695. package/tools/reference.mjs +1408 -0
  696. package/tools/render.mjs +426 -0
  697. package/tools/runner.mjs +156 -0
  698. package/tools/setup.mjs +466 -0
  699. package/tools/skills-release.mjs +93 -0
  700. package/tools/sound.mjs +1323 -0
  701. package/tools/soundtrack.mjs +217 -0
  702. package/tools/thumbnail.mjs +271 -0
  703. package/tools/timing.mjs +108 -0
  704. package/tools/usage.py +628 -0
  705. package/tools/voice-envelope.mjs +185 -0
  706. package/tools/voice.py +448 -0
  707. package/tools/voice_sample.py +75 -0
  708. package/tools/wireframe.mjs +230 -0
@@ -0,0 +1,324 @@
1
+ /* Readings of a line from the UI: generate another (`voice --regenerate`) and choose
2
+ which one counts for the video (`voice --use`). omnivideo.py writes them, the same
3
+ the chat would call; the reading that leaves goes to history/voice/<line>/.
4
+
5
+ Generating waits for its turn in the queue (may take a while if the machine is busy): the
6
+ process runs on its own and the UI follows it through the queue. Only errors before the
7
+ queue (missing line, no provider) come back in the response.
8
+
9
+ The line text is also edited here (`text` in scenes.json). A line that already has
10
+ a voice gets the new reading right away: text and audio never diverge.
11
+ Text that removes a beat's word becomes a request to the video's Claude, which changes
12
+ the line and the beat together.
13
+
14
+ Removing takes the line out of the narration (a sentence split in two that the user wants
15
+ as one) and asks the video's Claude for the beats that depended on it and what comes
16
+ after timing.
17
+
18
+ Every voice change pauses what comes after it (voice-pause.ts) until the user
19
+ clicks "Seguir com estas vozes" (`proceedWithVoices`). */
20
+
21
+ import { writeFileSync, renameSync, rmSync } from "node:fs";
22
+ import { join } from "node:path";
23
+ import { PYTHON, ROOT, TOOLS_DIR, run, readJson } from "./root";
24
+ import { getQueue } from "./queue";
25
+ import { chatWith } from "./create";
26
+ import { latestSessionFor } from "./sessions";
27
+ import { pauseAfterVoice, interruptAfterVoice, pauseNotice, readPause, PAUSE_FILE } from "./voice-pause";
28
+ import { keepPrevious } from "../../../tools/history.mjs";
29
+
30
+ type Result = { ok: true; output: string } | { ok: false; error: string };
31
+
32
+ const OMNIVIDEO = join(TOOLS_DIR, "omnivideo.py");
33
+ const LINE_ID = /^c\d+f\d+$/;
34
+ const VERSION = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z$/;
35
+
36
+ function lineExists(dir: string, line: string) {
37
+ const scenes = readJson<any>(join(dir, "scenes.json"));
38
+ return (scenes?.scenes ?? []).some((c: any) => (c.lines ?? []).some((l: any) => l.id === line));
39
+ }
40
+
41
+ const voiceTicketOf = (dir: string) => {
42
+ const q = getQueue();
43
+ return [...q.running, ...q.waiting].find(s => s.dir === dir && s.tool === "voice");
44
+ };
45
+
46
+ /* `omnivideo.py voice`: exits right away if something is missing before the queue; then runs on its own */
47
+ async function runVoice(dir: string, args: string[], generating: string): Promise<Result> {
48
+ const p = Bun.spawn([PYTHON, OMNIVIDEO, "voice", dir, ...args], {
49
+ cwd: ROOT,
50
+ stdout: "pipe",
51
+ stderr: "pipe",
52
+ env: { ...process.env, OMNIVIDEO_ORIGIN: "studio" },
53
+ });
54
+ const output = Promise.all([new Response(p.stdout).text(), new Response(p.stderr).text()]);
55
+ const early = await Promise.race([p.exited, Bun.sleep(2500).then(() => null)]);
56
+ if (early !== null && early !== 0) {
57
+ const [o, e] = await output;
58
+ return { ok: false, error: (e || o).trim() || `omnivideo.py saiu com ${early}` };
59
+ }
60
+ return { ok: true, output: early === 0 ? (await output)[0].trim() : generating };
61
+ }
62
+
63
+ /* another reading of a single line: `--regenerate` generates that one and no other, even if the
64
+ video's provider changed and the other lines are still from the old one */
65
+ export async function regenerateLine(dir: string, line: string, provider?: string): Promise<Result> {
66
+ if (!LINE_ID.test(line) || !lineExists(dir, line)) return { ok: false, error: `a fala ${line} não existe` };
67
+ /* `provider` is for this line only (omnivideo.py `--provider`): the video's doesn't change */
68
+ if (provider && !/^[a-z]+$/.test(provider)) return { ok: false, error: `provider inválido: ${provider}` };
69
+ const pause = pauseAfterVoice(dir, [line]);
70
+ const r = await runVoice(
71
+ dir,
72
+ ["--regenerate", line, ...(provider ? ["--provider", provider] : [])],
73
+ `${line}: gerando outra fala (acompanhe na etapa Voz ou na fila)`,
74
+ );
75
+ return r.ok ? { ok: true, output: `${r.output}; ${pauseNotice(pause)}` } : r;
76
+ }
77
+
78
+ /* every line not yet in the video's provider (`voice` with no arguments:
79
+ missing audio, changed text or changed requested provider). What is up to date is not redone. */
80
+ export async function generateAllLines(dir: string): Promise<Result> {
81
+ if (voiceTicketOf(dir)) return { ok: false, error: "a voz deste vídeo já está sendo gerada: espere terminar" };
82
+ const pause = pauseAfterVoice(dir, ["all"]);
83
+ const r = await runVoice(dir, [], "gerando as falas com a voz do vídeo (acompanhe na etapa Voz ou na fila)");
84
+ return r.ok ? { ok: true, output: `${r.output}; ${pauseNotice(pause)}` } : r;
85
+ }
86
+
87
+ /* every line again, even the up-to-date ones: that's what a voice (profile) change needs, since the
88
+ provider and the text are the same and `voice` with no arguments sees nothing to redo. Uses the
89
+ video's provider (the main select's); without `--provider`, each line follows the video again (`provider_pinned` drops). */
90
+ export async function regenerateAllLines(dir: string): Promise<Result> {
91
+ if (voiceTicketOf(dir)) return { ok: false, error: "a voz deste vídeo já está sendo gerada: espere terminar" };
92
+ const scenes = readJson<any>(join(dir, "scenes.json"));
93
+ const ids: string[] = (scenes?.scenes ?? []).flatMap((c: any) => (c.lines ?? []).map((l: any) => String(l.id)));
94
+ if (!ids.length) return { ok: false, error: "o vídeo ainda não tem falas" };
95
+ const pause = pauseAfterVoice(dir, ["all"]);
96
+ const r = await runVoice(
97
+ dir,
98
+ ["--regenerate", ids.join(",")],
99
+ `regerando as ${ids.length} falas com a voz do vídeo (acompanhe na etapa Voz ou na fila)`,
100
+ );
101
+ return r.ok ? { ok: true, output: `${r.output}; ${pauseNotice(pause)}` } : r;
102
+ }
103
+
104
+ /* the same word rule as tools/timing.mjs */
105
+ const norm = (w: string) =>
106
+ w
107
+ .toLowerCase()
108
+ .normalize("NFC")
109
+ .replace(/[^\p{L}\p{N}]/gu, "");
110
+ const REF = /^([^:+-]+)(?::([^+\-#]+)(?:#(\d+))?)?([+-]\d+(?:\.\d+)?)?$/;
111
+
112
+ /* the scene's beats that enter on a word of this line the new text no longer has */
113
+ function brokenBeats(scene: any, line: string, text: string) {
114
+ const words = text.split(/\s+/).map(norm);
115
+ return (scene.beats ?? []).flatMap((b: any) => {
116
+ const m = REF.exec(String(b.enters_with ?? "").trim());
117
+ /* `fim`: legacy alias of `end` */
118
+ if (!m || m[1] !== line || !m[2] || m[2] === "end" || m[2] === "fim") return [];
119
+ const times = words.filter(w => w === norm(m[2])).length;
120
+ return times >= Number(m[3] ?? 1) ? [] : [`${b.element} (entra com “${m[2]}”${m[3] ? ` #${m[3]}` : ""})`];
121
+ });
122
+ }
123
+
124
+ /* the new text removes a beat's word: the line and the beat change together, and
125
+ the video session's Claude picks the new word (av-narration rule) */
126
+ async function askAgent(
127
+ dir: string,
128
+ slug: string,
129
+ line: string,
130
+ text: string,
131
+ broken: string[],
132
+ hasVoice: boolean,
133
+ ): Promise<Result> {
134
+ const message = [
135
+ `Troquei pela tela o texto da fala ${line} para, palavra por palavra: «${text}».`,
136
+ `O texto novo não tem mais a palavra em que entra: ${broken.join(", ")}.`,
137
+ `Grave esse texto exato no scenes.json (não reescreva a fala) e, para cada beat desses, escolha no texto novo a palavra que melhor dispara o que ele mostra e atualize o enters_with, pela regra da av-narration.`,
138
+ hasVoice
139
+ ? `A fala já tinha voz: gere a leitura do texto novo com \`${PYTHON} tools/omnivideo.py voice ${dir} --regenerate ${line}\` e pare aí. O vídeo está pausado (voice-pause.json) até eu clicar em "Seguir com estas vozes": não rode tempo, render, legenda nem montagem.`
140
+ : "A fala ainda não tem voz: só texto e beat, sem gerar nada.",
141
+ "No fim, diga em uma linha qual palavra cada beat passou a usar.",
142
+ ].join("\n");
143
+ await waitAgentStopped(slug);
144
+ const r = chatWith("video", slug, message);
145
+ if (!r.ok)
146
+ return {
147
+ ok: false,
148
+ error: `o texto novo tira a palavra em que um beat entra (${broken.join(", ")}), e o agente não pôde ajustar agora: ${r.error}`,
149
+ };
150
+ return {
151
+ ok: true,
152
+ output: `o texto novo tira a palavra em que um beat entra (${broken.join(", ")}): pedi ao agente para gravar o texto e mudar o beat junto. Acompanhe na sessão do vídeo.`,
153
+ };
154
+ }
155
+
156
+ export async function editLineText(dir: string, slug: string, line: string, value: unknown): Promise<Result> {
157
+ if (!LINE_ID.test(line)) return { ok: false, error: `a fala ${line} não existe` };
158
+ const text = typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
159
+ if (!text) return { ok: false, error: "o texto da fala não pode ficar vazio" };
160
+ if (text.length > 600) return { ok: false, error: "texto longo demais para uma fala: divida em duas com o agente" };
161
+ if (voiceTicketOf(dir))
162
+ return { ok: false, error: "a voz deste vídeo está sendo gerada agora: edite quando ela terminar" };
163
+
164
+ const file = join(dir, "scenes.json");
165
+ const scenes = readJson<any>(file);
166
+ const scene = (scenes?.scenes ?? []).find((c: any) => (c.lines ?? []).some((l: any) => l.id === line));
167
+ if (!scene) return { ok: false, error: `a fala ${line} não existe` };
168
+ const l = scene.lines.find((x: any) => x.id === line);
169
+ const hasVoice = !!l.wav;
170
+ if (hasVoice) pauseAfterVoice(dir, [line]);
171
+ if (l.text === text) return hasVoice ? regenerateLine(dir, line) : { ok: true, output: "o texto já era este" };
172
+ const broken = brokenBeats(scene, line, text);
173
+ if (broken.length) return askAgent(dir, slug, line, text, broken, hasVoice);
174
+
175
+ l.text = text;
176
+ const tmp = `${file}.${process.pid}.tmp`;
177
+ writeFileSync(tmp, JSON.stringify(scenes, null, 2) + "\n");
178
+ renameSync(tmp, file);
179
+ const n = text.split(" ").length;
180
+ const warning = n > 22 ? ` (⚠ ${n} palavras: a narração pede até 22 por fala)` : "";
181
+ if (!hasVoice) return { ok: true, output: `texto gravado${warning}` };
182
+ const r = await regenerateLine(dir, line);
183
+ return r.ok
184
+ ? { ok: true, output: `texto gravado${warning}; ${r.output}` }
185
+ : { ok: false, error: `o texto foi gravado, mas a voz não saiu: ${r.error}` };
186
+ }
187
+
188
+ /* beats that enter on a line that no longer exists (removed from the UI) */
189
+ function orphanBeats(scenes: any) {
190
+ const ids = new Set((scenes?.scenes ?? []).flatMap((c: any) => (c.lines ?? []).map((l: any) => l.id)));
191
+ return (scenes?.scenes ?? []).flatMap((c: any) =>
192
+ (c.beats ?? []).flatMap((b: any) => {
193
+ const m = REF.exec(String(b.enters_with ?? "").trim());
194
+ return m && LINE_ID.test(m[1]) && !ids.has(m[1]) ? [`${c.id} ${b.element} (entrava com ${b.enters_with})`] : [];
195
+ }),
196
+ );
197
+ }
198
+
199
+ /* what Claude needs to know about removed lines: don't recreate them and change the beats that depended on them */
200
+ function removedLinesRequest(scenes: any, removed: string[]) {
201
+ const beats = orphanBeats(scenes);
202
+ return [
203
+ `Não recrie, não renumere e não gere voz para ${removed.length > 1 ? "essas falas" : "essa fala"}, nem reescreva as que ficaram.`,
204
+ ...(beats.length
205
+ ? [
206
+ `Estes beats entravam numa fala removida: ${beats.join(", ")}. Para cada um, escolha nas falas que ficaram na cena a palavra que melhor dispara o que ele mostra e atualize só o enters_with, pela regra da av-narration.`,
207
+ ]
208
+ : []),
209
+ ];
210
+ }
211
+
212
+ /* takes a line out of the narration (the sentence the narration split in two and the user wants as one): it leaves
213
+ scenes.json, the reading goes to history/voice/<line>/ and the video's Claude redoes from timing on.
214
+ With the video paused (voices being chosen), the removal joins the pause and reaches Claude through "Seguir". */
215
+ export async function removeLine(dir: string, slug: string, line: string): Promise<Result> {
216
+ if (!LINE_ID.test(line)) return { ok: false, error: `a fala ${line} não existe` };
217
+ if (voiceTicketOf(dir))
218
+ return { ok: false, error: "a voz deste vídeo está sendo gerada agora: remova quando ela terminar" };
219
+
220
+ const file = join(dir, "scenes.json");
221
+ const scenes = readJson<any>(file);
222
+ const scene = (scenes?.scenes ?? []).find((c: any) => (c.lines ?? []).some((l: any) => l.id === line));
223
+ if (!scene) return { ok: false, error: `a fala ${line} não existe` };
224
+ if (scene.lines.length === 1)
225
+ return {
226
+ ok: false,
227
+ error: `${line} é a única fala da cena ${scene.id}, e cena sem fala não tem tempo: para tirar a cena, peça ao agente`,
228
+ };
229
+ const l = scene.lines.find((x: any) => x.id === line);
230
+
231
+ const files = [...new Set([l.wav, `voice/${line}.wav`, `voice/${line}.json`].filter(Boolean))] as string[];
232
+ keepPrevious(dir, "voice", line, files).close({ keep: true });
233
+ for (const a of files) rmSync(join(dir, a), { force: true });
234
+ scene.lines = scene.lines.filter((x: any) => x !== l);
235
+ /* without the line, the scene lasts something else: `status` asks for timing again */
236
+ delete scene.duration;
237
+ /* edited position and text of the removed line's captions */
238
+ for (const k of ["individual", "texts"]) {
239
+ const o = scenes.captions?.[k];
240
+ if (o) for (const key of Object.keys(o)) if (key.startsWith(`${line}.`)) delete o[key];
241
+ }
242
+ const tmp = `${file}.${process.pid}.tmp`;
243
+ writeFileSync(tmp, JSON.stringify(scenes, null, 2) + "\n");
244
+ renameSync(tmp, file);
245
+
246
+ if (readPause(dir)) {
247
+ const pause = pauseAfterVoice(dir, [], [line]);
248
+ return {
249
+ ok: true,
250
+ output: `${line} saiu da narração; ${pauseNotice(pause)}: o agente ajusta os beats e refaz o vídeo quando você seguir`,
251
+ };
252
+ }
253
+ const { interrupted } = interruptAfterVoice(dir);
254
+ await waitAgentStopped(slug);
255
+ const message = [
256
+ `Tirei pela tela a fala ${line} da cena ${scene.id} («${l.text}»): saiu do scenes.json, e a leitura foi para history/voice/${line}/.`,
257
+ ...removedLinesRequest(scenes, [line]),
258
+ "Depois refaça o vídeo a partir do tempo, como na linha “fala removida” da tabela Retomar ou refazer da skill omnivideo: tempo, render das cenas afetadas, legenda e montagem.",
259
+ ].join("\n");
260
+ const r = chatWith("video", slug, message);
261
+ if (!r.ok) {
262
+ /* without Claude, the pause holds the video and "Seguir" carries the request */
263
+ pauseAfterVoice(dir, [], [line]);
264
+ return {
265
+ ok: false,
266
+ error: `${line} saiu da narração, mas o agente não pôde refazer agora (${r.error}): "Seguir com estas vozes" leva o pedido de novo`,
267
+ };
268
+ }
269
+ return {
270
+ ok: true,
271
+ output: `${line} saiu da narração${interrupted.length ? ` (parei ${interrupted.join(", ")})` : ""}: o agente ajusta os beats e refaz tempo, render, legenda e montagem. Acompanhe na sessão do vídeo.`,
272
+ };
273
+ }
274
+
275
+ export async function selectTake(dir: string, line: string, version: string): Promise<Result> {
276
+ if (!LINE_ID.test(line) || !lineExists(dir, line)) return { ok: false, error: `a fala ${line} não existe` };
277
+ if (!VERSION.test(version)) return { ok: false, error: `versão inválida: ${version}` };
278
+ if (voiceTicketOf(dir))
279
+ return { ok: false, error: "a voz deste vídeo está sendo gerada agora: escolha quando ela terminar" };
280
+ const pause = pauseAfterVoice(dir, [line]);
281
+ const r = await run([PYTHON, OMNIVIDEO, "voice", dir, "--use", line, version], { OMNIVIDEO_ORIGIN: "studio" });
282
+ return r.code === 0
283
+ ? { ok: true, output: `${line}: versão escolhida; ${pauseNotice(pause)}` }
284
+ : { ok: false, error: (r.stderr || r.stdout).trim() };
285
+ }
286
+
287
+ /* the pause has just stopped the session: the process takes a moment to exit, and only
288
+ then does it accept a new message */
289
+ async function waitAgentStopped(slug: string) {
290
+ for (let i = 0; i < 25 && latestSessionFor("video", slug)?.state === "running"; i++) await Bun.sleep(200);
291
+ }
292
+
293
+ /* "Seguir com estas vozes": lifts the pause and asks the video's Claude for what comes after
294
+ the voice, without touching any voice. With a voice still generating, waits for it to end. */
295
+ export async function proceedWithVoices(dir: string, slug: string): Promise<Result> {
296
+ const pause = readPause(dir);
297
+ if (!pause) return { ok: false, error: "o vídeo não está pausado" };
298
+ if (voiceTicketOf(dir))
299
+ return { ok: false, error: "ainda há voz sendo gerada para este vídeo: siga quando ela terminar" };
300
+ await waitAgentStopped(slug);
301
+ rmSync(join(dir, PAUSE_FILE), { force: true });
302
+ const changed = pause.lines.includes("all") ? "todas as falas" : `as falas ${pause.lines.join(", ")}`;
303
+ const removed = pause.removed ?? [];
304
+ const message = [
305
+ ...(pause.lines.length
306
+ ? [`Troquei vozes pela tela (${changed}) e terminei: as falas agora são as que eu escolhi.`]
307
+ : []),
308
+ ...(removed.length
309
+ ? [
310
+ `Tirei pela tela da narração ${removed.length > 1 ? "as falas" : "a fala"} ${removed.join(", ")}: ${removed.length > 1 ? "saíram" : "saiu"} do scenes.json, e a leitura foi para history/voice/.`,
311
+ ...removedLinesRequest(readJson<any>(join(dir, "scenes.json")), removed),
312
+ ]
313
+ : []),
314
+ `Siga o vídeo pelo \`${PYTHON} tools/omnivideo.py status videos/${slug}\`: tempo, render, legenda e montagem, o que ele pedir.`,
315
+ "Não gere, não refaça e não troque nenhuma voz, nem para unificar o provider: fala com provider_pinned é escolha minha.",
316
+ ].join("\n");
317
+ const r = chatWith("video", slug, message);
318
+ if (!r.ok) {
319
+ /* without Claude the pause comes back: otherwise nothing holds the video and nobody redoes the render */
320
+ writeFileSync(join(dir, PAUSE_FILE), JSON.stringify(pause, null, 2) + "\n");
321
+ return { ok: false, error: `não deu para retomar o agente: ${r.error}` };
322
+ }
323
+ return { ok: true, output: "seguindo com estas vozes: o agente refaz tempo, render, legenda e montagem" };
324
+ }
@@ -0,0 +1,244 @@
1
+ /* The runner: this studio, reached from the hosted one (cloud/studio). It opens a WebSocket to the
2
+ cloud's RunnerHub and answers each request that arrives on it with the same Hono app the local
3
+ server uses, so every route, check and tool call is the one that runs at 127.0.0.1. Protocol in
4
+ tools/lib/channel.mjs; the plan in docs/specs/phase-1-split-api.md.
5
+
6
+ Locally `Bun.serve` answers Range for a Bun.file by itself; `app.fetch` doesn't, so the media paths
7
+ get their 206 here, from a slice of the file's Blob (never read whole). */
8
+
9
+ import { hostname } from "node:os";
10
+ import { PROTOCOL, Peer, REPLACED, REVOKED, UPGRADE } from "../../tools/lib/channel.mjs";
11
+
12
+ /* the file routes of api/app.ts (API_PREFIXES) a player may ask a range of */
13
+ const MEDIA_PREFIXES = [
14
+ "/media/",
15
+ "/ref/",
16
+ "/kit/",
17
+ "/sound-draft/",
18
+ "/docs/",
19
+ "/api/voice-sample/",
20
+ "/api/voice-original/",
21
+ ];
22
+ const PING_MS = 30_000;
23
+ const DEAD_MS = 75_000;
24
+ /* without a key (not paired, or disconnected at /runner): how often to look for one in .env */
25
+ const KEY_CHECK_MS = 30_000;
26
+ /* a refused key pairs again by itself at most this often (the approval page opens on this Mac) */
27
+ const PAIR_EVERY_MS = 60 * 60_000;
28
+
29
+ export type RunnerOptions = {
30
+ /** the hosted studio, https://… */
31
+ url: string;
32
+ /** the Mac's credential, read on every attempt: the OAuth access token of `node tools/omni.mjs login`
33
+ * (refreshed as needed) or the Phase 1 key (OMNIVIDEO_RUNNER_TOKEN, from `node tools/runner.mjs login`)
34
+ * so a fresh login connects without restarting the studio */
35
+ token: () => string | Promise<string>;
36
+ /** the local server's port: the requests are rewritten to it, so `localOnly` admits them */
37
+ port: number;
38
+ handle: (request: Request) => Response | Promise<Response>;
39
+ log?: (line: string) => void;
40
+ /** whether the cloud still takes this key: false only when it says so (tools/lib/pairing.mjs checkKey) */
41
+ checkKey?: (token: string) => Promise<{ valid: boolean | null }>;
42
+ /** pairs this Mac again (opens the approval page, waits, saves the new key): the key, or null. Called when
43
+ * the cloud refuses the key, never after a "Desconectar", which is the user's own decision */
44
+ pair?: () => Promise<string | null>;
45
+ };
46
+
47
+ /** Range on a media response: 206 from a Blob slice, 416 out of bounds; the rest as it is. */
48
+ export async function withRange(request: Request, res: Response): Promise<Response> {
49
+ const path = new URL(request.url).pathname;
50
+ if (!MEDIA_PREFIXES.some(p => path.startsWith(p)) || res.status !== 200 || !res.body) return res;
51
+ const blob = await res.blob();
52
+ const headers = new Headers(res.headers);
53
+ headers.set("accept-ranges", "bytes");
54
+ if (blob.type && !headers.has("content-type")) headers.set("content-type", blob.type);
55
+ const range = /^bytes=(\d*)-(\d*)$/.exec(request.headers.get("range")?.trim() ?? "");
56
+ if (!range || (range[1] === "" && range[2] === "")) {
57
+ headers.set("content-length", String(blob.size));
58
+ return new Response(blob, { status: 200, headers });
59
+ }
60
+ const size = blob.size;
61
+ let start: number;
62
+ let end: number;
63
+ if (range[1] === "") {
64
+ start = Math.max(0, size - Number(range[2]));
65
+ end = size - 1;
66
+ } else {
67
+ start = Number(range[1]);
68
+ end = range[2] === "" ? size - 1 : Math.min(Number(range[2]), size - 1);
69
+ }
70
+ if (start >= size || start > end)
71
+ return new Response(null, { status: 416, headers: { "content-range": `bytes */${size}` } });
72
+ headers.set("content-range", `bytes ${start}-${end}/${size}`);
73
+ headers.set("content-length", String(end - start + 1));
74
+ return new Response(blob.slice(start, end + 1), { status: 206, headers });
75
+ }
76
+
77
+ /** A request from the channel, as the local server would have received it. */
78
+ export function asLocal(request: Request, port: number): Request {
79
+ const u = new URL(request.url);
80
+ const headers = new Headers(request.headers);
81
+ headers.set("host", `127.0.0.1:${port}`);
82
+ /* the Worker already checked the browser's Origin against its own host */
83
+ headers.delete("origin");
84
+ /* it came through the hosted studio: the admin panel reads who sent it (x-ov-user, from the hub) */
85
+ headers.set("x-ov-via", "cloud");
86
+ return new Request(`http://127.0.0.1:${port}${u.pathname}${u.search}`, {
87
+ /* HEAD goes in as GET, as studio.ts does; the channel sends no body back for a HEAD */
88
+ method: request.method === "HEAD" ? "GET" : request.method,
89
+ headers,
90
+ body: request.body,
91
+ signal: request.signal,
92
+ // @ts-expect-error Bun needs it for a streaming request body
93
+ duplex: "half",
94
+ });
95
+ }
96
+
97
+ /** Connects and stays connected (backoff 1 s doubling to 60 s, with jitter). */
98
+ export function startRunner(o: RunnerOptions) {
99
+ const log = o.log ?? ((line: string) => console.log(line));
100
+ const target = `${o.url.replace(/^http/, "ws")}/runner/connect`;
101
+ let delay = 1000;
102
+ let stopped = false;
103
+ let warned = false;
104
+ let socket: WebSocket | null = null;
105
+ let timer: ReturnType<typeof setTimeout> | null = null;
106
+ let revoked = "";
107
+ let hinted = false;
108
+ let pairing = false;
109
+ let pairedAt = 0;
110
+
111
+ const serve = async (request: Request) => withRange(request, await o.handle(asLocal(request, o.port)));
112
+
113
+ const connect = async () => {
114
+ /* an unreachable studio while refreshing says nothing about the login: try again later */
115
+ const token = await Promise.resolve(o.token()).catch(() => "");
116
+ if (!token || token === revoked) {
117
+ if (!hinted) log("aviso: este Mac não está pareado com o studio hospedado: node tools/runner.mjs login");
118
+ hinted = true;
119
+ timer = setTimeout(connect, KEY_CHECK_MS);
120
+ return;
121
+ }
122
+ hinted = false;
123
+ const ws = new WebSocket(target, {
124
+ headers: {
125
+ authorization: `Bearer ${token}`,
126
+ "x-ov-protocol": String(PROTOCOL),
127
+ /* the hub says which Mac it refused, on the hosted page */
128
+ "x-ov-runner-host": encodeURIComponent(hostname().replace(/\.local$/, "")),
129
+ },
130
+ } as unknown as string[]);
131
+ ws.binaryType = "arraybuffer";
132
+ socket = ws;
133
+ let peer: Peer | null = null;
134
+ let lastPong = Date.now();
135
+ let ping: ReturnType<typeof setInterval> | null = null;
136
+
137
+ ws.onopen = () => {
138
+ delay = 1000;
139
+ warned = false;
140
+ peer = new Peer({
141
+ send: (f: Uint8Array) => ws.send(f as Uint8Array<ArrayBuffer>),
142
+ onRequest: serve,
143
+ /* what the uplink still holds: media waits on it, so Omni's lines never queue behind a video */
144
+ backlog: () => ws.bufferedAmount,
145
+ });
146
+ log(`cloud: studio hospedado conectado (${o.url})`);
147
+ /* "ping" is answered by the hub's auto-response without waking it; no pong for a while = dead */
148
+ ping = setInterval(() => {
149
+ if (Date.now() - lastPong > DEAD_MS) return ws.close(4001, "sem resposta da cloud");
150
+ ws.send("ping");
151
+ }, PING_MS);
152
+ };
153
+ ws.onmessage = e => {
154
+ if (typeof e.data === "string") {
155
+ lastPong = Date.now();
156
+ return;
157
+ }
158
+ peer?.receive(e.data as ArrayBuffer);
159
+ };
160
+ ws.onclose = e => {
161
+ if (ping) clearInterval(ping);
162
+ const was = !!peer;
163
+ peer?.close("o studio hospedado desconectou");
164
+ peer = null;
165
+ if (stopped) return;
166
+ /* another studio took the hosted one over: retrying would only knock it off in turn */
167
+ if (e.code === REPLACED) {
168
+ stopped = true;
169
+ return log("aviso: outro studio assumiu o studio hospedado; este parou de atender por ele");
170
+ }
171
+ /* the key was deleted at /runner: wait for a new pairing instead of knocking with the old key */
172
+ if (e.code === REVOKED) {
173
+ revoked = token;
174
+ log("aviso: este Mac foi desconectado do studio hospedado; para voltar: node tools/runner.mjs login");
175
+ hinted = true;
176
+ timer = setTimeout(connect, KEY_CHECK_MS);
177
+ return;
178
+ }
179
+ /* the hub and this Mac speak different protocols: say which side is old, and look again in a minute
180
+ (the studio may be redeployed, or this Mac updated, without a restart) */
181
+ if (e.code === UPGRADE) {
182
+ if (!warned) log(`aviso: este Mac e o studio hospedado não se entendem (${e.reason}); tento de novo em 1 min`);
183
+ warned = true;
184
+ timer = setTimeout(connect, 60_000);
185
+ return;
186
+ }
187
+ if (was || !warned) {
188
+ log(`cloud: sem conexão com o studio hospedado (${e.code}${e.reason ? ` ${e.reason}` : ""}); tentando de novo`);
189
+ warned = true;
190
+ }
191
+ const retry = () => {
192
+ timer = setTimeout(connect, delay * (0.75 + Math.random() / 2));
193
+ delay = Math.min(delay * 2, 60_000);
194
+ };
195
+ /* never opened: the cloud may have refused the key itself (not an unreachable cloud) */
196
+ if (!was && o.checkKey && o.pair) void repair(token).then(handled => handled || retry());
197
+ else retry();
198
+ };
199
+ ws.onerror = () => {
200
+ /* onclose follows */
201
+ };
202
+ };
203
+ /* The key was refused (the login was redone, the key went missing in the cloud…): pair again, which opens
204
+ the approval page on this Mac, and connect with the new key. True when it took over the retry. */
205
+ const repair = async (token: string): Promise<boolean> => {
206
+ if (stopped || pairing) return true;
207
+ const k = await o.checkKey!(token).catch(() => ({ valid: null }));
208
+ if (k.valid !== false) return false;
209
+ revoked = token;
210
+ if (Date.now() - pairedAt < PAIR_EVERY_MS) {
211
+ timer = setTimeout(connect, KEY_CHECK_MS);
212
+ return true;
213
+ }
214
+ pairing = true;
215
+ pairedAt = Date.now();
216
+ log(
217
+ "aviso: o studio hospedado recusou a chave deste Mac; abri a página de aprovação no navegador: aprove para reconectar",
218
+ );
219
+ const key = await o.pair!().catch(e => {
220
+ log(
221
+ `aviso: o pareamento não terminou (${(e as Error).message}); de novo daqui a 1 h, ou: node tools/runner.mjs login`,
222
+ );
223
+ return null;
224
+ });
225
+ pairing = false;
226
+ if (stopped) return true;
227
+ if (key) {
228
+ log("cloud: Mac pareado de novo");
229
+ delay = 1000;
230
+ hinted = false;
231
+ void connect();
232
+ } else timer = setTimeout(connect, KEY_CHECK_MS);
233
+ return true;
234
+ };
235
+
236
+ void connect();
237
+ return {
238
+ stop() {
239
+ stopped = true;
240
+ if (timer) clearTimeout(timer);
241
+ socket?.close(1000, "runner parou");
242
+ },
243
+ };
244
+ }
@@ -0,0 +1,68 @@
1
+ /* Omni's staged actions (api/services/agent-tools.ts) as the screen follows them: the studio screen
2
+ where each one's result shows, which the page opens when the card appears (or, for something that
3
+ doesn't exist yet, once it is confirmed), and which ones leave a Claude working for minutes. One
4
+ catalog for the server, which hands the screen to the model, and for the browser, which opens it. */
5
+
6
+ import type { NavigateArgs } from "./omni-screens";
7
+
8
+ export type ActionKind =
9
+ | "change"
10
+ | "create"
11
+ | "thumbnail"
12
+ | "publish"
13
+ | "schedule"
14
+ | "approval"
15
+ | "reference"
16
+ | "new_reference"
17
+ | "choose_thumbnail"
18
+ | "analysis"
19
+ | "omnichat"
20
+ | "omnichat_send";
21
+
22
+ /* the screen of an action's result. `slug` is the video's slug, or the reference's name (kind
23
+ reference); null: no screen to open */
24
+ export function actionScreen(
25
+ kind: ActionKind,
26
+ slug: string | null,
27
+ payload: Record<string, unknown>,
28
+ ): NavigateArgs | null {
29
+ const p = payload as Record<string, any>;
30
+ switch (kind) {
31
+ case "thumbnail":
32
+ case "choose_thumbnail":
33
+ return slug ? { screen: "video", slug, stage: "publish", tab: "thumbnail" } : null;
34
+ case "publish":
35
+ case "schedule":
36
+ if (!slug) return null;
37
+ return p.content === "carousel"
38
+ ? { screen: "video", slug, stage: "carousel-publish", tab: "post" }
39
+ : { screen: "video", slug, stage: "publish", tab: "info" };
40
+ case "omnichat":
41
+ case "omnichat_send":
42
+ return slug ? { screen: "omnichat_rule", slug, content: p.content === "carousel" ? "carousel" : "video" } : null;
43
+ case "approval":
44
+ return slug ? { screen: "video", slug, stage: p.round === "scenes" ? "motion_script" : "script" } : null;
45
+ case "change":
46
+ if (!slug) return null;
47
+ return p.where === "edit" ? { screen: "video", slug, stage: "render", tab: "agent" } : { screen: "video", slug };
48
+ case "create":
49
+ return slug ? { screen: "video", slug } : null;
50
+ case "reference":
51
+ return slug ? { screen: "style", name: slug } : null;
52
+ case "new_reference":
53
+ return typeof p.name === "string" ? { screen: "style", name: p.name } : null;
54
+ case "analysis":
55
+ return slug ? { screen: "video_metrics", slug } : { screen: "analytics" };
56
+ }
57
+ }
58
+
59
+ /* a new content or reference has no screen until it exists: those open once confirmed, the rest when
60
+ the card appears */
61
+ export const opensOnConfirm = (kind: ActionKind) => kind === "create" || kind === "new_reference";
62
+
63
+ /* a confirmed action whose work takes minutes: Omni offers to wait instead of filling the silence */
64
+ const ASYNC_KINDS = new Set<string>(["change", "create", "thumbnail", "reference", "approval"]);
65
+ export const waitHint = (kind: string) =>
66
+ ASYNC_KINDS.has(kind)
67
+ ? " Isso deixa o Claude trabalhando por alguns minutos: diga numa frase que vai esperar e chame wait_for_work."
68
+ : "";