@crievo/omni 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +41 -0
- package/README.md +2 -2
- package/bin/omni.mjs +18 -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.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,624 @@
|
|
|
1
|
+
/* Instagram comment omnichat from the UI: the same `tools/omnichat.mjs`
|
|
2
|
+
as the terminal. The rule lives in <dir>/omnichat.json (key instagram) and what the
|
|
3
|
+
tool did with each comment in <dir>/omnichat-record.json; the UI reads
|
|
4
|
+
the rule straight from the file and the record through `log --json`, and writes only through the CLI.
|
|
5
|
+
|
|
6
|
+
The dir is a video's (videos/<slug>, the Reel), that of the same content's carousel
|
|
7
|
+
(videos/<slug>/carousel) or that of a post from outside omnivideo (.omnichat/posts/<code>,
|
|
8
|
+
created by `post`: Reel, carousel or photo). `status --json` asks the Instagram
|
|
9
|
+
API (the account and the comments permission): that part is cached for
|
|
10
|
+
2 min; agent, pause and rules are read on the spot, without the API. */
|
|
11
|
+
|
|
12
|
+
import { readdirSync, existsSync, statSync, realpathSync } from "node:fs";
|
|
13
|
+
import { join, relative, dirname, basename } from "node:path";
|
|
14
|
+
import { NODE, ROOT, TOOLS_DIR, run, readJson } from "./root";
|
|
15
|
+
import { videoDirs } from "./videos";
|
|
16
|
+
import { videoLink, narrationKeyword } from "../../../tools/omnichat.mjs";
|
|
17
|
+
import { resolveOmnichatConfig } from "../../../tools/lib/omnichat/config.mjs";
|
|
18
|
+
|
|
19
|
+
type Result = { ok: true; output: string } | { ok: false; error: string };
|
|
20
|
+
|
|
21
|
+
const OMNICHAT = join(TOOLS_DIR, "omnichat.mjs");
|
|
22
|
+
const OMNICHAT_DIR = join(ROOT, ".omnichat");
|
|
23
|
+
const POSTS_DIR = join(OMNICHAT_DIR, "posts");
|
|
24
|
+
const CODE_RE = /^[\w-]{1,64}$/;
|
|
25
|
+
|
|
26
|
+
const joinOutput = (r: { stdout: string; stderr: string }) =>
|
|
27
|
+
[r.stdout, r.stderr]
|
|
28
|
+
.map(s => s.trim())
|
|
29
|
+
.filter(Boolean)
|
|
30
|
+
.join("\n");
|
|
31
|
+
/* the CLI writes "erro: …" to stderr */
|
|
32
|
+
const stripPrefix = (s: string) => s.replace(/^erro: /gm, "");
|
|
33
|
+
/* exit 2 is the CLI help: the command or the dir were not recognized */
|
|
34
|
+
const failed = (r: { stdout: string; stderr: string; code: number }): Result =>
|
|
35
|
+
r.code === 2
|
|
36
|
+
? { ok: false, error: "o omnichat.mjs não reconheceu o pedido (a pasta tem scenes.json ou carousel.json?)" }
|
|
37
|
+
: { ok: false, error: stripPrefix(joinOutput(r)) || `omnichat.mjs saiu com ${r.code}` };
|
|
38
|
+
|
|
39
|
+
/* --- where the rule belongs ---------------------------------------------------------------- */
|
|
40
|
+
/* carousel: `id` is the content's slug, and the dir is videos/<slug>/carousel */
|
|
41
|
+
export type RuleTarget = { type: "video" | "post" | "carousel"; id: string; dir: string };
|
|
42
|
+
|
|
43
|
+
export function videoTarget(slug: string): RuleTarget | null {
|
|
44
|
+
const dir = videoDirs().get(slug);
|
|
45
|
+
return dir ? { type: "video", id: slug, dir } : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function carouselTarget(slug: string): RuleTarget | null {
|
|
49
|
+
const content = videoDirs().get(slug);
|
|
50
|
+
if (!content) return null;
|
|
51
|
+
const dir = join(content, "carousel");
|
|
52
|
+
return existsSync(join(dir, "carousel.json")) ? { type: "carousel", id: slug, dir } : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function postTarget(code: string): RuleTarget | null {
|
|
56
|
+
if (!CODE_RE.test(code)) return null;
|
|
57
|
+
const dir = join(POSTS_DIR, code);
|
|
58
|
+
return existsSync(join(dir, "publication.json")) ? { type: "post", id: code, dir } : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* the CLI only accepts a video dir with scenes.json, a carousel dir with carousel.json (or a
|
|
62
|
+
post's), and the agent only checks the contents in videos/ (not those in another dir opened with --videos) */
|
|
63
|
+
const real = (p: string) => {
|
|
64
|
+
try {
|
|
65
|
+
return realpathSync(p);
|
|
66
|
+
} catch {
|
|
67
|
+
return p;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
function checkTarget(a: RuleTarget): string | null {
|
|
71
|
+
if (a.type === "post") return null;
|
|
72
|
+
if (a.type === "carousel") {
|
|
73
|
+
if (!existsSync(join(a.dir, "carousel.json"))) return "este conteúdo ainda não tem carrossel";
|
|
74
|
+
if (real(dirname(dirname(a.dir))) !== real(join(ROOT, "videos")))
|
|
75
|
+
return "o agente do omnichat só confere os conteúdos de videos/ deste projeto";
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
if (!existsSync(join(a.dir, "scenes.json")))
|
|
79
|
+
return "este vídeo ainda não tem scenes.json: o omnichat vale depois do roteiro de motion";
|
|
80
|
+
if (real(dirname(a.dir)) !== real(join(ROOT, "videos")))
|
|
81
|
+
return "o agente do omnichat só confere os vídeos de videos/ deste projeto";
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const ruleOf = (dir: string) =>
|
|
86
|
+
readJson<{ instagram?: Record<string, unknown> }>(join(dir, "omnichat.json"))?.instagram ?? null;
|
|
87
|
+
const instagramOf = (dir: string) =>
|
|
88
|
+
readJson<{ instagram?: { id?: string; url?: string; published_at?: string } }>(join(dir, "publication.json"))
|
|
89
|
+
?.instagram ?? null;
|
|
90
|
+
const rel = (dir: string) => (relative(ROOT, dir).startsWith("..") ? dir : relative(ROOT, dir));
|
|
91
|
+
|
|
92
|
+
/* Each `log` pulls the rule's record from the cloud (one request). Every open page rereads every rule on
|
|
93
|
+
each omnichat notice, so one answer serves for LOG_TTL_MS while the record file stays the same; a
|
|
94
|
+
change to the file (a pull that brought news, a send, a retry) asks again. Two reads at once share one. */
|
|
95
|
+
const LOG_TTL_MS = 30_000;
|
|
96
|
+
const logCache = new Map<string, { at: number; mtime: number; value: { log: unknown } | { error: string } }>();
|
|
97
|
+
const logReads = new Map<string, Promise<{ log: unknown } | { error: string }>>();
|
|
98
|
+
const recordMtime = (dir: string) => {
|
|
99
|
+
try {
|
|
100
|
+
return statSync(join(dir, "omnichat-record.json")).mtimeMs;
|
|
101
|
+
} catch {
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
async function logOf(dir: string): Promise<{ log: unknown } | { error: string }> {
|
|
107
|
+
const hit = logCache.get(dir);
|
|
108
|
+
if (hit && Date.now() - hit.at < LOG_TTL_MS && hit.mtime === recordMtime(dir)) return hit.value;
|
|
109
|
+
const pending = logReads.get(dir);
|
|
110
|
+
if (pending) return pending;
|
|
111
|
+
const read = (async () => {
|
|
112
|
+
const r = await run([NODE, OMNICHAT, "log", dir, "--json"]);
|
|
113
|
+
let value: { log: unknown } | { error: string };
|
|
114
|
+
if (r.code !== 0) value = { error: (failed(r) as { error: string }).error };
|
|
115
|
+
else {
|
|
116
|
+
try {
|
|
117
|
+
value = { log: JSON.parse(r.stdout.trim().split("\n").pop()!) };
|
|
118
|
+
} catch (e: any) {
|
|
119
|
+
value = { error: `não deu para ler o log: ${e.message}` };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
logCache.set(dir, { at: Date.now(), mtime: recordMtime(dir), value });
|
|
123
|
+
return value;
|
|
124
|
+
})().finally(() => logReads.delete(dir));
|
|
125
|
+
logReads.set(dir, read);
|
|
126
|
+
return read;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* --- status --------------------------------------------------------------------------- */
|
|
130
|
+
/* a token swapped from the terminal (`publish.mjs auth`) changes .env without going through here:
|
|
131
|
+
a .env newer than the cached read counts as a refresh */
|
|
132
|
+
const ENV_FILE = join(ROOT, ".env");
|
|
133
|
+
const envChanged = (since: number) => {
|
|
134
|
+
try {
|
|
135
|
+
return statSync(ENV_FILE).mtimeMs > since;
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
let cachedAccount: { at: number; account: unknown; comments: unknown } | null = null;
|
|
142
|
+
let readingAccount: Promise<void> | null = null;
|
|
143
|
+
|
|
144
|
+
async function readAccount(refresh: boolean) {
|
|
145
|
+
if (!refresh && cachedAccount && Date.now() - cachedAccount.at < 2 * 60_000 && !envChanged(cachedAccount.at))
|
|
146
|
+
return cachedAccount;
|
|
147
|
+
/* two open pages asking at the same time: only one asks the API */
|
|
148
|
+
readingAccount ??= (async () => {
|
|
149
|
+
const r = await run([NODE, OMNICHAT, "status", "--json"]);
|
|
150
|
+
let s: any;
|
|
151
|
+
try {
|
|
152
|
+
s = JSON.parse(r.stdout.trim().split("\n").pop()!);
|
|
153
|
+
} catch {
|
|
154
|
+
s = null;
|
|
155
|
+
}
|
|
156
|
+
cachedAccount = s
|
|
157
|
+
? { at: Date.now(), account: s.account, comments: s.comments }
|
|
158
|
+
: {
|
|
159
|
+
at: Date.now(),
|
|
160
|
+
account: { ready: false, reason: stripPrefix(joinOutput(r)) || `omnichat.mjs saiu com ${r.code}` },
|
|
161
|
+
comments: null,
|
|
162
|
+
};
|
|
163
|
+
})().finally(() => {
|
|
164
|
+
readingAccount = null;
|
|
165
|
+
});
|
|
166
|
+
await readingAccount;
|
|
167
|
+
return cachedAccount!;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* the agent and the cloud's health: asked at most once a minute (each ask is a request to the cloud, and
|
|
171
|
+
every open page rereads this on each omnichat notice) */
|
|
172
|
+
const AGENT_TTL_MS = 60_000;
|
|
173
|
+
let cachedAgent: { at: number; value: any } | null = null;
|
|
174
|
+
let readingAgent: Promise<any> | null = null;
|
|
175
|
+
|
|
176
|
+
async function agentStatus(refresh = false) {
|
|
177
|
+
if (!refresh && cachedAgent && Date.now() - cachedAgent.at < AGENT_TTL_MS) return cachedAgent.value;
|
|
178
|
+
readingAgent ??= (async () => {
|
|
179
|
+
const r = await run([NODE, OMNICHAT, "agent", "--json"]);
|
|
180
|
+
let value: any = null;
|
|
181
|
+
try {
|
|
182
|
+
value = JSON.parse(r.stdout.trim());
|
|
183
|
+
} catch {}
|
|
184
|
+
cachedAgent = { at: Date.now(), value };
|
|
185
|
+
return value;
|
|
186
|
+
})().finally(() => {
|
|
187
|
+
readingAgent = null;
|
|
188
|
+
});
|
|
189
|
+
return readingAgent;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* the whole account's pause (Meta limit or block, token without permission): only holds until `until` */
|
|
193
|
+
function currentPause() {
|
|
194
|
+
const p = readJson<{ until: string; reason: string; at: string }>(join(OMNICHAT_DIR, "pause.json"));
|
|
195
|
+
return p && Date.parse(p.until) > Date.now() ? p : null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* the same dirs the CLI's `run` goes through: videos/ (and each one's carousel) and .omnichat/posts/ */
|
|
199
|
+
function dirsWithRule(): RuleTarget[] {
|
|
200
|
+
const targets: RuleTarget[] = [];
|
|
201
|
+
const names = (d: string) => {
|
|
202
|
+
try {
|
|
203
|
+
return readdirSync(d)
|
|
204
|
+
.filter(n => !n.startsWith("."))
|
|
205
|
+
.sort();
|
|
206
|
+
} catch {
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
for (const name of names(join(ROOT, "videos"))) {
|
|
211
|
+
const dir = join(ROOT, "videos", name);
|
|
212
|
+
if (existsSync(join(dir, "omnichat.json"))) targets.push({ type: "video", id: name, dir });
|
|
213
|
+
const car = join(dir, "carousel");
|
|
214
|
+
if (existsSync(join(car, "omnichat.json"))) targets.push({ type: "carousel", id: name, dir: car });
|
|
215
|
+
}
|
|
216
|
+
for (const name of names(POSTS_DIR)) {
|
|
217
|
+
const dir = join(POSTS_DIR, name);
|
|
218
|
+
if (existsSync(join(dir, "omnichat.json"))) targets.push({ type: "post", id: name, dir });
|
|
219
|
+
}
|
|
220
|
+
return targets;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function ruleSummary(a: RuleTarget) {
|
|
224
|
+
const r = ruleOf(a.dir);
|
|
225
|
+
if (!r) return null;
|
|
226
|
+
const ig = instagramOf(a.dir);
|
|
227
|
+
const l = await logOf(a.dir);
|
|
228
|
+
const log = "log" in l ? (l.log as any) : null;
|
|
229
|
+
return {
|
|
230
|
+
type: a.type,
|
|
231
|
+
id: a.id,
|
|
232
|
+
dir: rel(a.dir),
|
|
233
|
+
enabled: !!r.enabled,
|
|
234
|
+
published: !!ig?.id,
|
|
235
|
+
post: ig?.url ?? null,
|
|
236
|
+
published_at: ig?.published_at ?? null,
|
|
237
|
+
keywords: r.any_comment ? null : ((r.keywords as string[] | undefined) ?? []),
|
|
238
|
+
updated_at: (r.updated_at as string | undefined) ?? null,
|
|
239
|
+
totals: log?.totals ?? null,
|
|
240
|
+
checked_at: log?.checked_at ?? null,
|
|
241
|
+
warning: log?.warning ?? null,
|
|
242
|
+
log_error: "error" in l ? l.error : null,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* a content's Omnichat in one line, for Omni's schedule (get_schedule): is there a rule, is it on, what
|
|
247
|
+
triggers it, and, before the post goes up, what happens when it does (tools/omnichat.mjs `published`) */
|
|
248
|
+
export function ruleBrief(a: RuleTarget) {
|
|
249
|
+
const r = ruleOf(a.dir) as {
|
|
250
|
+
enabled?: boolean;
|
|
251
|
+
disabled_at?: string;
|
|
252
|
+
any_comment?: boolean;
|
|
253
|
+
keywords?: string[];
|
|
254
|
+
} | null;
|
|
255
|
+
const brief = {
|
|
256
|
+
rule: !!r,
|
|
257
|
+
enabled: !!r?.enabled,
|
|
258
|
+
keywords: r ? (r.any_comment ? "qualquer comentário" : (r.keywords ?? [])) : null,
|
|
259
|
+
};
|
|
260
|
+
if (instagramOf(a.dir)?.id) return brief;
|
|
261
|
+
const onPublish = r?.enabled
|
|
262
|
+
? "ligada: começa a responder quando o post subir"
|
|
263
|
+
: r?.disabled_at
|
|
264
|
+
? "desligada pelo usuário: não liga sozinha"
|
|
265
|
+
: omnichatConfig().enable_on_publish === true
|
|
266
|
+
? "liga sozinha quando o post subir, se a narração pedir o comentário e houver link"
|
|
267
|
+
: "não liga sozinha: só se o usuário pedir";
|
|
268
|
+
return { ...brief, on_publish: onPublish };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/* `rules`: the list of all of them (the /omnichat page); without it, only account, agent and pause */
|
|
272
|
+
export async function getOmnichatState(refresh = false, rules = true) {
|
|
273
|
+
const [c, agent, list] = await Promise.all([
|
|
274
|
+
readAccount(refresh),
|
|
275
|
+
agentStatus(refresh),
|
|
276
|
+
rules ? Promise.all(dirsWithRule().map(ruleSummary)) : Promise.resolve(null),
|
|
277
|
+
]);
|
|
278
|
+
/* while the cloud answers, its pause is the one that holds */
|
|
279
|
+
const cloud = agent?.cloud;
|
|
280
|
+
return {
|
|
281
|
+
agent,
|
|
282
|
+
pause: cloud?.ready ? (cloud.pause ? { ...cloud.pause, at: "" } : null) : currentPause(),
|
|
283
|
+
account: c.account,
|
|
284
|
+
comments: c.comments,
|
|
285
|
+
checked_at: new Date(c.at).toISOString(),
|
|
286
|
+
...(list ? { rules: list.filter(Boolean) } : {}),
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* the DM and public replies a new rule gets, whether it quotes the Reel, how many comments
|
|
291
|
+
go out per pass and whether the rule enables itself when the Reel goes up (omnichat.instagram in the config,
|
|
292
|
+
reread each time: the CLI rereads it too; the defaults are its own) */
|
|
293
|
+
const omnichatConfig = () => readJson<any>(join(ROOT, "omnivideo.config.json"))?.omnichat?.instagram ?? {};
|
|
294
|
+
function buttonDefaults(c: Record<string, unknown>) {
|
|
295
|
+
const r = resolveOmnichatConfig(c);
|
|
296
|
+
return {
|
|
297
|
+
opening_dm: r.default_opening_dm,
|
|
298
|
+
button_label: r.default_button_label,
|
|
299
|
+
not_following_dm: r.default_not_following_dm,
|
|
300
|
+
retry_label: r.default_retry_label,
|
|
301
|
+
link_label: r.default_link_label,
|
|
302
|
+
skip_label: r.default_skip_label,
|
|
303
|
+
skip_dm: r.default_skip_dm,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function ruleDefaults() {
|
|
307
|
+
const c = omnichatConfig();
|
|
308
|
+
return {
|
|
309
|
+
dm: typeof c.default_dm === "string" ? c.default_dm : null,
|
|
310
|
+
public_replies: Array.isArray(c.default_public_replies) ? c.default_public_replies.map(String) : null,
|
|
311
|
+
max_per_pass: typeof c.max_per_pass === "number" ? c.max_per_pass : null,
|
|
312
|
+
quote_post: c.quote_post !== false,
|
|
313
|
+
/* the button texts: the config's, or the core's defaults (the same the CLI falls back to) */
|
|
314
|
+
...buttonDefaults(c),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* the whole rule (from the file) and the log of a dir */
|
|
319
|
+
export async function getRuleDetail(a: RuleTarget) {
|
|
320
|
+
const ig = instagramOf(a.dir);
|
|
321
|
+
const l = await logOf(a.dir);
|
|
322
|
+
return {
|
|
323
|
+
type: a.type,
|
|
324
|
+
id: a.id,
|
|
325
|
+
dir: rel(a.dir),
|
|
326
|
+
rule: ruleOf(a.dir),
|
|
327
|
+
defaults: ruleDefaults(),
|
|
328
|
+
found_link: a.type !== "post" ? videoLink(a.dir) : null,
|
|
329
|
+
/* with enable_on_publish, a rule not yet enabled enables when the Reel (or the carousel) goes up
|
|
330
|
+
(no rule, with the term the narration or the carousel asks for: "comenta REA que eu te mando o link") */
|
|
331
|
+
on_publish:
|
|
332
|
+
a.type !== "post" ? { enable: omnichatConfig().enable_on_publish === true, term: narrationKeyword(a.dir) } : null,
|
|
333
|
+
published: !!ig?.id,
|
|
334
|
+
post: ig?.url ?? null,
|
|
335
|
+
published_at: ig?.published_at ?? null,
|
|
336
|
+
log: "log" in l ? l.log : null,
|
|
337
|
+
log_error: "error" in l ? l.error : null,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/* --- saving the rule ------------------------------------------------------------------- */
|
|
342
|
+
const asList = (v: unknown) =>
|
|
343
|
+
(Array.isArray(v) ? v : typeof v === "string" ? v.split(/[,\n]/) : []).map(s => String(s).trim()).filter(Boolean);
|
|
344
|
+
|
|
345
|
+
/* only the fields the rule has, with the right type: the rest of the body doesn't reach the CLI */
|
|
346
|
+
function ruleFields(b: any): Record<string, unknown> {
|
|
347
|
+
const j: Record<string, unknown> = {};
|
|
348
|
+
if (b?.keywords !== undefined) j.keywords = asList(b.keywords);
|
|
349
|
+
if (b?.any_comment !== undefined) j.any_comment = b.any_comment === true;
|
|
350
|
+
if (typeof b?.link === "string") j.link = b.link.trim();
|
|
351
|
+
if (typeof b?.dm === "string") j.dm = b.dm.trim();
|
|
352
|
+
/* a public reply may have a comma: one per line */
|
|
353
|
+
if (b?.public_replies !== undefined)
|
|
354
|
+
j.public_replies = (Array.isArray(b.public_replies) ? b.public_replies : String(b.public_replies ?? "").split("\n"))
|
|
355
|
+
.map((s: unknown) => String(s).trim())
|
|
356
|
+
.filter(Boolean);
|
|
357
|
+
if (b?.already_replied === "skip" || b?.already_replied === "send") j.already_replied = b.already_replied;
|
|
358
|
+
if (b?.already_replied_if !== undefined) j.already_replied_if = asList(b.already_replied_if);
|
|
359
|
+
/* generic requests ("quero", "me manda"…) and what the DM delivers, for Claude to read the context */
|
|
360
|
+
if (typeof b?.generic_phrases === "boolean") j.generic_phrases = b.generic_phrases;
|
|
361
|
+
if (typeof b?.offer === "string") j.offer = b.offer.trim();
|
|
362
|
+
/* the Reel link on the DM's first line, so it reads as a reply to it */
|
|
363
|
+
if (typeof b?.quote_post === "boolean") j.quote_post = b.quote_post;
|
|
364
|
+
/* the button and the follow gate */
|
|
365
|
+
if (typeof b?.button === "boolean") j.button = b.button;
|
|
366
|
+
if (typeof b?.require_follow === "boolean") j.require_follow = b.require_follow;
|
|
367
|
+
/* the link in a button that opens it */
|
|
368
|
+
if (typeof b?.link_button === "boolean") j.link_button = b.link_button;
|
|
369
|
+
if (typeof b?.follow_skip === "boolean") j.follow_skip = b.follow_skip;
|
|
370
|
+
for (const k of [
|
|
371
|
+
"opening_dm",
|
|
372
|
+
"button_label",
|
|
373
|
+
"not_following_dm",
|
|
374
|
+
"retry_label",
|
|
375
|
+
"link_label",
|
|
376
|
+
"skip_label",
|
|
377
|
+
"skip_dm",
|
|
378
|
+
])
|
|
379
|
+
if (typeof b?.[k] === "string") j[k] = b[k].trim();
|
|
380
|
+
return j;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const saving = new Set<string>();
|
|
384
|
+
|
|
385
|
+
export async function saveRule(
|
|
386
|
+
a: RuleTarget,
|
|
387
|
+
body: unknown,
|
|
388
|
+
): Promise<Result & { rule?: unknown; problems?: string[] }> {
|
|
389
|
+
const e = checkTarget(a);
|
|
390
|
+
if (e) return { ok: false, error: e };
|
|
391
|
+
if (saving.has(a.dir)) return { ok: false, error: "a regra deste vídeo já está sendo gravada" };
|
|
392
|
+
saving.add(a.dir);
|
|
393
|
+
try {
|
|
394
|
+
const r = await run([NODE, OMNICHAT, "rule", a.dir, "--json", JSON.stringify(ruleFields(body))]);
|
|
395
|
+
if (r.code !== 0) return failed(r);
|
|
396
|
+
const j = JSON.parse(r.stdout.trim().split("\n").pop()!);
|
|
397
|
+
const problems: string[] = j.problems ?? [];
|
|
398
|
+
const output = problems.length
|
|
399
|
+
? "regra gravada, desligada: veja acima o que falta para ligar"
|
|
400
|
+
: j.rule?.enabled
|
|
401
|
+
? "regra gravada: vale a partir da próxima passada"
|
|
402
|
+
: "regra gravada";
|
|
403
|
+
return { ok: true, output, rule: j.rule, problems };
|
|
404
|
+
} catch (e: any) {
|
|
405
|
+
return { ok: false, error: `não deu para ler a resposta do omnichat.mjs: ${e.message}` };
|
|
406
|
+
} finally {
|
|
407
|
+
saving.delete(a.dir);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/* A post already live (Reel, carousel or photo): `post` finds the media by link among the account's
|
|
412
|
+
posts and creates .omnichat/posts/<code>/ (or uses the video or carousel dir, if the post came from
|
|
413
|
+
omnivideo). Prints "<Reel|carrossel|foto|post> <permalink> → <dir>" and the rule's JSON on the last line. */
|
|
414
|
+
export async function createRuleFromLink(
|
|
415
|
+
body: any,
|
|
416
|
+
): Promise<Result & { type?: RuleTarget["type"]; id?: string; problems?: string[] }> {
|
|
417
|
+
const link = String(body?.link ?? "").trim();
|
|
418
|
+
if (!/^https?:\/\/(www\.)?instagram\.com\/\S+$/.test(link) && !/^\d{5,}$/.test(link))
|
|
419
|
+
return {
|
|
420
|
+
ok: false,
|
|
421
|
+
error:
|
|
422
|
+
"cole o link do post, como https://www.instagram.com/p/…/ (carrossel ou foto) ou https://www.instagram.com/reel/…/",
|
|
423
|
+
};
|
|
424
|
+
/* the body's `link` is the post's: what the DM delivers is left for the rule panel */
|
|
425
|
+
const { link: _post, ...rule } = body ?? {};
|
|
426
|
+
const r = await run([NODE, OMNICHAT, "post", link, "--json", JSON.stringify(ruleFields(rule))]);
|
|
427
|
+
if (r.code !== 0) return failed(r);
|
|
428
|
+
const lines = r.stdout.trim().split("\n");
|
|
429
|
+
const dest = /→ (.+)$/.exec(lines[0] ?? "")?.[1]?.trim() ?? "";
|
|
430
|
+
const m = /(?:^|\/)\.omnichat\/posts\/([\w-]+)$/.exec(dest);
|
|
431
|
+
const carousel = !m && basename(dest) === "carousel";
|
|
432
|
+
const video = m ? null : basename(carousel ? dirname(dest) : dest);
|
|
433
|
+
let problems: string[] = [];
|
|
434
|
+
try {
|
|
435
|
+
problems = JSON.parse(lines.at(-1)!).problems ?? [];
|
|
436
|
+
} catch {
|
|
437
|
+
/* the rule was saved; the UI rereads the rest */
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
ok: true,
|
|
441
|
+
output: lines[0] ?? "regra criada",
|
|
442
|
+
type: m ? "post" : carousel ? "carousel" : "video",
|
|
443
|
+
id: m?.[1] ?? video!,
|
|
444
|
+
problems,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/* --- enable, disable, retry, dry run -------------------------------------------------- */
|
|
449
|
+
/* enabling means replying outward, on behalf of the account: the Omnichat page only gets here with the
|
|
450
|
+
confirmation; the Publish stage enables through the term (enableRuleWithTerm). Disabling also works with no
|
|
451
|
+
rule enabled: it records "don't enable by yourself when the Reel goes up" */
|
|
452
|
+
export async function enableRule(a: RuleTarget, enable: unknown): Promise<Result> {
|
|
453
|
+
if (typeof enable !== "boolean") return { ok: false, error: "enable: true ou false" };
|
|
454
|
+
const e = checkTarget(a);
|
|
455
|
+
if (e) return { ok: false, error: e };
|
|
456
|
+
const r = await run([NODE, OMNICHAT, enable ? "enable" : "disable", a.dir, ...(enable ? ["--confirm"] : [])]);
|
|
457
|
+
return r.code === 0 ? { ok: true, output: joinOutput(r) } : failed(r);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/* The Publish stage asks only for the term, and saving the term already enables, no second click: writing the
|
|
461
|
+
term is the authorization, by the user's decision. Without a rule, it is born with the config defaults
|
|
462
|
+
(the CLI adds the DM and the public replies), generic requests on and the link found in the video.
|
|
463
|
+
With a disabled rule, only the term changes, and the link only goes in if it has none: link, DM, replies
|
|
464
|
+
and already-replied stay as the Omnichat page left them. A rule that doesn't add up (without the link
|
|
465
|
+
the DM uses) stays saved, disabled, and the response says what's missing. */
|
|
466
|
+
export async function enableRuleWithTerm(a: RuleTarget, term: unknown): Promise<Result & { problems?: string[] }> {
|
|
467
|
+
const e = checkTarget(a);
|
|
468
|
+
if (e) return { ok: false, error: e };
|
|
469
|
+
const keywords = asList(typeof term === "string" ? term : "");
|
|
470
|
+
if (!keywords.length) return { ok: false, error: "escreva o termo que a pessoa comenta, como QUERO" };
|
|
471
|
+
if (saving.has(a.dir)) return { ok: false, error: "a regra deste vídeo já está sendo gravada" };
|
|
472
|
+
saving.add(a.dir);
|
|
473
|
+
try {
|
|
474
|
+
const before = ruleOf(a.dir) as {
|
|
475
|
+
enabled?: boolean;
|
|
476
|
+
any_comment?: boolean;
|
|
477
|
+
keywords?: string[];
|
|
478
|
+
link?: string;
|
|
479
|
+
} | null;
|
|
480
|
+
if (before?.enabled) return { ok: false, error: "a regra já está ligada: troque o termo na página do Omnichat" };
|
|
481
|
+
if (before?.any_comment)
|
|
482
|
+
return { ok: false, error: "esta regra dispara com qualquer comentário: ligue pela página do Omnichat" };
|
|
483
|
+
const j: Record<string, unknown> = {};
|
|
484
|
+
if (!before) Object.assign(j, { keywords, any_comment: false, generic_phrases: true });
|
|
485
|
+
else if (JSON.stringify(before.keywords ?? []) !== JSON.stringify(keywords)) j.keywords = keywords;
|
|
486
|
+
const found = a.type !== "post" ? videoLink(a.dir) : null;
|
|
487
|
+
if (!before?.link && found) j.link = found.link;
|
|
488
|
+
if (Object.keys(j).length) {
|
|
489
|
+
const r = await run([NODE, OMNICHAT, "rule", a.dir, "--json", JSON.stringify(j)]);
|
|
490
|
+
if (r.code !== 0) return failed(r);
|
|
491
|
+
const problems: string[] = JSON.parse(r.stdout.trim().split("\n").pop()!).problems ?? [];
|
|
492
|
+
if (problems.length)
|
|
493
|
+
return {
|
|
494
|
+
ok: false,
|
|
495
|
+
error: `o termo ficou gravado, mas a regra não liga ainda:\n${problems.join("\n")}`,
|
|
496
|
+
problems,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
const r = await run([NODE, OMNICHAT, "enable", a.dir, "--confirm"]);
|
|
500
|
+
return r.code === 0 ? { ok: true, output: joinOutput(r) } : failed(r);
|
|
501
|
+
} catch (e: any) {
|
|
502
|
+
return { ok: false, error: `não deu para ler a resposta do omnichat.mjs: ${e.message}` };
|
|
503
|
+
} finally {
|
|
504
|
+
saving.delete(a.dir);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/* what failed without the DM goes back to the queue (within the 7 days) */
|
|
509
|
+
export async function retryFailed(a: RuleTarget): Promise<Result> {
|
|
510
|
+
const e = checkTarget(a);
|
|
511
|
+
if (e) return { ok: false, error: e };
|
|
512
|
+
const r = await run([NODE, OMNICHAT, "retry", a.dir]);
|
|
513
|
+
return r.code === 0 ? { ok: true, output: joinOutput(r) } : failed(r);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/* reads the comments and shows what it would send, without sending or saving anything; one per dir */
|
|
517
|
+
const dryRunning = new Set<string>();
|
|
518
|
+
export async function dryRunRule(a: RuleTarget): Promise<Result> {
|
|
519
|
+
const e = checkTarget(a);
|
|
520
|
+
if (e) return { ok: false, error: e };
|
|
521
|
+
if (dryRunning.has(a.dir)) return { ok: false, error: "a simulação deste vídeo já está rodando" };
|
|
522
|
+
dryRunning.add(a.dir);
|
|
523
|
+
try {
|
|
524
|
+
const r = await run([NODE, OMNICHAT, "run", a.dir, "--dry-run"]);
|
|
525
|
+
return r.code === 0 ? { ok: true, output: joinOutput(r) || "nada para mostrar" } : failed(r);
|
|
526
|
+
} finally {
|
|
527
|
+
dryRunning.delete(a.dir);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/* --- by hand: who got no DM, and the chosen send -------------------------------------
|
|
532
|
+
`comments --json` reads the Reel now (seconds) and returns who didn't get the DM; read only.
|
|
533
|
+
`send` sends the rule's DM to the chosen ones: the UI only gets here with the confirmation in two
|
|
534
|
+
clicks (in bulk) or with the written reply and the Reply of a focused comment, and
|
|
535
|
+
--confirm is it. `text` is the comment reply written on the UI, in place of
|
|
536
|
+
the rule's random ones; `public_reply_only` replies only on the comment, without the DM. One of each per dir. */
|
|
537
|
+
const readingComments = new Set<string>();
|
|
538
|
+
export async function listMissingDms(a: RuleTarget): Promise<Result & { list?: unknown }> {
|
|
539
|
+
const e = checkTarget(a);
|
|
540
|
+
if (e) return { ok: false, error: e };
|
|
541
|
+
if (readingComments.has(a.dir)) return { ok: false, error: "os comentários deste vídeo já estão sendo lidos" };
|
|
542
|
+
readingComments.add(a.dir);
|
|
543
|
+
try {
|
|
544
|
+
const r = await run([NODE, OMNICHAT, "comments", a.dir, "--json"]);
|
|
545
|
+
if (r.code !== 0) return failed(r);
|
|
546
|
+
return { ok: true, output: "", list: JSON.parse(r.stdout.trim().split("\n").pop()!) };
|
|
547
|
+
} catch (e: any) {
|
|
548
|
+
return { ok: false, error: `não deu para ler a resposta do omnichat.mjs: ${e.message}` };
|
|
549
|
+
} finally {
|
|
550
|
+
readingComments.delete(a.dir);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const sending = new Set<string>();
|
|
555
|
+
export async function sendManual(a: RuleTarget, body: any): Promise<Result & { sent?: number; results?: unknown }> {
|
|
556
|
+
const e = checkTarget(a);
|
|
557
|
+
if (e) return { ok: false, error: e };
|
|
558
|
+
const ids: string[] = Array.isArray(body?.ids) ? body.ids.map(String) : [];
|
|
559
|
+
if (!ids.length || ids.length > 500 || ids.some(i => !/^\d{1,30}$/.test(i)))
|
|
560
|
+
return { ok: false, error: "escolha os comentários" };
|
|
561
|
+
const publicOnly = body?.public_reply_only === true;
|
|
562
|
+
const text = typeof body?.text === "string" ? body.text.trim() : "";
|
|
563
|
+
if (text.startsWith("--")) return { ok: false, error: "a resposta no comentário não pode começar com --" };
|
|
564
|
+
if (text.length > 2200) return { ok: false, error: "a resposta no comentário passa de 2.200 caracteres" };
|
|
565
|
+
if (publicOnly && !text) return { ok: false, error: "sem a DM, escreva a resposta no comentário" };
|
|
566
|
+
if (sending.has(a.dir)) return { ok: false, error: "já estou mandando as respostas deste vídeo" };
|
|
567
|
+
sending.add(a.dir);
|
|
568
|
+
try {
|
|
569
|
+
const publicFlags = publicOnly
|
|
570
|
+
? ["--public-reply-only", "--public-reply", text]
|
|
571
|
+
: body?.public_reply === false
|
|
572
|
+
? ["--no-public-reply"]
|
|
573
|
+
: text
|
|
574
|
+
? ["--public-reply", text]
|
|
575
|
+
: [];
|
|
576
|
+
const r = await run([NODE, OMNICHAT, "send", a.dir, ...ids, "--confirm", "--json", ...publicFlags]);
|
|
577
|
+
if (r.code !== 0) return failed(r);
|
|
578
|
+
const j = JSON.parse(r.stdout.trim().split("\n").pop()!);
|
|
579
|
+
return {
|
|
580
|
+
ok: true,
|
|
581
|
+
output: `${j.sent} de ${ids.length} ${publicOnly ? "respondidos no comentário" : "com a DM entregue"}`,
|
|
582
|
+
sent: j.sent,
|
|
583
|
+
results: j.results,
|
|
584
|
+
};
|
|
585
|
+
} catch (e: any) {
|
|
586
|
+
return { ok: false, error: `não deu para ler a resposta do omnichat.mjs: ${e.message}` };
|
|
587
|
+
} finally {
|
|
588
|
+
sending.delete(a.dir);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/* Apagar: deletes comments on Instagram (a person's or the account's own reply), no way back. The UI asks
|
|
593
|
+
for a second click first; `delete --confirm` is that. One per dir. */
|
|
594
|
+
const deleting = new Set<string>();
|
|
595
|
+
export async function deleteComments(
|
|
596
|
+
a: RuleTarget,
|
|
597
|
+
ids: string[],
|
|
598
|
+
): Promise<Result & { deleted?: number; results?: unknown }> {
|
|
599
|
+
const e = checkTarget(a);
|
|
600
|
+
if (e) return { ok: false, error: e };
|
|
601
|
+
if (!ids.length || ids.length > 50 || ids.some(i => !/^\d{1,30}$/.test(i)))
|
|
602
|
+
return { ok: false, error: "escolha os comentários" };
|
|
603
|
+
if (deleting.has(a.dir)) return { ok: false, error: "já estou apagando comentários deste vídeo" };
|
|
604
|
+
deleting.add(a.dir);
|
|
605
|
+
try {
|
|
606
|
+
const r = await run([NODE, OMNICHAT, "delete", a.dir, ...ids, "--confirm", "--json"]);
|
|
607
|
+
if (r.code !== 0) return failed(r);
|
|
608
|
+
const j = JSON.parse(r.stdout.trim().split("\n").pop()!);
|
|
609
|
+
if (!j.deleted) return { ok: false, error: j.results?.[0]?.reason ?? "não deu para apagar" };
|
|
610
|
+
return { ok: true, output: `${j.deleted} de ${ids.length} apagados`, deleted: j.deleted, results: j.results };
|
|
611
|
+
} catch (e: any) {
|
|
612
|
+
return { ok: false, error: `não deu para ler a resposta do omnichat.mjs: ${e.message}` };
|
|
613
|
+
} finally {
|
|
614
|
+
deleting.delete(a.dir);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/* enables and disables the launchd agent: installs with the studio's node */
|
|
619
|
+
export async function setOmnichatAgent(enable: unknown): Promise<Result> {
|
|
620
|
+
if (typeof enable !== "boolean") return { ok: false, error: "enable: true ou false" };
|
|
621
|
+
const r = await run([NODE, OMNICHAT, "agent", enable ? "install" : "uninstall"]);
|
|
622
|
+
cachedAgent = null;
|
|
623
|
+
return r.code === 0 ? { ok: true, output: joinOutput(r) } : failed(r);
|
|
624
|
+
}
|