@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,180 @@
1
+ /* ============================================================================
2
+ Machine queue turn for the Node tools: final render, sound and
3
+ Premiere. The protocol lives in tools/job_queue.py; here it runs as a
4
+ guardian, a Python process that waits for the turn on behalf of this process,
5
+ holds the flock while it lives and releases it when it exits, even on kill -9
6
+ (the stdin pipe closes and the guardian sees the end).
7
+
8
+ import { turn } from "./job-queue.mjs";
9
+ await turn("render", dir); // returns on our turn; the turn ends when the process exits
10
+
11
+ During the turn, stdout and stderr also go to .queue/<id>.log, which the
12
+ studio shows live. Without contention, terminal output is the same as before.
13
+ ============================================================================ */
14
+
15
+ import { spawn, spawnSync } from "node:child_process";
16
+ import {
17
+ readFileSync,
18
+ writeFileSync,
19
+ renameSync,
20
+ openSync,
21
+ writeSync,
22
+ existsSync,
23
+ readdirSync,
24
+ statSync,
25
+ } from "node:fs";
26
+ import { join, dirname } from "node:path";
27
+ import { CODE_ROOT, DATA_ROOT as ROOT } from "./lib/paths.mjs";
28
+
29
+ const CFG = JSON.parse(readFileSync(join(ROOT, "omnivideo.config.json"), "utf8"));
30
+ const QUEUE_DIR = join(ROOT, ".queue");
31
+
32
+ /* a zombie counts as dead (see studio/api/services/queue.ts) */
33
+ function alive(pid) {
34
+ try {
35
+ process.kill(pid, 0);
36
+ } catch (e) {
37
+ if (e.code !== "EPERM") return false;
38
+ }
39
+ const ps = spawnSync("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8" });
40
+ return !String(ps.stdout).trim().startsWith("Z");
41
+ }
42
+
43
+ /* a child of a task that already holds the turn doesn't wait for its parent's turn */
44
+ function insideTurn() {
45
+ const id = process.env.OMNIVIDEO_TURN;
46
+ if (!id) return false;
47
+ try {
48
+ const ticket = JSON.parse(readFileSync(join(QUEUE_DIR, `${id}.json`), "utf8"));
49
+ return ticket.state === "running" && alive(ticket.pid);
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ /* tasks holding the turn now, with the same liveness rule as job_queue.py:
56
+ process alive and ticket heartbeat less than 15 s ago */
57
+ export function running() {
58
+ if (!existsSync(QUEUE_DIR)) return [];
59
+ const now = Date.now();
60
+ return readdirSync(QUEUE_DIR)
61
+ .filter(n => n.endsWith(".json"))
62
+ .flatMap(n => {
63
+ try {
64
+ const ticket = JSON.parse(readFileSync(join(QUEUE_DIR, n), "utf8"));
65
+ const live =
66
+ ticket.state === "running" && alive(ticket.pid) && now - statSync(join(QUEUE_DIR, n)).mtimeMs < 15_000;
67
+ return live ? [ticket] : [];
68
+ } catch {
69
+ return [];
70
+ }
71
+ });
72
+ }
73
+
74
+ function mirror(log) {
75
+ const fd = openSync(log, "a");
76
+ for (const out of [process.stdout, process.stderr]) {
77
+ const write = out.write.bind(out);
78
+ out.write = (chunk, ...rest) => {
79
+ try {
80
+ writeSync(fd, chunk);
81
+ } catch {
82
+ /* the log is a copy; the terminal is what counts */
83
+ }
84
+ return write(chunk, ...rest);
85
+ };
86
+ }
87
+ }
88
+
89
+ /* on 'exit' only synchronous code runs; death by signal doesn't pass here, and
90
+ the guardian closes the ticket as interrupted */
91
+ function close(file, exitCode) {
92
+ try {
93
+ const ticket = JSON.parse(readFileSync(file, "utf8"));
94
+ Object.assign(ticket, {
95
+ state: exitCode === 0 ? "ok" : "error",
96
+ exit_code: exitCode,
97
+ ended_at: new Date().toISOString(),
98
+ });
99
+ const tmp = join(dirname(file), `.${ticket.id}.${process.pid}.tmp`);
100
+ writeFileSync(tmp, JSON.stringify(ticket, null, 1) + "\n");
101
+ renameSync(tmp, file);
102
+ } catch {
103
+ /* the guardian closes it */
104
+ }
105
+ }
106
+
107
+ /* what comes after the voice doesn't run while the user swaps voices in the UI
108
+ (voice-pause.json in the video dir; "Seguir com estas vozes" removes it).
109
+ The same rule is in job_queue.py and studio/api/services/voice-pause.ts */
110
+ const AFTER_VOICE = new Set([
111
+ "render",
112
+ "sound",
113
+ "captions",
114
+ "assembly",
115
+ "premiere-assemble",
116
+ "premiere-check",
117
+ "premiere-export",
118
+ ]);
119
+ function checkVoicePause(tool, dir) {
120
+ if (!AFTER_VOICE.has(tool) || !dir || !existsSync(join(dir, "voice-pause.json"))) return;
121
+ process.stderr.write(
122
+ `pausado: o usuário está trocando vozes deste vídeo na tela do studio (voice-pause.json). ` +
123
+ `${tool} só roda quando ele clicar em "Seguir com estas vozes" na etapa Voz. Pare aqui e não refaça vozes.\n`,
124
+ );
125
+ process.exit(3);
126
+ }
127
+
128
+ export async function turn(tool, dir) {
129
+ checkVoicePause(tool, dir);
130
+ if (process.env.OMNIVIDEO_QUEUE === "0" || insideTurn()) return null;
131
+ /* the project's Python (.venv); without it, the config's and the system python3 */
132
+ const python =
133
+ [join(CODE_ROOT, ".venv", "bin", "python"), CFG.voicestudio?.python ?? ""].find(p => p && existsSync(p)) ??
134
+ "python3";
135
+ const guardian = spawn(
136
+ python,
137
+ [
138
+ join(CODE_ROOT, "tools", "job_queue.py"),
139
+ "guardian",
140
+ tool,
141
+ dir ?? "-",
142
+ String(process.pid),
143
+ "--",
144
+ ...process.argv.slice(1),
145
+ ],
146
+ { stdio: ["pipe", "pipe", "inherit"] },
147
+ );
148
+ const ticket = await new Promise((resolveTicket, reject) => {
149
+ let buf = "";
150
+ guardian.stdout.on("data", d => {
151
+ buf += d;
152
+ const i = buf.indexOf("\n");
153
+ if (i >= 0) resolveTicket(JSON.parse(buf.slice(0, i)));
154
+ });
155
+ guardian.on("error", reject);
156
+ guardian.on("exit", c =>
157
+ reject(
158
+ new Error(
159
+ `a fila da máquina falhou: o guardião saiu com ${c} antes da vez ` + "(OMNIVIDEO_QUEUE=0 roda sem fila)",
160
+ ),
161
+ ),
162
+ );
163
+ });
164
+ /* the guardian lives until this process exits, without holding the event
165
+ loop. If it dies earlier (kill), the lock was released: warn, because
166
+ another heavy task may start alongside this one */
167
+ guardian.stdout.destroy();
168
+ guardian.stdin.unref();
169
+ guardian.unref();
170
+ guardian.removeAllListeners("exit");
171
+ guardian.on("exit", () =>
172
+ process.stderr.write(
173
+ "fila: ⚠ o guardião saiu antes desta tarefa terminar; a vez foi solta e outra tarefa pesada pode rodar junto\n",
174
+ ),
175
+ );
176
+ process.env.OMNIVIDEO_TURN = ticket.id;
177
+ mirror(ticket.log);
178
+ process.on("exit", code => close(ticket.file, code));
179
+ return ticket;
180
+ }
@@ -0,0 +1,430 @@
1
+ #!/usr/bin/env python3
2
+ """Machine queue: one heavy task at a time, with the studio running or not.
3
+
4
+ job_queue.py who runs, who waits and the last ones that finished
5
+ job_queue.py --json the same as data
6
+ job_queue.py guardian <tool> <dir|-> <pid> -- <command…>
7
+ holds the turn of a Node tool (tools/job-queue.mjs)
8
+
9
+ In the Python tools:
10
+
11
+ from job_queue import turn
12
+ with turn("voice", video_dir):
13
+ ... # runs only on this task's turn
14
+
15
+ Heavy: voice, captions, final render, sound, assembly and Premiere
16
+ (assemble, check, export). Each writes a ticket, .queue/<id>.json, and waits to
17
+ be the oldest live one waiting; then it takes the flock on .queue/machine.lock.
18
+ The kernel releases the flock when the process dies, even on kill -9: there is
19
+ no forgotten lock. The task output also goes to .queue/<id>.log.
20
+
21
+ The studio only reads this dir. Running, stopped or restarting in the middle of
22
+ a task, the queue is the same.
23
+
24
+ OMNIVIDEO_QUEUE=0 disables the queue (emergency). OMNIVIDEO_TURN, which the task
25
+ holding the turn passes to its children, keeps a child from waiting for its own
26
+ parent's turn.
27
+ """
28
+
29
+ import fcntl
30
+ import json
31
+ import os
32
+ import select
33
+ import shlex
34
+ import signal
35
+ import subprocess
36
+ import sys
37
+ import threading
38
+ import time
39
+ from contextlib import contextmanager
40
+ from datetime import datetime, timezone
41
+ from pathlib import Path
42
+
43
+ from omni_paths import DATA_ROOT as ROOT
44
+
45
+ QUEUE_DIR = ROOT / ".queue"
46
+ LOCK = QUEUE_DIR / "machine.lock"
47
+
48
+ POLL_S = 0.4 # between one look at the queue and the next
49
+ HEARTBEAT_S = 2.0 # a live ticket renews its mtime at this pace
50
+ STALE_S = 15.0 # no heartbeat for longer than this: process stopped or pid reused
51
+ KEPT = 100 # finished tickets left in the dir, with their log
52
+ ACTIVE = ("waiting", "running")
53
+
54
+
55
+ def now():
56
+ return datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
57
+
58
+
59
+ def alive(pid):
60
+ if not pid:
61
+ return False
62
+ try:
63
+ os.kill(pid, 0)
64
+ except ProcessLookupError:
65
+ return False
66
+ except PermissionError:
67
+ pass
68
+ # a zombie counts as dead (see studio/api/services/queue.ts)
69
+ ps = subprocess.run(["ps", "-o", "stat=", "-p", str(pid)], capture_output=True, text=True)
70
+ return not ps.stdout.strip().startswith("Z")
71
+
72
+
73
+ def ticket_path(id_):
74
+ return QUEUE_DIR / f"{id_}.json"
75
+
76
+
77
+ def write(ticket):
78
+ """Atomic swap: a reader never gets a half-written ticket."""
79
+ dest = ticket_path(ticket["id"])
80
+ tmp = dest.with_name(f".{ticket['id']}.{os.getpid()}.tmp")
81
+ tmp.write_text(json.dumps(ticket, ensure_ascii=False, indent=1) + "\n")
82
+ tmp.replace(dest)
83
+
84
+
85
+ def read_all():
86
+ """Tickets with their effective state: an active one whose process died, or with no heartbeat, counts as interrupted."""
87
+ if not QUEUE_DIR.is_dir():
88
+ return []
89
+ instant = time.time()
90
+ tickets = []
91
+ for p in QUEUE_DIR.glob("*.json"):
92
+ try:
93
+ ticket = json.loads(p.read_text())
94
+ mtime = p.stat().st_mtime
95
+ except (OSError, ValueError):
96
+ continue
97
+ effective = ticket.get("state")
98
+ if effective in ACTIVE and (not alive(ticket.get("pid")) or instant - mtime > STALE_S):
99
+ effective = "interrupted"
100
+ ticket["effective"] = effective
101
+ tickets.append(ticket)
102
+ return tickets
103
+
104
+
105
+ def sort_key(ticket):
106
+ return (ticket.get("order", 0), ticket.get("pid", 0))
107
+
108
+
109
+ def close_dead(id_):
110
+ """Re-read after learning the owner died: what it wrote before exiting stands."""
111
+ try:
112
+ ticket = json.loads(ticket_path(id_).read_text())
113
+ except (OSError, ValueError):
114
+ return
115
+ if ticket.get("state") in ACTIVE:
116
+ ticket.update(state="interrupted", ended_at=now())
117
+ write(ticket)
118
+
119
+
120
+ def prune():
121
+ """Closes tickets of dead processes and deletes the oldest finished ones."""
122
+ for ticket in read_all():
123
+ if ticket["state"] in ACTIVE and not alive(ticket.get("pid")):
124
+ close_dead(ticket["id"])
125
+ finished = [t for t in read_all() if t["state"] not in ACTIVE]
126
+ finished.sort(key=lambda t: t.get("ended_at") or t.get("arrived_at") or "", reverse=True)
127
+ for ticket in finished[KEPT:]:
128
+ for p in (ticket_path(ticket["id"]), QUEUE_DIR / f"{ticket['id']}.log"):
129
+ try:
130
+ p.unlink()
131
+ except FileNotFoundError:
132
+ pass
133
+
134
+
135
+ def origin():
136
+ if os.environ.get("OMNIVIDEO_ORIGIN"):
137
+ return os.environ["OMNIVIDEO_ORIGIN"]
138
+ return "chat" if os.environ.get("CLAUDECODE") else "terminal"
139
+
140
+
141
+ def new_ticket(tool, dir, command, pid, guardian=None):
142
+ QUEUE_DIR.mkdir(exist_ok=True)
143
+ ns = time.time_ns()
144
+ id_ = f"{time.strftime('%Y%m%d-%H%M%S', time.localtime(ns // 1_000_000_000))}-{ns // 1_000_000 % 1000:03d}-{pid}"
145
+ video = Path(dir).resolve() if dir else None
146
+ ticket = {
147
+ "id": id_,
148
+ "tool": tool,
149
+ "video": video.name if video else None,
150
+ "dir": str(video) if video else None,
151
+ "command": shlex.join([Path(command[0]).name, *command[1:]]) if command else "",
152
+ "pid": pid,
153
+ "guardian": guardian,
154
+ "origin": origin(),
155
+ "session": os.environ.get("CLAUDE_CODE_SESSION_ID"),
156
+ "state": "waiting",
157
+ "order": ns,
158
+ "arrived_at": now(),
159
+ "started_at": None,
160
+ "ended_at": None,
161
+ "exit_code": None,
162
+ "log": f".queue/{id_}.log",
163
+ }
164
+ write(ticket)
165
+ return ticket
166
+
167
+
168
+ class Heartbeat(threading.Thread):
169
+ """Renews the ticket's mtime: this is how a reused pid does not pass for alive."""
170
+
171
+ def __init__(self, id_):
172
+ super().__init__(daemon=True)
173
+ self.path = ticket_path(id_)
174
+ self.done = threading.Event()
175
+ self.start()
176
+
177
+ def run(self):
178
+ while not self.done.wait(HEARTBEAT_S):
179
+ try:
180
+ os.utime(self.path)
181
+ except OSError:
182
+ pass
183
+
184
+ def stop(self):
185
+ self.done.set()
186
+
187
+
188
+ def duration(seconds):
189
+ s = int(seconds)
190
+ if s < 60:
191
+ return f"{s}s"
192
+ if s < 3600:
193
+ return f"{s // 60}min{s % 60:02d}s"
194
+ return f"{s // 3600}h{s % 3600 // 60:02d}min"
195
+
196
+
197
+ def since(iso):
198
+ if not iso:
199
+ return 0
200
+ return time.time() - datetime.fromisoformat(iso.replace("Z", "+00:00")).timestamp()
201
+
202
+
203
+ def summary(ticket):
204
+ who = ticket["origin"] + (f" {ticket['session'][:8]}" if ticket.get("session") else "")
205
+ return f"{ticket['tool']} · {ticket.get('video') or '-'} ({who}, pid {ticket['pid']})"
206
+
207
+
208
+ def wait(ticket, fd, pause):
209
+ """Returns the seconds waited when this ticket is the oldest live one waiting
210
+ and the flock is its own; None if `pause` gives up midway."""
211
+ t0 = time.time()
212
+ said = None
213
+ lock_held_since = None
214
+ while True:
215
+ tickets = read_all()
216
+ ahead = [
217
+ t
218
+ for t in tickets
219
+ if t["effective"] == "waiting" and t["id"] != ticket["id"] and sort_key(t) < sort_key(ticket)
220
+ ]
221
+ if not ahead:
222
+ try:
223
+ fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
224
+ return time.time() - t0
225
+ except BlockingIOError:
226
+ pass
227
+ running = [t for t in tickets if t["effective"] == "running"]
228
+ # lock busy with nobody running is usually the handover between two tasks
229
+ # (Node closes the ticket an instant before the guardian releases the lock)
230
+ lock_held_since = (lock_held_since or time.time()) if not running and not ahead else None
231
+ snapshot = (len(ahead), tuple(t["id"] for t in running))
232
+ # nobody running with someone ahead is also a handover: no notice
233
+ if snapshot != said and (running or (not ahead and time.time() - lock_held_since > 3)):
234
+ said = snapshot
235
+ parts = [f"rodando {summary(t)} há {duration(since(t.get('started_at')))}" for t in running]
236
+ if not running:
237
+ parts.append("o lock está com um processo fora da fila")
238
+ if ahead:
239
+ parts.append(f"{len(ahead)} na frente")
240
+ print(f"fila: esperando a vez de {ticket['tool']} · {'; '.join(parts)}", file=sys.stderr, flush=True)
241
+ if not pause(POLL_S):
242
+ return None
243
+
244
+
245
+ def begin(ticket, waited):
246
+ ticket.update(state="running", started_at=now())
247
+ write(ticket)
248
+ # with the flock in hand, another "running" ticket can only belong to a dead process
249
+ for t in read_all():
250
+ if t["id"] != ticket["id"] and t["state"] == "running" and not alive(t.get("pid")):
251
+ close_dead(t["id"])
252
+ if waited >= 1:
253
+ print(f"fila: vez de {ticket['tool']} depois de {duration(waited)} de espera", file=sys.stderr, flush=True)
254
+
255
+
256
+ class Tee:
257
+ """Writes to the terminal as before and copies to the ticket's log, which the studio shows live."""
258
+
259
+ def __init__(self, original, log):
260
+ self._original = original
261
+ self._log = log
262
+
263
+ def write(self, text):
264
+ n = self._original.write(text)
265
+ try:
266
+ self._log.write(text)
267
+ self._log.flush()
268
+ except (OSError, ValueError):
269
+ pass
270
+ return n
271
+
272
+ def __getattr__(self, name):
273
+ return getattr(self._original, name)
274
+
275
+
276
+ def tee(path):
277
+ log = open(path, "a", encoding="utf-8")
278
+ originals = sys.stdout, sys.stderr
279
+ sys.stdout, sys.stderr = Tee(sys.stdout, log), Tee(sys.stderr, log)
280
+
281
+ def restore():
282
+ sys.stdout, sys.stderr = originals
283
+ log.close()
284
+
285
+ return restore
286
+
287
+
288
+ def inside_turn():
289
+ """A child of a task that already holds the turn does not wait for its parent's turn."""
290
+ id_ = os.environ.get("OMNIVIDEO_TURN")
291
+ if not id_:
292
+ return False
293
+ try:
294
+ ticket = json.loads(ticket_path(id_).read_text())
295
+ except (OSError, ValueError):
296
+ return False
297
+ return ticket.get("state") == "running" and alive(ticket.get("pid"))
298
+
299
+
300
+ # what comes after the voice does not run while the user swaps voices in the studio
301
+ # (voice-pause.json in the video dir; "Seguir com estas vozes" removes it).
302
+ # The same rule is in job-queue.mjs and studio/api/services/voice-pause.ts
303
+ AFTER_VOICE = {"render", "sound", "captions", "assembly", "premiere-assemble", "premiere-check", "premiere-export"}
304
+
305
+
306
+ def check_voice_pause(tool, dir):
307
+ if tool in AFTER_VOICE and dir and (Path(dir) / "voice-pause.json").exists():
308
+ sys.stderr.write(
309
+ f"pausado: o usuário está trocando vozes deste vídeo na tela do studio (voice-pause.json). "
310
+ f'{tool} só roda quando ele clicar em "Seguir com estas vozes" na etapa Voz. '
311
+ "Pare aqui e não refaça vozes.\n"
312
+ )
313
+ sys.exit(3)
314
+
315
+
316
+ @contextmanager
317
+ def turn(tool, dir=None):
318
+ check_voice_pause(tool, dir)
319
+ if os.environ.get("OMNIVIDEO_QUEUE") == "0" or inside_turn():
320
+ yield None
321
+ return
322
+ prune()
323
+ ticket = new_ticket(tool, dir, sys.argv, os.getpid())
324
+ heartbeat = Heartbeat(ticket["id"])
325
+ fd = os.open(LOCK, os.O_RDWR | os.O_CREAT, 0o644)
326
+ restore = None
327
+ state, exit_code = "ok", 0
328
+ try:
329
+ begin(ticket, wait(ticket, fd, lambda s: time.sleep(s) or True))
330
+ os.environ["OMNIVIDEO_TURN"] = ticket["id"]
331
+ restore = tee(ROOT / ticket["log"])
332
+ yield ticket
333
+ except KeyboardInterrupt:
334
+ state, exit_code = "interrupted", 130
335
+ raise
336
+ except SystemExit as e:
337
+ exit_code = e.code if isinstance(e.code, int) else (0 if e.code is None else 1)
338
+ state = "ok" if exit_code == 0 else "error"
339
+ raise
340
+ except BaseException:
341
+ state, exit_code = "error", 1
342
+ raise
343
+ finally:
344
+ if restore:
345
+ restore()
346
+ if ticket["started_at"]:
347
+ ticket.update(state=state, exit_code=exit_code, ended_at=now())
348
+ else:
349
+ ticket.update(state="interrupted", ended_at=now())
350
+ write(ticket)
351
+ heartbeat.stop()
352
+ os.close(fd)
353
+ if os.environ.get("OMNIVIDEO_TURN") == ticket["id"]:
354
+ del os.environ["OMNIVIDEO_TURN"]
355
+
356
+
357
+ def guardian(tool, dir, pid, command):
358
+ """Holds the turn of a Node process (tools/job-queue.mjs).
359
+
360
+ Waits like any task; on its turn, writes one JSON line to stdout and keeps
361
+ reading stdin, which is a pipe from the Node process. The pipe closes when
362
+ it exits, in any way, and then the ticket is closed and the flock released.
363
+ """
364
+ # Ctrl-C kills Node; the guardian stays to close the ticket when the pipe closes
365
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
366
+ signal.signal(signal.SIGTERM, signal.SIG_IGN)
367
+ prune()
368
+ ticket = new_ticket(tool, dir, command, pid, guardian=os.getpid())
369
+ heartbeat = Heartbeat(ticket["id"])
370
+ fd = os.open(LOCK, os.O_RDWR | os.O_CREAT, 0o644)
371
+
372
+ def pause(seconds):
373
+ readable, _, _ = select.select([0], [], [], seconds)
374
+ return not (readable and not os.read(0, 4096))
375
+
376
+ waited = wait(ticket, fd, pause)
377
+ if waited is None:
378
+ ticket.update(state="interrupted", ended_at=now())
379
+ write(ticket)
380
+ else:
381
+ begin(ticket, waited)
382
+ print(
383
+ json.dumps({"id": ticket["id"], "file": str(ticket_path(ticket["id"])), "log": str(ROOT / ticket["log"])}),
384
+ flush=True,
385
+ )
386
+ while os.read(0, 4096):
387
+ pass
388
+ close_dead(ticket["id"])
389
+ heartbeat.stop()
390
+ os.close(fd)
391
+
392
+
393
+ def show(as_json):
394
+ tickets = read_all()
395
+ running = sorted((t for t in tickets if t["effective"] == "running"), key=sort_key)
396
+ waiting = sorted((t for t in tickets if t["effective"] == "waiting"), key=sort_key)
397
+ recent = sorted(
398
+ (t for t in tickets if t["effective"] not in ACTIVE), key=lambda t: t.get("ended_at") or "", reverse=True
399
+ )[:10]
400
+ if as_json:
401
+ print(json.dumps({"running": running, "waiting": waiting, "recent": recent}, ensure_ascii=False, indent=1))
402
+ return
403
+ if not running and not waiting:
404
+ print("fila livre")
405
+ for t in running:
406
+ print(f"rodando {summary(t)} há {duration(since(t.get('started_at')))}")
407
+ for n, t in enumerate(waiting, 1):
408
+ print(f"espera {n}. {summary(t)} há {duration(since(t.get('arrived_at')))}")
409
+ for t in recent:
410
+ mark = {"ok": "✓", "error": "✗"}.get(t["effective"], "·")
411
+ took = (
412
+ duration(since(t["started_at"]) - since(t["ended_at"]))
413
+ if t.get("started_at") and t.get("ended_at")
414
+ else "sem rodar"
415
+ )
416
+ print(f"{mark} {t['effective']:<12} {summary(t)} · {took}")
417
+
418
+
419
+ if __name__ == "__main__":
420
+ args = sys.argv[1:]
421
+ if args[:1] == ["guardian"]:
422
+ if len(args) < 5 or args[4] != "--":
423
+ print(__doc__)
424
+ sys.exit(2)
425
+ guardian(args[1], None if args[2] == "-" else args[2], int(args[3]), args[5:])
426
+ elif args in ([], ["--json"]):
427
+ show(as_json=bool(args))
428
+ else:
429
+ print(__doc__)
430
+ sys.exit(2)