@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.
- package/.env.example +41 -0
- package/README.md +2 -2
- package/bin/omni.mjs +14 -0
- package/docs/onboarding/clone-voice.png +0 -0
- package/docs/onboarding/getting-started.png +0 -0
- package/docs/onboarding/new-video.png +0 -0
- package/docs/onboarding/providers.png +0 -0
- package/docs/onboarding/publish.png +0 -0
- package/docs/onboarding/references.png +0 -0
- package/docs/onboarding.md +212 -0
- package/omnivideo.config.example.json +437 -0
- package/package.json +40 -4
- package/reference/captions.md +156 -0
- package/reference/carousel-contract.md +91 -0
- package/reference/cloud.md +96 -0
- package/reference/learnings.md +792 -0
- package/reference/omnichat.md +305 -0
- package/reference/premiere-mcp.md +91 -0
- package/reference/publish.md +305 -0
- package/reference/scenes-contract.md +109 -0
- package/reference/studio.md +666 -0
- package/reference/voicestudio.md +81 -0
- package/reference/writing-for-voice.md +67 -0
- package/requirements.txt +3 -0
- package/studio/api/app.ts +87 -0
- package/studio/api/lib/errors.ts +33 -0
- package/studio/api/lib/files.ts +41 -0
- package/studio/api/lib/hono.ts +9 -0
- package/studio/api/lib/result.ts +14 -0
- package/studio/api/lib/validate.ts +31 -0
- package/studio/api/lib/video.ts +10 -0
- package/studio/api/middleware/json-charset.ts +11 -0
- package/studio/api/middleware/local-only.ts +32 -0
- package/studio/api/middleware/no-timeout.ts +10 -0
- package/studio/api/routes/admin.ts +18 -0
- package/studio/api/routes/agent-cascade.ts +100 -0
- package/studio/api/routes/agent-history.ts +23 -0
- package/studio/api/routes/agent.ts +162 -0
- package/studio/api/routes/analytics.ts +31 -0
- package/studio/api/routes/approval.ts +60 -0
- package/studio/api/routes/archive.ts +12 -0
- package/studio/api/routes/captions.ts +28 -0
- package/studio/api/routes/carousel.ts +38 -0
- package/studio/api/routes/clone.ts +27 -0
- package/studio/api/routes/create.ts +19 -0
- package/studio/api/routes/events.ts +11 -0
- package/studio/api/routes/export.ts +14 -0
- package/studio/api/routes/media.ts +30 -0
- package/studio/api/routes/omnichat.ts +113 -0
- package/studio/api/routes/onboarding.ts +14 -0
- package/studio/api/routes/publish.ts +40 -0
- package/studio/api/routes/queue.ts +14 -0
- package/studio/api/routes/references.ts +40 -0
- package/studio/api/routes/runner.ts +17 -0
- package/studio/api/routes/schedule.ts +47 -0
- package/studio/api/routes/services.ts +11 -0
- package/studio/api/routes/sessions.ts +27 -0
- package/studio/api/routes/settings.ts +102 -0
- package/studio/api/routes/sound.ts +45 -0
- package/studio/api/routes/thumbnail.ts +18 -0
- package/studio/api/routes/usage.ts +13 -0
- package/studio/api/routes/videos.ts +41 -0
- package/studio/api/routes/voices.ts +71 -0
- package/studio/api/services/admin.ts +113 -0
- package/studio/api/services/agent-cascade.ts +975 -0
- package/studio/api/services/agent-claude.ts +47 -0
- package/studio/api/services/agent-history.ts +103 -0
- package/studio/api/services/agent-persona.ts +77 -0
- package/studio/api/services/agent-tools.ts +2237 -0
- package/studio/api/services/agent-ui-tools.ts +259 -0
- package/studio/api/services/agent-wait.ts +32 -0
- package/studio/api/services/agent.ts +553 -0
- package/studio/api/services/analytics.ts +92 -0
- package/studio/api/services/approval.ts +134 -0
- package/studio/api/services/archive.ts +38 -0
- package/studio/api/services/captions.ts +268 -0
- package/studio/api/services/carousel.ts +206 -0
- package/studio/api/services/classify.ts +196 -0
- package/studio/api/services/clone-phrases.ts +75 -0
- package/studio/api/services/clone.ts +722 -0
- package/studio/api/services/create.ts +421 -0
- package/studio/api/services/events.ts +215 -0
- package/studio/api/services/media.ts +32 -0
- package/studio/api/services/omnichat.ts +624 -0
- package/studio/api/services/onboarding.ts +77 -0
- package/studio/api/services/publish.ts +287 -0
- package/studio/api/services/queue.ts +150 -0
- package/studio/api/services/references.ts +269 -0
- package/studio/api/services/root.ts +60 -0
- package/studio/api/services/services.ts +114 -0
- package/studio/api/services/sessions.ts +1007 -0
- package/studio/api/services/settings.ts +983 -0
- package/studio/api/services/sound.ts +311 -0
- package/studio/api/services/thumbnail.ts +133 -0
- package/studio/api/services/usage.ts +27 -0
- package/studio/api/services/videos.ts +418 -0
- package/studio/api/services/voice-pause.ts +98 -0
- package/studio/api/services/voices.ts +324 -0
- package/studio/runner/runner.ts +244 -0
- package/studio/shared/omni-actions.ts +68 -0
- package/studio/shared/omni-admin.ts +52 -0
- package/studio/shared/omni-farewell.ts +48 -0
- package/studio/shared/omni-hotword.ts +20 -0
- package/studio/shared/omni-screens.ts +228 -0
- package/studio/shared/schemas/admin.ts +15 -0
- package/studio/shared/schemas/agent-cascade.ts +58 -0
- package/studio/shared/schemas/agent-history.ts +73 -0
- package/studio/shared/schemas/agent.ts +50 -0
- package/studio/shared/schemas/analytics.ts +6 -0
- package/studio/shared/schemas/approval.ts +22 -0
- package/studio/shared/schemas/captions.ts +19 -0
- package/studio/shared/schemas/carousel.ts +26 -0
- package/studio/shared/schemas/clone.ts +40 -0
- package/studio/shared/schemas/common.ts +16 -0
- package/studio/shared/schemas/omnichat.ts +54 -0
- package/studio/shared/schemas/onboarding.ts +5 -0
- package/studio/shared/schemas/publish.ts +45 -0
- package/studio/shared/schemas/references.ts +20 -0
- package/studio/shared/schemas/settings.ts +82 -0
- package/studio/shared/schemas/sound.ts +22 -0
- package/studio/shared/schemas/videos.ts +38 -0
- package/studio/shared/schemas/voices.ts +23 -0
- package/studio/shared/types/api.ts +1290 -0
- package/studio/studio.ts +148 -0
- package/studio/tsconfig.json +15 -0
- package/studio/web/assets/logos/cartesia.svg +4 -0
- package/studio/web/assets/logos/elevenlabs.svg +12 -0
- package/studio/web/assets/logos/fish.svg +24 -0
- package/studio/web/assets/logos/omni-black.png +0 -0
- package/studio/web/assets/logos/omni-white.png +0 -0
- package/studio/web/assets/logos/voicestudio.svg +21 -0
- package/studio/web/components/AgentMark.tsx +7 -0
- package/studio/web/components/AgentWalking.tsx +11 -0
- package/studio/web/components/ApiKeyField.tsx +104 -0
- package/studio/web/components/AppShell.tsx +64 -0
- package/studio/web/components/ApprovalNotice.tsx +65 -0
- package/studio/web/components/AvatarGroupIcon.tsx +22 -0
- package/studio/web/components/AvatarNameField.tsx +86 -0
- package/studio/web/components/AvatarPicker.tsx +84 -0
- package/studio/web/components/BrandLogo.tsx +16 -0
- package/studio/web/components/CartesiaPlanSetting.tsx +90 -0
- package/studio/web/components/ClaudeWalking.tsx +32 -0
- package/studio/web/components/CloneVoiceDialog/GuidedScript.tsx +39 -0
- package/studio/web/components/CloneVoiceDialog/LanguageBar.tsx +37 -0
- package/studio/web/components/CloneVoiceDialog/LevelMeter.tsx +31 -0
- package/studio/web/components/CloneVoiceDialog/ProviderChoice.tsx +81 -0
- package/studio/web/components/CloneVoiceDialog/QualityPanel.tsx +62 -0
- package/studio/web/components/CloneVoiceDialog/RecordStep.tsx +50 -0
- package/studio/web/components/CloneVoiceDialog/RecorderBar.tsx +79 -0
- package/studio/web/components/CloneVoiceDialog/RedoSummary.tsx +54 -0
- package/studio/web/components/CloneVoiceDialog/SaveStep.tsx +91 -0
- package/studio/web/components/CloneVoiceDialog/SourceBar.tsx +30 -0
- package/studio/web/components/CloneVoiceDialog/index.tsx +141 -0
- package/studio/web/components/CloneVoiceDialog/types.ts +28 -0
- package/studio/web/components/CloneVoiceDialog/useCloneVoice.ts +528 -0
- package/studio/web/components/ConnectionSignals.tsx +51 -0
- package/studio/web/components/EditVoiceDialog/EditVoiceFooter.tsx +72 -0
- package/studio/web/components/EditVoiceDialog/ProviderCards.tsx +81 -0
- package/studio/web/components/EditVoiceDialog/index.tsx +256 -0
- package/studio/web/components/ErrorBanner.tsx +115 -0
- package/studio/web/components/FinishedTask.tsx +41 -0
- package/studio/web/components/FishModelSetting.tsx +69 -0
- package/studio/web/components/FishPriceSetting.tsx +44 -0
- package/studio/web/components/HumanBanner.tsx +64 -0
- package/studio/web/components/Lightbox.tsx +41 -0
- package/studio/web/components/LogStream.tsx +42 -0
- package/studio/web/components/Markdown.tsx +21 -0
- package/studio/web/components/MissingDmsPanel/BatchSend.tsx +137 -0
- package/studio/web/components/MissingDmsPanel/CommentCard.tsx +168 -0
- package/studio/web/components/MissingDmsPanel/CommentFeed.tsx +45 -0
- package/studio/web/components/MissingDmsPanel/DmBubble.tsx +38 -0
- package/studio/web/components/MissingDmsPanel/ReplyComposer.tsx +63 -0
- package/studio/web/components/MissingDmsPanel/index.tsx +98 -0
- package/studio/web/components/NotFound.tsx +48 -0
- package/studio/web/components/Omni/OmniAvatarPicker.tsx +92 -0
- package/studio/web/components/Omni/OmniCards.tsx +202 -0
- package/studio/web/components/Omni/OmniGlow.tsx +183 -0
- package/studio/web/components/Omni/OmniHistory.tsx +119 -0
- package/studio/web/components/Omni/OmniHotword.tsx +40 -0
- package/studio/web/components/Omni/OmniMicSelect.tsx +92 -0
- package/studio/web/components/Omni/OmniVoiceSample.tsx +81 -0
- package/studio/web/components/Omni/OmniWidget.tsx +743 -0
- package/studio/web/components/Omni/PendingActions.tsx +200 -0
- package/studio/web/components/Omni/Transcript.tsx +127 -0
- package/studio/web/components/OmnichatAccountStatus.tsx +87 -0
- package/studio/web/components/OmnichatRulePanel/RuleConfig.tsx +150 -0
- package/studio/web/components/OmnichatRulePanel/RuleDryRun.tsx +15 -0
- package/studio/web/components/OmnichatRulePanel/RuleEnableConfirm.tsx +42 -0
- package/studio/web/components/OmnichatRulePanel/RuleSkeleton.tsx +18 -0
- package/studio/web/components/OmnichatRulePanel/RuleStatus.tsx +55 -0
- package/studio/web/components/OmnichatRulePanel/index.tsx +115 -0
- package/studio/web/components/OmnichatRulePanel/rule-steps.ts +67 -0
- package/studio/web/components/OmnichatRulePanel/steps/MessageStep.tsx +91 -0
- package/studio/web/components/OmnichatRulePanel/steps/RepliesStep.tsx +201 -0
- package/studio/web/components/OmnichatRulePanel/steps/TriggerStep.tsx +76 -0
- package/studio/web/components/OmnichatRulePanel/steps/types.ts +11 -0
- package/studio/web/components/Palette.tsx +17 -0
- package/studio/web/components/PlatformCard.tsx +109 -0
- package/studio/web/components/PlatformIcon.tsx +19 -0
- package/studio/web/components/ProviderCard.tsx +80 -0
- package/studio/web/components/ProviderDetail.tsx +367 -0
- package/studio/web/components/ProviderLogo.tsx +20 -0
- package/studio/web/components/QueuePanel.tsx +77 -0
- package/studio/web/components/ReferenceActions.tsx +89 -0
- package/studio/web/components/ReferencePicker.tsx +136 -0
- package/studio/web/components/ResultMessage.tsx +7 -0
- package/studio/web/components/RuleForm.tsx +138 -0
- package/studio/web/components/RuleLog.tsx +151 -0
- package/studio/web/components/RunningTask.tsx +23 -0
- package/studio/web/components/SampleList.tsx +55 -0
- package/studio/web/components/SessionPanel.tsx +352 -0
- package/studio/web/components/SettingRow.tsx +14 -0
- package/studio/web/components/SettingsCategorySkeleton.tsx +25 -0
- package/studio/web/components/SidePanel.tsx +77 -0
- package/studio/web/components/Sidebar.tsx +289 -0
- package/studio/web/components/StageStepper.tsx +87 -0
- package/studio/web/components/StudioOffline.tsx +80 -0
- package/studio/web/components/TaskName.tsx +17 -0
- package/studio/web/components/TopBanners.tsx +30 -0
- package/studio/web/components/VideoOmnichatPanel.tsx +287 -0
- package/studio/web/components/VoiceAvatar.tsx +33 -0
- package/studio/web/components/VoiceCarousel.tsx +196 -0
- package/studio/web/components/VoiceCategory.tsx +9 -0
- package/studio/web/components/VoiceModal.tsx +39 -0
- package/studio/web/components/VoiceProfileCard.tsx +75 -0
- package/studio/web/components/VoiceProfilePicker.tsx +107 -0
- package/studio/web/components/VoiceProfilesSettings.tsx +150 -0
- package/studio/web/components/VoiceProviderCard.tsx +118 -0
- package/studio/web/components/VoiceProvidersCard.tsx +26 -0
- package/studio/web/components/WaitList.tsx +21 -0
- package/studio/web/components/icons.tsx +247 -0
- package/studio/web/components/ui/accordion.tsx +47 -0
- package/studio/web/components/ui/badge.tsx +18 -0
- package/studio/web/components/ui/calendar.tsx +43 -0
- package/studio/web/components/ui/chart.tsx +229 -0
- package/studio/web/components/ui/checkbox.tsx +26 -0
- package/studio/web/components/ui/cx.ts +2 -0
- package/studio/web/components/ui/date-time-picker.tsx +84 -0
- package/studio/web/components/ui/panel.tsx +23 -0
- package/studio/web/components/ui/picker.tsx +58 -0
- package/studio/web/components/ui/popover.tsx +24 -0
- package/studio/web/components/ui/select.tsx +89 -0
- package/studio/web/components/ui/skeleton.tsx +10 -0
- package/studio/web/components/ui/slider.tsx +68 -0
- package/studio/web/components/ui/switch.tsx +13 -0
- package/studio/web/components/ui/tooltip.test.tsx +30 -0
- package/studio/web/components/ui/tooltip.tsx +59 -0
- package/studio/web/hooks/useAccounts.ts +27 -0
- package/studio/web/hooks/useAdmin.ts +12 -0
- package/studio/web/hooks/useAnalytics.ts +21 -0
- package/studio/web/hooks/useAvatarAudioReactive.ts +87 -0
- package/studio/web/hooks/useBrowserConnect.ts +48 -0
- package/studio/web/hooks/useClonePlan.ts +14 -0
- package/studio/web/hooks/useConnected.ts +7 -0
- package/studio/web/hooks/useLiveQuery.ts +29 -0
- package/studio/web/hooks/useMenuCollapsed.ts +32 -0
- package/studio/web/hooks/useMicDevices.ts +53 -0
- package/studio/web/hooks/useMissingDms.ts +211 -0
- package/studio/web/hooks/useMissingSetupSteps.ts +26 -0
- package/studio/web/hooks/useNow.ts +11 -0
- package/studio/web/hooks/useOmniAgent.ts +278 -0
- package/studio/web/hooks/useOmniClaude.ts +124 -0
- package/studio/web/hooks/useOmniDock.ts +142 -0
- package/studio/web/hooks/useOmniGreeting.ts +60 -0
- package/studio/web/hooks/useOmniHotword.ts +128 -0
- package/studio/web/hooks/useOmnichat.ts +26 -0
- package/studio/web/hooks/useOnboarding.ts +27 -0
- package/studio/web/hooks/usePost.ts +23 -0
- package/studio/web/hooks/useQueue.ts +10 -0
- package/studio/web/hooks/useReferences.ts +20 -0
- package/studio/web/hooks/useRuleEditor.ts +101 -0
- package/studio/web/hooks/useRunner.ts +24 -0
- package/studio/web/hooks/useSaveAction.ts +23 -0
- package/studio/web/hooks/useSchedule.ts +11 -0
- package/studio/web/hooks/useServices.ts +10 -0
- package/studio/web/hooks/useSessions.ts +15 -0
- package/studio/web/hooks/useSettings.ts +11 -0
- package/studio/web/hooks/useSettingsSection.ts +30 -0
- package/studio/web/hooks/useSidePanel.ts +23 -0
- package/studio/web/hooks/useSound.ts +30 -0
- package/studio/web/hooks/useUsage.ts +11 -0
- package/studio/web/hooks/useVideoVoice.ts +36 -0
- package/studio/web/hooks/useVideos.ts +21 -0
- package/studio/web/hooks/useVoiceProfilePreview.ts +52 -0
- package/studio/web/hooks/useVoiceProfiles.ts +7 -0
- package/studio/web/hooks/useVoiceRunning.ts +10 -0
- package/studio/web/hooks/useVoiceStudioClosed.ts +8 -0
- package/studio/web/index.html +19 -0
- package/studio/web/lib/agent.ts +7 -0
- package/studio/web/lib/alerts.ts +210 -0
- package/studio/web/lib/analytics-metrics.ts +351 -0
- package/studio/web/lib/api.ts +70 -0
- package/studio/web/lib/audio.ts +39 -0
- package/studio/web/lib/avatars.ts +41 -0
- package/studio/web/lib/calendar.ts +218 -0
- package/studio/web/lib/caption-position.ts +249 -0
- package/studio/web/lib/carousel.ts +24 -0
- package/studio/web/lib/chime.ts +42 -0
- package/studio/web/lib/clone-voice.ts +55 -0
- package/studio/web/lib/export.ts +11 -0
- package/studio/web/lib/files.ts +8 -0
- package/studio/web/lib/format.ts +93 -0
- package/studio/web/lib/media.ts +29 -0
- package/studio/web/lib/mic-device.ts +72 -0
- package/studio/web/lib/missing-dms.ts +70 -0
- package/studio/web/lib/omni-cards.ts +42 -0
- package/studio/web/lib/omni-cascade.ts +1067 -0
- package/studio/web/lib/omni-greeting-audio.ts +24 -0
- package/studio/web/lib/omni-hotword.ts +231 -0
- package/studio/web/lib/omni-panel.ts +67 -0
- package/studio/web/lib/omni-realtime.ts +811 -0
- package/studio/web/lib/omni-ui.ts +402 -0
- package/studio/web/lib/omni-wait.ts +174 -0
- package/studio/web/lib/omnichat-api.ts +44 -0
- package/studio/web/lib/orb.ts +270 -0
- package/studio/web/lib/pcm-recorder.ts +115 -0
- package/studio/web/lib/platforms.ts +17 -0
- package/studio/web/lib/providers.ts +27 -0
- package/studio/web/lib/publish-draft.ts +52 -0
- package/studio/web/lib/publish-settings.ts +81 -0
- package/studio/web/lib/query-client.ts +122 -0
- package/studio/web/lib/references.ts +13 -0
- package/studio/web/lib/services.ts +40 -0
- package/studio/web/lib/sessions.ts +103 -0
- package/studio/web/lib/settings-categories.ts +58 -0
- package/studio/web/lib/sound-audio.ts +33 -0
- package/studio/web/lib/sound-timeline.ts +291 -0
- package/studio/web/lib/sse.ts +86 -0
- package/studio/web/lib/stage-history.ts +141 -0
- package/studio/web/lib/stages.ts +344 -0
- package/studio/web/lib/studio-offline.ts +60 -0
- package/studio/web/lib/upload.ts +24 -0
- package/studio/web/lib/video-tabs.ts +118 -0
- package/studio/web/lib/voice-profile-draft.ts +34 -0
- package/studio/web/lib/voice-profiles.ts +3 -0
- package/studio/web/main.tsx +14 -0
- package/studio/web/pages/AdminPage/index.tsx +123 -0
- package/studio/web/pages/AgentPage/components/Measurements.tsx +102 -0
- package/studio/web/pages/AgentPage/index.tsx +28 -0
- package/studio/web/pages/AnalyticsPage/components/About.tsx +52 -0
- package/studio/web/pages/AnalyticsPage/components/ChannelCards.tsx +106 -0
- package/studio/web/pages/AnalyticsPage/components/ChannelView.tsx +137 -0
- package/studio/web/pages/AnalyticsPage/components/CollectionStatus.tsx +46 -0
- package/studio/web/pages/AnalyticsPage/components/Comparison.tsx +68 -0
- package/studio/web/pages/AnalyticsPage/components/Diagnosis.tsx +50 -0
- package/studio/web/pages/AnalyticsPage/components/Highlights.tsx +49 -0
- package/studio/web/pages/AnalyticsPage/components/Hint.tsx +11 -0
- package/studio/web/pages/AnalyticsPage/components/HookVsBody.tsx +100 -0
- package/studio/web/pages/AnalyticsPage/components/HourlyWave.tsx +133 -0
- package/studio/web/pages/AnalyticsPage/components/Insights.tsx +70 -0
- package/studio/web/pages/AnalyticsPage/components/MetricCard.tsx +46 -0
- package/studio/web/pages/AnalyticsPage/components/PlanSection.tsx +40 -0
- package/studio/web/pages/AnalyticsPage/components/ProgressionChart.tsx +155 -0
- package/studio/web/pages/AnalyticsPage/components/ReelBarsChart.tsx +154 -0
- package/studio/web/pages/AnalyticsPage/components/ReelThumb.tsx +19 -0
- package/studio/web/pages/AnalyticsPage/components/ReelView.tsx +290 -0
- package/studio/web/pages/AnalyticsPage/components/ReelsTable.tsx +269 -0
- package/studio/web/pages/AnalyticsPage/components/RequestWithFocus.tsx +38 -0
- package/studio/web/pages/AnalyticsPage/components/SegmentedControl.tsx +35 -0
- package/studio/web/pages/AnalyticsPage/components/VsMedian.tsx +31 -0
- package/studio/web/pages/AnalyticsPage/index.tsx +17 -0
- package/studio/web/pages/GettingStartedPage/index.tsx +174 -0
- package/studio/web/pages/NewVideoPage/components/DeliverablesPicker.tsx +62 -0
- package/studio/web/pages/NewVideoPage/components/FormatPicker.tsx +33 -0
- package/studio/web/pages/NewVideoPage/index.tsx +133 -0
- package/studio/web/pages/NotFoundPage/index.tsx +9 -0
- package/studio/web/pages/OmnichatPage/components/NewFromLink.tsx +59 -0
- package/studio/web/pages/OmnichatPage/components/RuleListItem.tsx +64 -0
- package/studio/web/pages/OmnichatPage/components/RulePreview.tsx +14 -0
- package/studio/web/pages/OmnichatPage/components/RuleView.tsx +88 -0
- package/studio/web/pages/OmnichatPage/index.tsx +153 -0
- package/studio/web/pages/QueuePage/index.tsx +22 -0
- package/studio/web/pages/ReferencePage/components/ReferenceTexts.tsx +131 -0
- package/studio/web/pages/ReferencePage/index.tsx +216 -0
- package/studio/web/pages/ReferencesPage/components/NewReferenceForm.tsx +150 -0
- package/studio/web/pages/ReferencesPage/components/ReferenceCard.tsx +42 -0
- package/studio/web/pages/ReferencesPage/components/ReferenceGridSkeleton.tsx +31 -0
- package/studio/web/pages/ReferencesPage/index.tsx +67 -0
- package/studio/web/pages/SchedulePage/components/CalendarSkeleton.tsx +18 -0
- package/studio/web/pages/SchedulePage/components/DayView.tsx +177 -0
- package/studio/web/pages/SchedulePage/components/HourPicker.tsx +103 -0
- package/studio/web/pages/SchedulePage/components/MonthDayCell.tsx +111 -0
- package/studio/web/pages/SchedulePage/components/MonthView.tsx +47 -0
- package/studio/web/pages/SchedulePage/components/PlatformIcons.tsx +12 -0
- package/studio/web/pages/SchedulePage/components/PostThumb.tsx +15 -0
- package/studio/web/pages/SchedulePage/components/WeekColumn.tsx +75 -0
- package/studio/web/pages/SchedulePage/components/WeekView.tsx +35 -0
- package/studio/web/pages/SchedulePage/index.tsx +285 -0
- package/studio/web/pages/SettingsPage/components/AgentAccounts.tsx +263 -0
- package/studio/web/pages/SettingsPage/components/AgentCategory.tsx +20 -0
- package/studio/web/pages/SettingsPage/components/ModelPicker.tsx +49 -0
- package/studio/web/pages/SettingsPage/components/ModelsSettings.tsx +138 -0
- package/studio/web/pages/SettingsPage/components/PlatformsCategory.tsx +23 -0
- package/studio/web/pages/SettingsPage/components/PublishingCategory/AccountStatus.tsx +29 -0
- package/studio/web/pages/SettingsPage/components/PublishingCategory/AutoPublishPanel.tsx +90 -0
- package/studio/web/pages/SettingsPage/components/PublishingCategory/BrowserConnect.tsx +62 -0
- package/studio/web/pages/SettingsPage/components/PublishingCategory/CheckField.tsx +19 -0
- package/studio/web/pages/SettingsPage/components/PublishingCategory/InstagramPanel.tsx +113 -0
- package/studio/web/pages/SettingsPage/components/PublishingCategory/OmnichatSettingsPanel.tsx +146 -0
- package/studio/web/pages/SettingsPage/components/PublishingCategory/SaveBar.tsx +33 -0
- package/studio/web/pages/SettingsPage/components/PublishingCategory/SchedulePanel.tsx +132 -0
- package/studio/web/pages/SettingsPage/components/PublishingCategory/SettingsCard.tsx +47 -0
- package/studio/web/pages/SettingsPage/components/PublishingCategory/SettingsCardGroup.tsx +19 -0
- package/studio/web/pages/SettingsPage/components/PublishingCategory/TextPanel.tsx +55 -0
- package/studio/web/pages/SettingsPage/components/PublishingCategory/TiktokPanel.tsx +123 -0
- package/studio/web/pages/SettingsPage/components/PublishingCategory/YoutubePanel.tsx +100 -0
- package/studio/web/pages/SettingsPage/components/PublishingCategory/badges.tsx +15 -0
- package/studio/web/pages/SettingsPage/components/PublishingCategory/index.tsx +40 -0
- package/studio/web/pages/SettingsPage/components/PublishingCategory/types.ts +10 -0
- package/studio/web/pages/SettingsPage/components/UsageCategory.tsx +389 -0
- package/studio/web/pages/SettingsPage/components/VoiceProvidersCategory.tsx +97 -0
- package/studio/web/pages/SettingsPage/index.tsx +85 -0
- package/studio/web/pages/VideoPage/components/AudioPlayer.tsx +113 -0
- package/studio/web/pages/VideoPage/components/CaptionPositionEditor/AxisField.tsx +50 -0
- package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionList.tsx +56 -0
- package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionListItem.tsx +92 -0
- package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionMargins.tsx +57 -0
- package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionStyleFields.tsx +70 -0
- package/studio/web/pages/VideoPage/components/CaptionPositionEditor/CaptionTextForm.tsx +87 -0
- package/studio/web/pages/VideoPage/components/CaptionPositionEditor/ColorField.tsx +37 -0
- package/studio/web/pages/VideoPage/components/CaptionPositionEditor/MarginField.tsx +59 -0
- package/studio/web/pages/VideoPage/components/CaptionPositionEditor/NumberField.tsx +57 -0
- package/studio/web/pages/VideoPage/components/CaptionPositionEditor/PositionControls.tsx +140 -0
- package/studio/web/pages/VideoPage/components/CaptionPositionEditor/captionEditor.ts +215 -0
- package/studio/web/pages/VideoPage/components/CaptionPositionEditor/useCaptionPosition.ts +154 -0
- package/studio/web/pages/VideoPage/components/CarouselChangeRequest.tsx +72 -0
- package/studio/web/pages/VideoPage/components/CarouselCompositionStage.tsx +80 -0
- package/studio/web/pages/VideoPage/components/CarouselCreate.tsx +63 -0
- package/studio/web/pages/VideoPage/components/CarouselMissing.tsx +19 -0
- package/studio/web/pages/VideoPage/components/CarouselPublishStage.tsx +173 -0
- package/studio/web/pages/VideoPage/components/CarouselScriptStage.tsx +82 -0
- package/studio/web/pages/VideoPage/components/CarouselSlideCard.tsx +49 -0
- package/studio/web/pages/VideoPage/components/CarouselViewer.tsx +55 -0
- package/studio/web/pages/VideoPage/components/ChangedVoices.tsx +53 -0
- package/studio/web/pages/VideoPage/components/CompositionStage.tsx +78 -0
- package/studio/web/pages/VideoPage/components/CoverPicker.tsx +32 -0
- package/studio/web/pages/VideoPage/components/EditorStage.tsx +53 -0
- package/studio/web/pages/VideoPage/components/LineTakes.tsx +160 -0
- package/studio/web/pages/VideoPage/components/LineTextEditor.tsx +124 -0
- package/studio/web/pages/VideoPage/components/LineVoicePicker.tsx +37 -0
- package/studio/web/pages/VideoPage/components/MediaImage.tsx +25 -0
- package/studio/web/pages/VideoPage/components/MotionScriptStage.tsx +39 -0
- package/studio/web/pages/VideoPage/components/NarrationStage.tsx +70 -0
- package/studio/web/pages/VideoPage/components/PublishForm.tsx +387 -0
- package/studio/web/pages/VideoPage/components/PublishStage.tsx +76 -0
- package/studio/web/pages/VideoPage/components/PublishText.tsx +149 -0
- package/studio/web/pages/VideoPage/components/ScenesApprovalPanel.tsx +278 -0
- package/studio/web/pages/VideoPage/components/ScheduledInfo.tsx +72 -0
- package/studio/web/pages/VideoPage/components/ScriptApprovalPanel.tsx +189 -0
- package/studio/web/pages/VideoPage/components/ScriptStage.tsx +17 -0
- package/studio/web/pages/VideoPage/components/SequencePlayer.tsx +146 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/AddSound.tsx +117 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/AreaSelection.tsx +74 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/BedPanel.tsx +94 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/BedRow.tsx +45 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/CaptionOverlay.tsx +44 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/CaptionSaveBar.tsx +32 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/CaptionsPanel.tsx +22 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/ClipBlock.tsx +111 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/ClipPanel.tsx +76 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/CutsPanel.tsx +44 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/EditorTabBar.tsx +73 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/EffectChoice.tsx +46 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/EffectItem.tsx +89 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/EffectPanel.tsx +232 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/EffectsRow.tsx +21 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/Lane.tsx +39 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/MarkerRow.tsx +40 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/MuteButton.tsx +36 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/NumberInput.tsx +37 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/PlanWarnings.tsx +19 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/PlayerResizeHandle.tsx +50 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/PreviewControls.tsx +51 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/SceneRow.tsx +54 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/SettingsPanel.tsx +65 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/SoundLibrary.tsx +99 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/SoundPreview.tsx +82 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/SpeedPanel.tsx +128 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineDock.tsx +56 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineFooter.tsx +26 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineSkeleton.tsx +21 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineToolbar.tsx +160 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineTracks.tsx +121 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/TrackSidebar.tsx +30 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/VoiceRow.tsx +53 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/Waveform.tsx +97 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/clipActions.ts +50 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/effectActions.ts +161 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/index.tsx +161 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/timelineGeometry.ts +45 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/types.ts +43 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/useAutosave.ts +107 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/useEditorShortcuts.ts +168 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/useFitHeight.ts +40 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/useFollowPlayhead.ts +21 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/useListenMix.ts +43 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/usePlayback.ts +231 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/usePlayerWidth.ts +27 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/useSoundData.ts +124 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/useSoundTimeline.ts +511 -0
- package/studio/web/pages/VideoPage/components/StageDrawer/CompareVersions.tsx +111 -0
- package/studio/web/pages/VideoPage/components/StageDrawer/VersionCard.tsx +81 -0
- package/studio/web/pages/VideoPage/components/StageDrawer/VersionMedia.tsx +24 -0
- package/studio/web/pages/VideoPage/components/StageDrawer/VersionText.tsx +15 -0
- package/studio/web/pages/VideoPage/components/StageDrawer/index.tsx +109 -0
- package/studio/web/pages/VideoPage/components/StageProcess.tsx +126 -0
- package/studio/web/pages/VideoPage/components/StageResult.tsx +69 -0
- package/studio/web/pages/VideoPage/components/SwapReference.tsx +92 -0
- package/studio/web/pages/VideoPage/components/ThumbnailPanel.tsx +252 -0
- package/studio/web/pages/VideoPage/components/TimingPanel.tsx +89 -0
- package/studio/web/pages/VideoPage/components/VideoPlayer.tsx +18 -0
- package/studio/web/pages/VideoPage/components/VideoSkeleton.tsx +50 -0
- package/studio/web/pages/VideoPage/components/VideoVoiceInfo.tsx +33 -0
- package/studio/web/pages/VideoPage/components/VideoVoicePicker.tsx +165 -0
- package/studio/web/pages/VideoPage/components/VoicePauseBanner.tsx +56 -0
- package/studio/web/pages/VideoPage/components/WithLogo.tsx +10 -0
- package/studio/web/pages/VideoPage/index.tsx +295 -0
- package/studio/web/pages/VideosPage/components/PublishedOn.tsx +85 -0
- package/studio/web/pages/VideosPage/components/ServicesPanel.tsx +81 -0
- package/studio/web/pages/VideosPage/components/SessionsPanel.tsx +62 -0
- package/studio/web/pages/VideosPage/components/VideoListSkeleton.tsx +25 -0
- package/studio/web/pages/VideosPage/components/VideoRow.tsx +122 -0
- package/studio/web/pages/VideosPage/index.tsx +134 -0
- package/studio/web/pages/VoicePage/index.tsx +28 -0
- package/studio/web/router.tsx +201 -0
- package/studio/web/styles/base/elements.css +55 -0
- package/studio/web/styles/base/utilities.css +25 -0
- package/studio/web/styles/components/badge.css +33 -0
- package/studio/web/styles/components/claude-running.css +27 -0
- package/studio/web/styles/components/claude-session.css +212 -0
- package/studio/web/styles/components/claude-walking.css +92 -0
- package/studio/web/styles/components/clone-voice-save.css +136 -0
- package/studio/web/styles/components/clone-voice.css +379 -0
- package/studio/web/styles/components/controls.css +70 -0
- package/studio/web/styles/components/data-and-image.css +54 -0
- package/studio/web/styles/components/forms.css +132 -0
- package/studio/web/styles/components/human-banner.css +99 -0
- package/studio/web/styles/components/lightbox.css +50 -0
- package/studio/web/styles/components/markdown.css +68 -0
- package/studio/web/styles/components/modal.css +70 -0
- package/studio/web/styles/components/omni-cards.css +105 -0
- package/studio/web/styles/components/omni.css +859 -0
- package/studio/web/styles/components/omnichat.css +538 -0
- package/studio/web/styles/components/panel.css +115 -0
- package/studio/web/styles/components/platform-card.css +140 -0
- package/studio/web/styles/components/provider-card.css +132 -0
- package/studio/web/styles/components/queue.css +185 -0
- package/studio/web/styles/components/reference-actions.css +133 -0
- package/studio/web/styles/components/reference-picker.css +146 -0
- package/studio/web/styles/components/results-drawer.css +224 -0
- package/studio/web/styles/components/segmented.css +115 -0
- package/studio/web/styles/components/side-panel-tab.css +23 -0
- package/studio/web/styles/components/status-signals.css +66 -0
- package/studio/web/styles/components/stepper.css +208 -0
- package/studio/web/styles/components/table.css +19 -0
- package/studio/web/styles/components/tabs.css +24 -0
- package/studio/web/styles/components/ui/accordion.css +71 -0
- package/studio/web/styles/components/ui/chart.css +162 -0
- package/studio/web/styles/components/ui/checkbox.css +37 -0
- package/studio/web/styles/components/ui/date-picker.css +153 -0
- package/studio/web/styles/components/ui/select.css +110 -0
- package/studio/web/styles/components/ui/skeleton.css +25 -0
- package/studio/web/styles/components/ui/slider.css +81 -0
- package/studio/web/styles/components/ui/switch.css +41 -0
- package/studio/web/styles/components/ui/tooltip.css +24 -0
- package/studio/web/styles/components/voice-carousel.css +255 -0
- package/studio/web/styles/components/voice-editor.css +179 -0
- package/studio/web/styles/components/voice-grid.css +56 -0
- package/studio/web/styles/components/voice-profiles.css +192 -0
- package/studio/web/styles/design-system.md +136 -0
- package/studio/web/styles/index.css +116 -0
- package/studio/web/styles/layout/page.css +41 -0
- package/studio/web/styles/layout/sidebar-groups.css +39 -0
- package/studio/web/styles/layout/sidebar.css +111 -0
- package/studio/web/styles/pages/agent.css +26 -0
- package/studio/web/styles/pages/analytics/dashboard.css +614 -0
- package/studio/web/styles/pages/analytics/insights.css +48 -0
- package/studio/web/styles/pages/getting-started.css +166 -0
- package/studio/web/styles/pages/new-video/form.css +32 -0
- package/studio/web/styles/pages/not-found.css +59 -0
- package/studio/web/styles/pages/references.css +204 -0
- package/studio/web/styles/pages/schedule/calendar.css +541 -0
- package/studio/web/styles/pages/schedule/chip-states.css +60 -0
- package/studio/web/styles/pages/settings/models.css +72 -0
- package/studio/web/styles/pages/settings/shell.css +361 -0
- package/studio/web/styles/pages/settings/usage.css +63 -0
- package/studio/web/styles/pages/studio-offline.css +105 -0
- package/studio/web/styles/pages/video/caption-controls.css +194 -0
- package/studio/web/styles/pages/video/captions-grid.css +70 -0
- package/studio/web/styles/pages/video/captions.css +79 -0
- package/studio/web/styles/pages/video/editor-chat.css +103 -0
- package/studio/web/styles/pages/video/export.css +50 -0
- package/studio/web/styles/pages/video/frames.css +29 -0
- package/studio/web/styles/pages/video/fullscreen-stage.css +400 -0
- package/studio/web/styles/pages/video/instagram-carousel.css +309 -0
- package/studio/web/styles/pages/video/layout.css +285 -0
- package/studio/web/styles/pages/video/narration.css +138 -0
- package/studio/web/styles/pages/video/preview-overview.css +11 -0
- package/studio/web/styles/pages/video/processing-notice.css +34 -0
- package/studio/web/styles/pages/video/publish-when.css +76 -0
- package/studio/web/styles/pages/video/publish.css +97 -0
- package/studio/web/styles/pages/video/result.css +96 -0
- package/studio/web/styles/pages/video/scenes.css +167 -0
- package/studio/web/styles/pages/video/sound-timeline.css +672 -0
- package/studio/web/styles/pages/video/sound-toolbar.css +149 -0
- package/studio/web/styles/pages/video/take-player.css +100 -0
- package/studio/web/styles/pages/video/thumbnail.css +157 -0
- package/studio/web/styles/pages/video/timeline.css +117 -0
- package/studio/web/styles/pages/video/voice-picker.css +14 -0
- package/studio/web/styles/pages/video-list/active-row.css +27 -0
- package/studio/web/styles/pages/video-list/card-link.css +49 -0
- package/studio/web/styles/pages/video-list/list.css +86 -0
- package/studio/web/styles/theme/identity/details.css +204 -0
- package/studio/web/styles/theme/identity/shell.css +249 -0
- package/studio/web/styles/theme/identity/surfaces.css +183 -0
- package/studio/web/styles/theme/identity/titles-and-buttons.css +120 -0
- package/studio/web/styles/theme/no-borders/blocks.css +208 -0
- package/studio/web/styles/theme/no-borders/controls.css +256 -0
- package/studio/web/styles/tokens.css +245 -0
- package/studio/web/types/api.ts +2 -0
- package/studio/web/types/assets.d.ts +9 -0
- package/templates/brief.md +27 -0
- package/templates/captions.html +204 -0
- package/templates/carousel-composition.md +47 -0
- package/templates/carousel-script.md +60 -0
- package/templates/components/voiceavatar/README.md +87 -0
- package/templates/components/voiceavatar/demo.html +50 -0
- package/templates/components/voiceavatar/voiceavatar.js +415 -0
- package/templates/motion.md +80 -0
- package/templates/playbook.md +43 -0
- package/templates/reference.md +123 -0
- package/templates/scene.html +112 -0
- package/templates/scenes.example.json +96 -0
- package/templates/script.md +63 -0
- package/templates/slide.html +71 -0
- package/templates/wireframe.html +91 -0
- package/tools/analysis.mjs +300 -0
- package/tools/approval.mjs +442 -0
- package/tools/assemble.mjs +942 -0
- package/tools/build-motion.mjs +437 -0
- package/tools/captions.mjs +246 -0
- package/tools/carousel.mjs +558 -0
- package/tools/compose.mjs +307 -0
- package/tools/cuts.mjs +74 -0
- package/tools/editor.mjs +419 -0
- package/tools/epidemic-capture.js +54 -0
- package/tools/fanout.mjs +69 -0
- package/tools/history.mjs +70 -0
- package/tools/history.py +61 -0
- package/tools/job-queue.mjs +180 -0
- package/tools/job_queue.py +430 -0
- package/tools/lib/agents.mjs +412 -0
- package/tools/lib/channel.mjs +399 -0
- package/tools/lib/claude-settings.mjs +67 -0
- package/tools/lib/cli.mjs +9 -0
- package/tools/lib/fanout.mjs +234 -0
- package/tools/lib/http.mjs +94 -0
- package/tools/lib/index.js +14 -0
- package/tools/lib/mcp-server.mjs +69 -0
- package/tools/lib/metrics/metrics.mjs +204 -0
- package/tools/lib/narration.mjs +15 -0
- package/tools/lib/oauth-client.mjs +295 -0
- package/tools/lib/omnichat/config.mjs +139 -0
- package/tools/lib/omnichat/errors.mjs +42 -0
- package/tools/lib/omnichat/extract.mjs +82 -0
- package/tools/lib/omnichat/gate.mjs +151 -0
- package/tools/lib/omnichat/manual.mjs +166 -0
- package/tools/lib/omnichat/match.mjs +78 -0
- package/tools/lib/omnichat/pass.mjs +393 -0
- package/tools/lib/omnichat/record.mjs +208 -0
- package/tools/lib/omnichat/text.mjs +315 -0
- package/tools/lib/omnichat/webhook.mjs +166 -0
- package/tools/lib/pairing.mjs +99 -0
- package/tools/lib/paths.mjs +41 -0
- package/tools/lib/platforms/instagram.mjs +469 -0
- package/tools/lib/platforms/tiktok.mjs +343 -0
- package/tools/lib/platforms/youtube.mjs +228 -0
- package/tools/lib/publish/publish.mjs +244 -0
- package/tools/lib/schedule/schedule.mjs +191 -0
- package/tools/lib/service.mjs +134 -0
- package/tools/lib/skill-client.mjs +126 -0
- package/tools/lib/skills-release.mjs +196 -0
- package/tools/lib/testing.mjs +52 -0
- package/tools/lib/up.mjs +187 -0
- package/tools/metrics.mjs +415 -0
- package/tools/models.mjs +134 -0
- package/tools/narrate.mjs +257 -0
- package/tools/omni.mjs +298 -0
- package/tools/omni_paths.py +12 -0
- package/tools/omnichat.mjs +1577 -0
- package/tools/omnivideo.py +1122 -0
- package/tools/package.mjs +130 -0
- package/tools/platforms.mjs +236 -0
- package/tools/premiere.mjs +533 -0
- package/tools/publish.mjs +1496 -0
- package/tools/reference.mjs +1408 -0
- package/tools/render.mjs +426 -0
- package/tools/runner.mjs +156 -0
- package/tools/setup.mjs +466 -0
- package/tools/skills-release.mjs +93 -0
- package/tools/sound.mjs +1323 -0
- package/tools/soundtrack.mjs +217 -0
- package/tools/thumbnail.mjs +271 -0
- package/tools/timing.mjs +108 -0
- package/tools/usage.py +628 -0
- package/tools/voice-envelope.mjs +185 -0
- package/tools/voice.py +448 -0
- package/tools/voice_sample.py +75 -0
- package/tools/wireframe.mjs +230 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/* `omni skill <name>`: what a plugin header's `!` line runs (docs/specs/phase-2-plugin-cli.md, step 3).
|
|
2
|
+
It asks the registry for one part of the skill's body with this machine's login, writes the skill's
|
|
3
|
+
sibling files to a temporary folder, fills the release's placeholders ({{OMNI_CODE}} with the code
|
|
4
|
+
root, {{OMNI_SKILLS}}/<name>/ with that folder) and returns the text the model reads.
|
|
5
|
+
|
|
6
|
+
Whatever happens, it returns text and never fails: Claude Code puts the output where the skill's
|
|
7
|
+
instructions go, so a refusal has to be readable instructions too ("pare e avise o usuário"). No
|
|
8
|
+
network, no skill: there is no offline copy.
|
|
9
|
+
|
|
10
|
+
The folder is $TMPDIR/omni-skills/<name>@<hash>/ (0700, files 0600). It goes away on a refusal for
|
|
11
|
+
that skill and after an hour unused, swept on every call. */
|
|
12
|
+
|
|
13
|
+
import { chmodSync, mkdirSync, readdirSync, rmSync, statSync, utimesSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { dirname, join, normalize, relative } from "node:path";
|
|
16
|
+
|
|
17
|
+
export const UNUSED_MS = 60 * 60_000;
|
|
18
|
+
|
|
19
|
+
const stop = (/** @type {string} */ why) =>
|
|
20
|
+
`Pare aqui: não siga esta skill nem a refaça de memória. Avise o usuário, em português: ${why}`;
|
|
21
|
+
|
|
22
|
+
/** @param {string} base @param {() => number} now */
|
|
23
|
+
function sweep(base, now) {
|
|
24
|
+
let names = [];
|
|
25
|
+
try {
|
|
26
|
+
names = readdirSync(base);
|
|
27
|
+
} catch {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
for (const n of names) {
|
|
31
|
+
const dir = join(base, n);
|
|
32
|
+
try {
|
|
33
|
+
if (now() - statSync(dir).mtimeMs > UNUSED_MS) rmSync(dir, { recursive: true, force: true });
|
|
34
|
+
} catch {
|
|
35
|
+
/* gone already */
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** removes every folder of one skill (a refusal) @param {string} base @param {string} name */
|
|
41
|
+
function wipe(base, name) {
|
|
42
|
+
try {
|
|
43
|
+
for (const n of readdirSync(base))
|
|
44
|
+
if (n.startsWith(`${name}@`)) rmSync(join(base, n), { recursive: true, force: true });
|
|
45
|
+
} catch {
|
|
46
|
+
/* nothing there */
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** writes the files into the skill's folder, refusing any path that leaves it
|
|
51
|
+
* @param {string} dir @param {Record<string, string>} files */
|
|
52
|
+
function materialize(dir, files) {
|
|
53
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
54
|
+
chmodSync(dir, 0o700);
|
|
55
|
+
for (const [path, text] of Object.entries(files)) {
|
|
56
|
+
const target = normalize(join(dir, path));
|
|
57
|
+
if (relative(dir, target).startsWith("..")) continue;
|
|
58
|
+
mkdirSync(dirname(target), { recursive: true, mode: 0o700 });
|
|
59
|
+
writeFileSync(target, text, { mode: 0o600 });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {{
|
|
65
|
+
* studioUrl: string,
|
|
66
|
+
* name: string,
|
|
67
|
+
* version?: string,
|
|
68
|
+
* part?: number,
|
|
69
|
+
* codeRoot: string,
|
|
70
|
+
* token: () => Promise<string | null>,
|
|
71
|
+
* fetch?: typeof fetch,
|
|
72
|
+
* base?: string,
|
|
73
|
+
* now?: () => number,
|
|
74
|
+
* }} o
|
|
75
|
+
* @returns {Promise<string>} the text for the model
|
|
76
|
+
*/
|
|
77
|
+
export async function skillText(o) {
|
|
78
|
+
const base = o.base ?? join(tmpdir(), "omni-skills");
|
|
79
|
+
const now = o.now ?? Date.now;
|
|
80
|
+
const part = o.part ?? 1;
|
|
81
|
+
sweep(base, now);
|
|
82
|
+
if (!/^[a-z0-9-]{1,64}$/.test(o.name)) return stop(`o nome de skill "${o.name}" não é válido.`);
|
|
83
|
+
|
|
84
|
+
let token;
|
|
85
|
+
try {
|
|
86
|
+
token = await o.token();
|
|
87
|
+
} catch {
|
|
88
|
+
return stop(
|
|
89
|
+
"o OmniVideo está sem conexão, e as skills não abrem sem rede. Tente de novo quando a internet voltar.",
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (!token) return stop("este computador não está logado no OmniVideo. Rode `omni login` e peça de novo.");
|
|
93
|
+
|
|
94
|
+
const url = new URL(`/skills/${o.name}`, o.studioUrl);
|
|
95
|
+
if (o.version) url.searchParams.set("v", o.version);
|
|
96
|
+
url.searchParams.set("part", String(part));
|
|
97
|
+
/** @type {Response} */
|
|
98
|
+
let r;
|
|
99
|
+
try {
|
|
100
|
+
r = await (o.fetch ?? fetch)(url, { headers: { authorization: `Bearer ${token}` } });
|
|
101
|
+
} catch {
|
|
102
|
+
return stop(
|
|
103
|
+
"o OmniVideo está sem conexão, e as skills não abrem sem rede. Tente de novo quando a internet voltar.",
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
const j = /** @type {any} */ (await r.json().catch(() => ({})));
|
|
107
|
+
if (r.status === 401)
|
|
108
|
+
return stop("o login deste computador no OmniVideo não vale mais. Rode `omni login` e peça de novo.");
|
|
109
|
+
if (r.status === 403) {
|
|
110
|
+
wipe(base, o.name);
|
|
111
|
+
return stop(
|
|
112
|
+
`${j.error ?? "a assinatura do OmniVideo não está ativa."} Os vídeos continuam no computador; as skills voltam com o plano.`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
if (!r.ok || typeof j.body !== "string")
|
|
116
|
+
return stop(`o OmniVideo não entregou a skill ${o.name} (${j.error ?? `HTTP ${r.status}`}).`);
|
|
117
|
+
|
|
118
|
+
const dir = join(base, `${o.name}@${String(j.hash).slice(0, 16)}`);
|
|
119
|
+
if (part === 1 && j.files && Object.keys(j.files).length) materialize(dir, j.files);
|
|
120
|
+
try {
|
|
121
|
+
utimesSync(dir, new Date(now()), new Date(now()));
|
|
122
|
+
} catch {
|
|
123
|
+
/* no files for this skill */
|
|
124
|
+
}
|
|
125
|
+
return j.body.replaceAll("{{OMNI_CODE}}", o.codeRoot).replaceAll(`{{OMNI_SKILLS}}/${o.name}/`, `${dir}/`);
|
|
126
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/* A release of the skills for the registry and the public plugin (docs/specs/phase-2-plugin-cli.md, step 3).
|
|
2
|
+
One source, .claude/skills, serves both this repo (as it is) and the product (as built here):
|
|
3
|
+
|
|
4
|
+
- The body is rewritten for a machine that has the CLI and not this repo: `node tools/x.mjs …` and
|
|
5
|
+
`$PY tools/x.py …` become `<run> x …` (by default `npx -y @crievo/omni run x …`), `templates/…` and
|
|
6
|
+
`reference/…` become `{{OMNI_CODE}}/templates/…` and `{{OMNI_CODE}}/reference/…` and `.claude/skills/<name>/<file>` becomes
|
|
7
|
+
`{{OMNI_SKILLS}}/<name>/<file>`. `omni skill` fills the two placeholders when it prints the body.
|
|
8
|
+
- A body longer than one `!` line carries (MAX_PART characters, measured in the encrypted-skills study)
|
|
9
|
+
is cut at its `## ` headings into parts; the plugin's header gets one `!` line per part.
|
|
10
|
+
- The header keeps the frontmatter Claude Code reads (name, description, model, effort, context) and
|
|
11
|
+
only the lines that fetch the body, plus the plan-B line for when shell execution in skills is off.
|
|
12
|
+
- Every skill gets a hash of what it serves; the release's version is the hash of all of them.
|
|
13
|
+
|
|
14
|
+
Pure: the files come in as strings, so the tests need no disk. */
|
|
15
|
+
|
|
16
|
+
import { createHash } from "node:crypto";
|
|
17
|
+
import { CLI_COMMAND } from "./cli.mjs";
|
|
18
|
+
|
|
19
|
+
export const MAX_PART = 24_000;
|
|
20
|
+
export const DEFAULT_RUN = `${CLI_COMMAND} run`;
|
|
21
|
+
export const DEFAULT_SKILL = `${CLI_COMMAND} skill`;
|
|
22
|
+
/* the frontmatter keys the header keeps; anything else stays in the private body */
|
|
23
|
+
const HEADER_KEYS = ["name", "description", "model", "effort", "context"];
|
|
24
|
+
|
|
25
|
+
/** @param {string} text @returns {{ meta: Record<string, string>, metaLines: string[], body: string }} */
|
|
26
|
+
export function splitFrontmatter(text) {
|
|
27
|
+
const m = text.match(/^---\n([\s\S]*?)\n---\n?/);
|
|
28
|
+
if (!m) return { meta: {}, metaLines: [], body: text };
|
|
29
|
+
const metaLines = m[1].split("\n");
|
|
30
|
+
/** @type {Record<string, string>} */
|
|
31
|
+
const meta = {};
|
|
32
|
+
for (const line of metaLines) {
|
|
33
|
+
const kv = line.match(/^([A-Za-z_-]+):\s*(.*)$/);
|
|
34
|
+
if (kv) meta[kv[1]] = kv[2];
|
|
35
|
+
}
|
|
36
|
+
return { meta, metaLines, body: text.slice(m[0].length) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The body as a CLI machine runs it, and what the rewrite couldn't place.
|
|
40
|
+
* @param {string} body @param {{ run?: string }} [o] @returns {{ body: string, warnings: string[] }} */
|
|
41
|
+
export function rewriteBody(body, { run = DEFAULT_RUN } = {}) {
|
|
42
|
+
let out = body
|
|
43
|
+
.replace(/\bnode (?:\.\/)?tools\/([A-Za-z0-9_-]+)\.mjs\b/g, (_, t) => `${run} ${t}`)
|
|
44
|
+
.replace(/(?:"\$PY"|\$PY|\.venv\/bin\/python3?) (?:\.\/)?tools\/([A-Za-z0-9_-]+)\.py\b/g, (_, t) => `${run} ${t}`)
|
|
45
|
+
.replace(/(^|[^\w./{}-])(templates|reference)\//g, (_, pre, dir) => `${pre}{{OMNI_CODE}}/${dir}/`)
|
|
46
|
+
.replace(/(^|[^\w./{}-])\.claude\/skills\//g, (_, pre) => `${pre}{{OMNI_SKILLS}}/`);
|
|
47
|
+
/** @type {string[]} */
|
|
48
|
+
const warnings = [];
|
|
49
|
+
/* a command that still runs a repo tool by path would fail on a CLI machine */
|
|
50
|
+
for (const m of out.matchAll(/\b(?:node|python3?|bun) (?:\.\/)?tools\/[\w./-]+/g))
|
|
51
|
+
warnings.push(`chamada de ferramenta sem reescrita: ${m[0]}`);
|
|
52
|
+
out = out.replace(/\n{3,}/g, "\n\n");
|
|
53
|
+
return { body: out, warnings };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Cuts a body into parts of at most `max` characters, at `## ` headings, then `### `, then blank lines.
|
|
57
|
+
* @param {string} body @param {number} [max] @returns {string[]} */
|
|
58
|
+
export function splitParts(body, max = MAX_PART) {
|
|
59
|
+
if (body.length <= max) return [body];
|
|
60
|
+
/** @param {string} text @param {RegExp[]} seps @returns {string[]} */
|
|
61
|
+
const cut = (text, seps) => {
|
|
62
|
+
if (text.length <= max) return [text];
|
|
63
|
+
const [sep, ...rest] = seps;
|
|
64
|
+
if (!sep) {
|
|
65
|
+
/* no boundary left: a hard cut at a line end */
|
|
66
|
+
const at = text.lastIndexOf("\n", max) > 0 ? text.lastIndexOf("\n", max) : max;
|
|
67
|
+
return [text.slice(0, at), ...cut(text.slice(at), [])];
|
|
68
|
+
}
|
|
69
|
+
const pieces = text.split(sep).filter(p => p.length > 0);
|
|
70
|
+
/** @type {string[]} */
|
|
71
|
+
const parts = [];
|
|
72
|
+
let current = "";
|
|
73
|
+
for (const piece of pieces.flatMap(p => (p.length > max ? cut(p, rest) : [p]))) {
|
|
74
|
+
if (current && current.length + piece.length > max) {
|
|
75
|
+
parts.push(current);
|
|
76
|
+
current = "";
|
|
77
|
+
}
|
|
78
|
+
current += piece;
|
|
79
|
+
}
|
|
80
|
+
if (current) parts.push(current);
|
|
81
|
+
return parts;
|
|
82
|
+
};
|
|
83
|
+
return cut(body, [/(?=\n## )/, /(?=\n### )/, /(?=\n\n)/]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const sha = (/** @type {string} */ s) => createHash("sha256").update(s).digest("hex");
|
|
87
|
+
|
|
88
|
+
/** The plugin's SKILL.md for one skill: the public frontmatter and the lines that fetch the body.
|
|
89
|
+
* @param {{ name: string, metaLines: string[], parts: number, version: string }} s @param {{ skill?: string }} [o] */
|
|
90
|
+
export function header(s, { skill = DEFAULT_SKILL } = {}) {
|
|
91
|
+
const meta = s.metaLines.filter(l => HEADER_KEYS.includes(l.split(":")[0].trim()));
|
|
92
|
+
const fetchLines = Array.from({ length: s.parts }, (_, i) =>
|
|
93
|
+
s.parts === 1
|
|
94
|
+
? `!\`${skill} ${s.name} --v ${s.version}\``
|
|
95
|
+
: `!\`${skill} ${s.name} --v ${s.version} --part ${i + 1}/${s.parts}\``,
|
|
96
|
+
);
|
|
97
|
+
const planB =
|
|
98
|
+
s.parts === 1
|
|
99
|
+
? `Se acima aparecer o comando em vez das instruções, rode via Bash \`${skill} ${s.name} --v ${s.version}\` e siga a saída.`
|
|
100
|
+
: `Se acima aparecerem os comandos em vez das instruções, rode via Bash \`${skill} ${s.name} --v ${s.version} --part i/${s.parts}\` para cada parte, de 1 a ${s.parts}, e siga a saída.`;
|
|
101
|
+
return `---\n${meta.join("\n")}\n---\n\n${fetchLines.join("\n\n")}\n\n${planB}\n`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The release: what the registry stores and what the plugin repo gets.
|
|
106
|
+
* @param {{ name: string, files: Record<string, string> }[]} skills each skill's files by path in its folder
|
|
107
|
+
* (SKILL.md among them)
|
|
108
|
+
* @param {{ run?: string, skill?: string, max?: number }} [o]
|
|
109
|
+
*/
|
|
110
|
+
export function buildRelease(skills, o = {}) {
|
|
111
|
+
/** @type {string[]} */
|
|
112
|
+
const warnings = [];
|
|
113
|
+
const built = skills
|
|
114
|
+
.slice()
|
|
115
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
116
|
+
.map(({ name, files }) => {
|
|
117
|
+
const source = files["SKILL.md"];
|
|
118
|
+
if (source === undefined) throw new Error(`${name}: falta SKILL.md`);
|
|
119
|
+
const { meta, metaLines, body } = splitFrontmatter(source);
|
|
120
|
+
if (meta.name !== name) throw new Error(`${name}: o name do frontmatter é ${meta.name ?? "nenhum"}`);
|
|
121
|
+
const rewritten = rewriteBody(body, o);
|
|
122
|
+
warnings.push(...rewritten.warnings.map(w => `${name}: ${w}`));
|
|
123
|
+
const parts = splitParts(rewritten.body, o.max);
|
|
124
|
+
/** @type {Record<string, string>} */
|
|
125
|
+
const extra = {};
|
|
126
|
+
for (const [path, text] of Object.entries(files))
|
|
127
|
+
if (path !== "SKILL.md") extra[path] = rewriteBody(text, o).body;
|
|
128
|
+
const hash = sha(JSON.stringify({ parts, extra }));
|
|
129
|
+
return { name, meta, metaLines, parts, files: extra, hash };
|
|
130
|
+
});
|
|
131
|
+
const version = sha(built.map(s => `${s.name}:${s.hash}`).join("\n")).slice(0, 12);
|
|
132
|
+
return {
|
|
133
|
+
version,
|
|
134
|
+
warnings,
|
|
135
|
+
skills: built.map(s => ({
|
|
136
|
+
name: s.name,
|
|
137
|
+
hash: s.hash,
|
|
138
|
+
parts: s.parts,
|
|
139
|
+
files: s.files,
|
|
140
|
+
size: s.parts.join("").length,
|
|
141
|
+
})),
|
|
142
|
+
headers: Object.fromEntries(
|
|
143
|
+
built.map(s => [s.name, header({ name: s.name, metaLines: s.metaLines, parts: s.parts.length, version }, o)]),
|
|
144
|
+
),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The public plugin, ready to be the marketplace repo (pedroflp/omni-plugin): the manifest, the
|
|
150
|
+
* marketplace that lists it, the SessionStart hook and the MCP server, both through the CLI, and the
|
|
151
|
+
* skills' headers. Its version is the hash of everything else, so Claude Code updates it only when some
|
|
152
|
+
* file changed.
|
|
153
|
+
* @param {{ headers: Record<string, string> }} release
|
|
154
|
+
* @param {{ cli: string, owner?: string }} o cli: the command that runs the CLI (`npx -y @crievo/omni`)
|
|
155
|
+
* @returns {Record<string, string>} files by path
|
|
156
|
+
*/
|
|
157
|
+
export function pluginFiles(release, { cli, owner = "pedroflp" }) {
|
|
158
|
+
const description = "OmniVideo: roteiro, motion em código, voz clonada, legenda e publicação, do pedido ao MP4.";
|
|
159
|
+
const [command, ...args] = cli.split(" ");
|
|
160
|
+
/** @type {Record<string, string>} */
|
|
161
|
+
const files = {
|
|
162
|
+
"hooks/hooks.json": `${JSON.stringify(
|
|
163
|
+
{
|
|
164
|
+
hooks: {
|
|
165
|
+
SessionStart: [{ hooks: [{ type: "command", command: `${cli} up --hook`, async: true, timeout: 600 }] }],
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
null,
|
|
169
|
+
2,
|
|
170
|
+
)}\n`,
|
|
171
|
+
".mcp.json": `${JSON.stringify({ mcpServers: { omnivideo: { command, args: [...args, "mcp"] } } }, null, 2)}\n`,
|
|
172
|
+
"README.md": `# OmniVideo para o Claude Code
|
|
173
|
+
|
|
174
|
+
${description}
|
|
175
|
+
|
|
176
|
+
claude plugin install omnivideo --marketplace ${owner}/omni-plugin
|
|
177
|
+
|
|
178
|
+
Na primeira sessão o plugin prepara o computador sozinho: abre o navegador para você entrar no OmniVideo e
|
|
179
|
+
liga o studio local. Precisa do Chrome e do ffmpeg instalados. Este repositório é gerado automaticamente:
|
|
180
|
+
as skills chegam do registro do OmniVideo a cada uso.
|
|
181
|
+
`,
|
|
182
|
+
...Object.fromEntries(Object.entries(release.headers).map(([name, text]) => [`skills/${name}/SKILL.md`, text])),
|
|
183
|
+
};
|
|
184
|
+
const version = `0.1.0-${sha(JSON.stringify(files)).slice(0, 10)}`;
|
|
185
|
+
files[".claude-plugin/plugin.json"] = `${JSON.stringify(
|
|
186
|
+
{ name: "omnivideo", version, description, author: { name: "Crievo" }, homepage: "https://omni.crievo.com" },
|
|
187
|
+
null,
|
|
188
|
+
2,
|
|
189
|
+
)}\n`;
|
|
190
|
+
files[".claude-plugin/marketplace.json"] = `${JSON.stringify(
|
|
191
|
+
{ name: "omnivideo", owner: { name: owner }, plugins: [{ name: "omnivideo", source: "./", description, version }] },
|
|
192
|
+
null,
|
|
193
|
+
2,
|
|
194
|
+
)}\n`;
|
|
195
|
+
return files;
|
|
196
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/* Test helpers for the core: a fake fetch with canned routes and a fixed clock. Not used at runtime. */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A fake fetch for tests: each call takes the first route whose `match`
|
|
5
|
+
* (substring or RegExp, plus optional method) fits; records every call.
|
|
6
|
+
* @param {Array<{ match: string | RegExp, method?: string, status?: number, body?: any, headers?: Record<string, string> } | ((url: string, opts: any) => any)>} routes
|
|
7
|
+
*/
|
|
8
|
+
export function fakeFetch(routes) {
|
|
9
|
+
/** @type {Array<{ url: string, opts: any }>} */
|
|
10
|
+
const calls = [];
|
|
11
|
+
/** @type {typeof globalThis.fetch & { calls: typeof calls }} */
|
|
12
|
+
const f = /** @type {any} */ (
|
|
13
|
+
async (/** @type {string} */ url, /** @type {any} */ opts = {}) => {
|
|
14
|
+
calls.push({ url: String(url), opts });
|
|
15
|
+
for (const route of routes) {
|
|
16
|
+
const hit =
|
|
17
|
+
typeof route === "function"
|
|
18
|
+
? route(String(url), opts)
|
|
19
|
+
: (typeof route.match === "string" ? String(url).includes(route.match) : route.match.test(String(url))) &&
|
|
20
|
+
(!route.method || route.method === (opts.method ?? "GET"))
|
|
21
|
+
? route
|
|
22
|
+
: null;
|
|
23
|
+
if (!hit) continue;
|
|
24
|
+
const status = hit.status ?? 200;
|
|
25
|
+
const text = typeof hit.body === "string" ? hit.body : JSON.stringify(hit.body ?? {});
|
|
26
|
+
return new Response(text, { status, headers: hit.headers ?? {} });
|
|
27
|
+
}
|
|
28
|
+
throw new Error(`fakeFetch: no route for ${opts.method ?? "GET"} ${url}`);
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
f.calls = calls;
|
|
32
|
+
return f;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A clock that only moves when told to.
|
|
37
|
+
* @param {string | number} start ISO date or epoch ms
|
|
38
|
+
*/
|
|
39
|
+
export function fixedClock(start) {
|
|
40
|
+
let t = typeof start === "number" ? start : Date.parse(start);
|
|
41
|
+
return {
|
|
42
|
+
now: () => t,
|
|
43
|
+
/** @param {number} ms */
|
|
44
|
+
advance: ms => {
|
|
45
|
+
t += ms;
|
|
46
|
+
},
|
|
47
|
+
/** @param {number} ms */
|
|
48
|
+
sleep: async ms => {
|
|
49
|
+
t += ms;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
package/tools/lib/up.mjs
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/* `omni up`: everything the CLI needs on this machine, done without the user (docs/specs/phase-2-plugin-cli.md,
|
|
2
|
+
step 4). The plugin's SessionStart hook runs it on every session, so every piece checks first and does
|
|
3
|
+
nothing when it is already there:
|
|
4
|
+
|
|
5
|
+
workspace ~/OmniVideo (OMNI_HOME): videos/, omnivideo.config.json pointing at the hosted studio, .env
|
|
6
|
+
app a stable copy of the package in ~/.omni/app/<version>/ (npx's cache can be wiped), and
|
|
7
|
+
~/.omni/bin/omni
|
|
8
|
+
python the venv of the voice and timing tools, in the app's .venv (in the background on a hook)
|
|
9
|
+
login none yet: the browser opens on the approval page (at most once an hour)
|
|
10
|
+
service the studio as a launchd agent, from the app, so the hosted studio reaches this Mac
|
|
11
|
+
|
|
12
|
+
In this repo (a git checkout) the app, the venv and the service are left to `tools/setup.mjs` and
|
|
13
|
+
`tools/runner.mjs service`: `up` never rewires a development machine. */
|
|
14
|
+
|
|
15
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
16
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { missingRules } from "./claude-settings.mjs";
|
|
20
|
+
import { CLI_COMMAND, PACKAGE, STUDIO_URL } from "./cli.mjs";
|
|
21
|
+
import { readTokens } from "./oauth-client.mjs";
|
|
22
|
+
import { CODE_ROOT, DATA_ROOT } from "./paths.mjs";
|
|
23
|
+
import { PLIST, service, serviceInstall } from "./service.mjs";
|
|
24
|
+
|
|
25
|
+
export const OMNI_DIR = join(homedir(), ".omni");
|
|
26
|
+
const LOGIN_EVERY_MS = 60 * 60_000;
|
|
27
|
+
|
|
28
|
+
const isDev = (/** @type {string} */ codeRoot) => existsSync(join(codeRoot, ".git"));
|
|
29
|
+
|
|
30
|
+
/** the studio URL of a workspace's config, else the default */
|
|
31
|
+
export function studioUrlOf(home = DATA_ROOT) {
|
|
32
|
+
try {
|
|
33
|
+
const c = JSON.parse(readFileSync(join(home, "omnivideo.config.json"), "utf8"));
|
|
34
|
+
return String(c.cloud?.studio_url || STUDIO_URL).replace(/\/+$/, "");
|
|
35
|
+
} catch {
|
|
36
|
+
return STUDIO_URL;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** videos/, a config that talks to the hosted studio, and .env, from the package's examples
|
|
41
|
+
* @returns {boolean} whether it created anything */
|
|
42
|
+
export function ensureWorkspace(home = DATA_ROOT, codeRoot = CODE_ROOT) {
|
|
43
|
+
let created = false;
|
|
44
|
+
if (!existsSync(join(home, "videos"))) {
|
|
45
|
+
mkdirSync(join(home, "videos"), { recursive: true });
|
|
46
|
+
created = true;
|
|
47
|
+
}
|
|
48
|
+
const config = join(home, "omnivideo.config.json");
|
|
49
|
+
if (!existsSync(config)) {
|
|
50
|
+
const c = JSON.parse(readFileSync(join(codeRoot, "omnivideo.config.example.json"), "utf8"));
|
|
51
|
+
c.cloud = { ...(c.cloud ?? {}), studio: true, studio_url: STUDIO_URL };
|
|
52
|
+
writeFileSync(config, `${JSON.stringify(c, null, 2)}\n`);
|
|
53
|
+
created = true;
|
|
54
|
+
}
|
|
55
|
+
const env = join(home, ".env");
|
|
56
|
+
if (!existsSync(env) && existsSync(join(codeRoot, ".env.example"))) {
|
|
57
|
+
copyFileSync(join(codeRoot, ".env.example"), env);
|
|
58
|
+
created = true;
|
|
59
|
+
}
|
|
60
|
+
return created;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The code root the service should run: this checkout, an already stable install, or a fresh copy in
|
|
64
|
+
* ~/.omni/app/<version>/ when this runs from npx's cache. @param {(line: string) => void} log */
|
|
65
|
+
export function ensureApp(log = () => {}, codeRoot = CODE_ROOT) {
|
|
66
|
+
if (isDev(codeRoot) || !codeRoot.includes("/_npx/")) return codeRoot;
|
|
67
|
+
const version = JSON.parse(readFileSync(join(codeRoot, "package.json"), "utf8")).version;
|
|
68
|
+
const prefix = join(OMNI_DIR, "app", version);
|
|
69
|
+
const installed = join(prefix, "node_modules", ...PACKAGE.split("/"));
|
|
70
|
+
if (!existsSync(join(installed, "package.json"))) {
|
|
71
|
+
log(`instalando o OmniVideo ${version} em ${prefix}…`);
|
|
72
|
+
const spec = process.env.OMNI_PACKAGE_SPEC || `${PACKAGE}@${version}`;
|
|
73
|
+
const r = spawnSync("npm", ["install", "--prefix", prefix, "--no-audit", "--no-fund", "--loglevel=error", spec], {
|
|
74
|
+
encoding: "utf8",
|
|
75
|
+
});
|
|
76
|
+
if (r.status !== 0) throw new Error(`npm install falhou: ${(r.stderr || r.stdout).trim().split("\n").at(-1)}`);
|
|
77
|
+
}
|
|
78
|
+
mkdirSync(join(OMNI_DIR, "bin"), { recursive: true });
|
|
79
|
+
writeFileSync(join(OMNI_DIR, "bin", "omni"), `#!/bin/sh\nexec node "${join(installed, "bin", "omni.mjs")}" "$@"\n`, {
|
|
80
|
+
mode: 0o755,
|
|
81
|
+
});
|
|
82
|
+
return installed;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** the venv of the Python tools, inside the app; on a hook in the background, logging to ~/.omni/venv.log
|
|
86
|
+
* @returns {"ok" | "started" | "creating" | "failed"} */
|
|
87
|
+
export function ensureVenv(codeRoot = CODE_ROOT, { background = false } = {}) {
|
|
88
|
+
if (existsSync(join(codeRoot, ".venv", "bin", "python"))) return "ok";
|
|
89
|
+
const lock = join(OMNI_DIR, "venv.lock");
|
|
90
|
+
mkdirSync(OMNI_DIR, { recursive: true });
|
|
91
|
+
try {
|
|
92
|
+
if (Date.now() - statSync(lock).mtimeMs < 15 * 60_000) return "creating";
|
|
93
|
+
} catch {
|
|
94
|
+
/* no one is creating it */
|
|
95
|
+
}
|
|
96
|
+
writeFileSync(lock, String(process.pid));
|
|
97
|
+
const script = `python3 -m venv .venv && .venv/bin/python -m pip install -q -r requirements.txt; rm -f "${lock}"`;
|
|
98
|
+
if (background) {
|
|
99
|
+
const out = join(OMNI_DIR, "venv.log");
|
|
100
|
+
spawn("/bin/sh", ["-c", `${script} >> "${out}" 2>&1`], { cwd: codeRoot, detached: true, stdio: "ignore" }).unref();
|
|
101
|
+
return "started";
|
|
102
|
+
}
|
|
103
|
+
const r = spawnSync("/bin/sh", ["-c", script], { cwd: codeRoot, stdio: "inherit" });
|
|
104
|
+
return r.status === 0 ? "ok" : "failed";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** opens the browser on the approval page in a process of its own, at most once an hour
|
|
108
|
+
* @returns {boolean} whether it opened now */
|
|
109
|
+
export function startLogin(codeRoot = CODE_ROOT) {
|
|
110
|
+
const marker = join(OMNI_DIR, "login-opened");
|
|
111
|
+
mkdirSync(OMNI_DIR, { recursive: true });
|
|
112
|
+
try {
|
|
113
|
+
if (Date.now() - statSync(marker).mtimeMs < LOGIN_EVERY_MS) return false;
|
|
114
|
+
} catch {
|
|
115
|
+
/* never opened */
|
|
116
|
+
}
|
|
117
|
+
writeFileSync(marker, new Date().toISOString());
|
|
118
|
+
spawn(process.execPath, [join(codeRoot, "tools", "omni.mjs"), "login"], {
|
|
119
|
+
detached: true,
|
|
120
|
+
stdio: "ignore",
|
|
121
|
+
env: { ...process.env, OMNI_HOME: DATA_ROOT },
|
|
122
|
+
}).unref();
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** the checkout the installed studio service runs from, when it is a git checkout (a development machine) */
|
|
127
|
+
function devServiceRoot() {
|
|
128
|
+
try {
|
|
129
|
+
const wd = /<key>WorkingDirectory<\/key><string>([^<]+)<\/string>/.exec(readFileSync(PLIST, "utf8"))?.[1];
|
|
130
|
+
const root = wd ? join(wd.replace(/&/g, "&"), "..") : "";
|
|
131
|
+
return root && existsSync(join(root, ".git")) ? root : "";
|
|
132
|
+
} catch {
|
|
133
|
+
return "";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {{ hook?: boolean, log?: (line: string) => void }} [o]
|
|
139
|
+
* @returns {Promise<{ line: string, codeRoot: string, loggedIn: boolean, missingRules: string[] }>}
|
|
140
|
+
*/
|
|
141
|
+
export async function up({ hook = false, log = () => {} } = {}) {
|
|
142
|
+
const notes = [];
|
|
143
|
+
if (ensureWorkspace()) notes.push(`workspace criado em ${DATA_ROOT}`);
|
|
144
|
+
const codeRoot = ensureApp(log);
|
|
145
|
+
const dev = isDev(codeRoot);
|
|
146
|
+
const url = studioUrlOf();
|
|
147
|
+
|
|
148
|
+
if (!dev) {
|
|
149
|
+
const venv = ensureVenv(codeRoot, { background: hook });
|
|
150
|
+
if (venv === "started" || venv === "creating") notes.push("preparando o Python das ferramentas de voz");
|
|
151
|
+
if (venv === "failed") notes.push("o Python das ferramentas não instalou (veja ~/.omni/venv.log)");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const loggedIn = !!readTokens(url);
|
|
155
|
+
if (!loggedIn)
|
|
156
|
+
notes.push(
|
|
157
|
+
startLogin(codeRoot) ? "abri o navegador para entrar no OmniVideo" : "falta entrar no OmniVideo (omni login)",
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
/* a development studio already kept on by launchd (this repo's runner.mjs) is never swapped for the app */
|
|
161
|
+
const devService = devServiceRoot();
|
|
162
|
+
if (!dev && devService)
|
|
163
|
+
notes.push(`o studio de desenvolvimento (${devService}) continua sendo o serviço deste computador`);
|
|
164
|
+
if (!dev && !devService && process.platform === "darwin") {
|
|
165
|
+
try {
|
|
166
|
+
if (serviceInstall({ codeRoot, onlyIfChanged: true, log }).changed) notes.push("studio ligado como serviço");
|
|
167
|
+
} catch (e) {
|
|
168
|
+
notes.push(`o serviço do studio não ligou: ${/** @type {Error} */ (e).message}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const rules = missingRules({ cli: CLI_COMMAND });
|
|
173
|
+
if (rules.length)
|
|
174
|
+
notes.push("as skills ainda precisam de permissão: peça ao Claude para liberar as skills do OmniVideo");
|
|
175
|
+
|
|
176
|
+
const s = service();
|
|
177
|
+
const state = [
|
|
178
|
+
loggedIn ? "logado" : "sem login",
|
|
179
|
+
dev ? "modo desenvolvimento" : s.loaded ? "studio ligado" : "studio desligado",
|
|
180
|
+
];
|
|
181
|
+
return {
|
|
182
|
+
line: `OmniVideo: ${state.join(", ")}${notes.length ? `. ${notes.join("; ")}.` : "."}`,
|
|
183
|
+
codeRoot,
|
|
184
|
+
loggedIn,
|
|
185
|
+
missingRules: rules,
|
|
186
|
+
};
|
|
187
|
+
}
|