machinaos 0.0.76 → 0.0.78
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/README.md +143 -107
- package/client/dist/assets/ActionBar-Du2MSFSz.js +1 -0
- package/client/dist/assets/ApiKeyInput-k2LBmBjb.js +1 -0
- package/client/dist/assets/ApiKeyPanel-C_bV9U0X.js +1 -0
- package/client/dist/assets/ApiUsageSection-CmVfwZzL.js +1 -0
- package/client/dist/assets/EmailPanel-CeKIMGu-.js +1 -0
- package/client/dist/assets/OAuthPanel-KA3t3Q2K.js +1 -0
- package/client/dist/assets/QrPairingPanel-NgNpJNuk.js +1 -0
- package/client/dist/assets/RateLimitSection-Du5YNVIA.js +1 -0
- package/client/dist/assets/StatusCard-DNLyayXc.js +1 -0
- package/client/dist/assets/index-DQ0nwhec.js +257 -0
- package/client/dist/assets/index-DxmbVskS.css +1 -0
- package/client/dist/assets/vendor-flow-CZmBvHRo.js +1 -0
- package/client/dist/assets/vendor-icons-CVrPjN2Q.js +22 -0
- package/client/dist/assets/vendor-markdown-CRou3yQ5.js +62 -0
- package/client/dist/assets/vendor-misc-C4VxKHs5.js +1 -0
- package/client/dist/assets/vendor-query-SzWcOU0G.js +1 -0
- package/client/dist/assets/vendor-radix-Dnos29jG.js +56 -0
- package/client/dist/assets/vendor-react-DvWIbVx0.js +1 -0
- package/client/dist/index.html +37 -3
- package/client/index.html +28 -1
- package/client/package.json +44 -40
- package/client/src/App.tsx +2 -0
- package/client/src/Dashboard.tsx +157 -45
- package/client/src/ParameterPanel.tsx +3 -5
- package/client/src/adapters/nodeSpecToDescription.ts +1 -0
- package/client/src/assets/icons/NodeIcon.tsx +32 -0
- package/client/src/assets/icons/index.ts +4 -0
- package/client/src/assets/icons/stripe.svg +1 -0
- package/client/src/assets/icons/themedGlyphs.ts +404 -0
- package/client/src/components/AIAgentNode.tsx +77 -53
- package/client/src/components/GenericNode.tsx +34 -52
- package/client/src/components/OutputPanel.tsx +64 -147
- package/client/src/components/ParameterRenderer.tsx +5 -3
- package/client/src/components/SkillEditorModal.tsx +9 -18
- package/client/src/components/SquareNode.tsx +97 -115
- package/client/src/components/StartNode.tsx +32 -42
- package/client/src/components/SvgFilterDefs.tsx +54 -0
- package/client/src/components/TeamMonitorNode.tsx +12 -14
- package/client/src/components/ToolkitNode.tsx +35 -60
- package/client/src/components/TriggerNode.tsx +43 -77
- package/client/src/components/__tests__/CredentialsModal.test.tsx +49 -45
- package/client/src/components/credentials/CredentialsModal.tsx +98 -30
- package/client/src/components/credentials/CredentialsPalette.tsx +73 -5
- package/client/src/components/credentials/catalogueAdapter.ts +17 -1
- package/client/src/components/credentials/panels/ApiKeyPanel.tsx +102 -37
- package/client/src/components/credentials/panels/EmailPanel.tsx +7 -19
- package/client/src/components/credentials/panels/OAuthPanel.tsx +5 -1
- package/client/src/components/credentials/panels/QrPairingPanel.tsx +1 -3
- package/client/src/components/credentials/primitives/ActionBar.tsx +7 -11
- package/client/src/components/credentials/primitives/OAuthConnect.tsx +19 -28
- package/client/src/components/credentials/sections/ProviderDefaultsSection.tsx +24 -3
- package/client/src/components/credentials/types.ts +12 -2
- package/client/src/components/credentials/useCredentialPanel.ts +43 -19
- package/client/src/components/icons/AIProviderIcons.tsx +16 -0
- package/client/src/components/onboarding/OnboardingWizard.tsx +23 -63
- package/client/src/components/onboarding/nodeRoleClasses.ts +23 -0
- package/client/src/components/onboarding/steps/CanvasStep.tsx +15 -21
- package/client/src/components/onboarding/steps/ConceptsStep.tsx +2 -11
- package/client/src/components/onboarding/steps/GetStartedStep.tsx +2 -10
- package/client/src/components/parameterPanel/InputSection.tsx +9 -7
- package/client/src/components/parameterPanel/MasterSkillEditor.tsx +84 -198
- package/client/src/components/parameterPanel/MiddleSection.tsx +57 -80
- package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +31 -25
- package/client/src/components/parameterPanel/__tests__/InputSection.test.tsx +7 -2
- package/client/src/components/ui/AIResultModal.tsx +1 -1
- package/client/src/components/ui/CollapsibleSection.tsx +9 -5
- package/client/src/components/ui/CommandPalette.tsx +147 -0
- package/client/src/components/ui/CommandPaletteHost.tsx +189 -0
- package/client/src/components/ui/ComponentItem.tsx +13 -7
- package/client/src/components/ui/ComponentPalette.tsx +24 -13
- package/client/src/components/ui/ConsolePanel.tsx +19 -11
- package/client/src/components/ui/DropCap.tsx +28 -0
- package/client/src/components/ui/EditableNodeLabel.tsx +10 -2
- package/client/src/components/ui/InputNodesPanel.tsx +1 -1
- package/client/src/components/ui/Modal.tsx +38 -6
- package/client/src/components/ui/OutputDisplayPanel.tsx +1 -1
- package/client/src/components/ui/SettingsPanel.tsx +42 -13
- package/client/src/components/ui/StatusBar.tsx +108 -0
- package/client/src/components/ui/ThemeSwitcher.tsx +109 -0
- package/client/src/components/ui/TopToolbar.tsx +42 -25
- package/client/src/components/ui/WorkflowSidebar.tsx +32 -16
- package/client/src/components/ui/action-button.tsx +40 -15
- package/client/src/components/ui/button.tsx +24 -1
- package/client/src/components/ui/dropdown-menu.tsx +24 -2
- package/client/src/components/ui/input.tsx +19 -2
- package/client/src/components/ui/select.tsx +15 -0
- package/client/src/components/ui/textarea.tsx +15 -2
- package/client/src/contexts/AuthContext.tsx +148 -109
- package/client/src/contexts/ThemeContext.tsx +93 -17
- package/client/src/contexts/WebSocketContext.tsx +373 -206
- package/client/src/contexts/__tests__/AuthContext.test.tsx +221 -0
- package/client/src/hooks/__tests__/useDragVariable.test.ts +7 -1
- package/client/src/hooks/__tests__/useWorkflowOpsListener.test.ts +142 -0
- package/client/src/hooks/useAppTheme.ts +209 -7
- package/client/src/hooks/useAutoSkillEdges.ts +7 -2
- package/client/src/hooks/useCatalogueQuery.ts +67 -1
- package/client/src/hooks/useDragVariable.ts +1 -1
- package/client/src/hooks/useNodeAllowlist.ts +115 -8
- package/client/src/hooks/useOnboarding.ts +20 -8
- package/client/src/hooks/useParameterPanel.ts +2 -1
- package/client/src/hooks/useReactFlowNodes.ts +2 -1
- package/client/src/hooks/useSound.ts +185 -0
- package/client/src/hooks/useWorkflowManagement.ts +6 -8
- package/client/src/hooks/useWorkflowOpsListener.ts +90 -0
- package/client/src/index.css +65 -3
- package/client/src/lib/__tests__/connectionConfig.test.ts +91 -0
- package/client/src/lib/aiModelProviders.ts +8 -0
- package/client/src/lib/connectionConfig.ts +107 -0
- package/client/src/lib/queryPersist.ts +13 -5
- package/client/src/lib/sound.ts +393 -0
- package/client/src/main.tsx +20 -0
- package/client/src/store/useAppStore.ts +26 -0
- package/client/src/styles/canvasAnimations.ts +37 -36
- package/client/src/styles/theme.ts +36 -20
- package/client/src/test/setup.ts +1 -0
- package/client/src/themes/atomic.css +253 -0
- package/client/src/themes/base.css +373 -0
- package/client/src/themes/cyber.css +890 -0
- package/client/src/themes/dark.css +70 -0
- package/client/src/themes/edo.css +246 -0
- package/client/src/themes/greek.css +293 -0
- package/client/src/themes/light.css +78 -0
- package/client/src/themes/plague.css +253 -0
- package/client/src/themes/renaissance.css +727 -0
- package/client/src/themes/rot.css +249 -0
- package/client/src/themes/steampunk.css +272 -0
- package/client/src/themes/surveillance.css +289 -0
- package/client/src/themes/wasteland.css +250 -0
- package/client/src/types/INodeProperties.ts +5 -0
- package/client/src/types/NodeTypes.ts +11 -1
- package/client/src/types/__tests__/cloudEvents.test.ts +99 -0
- package/client/src/types/cloudEvents.ts +78 -0
- package/client/src/vite-env.d.ts +7 -0
- package/client/tsconfig.json +1 -1
- package/client/vite.config.js +62 -2
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- package/machina/commands/build.py +51 -7
- package/machina/pyproject.toml +4 -0
- package/machina/supervisor.py +12 -2
- package/machina/tree.py +71 -21
- package/package.json +4 -4
- package/scripts/install.js +16 -1
- package/server/config/ai_cli_providers.json +54 -0
- package/server/config/credential_providers.json +109 -2
- package/server/config/llm_defaults.json +24 -0
- package/server/config/model_registry.json +338 -499
- package/server/config/node_allowlist.json +16 -1
- package/server/config/pricing.json +8 -0
- package/server/constants.py +38 -15
- package/server/core/container.py +2 -2
- package/server/core/credentials_database.py +35 -2
- package/server/core/logging.py +4 -3
- package/server/main.py +99 -13
- package/server/models/node_metadata.py +1 -0
- package/server/nodejs/package.json +8 -6
- package/server/nodejs/src/index.ts +22 -5
- package/server/nodes/README.md +31 -4
- package/server/nodes/agent/_inline.py +2 -0
- package/server/nodes/agent/_specialized.py +6 -3
- package/server/nodes/agent/ai_agent.py +13 -3
- package/server/nodes/agent/chat_agent.py +6 -3
- package/server/nodes/agent/claude_code_agent.py +287 -75
- package/server/nodes/agent/codex_agent.py +239 -0
- package/server/nodes/agent/deep_agent.py +3 -3
- package/server/nodes/agent/rlm_agent.py +3 -3
- package/server/nodes/android/__init__.py +31 -1
- package/server/nodes/android/_base.py +9 -5
- package/server/{services/android_service.py → nodes/android/_dispatcher.py} +2 -2
- package/server/nodes/android/_handlers.py +154 -0
- package/server/nodes/android/_option_loaders.py +44 -0
- package/server/nodes/android/_refresh.py +127 -0
- package/server/{services/android → nodes/android/_relay}/client.py +4 -4
- package/server/{routers/android.py → nodes/android/_router.py} +27 -8
- package/server/nodes/browser/browser.py +2 -2
- package/server/nodes/code/_base.py +6 -2
- package/server/nodes/code/_claude_code.py +134 -0
- package/server/nodes/document/embedding_generator.py +3 -3
- package/server/nodes/document/http_scraper.py +3 -3
- package/server/nodes/document/vector_store.py +5 -5
- package/server/nodes/email/__init__.py +11 -1
- package/server/nodes/email/_filters.py +21 -0
- package/server/{services/himalaya_service.py → nodes/email/_himalaya.py} +6 -10
- package/server/{services/email_service.py → nodes/email/_service.py} +9 -13
- package/server/nodes/email/email_read.py +1 -1
- package/server/nodes/email/email_receive.py +54 -5
- package/server/nodes/email/email_send.py +1 -1
- package/server/nodes/filesystem/shell.py +24 -1
- package/server/nodes/google/__init__.py +55 -1
- package/server/{services/handlers/google_auth.py → nodes/google/_auth_helper.py} +8 -5
- package/server/nodes/google/_base.py +2 -2
- package/server/nodes/google/_credentials.py +5 -5
- package/server/nodes/google/_filters.py +25 -0
- package/server/nodes/google/_handlers.py +57 -0
- package/server/{services/google_oauth.py → nodes/google/_oauth.py} +195 -162
- package/server/nodes/google/_option_loaders.py +107 -0
- package/server/nodes/google/_refresh.py +66 -0
- package/server/nodes/google/_router.py +131 -0
- package/server/nodes/google/gmail_receive.py +41 -4
- package/server/nodes/groups.py +1 -0
- package/server/nodes/location/_credentials.py +45 -1
- package/server/{services/maps.py → nodes/location/_service.py} +18 -3
- package/server/nodes/location/gmaps_create.py +4 -4
- package/server/nodes/location/gmaps_locations.py +4 -4
- package/server/nodes/location/gmaps_nearby_places.py +4 -4
- package/server/nodes/model/_base.py +8 -3
- package/server/nodes/model/_credentials.py +96 -8
- package/server/nodes/model/_local_validator.py +345 -0
- package/server/nodes/model/lmstudio_chat_model.py +23 -0
- package/server/nodes/model/ollama_chat_model.py +25 -0
- package/server/nodes/proxy/_usage.py +2 -2
- package/server/nodes/proxy/proxy_config.py +14 -14
- package/server/nodes/proxy/proxy_request.py +4 -4
- package/server/nodes/scraper/_credentials.py +29 -1
- package/server/nodes/scraper/apify_actor.py +9 -9
- package/server/nodes/scraper/crawlee_scraper.py +5 -5
- package/server/nodes/search/brave_search.py +4 -0
- package/server/nodes/search/perplexity_search.py +9 -0
- package/server/nodes/search/serper_search.py +3 -0
- package/server/nodes/skill/simple_memory.py +12 -0
- package/server/nodes/social/_base.py +2 -2
- package/server/nodes/stripe/__init__.py +46 -0
- package/server/nodes/stripe/_credentials.py +33 -0
- package/server/nodes/stripe/_handlers.py +270 -0
- package/server/nodes/stripe/_install.py +127 -0
- package/server/nodes/stripe/_source.py +174 -0
- package/server/nodes/stripe/stripe_action.py +81 -0
- package/server/nodes/stripe/stripe_receive.py +92 -0
- package/server/nodes/telegram/_credentials.py +52 -1
- package/server/nodes/telegram/_handlers.py +19 -18
- package/server/nodes/telegram/_service.py +134 -32
- package/server/nodes/telegram/telegram_send.py +5 -6
- package/server/nodes/text/file_handler.py +2 -2
- package/server/nodes/text/text_generator.py +2 -2
- package/server/nodes/tool/agent_builder.py +630 -0
- package/server/nodes/tool/task_manager.py +144 -2
- package/server/nodes/twitter/__init__.py +38 -1
- package/server/nodes/twitter/_base.py +7 -7
- package/server/nodes/twitter/_credentials.py +1 -1
- package/server/nodes/twitter/_filters.py +37 -0
- package/server/nodes/twitter/_handlers.py +77 -0
- package/server/nodes/twitter/_oauth.py +124 -0
- package/server/nodes/twitter/_refresh.py +78 -0
- package/server/nodes/twitter/_router.py +29 -0
- package/server/nodes/twitter/twitter_receive.py +4 -0
- package/server/nodes/visuals.json +64 -19
- package/server/nodes/whatsapp/__init__.py +45 -5
- package/server/nodes/whatsapp/_base.py +3 -3
- package/server/nodes/whatsapp/_filters.py +137 -0
- package/server/nodes/whatsapp/_handlers.py +167 -0
- package/server/nodes/whatsapp/_option_loaders.py +68 -0
- package/server/nodes/whatsapp/_refresh.py +62 -0
- package/server/nodes/whatsapp/_runtime.py +1 -1
- package/server/pyproject.toml +29 -7
- package/server/routers/schemas.py +2 -2
- package/server/routers/webhook.py +26 -9
- package/server/routers/websocket.py +149 -810
- package/server/services/ai.py +89 -8
- package/server/services/auth.py +220 -43
- package/server/services/claude_oauth.py +126 -100
- package/server/services/cli_agent/__init__.py +78 -0
- package/server/services/cli_agent/_handlers.py +237 -0
- package/server/services/cli_agent/config.py +112 -0
- package/server/services/cli_agent/factory.py +48 -0
- package/server/services/cli_agent/lockfile.py +141 -0
- package/server/services/cli_agent/mcp_server.py +482 -0
- package/server/services/cli_agent/protocol.py +173 -0
- package/server/services/cli_agent/providers/__init__.py +9 -0
- package/server/services/cli_agent/providers/anthropic_claude.py +419 -0
- package/server/services/cli_agent/providers/google_gemini.py +80 -0
- package/server/services/cli_agent/providers/openai_codex.py +310 -0
- package/server/services/cli_agent/service.py +607 -0
- package/server/services/cli_agent/session.py +618 -0
- package/server/services/cli_agent/types.py +227 -0
- package/server/services/cli_agent/workflow_tools.py +233 -0
- package/server/services/credential_registry.py +26 -1
- package/server/services/deployment/manager.py +26 -145
- package/server/services/deployment/poll_registry.py +59 -0
- package/server/services/event_waiter.py +76 -246
- package/server/services/events/__init__.py +54 -0
- package/server/services/events/cli.py +78 -0
- package/server/services/events/daemon.py +163 -0
- package/server/services/events/envelope.py +281 -0
- package/server/services/events/lifecycle.py +99 -0
- package/server/services/events/oauth_lifecycle.py +534 -0
- package/server/services/events/polling.py +60 -0
- package/server/services/events/push.py +36 -0
- package/server/services/events/source.py +63 -0
- package/server/services/events/triggers.py +118 -0
- package/server/services/events/verifiers/__init__.py +25 -0
- package/server/services/events/verifiers/base.py +28 -0
- package/server/services/events/verifiers/github.py +25 -0
- package/server/services/events/verifiers/hmac_basic.py +32 -0
- package/server/services/events/verifiers/standard_webhooks.py +47 -0
- package/server/services/events/verifiers/stripe.py +42 -0
- package/server/services/events/webhook.py +105 -0
- package/server/services/handlers/tools.py +28 -186
- package/server/services/llm/config.py +7 -0
- package/server/services/llm/factory.py +8 -2
- package/server/services/memory/__init__.py +52 -0
- package/server/services/memory/jsonl.py +80 -0
- package/server/services/memory/markdown.py +65 -0
- package/server/services/memory/state.py +112 -0
- package/server/services/memory/vector_store.py +40 -0
- package/server/services/model_registry.py +76 -0
- package/server/services/node_allowlist.py +71 -15
- package/server/services/node_executor.py +2 -2
- package/server/services/node_output_schemas.py +21 -10
- package/server/services/node_spec.py +1 -1
- package/server/services/oauth_utils.py +1 -1
- package/server/services/plugin/__init__.py +2 -0
- package/server/services/plugin/base.py +44 -2
- package/server/services/plugin/credential.py +288 -1
- package/server/services/plugin/deps.py +105 -0
- package/server/services/plugin/edge_walker.py +12 -4
- package/server/services/plugin/oauth.py +381 -0
- package/server/services/plugin/polling.py +247 -0
- package/server/services/plugin/registry.py +145 -0
- package/server/services/plugin/singleton.py +65 -0
- package/server/services/plugin/ws.py +81 -0
- package/server/services/process_service.py +31 -2
- package/server/services/status_broadcaster.py +155 -238
- package/server/services/temporal/workflow.py +7 -7
- package/server/services/workflow.py +21 -3
- package/server/services/ws_handler_registry.py +111 -28
- package/server/skills/GUIDE.md +16 -1
- package/server/skills/assistant/agent-builder-skill/SKILL.md +166 -0
- package/server/skills/payments_agent/stripe-skill/SKILL.md +306 -0
- package/server/tests/credentials/test_auth_service.py +16 -9
- package/server/tests/credentials/test_credential_broadcasts.py +219 -0
- package/server/tests/credentials/test_google_oauth.py +6 -6
- package/server/tests/credentials/test_oauth_utils.py +1 -1
- package/server/tests/credentials/test_twitter_oauth.py +2 -2
- package/server/tests/credentials/test_websocket_handlers.py +44 -20
- package/server/tests/llm/test_factory.py +1 -0
- package/server/tests/llm/test_wiring.py +5 -1
- package/server/tests/nodes/_compat.py +24 -24
- package/server/tests/nodes/test_agent_builder.py +439 -0
- package/server/tests/nodes/test_ai_tools.py +18 -14
- package/server/tests/nodes/test_code_fs_process.py +17 -8
- package/server/tests/nodes/test_email.py +10 -9
- package/server/tests/nodes/test_google_workspace.py +2 -2
- package/server/tests/nodes/test_specialized_agents.py +100 -53
- package/server/tests/nodes/test_stripe_plugin.py +293 -0
- package/server/tests/nodes/test_telegram_social.py +4 -4
- package/server/tests/nodes/test_twitter.py +1 -1
- package/server/tests/nodes/test_web_automation.py +2 -2
- package/server/tests/nodes/test_whatsapp.py +9 -9
- package/server/tests/services/cli_agent/__init__.py +0 -0
- package/server/tests/services/cli_agent/test_mcp_server.py +432 -0
- package/server/tests/services/cli_agent/test_providers.py +358 -0
- package/server/tests/services/cli_agent/test_service.py +298 -0
- package/server/tests/services/memory/__init__.py +0 -0
- package/server/tests/services/memory/test_jsonl.py +188 -0
- package/server/tests/services/test_events.py +333 -0
- package/server/tests/test_node_spec.py +56 -16
- package/server/tests/test_plugin_helpers.py +116 -0
- package/server/tests/test_plugin_self_containment.py +486 -0
- package/server/tests/test_status_broadcasts.py +425 -0
- package/workflows/{AI Assistant_workflow-1777421105154-0m4snkzjf.json → AI Assistant_workflow-1778504793388-ou1m1tz2x.json } +70 -266
- package/workflows/{AI Employee_workflow-1777720598005-u4cm858dv.json → AI Employee_example_workflow-1777720598005-u4cm858dv.json } +112 -112
- package/workflows/Claude Assistant_workflow-1778380124051-mdibn807c.json +709 -0
- package/client/dist/assets/ActionBar-vzPpSR77.js +0 -1
- package/client/dist/assets/ApiKeyInput-Ds7AKFe8.js +0 -1
- package/client/dist/assets/ApiKeyPanel-gfblELep.js +0 -1
- package/client/dist/assets/ApiUsageSection-BMNWTe2r.js +0 -1
- package/client/dist/assets/EmailPanel-B1Om64p5.js +0 -1
- package/client/dist/assets/OAuthPanel-CXyQYGBz.js +0 -1
- package/client/dist/assets/QrPairingPanel-BgNuI1we.js +0 -1
- package/client/dist/assets/RateLimitSection-YYK8sx1T.js +0 -1
- package/client/dist/assets/StatusCard-DuYA5hJR.js +0 -1
- package/client/dist/assets/index-D9tZfgvi.js +0 -363
- package/client/dist/assets/index-al7snTkG.css +0 -1
- package/client/src/components/credentials/providers.tsx +0 -177
- package/server/routers/google.py +0 -277
- package/server/routers/maps.py +0 -142
- package/server/routers/twitter.py +0 -365
- package/server/services/claude_code_service.py +0 -106
- package/server/services/memory.py +0 -159
- package/server/services/node_option_loaders/__init__.py +0 -77
- package/server/services/node_option_loaders/android_loaders.py +0 -55
- package/server/services/node_option_loaders/google_loaders.py +0 -97
- package/server/services/node_option_loaders/whatsapp_loaders.py +0 -69
- package/server/services/twitter_oauth.py +0 -411
- package/server/services/websocket_client.py +0 -29
- /package/server/{services/android → nodes/android/_relay}/__init__.py +0 -0
- /package/server/{services/android → nodes/android/_relay}/broadcaster.py +0 -0
- /package/server/{services/android → nodes/android/_relay}/manager.py +0 -0
- /package/server/{services/android → nodes/android/_relay}/protocol.py +0 -0
- /package/server/{services/browser_service.py → nodes/browser/_service.py} +0 -0
- /package/server/{services/whatsapp_service.py → nodes/whatsapp/_service.py} +0 -0
- /package/server/skills/{task_agent → assistant}/write-todos-skill/SKILL.md +0 -0
package/server/routers/google.py
DELETED
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Google Workspace OAuth 2.0 callback and API routes.
|
|
3
|
-
|
|
4
|
-
OAuth flow:
|
|
5
|
-
1. Frontend calls WebSocket 'google_oauth_login' handler
|
|
6
|
-
2. Backend generates authorization URL, opens browser
|
|
7
|
-
3. User authorizes on Google
|
|
8
|
-
4. Google redirects to /api/google/callback with code
|
|
9
|
-
5. Backend exchanges code for tokens, stores them via auth_service
|
|
10
|
-
6. Frontend polls WebSocket 'google_oauth_status' for completion
|
|
11
|
-
|
|
12
|
-
Two access modes:
|
|
13
|
-
- Owner Mode: Tokens stored via auth_service (single account)
|
|
14
|
-
- Customer Mode: Tokens stored in google_connections table (multi-account)
|
|
15
|
-
|
|
16
|
-
Supports all Google Workspace services:
|
|
17
|
-
- Gmail, Calendar, Drive, Sheets, Tasks, Contacts
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
from typing import Optional
|
|
21
|
-
|
|
22
|
-
from fastapi import APIRouter, Query, Request
|
|
23
|
-
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
24
|
-
|
|
25
|
-
from core.container import container
|
|
26
|
-
from core.logging import get_logger
|
|
27
|
-
from services.google_oauth import GoogleOAuth, get_pending_state
|
|
28
|
-
|
|
29
|
-
logger = get_logger(__name__)
|
|
30
|
-
router = APIRouter(prefix="/api/google", tags=["google"])
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def get_auth_service():
|
|
34
|
-
"""Get auth service for API key storage."""
|
|
35
|
-
return container.auth_service()
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def get_database():
|
|
39
|
-
"""Get database for customer connections (non-sensitive data only)."""
|
|
40
|
-
return container.database()
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
@router.get("/callback")
|
|
46
|
-
async def google_oauth_callback(
|
|
47
|
-
code: Optional[str] = Query(None),
|
|
48
|
-
state: Optional[str] = Query(None),
|
|
49
|
-
error: Optional[str] = Query(None),
|
|
50
|
-
error_description: Optional[str] = Query(None),
|
|
51
|
-
):
|
|
52
|
-
"""
|
|
53
|
-
Handle Google OAuth callback.
|
|
54
|
-
|
|
55
|
-
Google redirects here after user authorizes (or denies) the app.
|
|
56
|
-
"""
|
|
57
|
-
# Handle authorization denied
|
|
58
|
-
if error:
|
|
59
|
-
logger.warning(f"Google OAuth denied: {error} - {error_description}")
|
|
60
|
-
return HTMLResponse(
|
|
61
|
-
content=_callback_html(success=False, error=error_description or error),
|
|
62
|
-
status_code=200,
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
# Validate required parameters
|
|
66
|
-
if not code or not state:
|
|
67
|
-
logger.error("Google OAuth callback missing code or state")
|
|
68
|
-
return HTMLResponse(
|
|
69
|
-
content=_callback_html(success=False, error="Missing authorization code or state"),
|
|
70
|
-
status_code=400,
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
# Verify state exists (CSRF protection)
|
|
74
|
-
pending_state = get_pending_state(state)
|
|
75
|
-
if not pending_state:
|
|
76
|
-
logger.error("Google OAuth callback with invalid/expired state")
|
|
77
|
-
return HTMLResponse(
|
|
78
|
-
content=_callback_html(success=False, error="Invalid or expired state. Please try again."),
|
|
79
|
-
status_code=400,
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
# Get state data
|
|
83
|
-
state_data = pending_state.get("data", {})
|
|
84
|
-
mode = state_data.get("mode", "owner")
|
|
85
|
-
customer_id = state_data.get("customer_id")
|
|
86
|
-
redirect_after = state_data.get("redirect_after")
|
|
87
|
-
|
|
88
|
-
# Retrieve redirect_uri from state (set during auth initiation)
|
|
89
|
-
redirect_uri = pending_state.get("redirect_uri")
|
|
90
|
-
if not redirect_uri:
|
|
91
|
-
logger.error("Google OAuth callback missing redirect_uri in state")
|
|
92
|
-
return HTMLResponse(
|
|
93
|
-
content=_callback_html(success=False, error="Invalid state: missing redirect URI. Please try again."),
|
|
94
|
-
status_code=400,
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
# Get credentials
|
|
98
|
-
auth_service = get_auth_service()
|
|
99
|
-
client_id = await auth_service.get_api_key("google_client_id") or ""
|
|
100
|
-
client_secret = await auth_service.get_api_key("google_client_secret") or ""
|
|
101
|
-
|
|
102
|
-
if not client_id or not client_secret:
|
|
103
|
-
return HTMLResponse(
|
|
104
|
-
content=_callback_html(success=False, error="Google not configured. Add Client ID and Secret in Credentials."),
|
|
105
|
-
status_code=400,
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
oauth = GoogleOAuth(
|
|
109
|
-
client_id=client_id,
|
|
110
|
-
client_secret=client_secret,
|
|
111
|
-
redirect_uri=redirect_uri,
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
# Exchange code for tokens
|
|
115
|
-
result = oauth.exchange_code(code=code, state=state)
|
|
116
|
-
|
|
117
|
-
if not result.get("success"):
|
|
118
|
-
logger.error(f"Google token exchange failed: {result.get('error')}")
|
|
119
|
-
return HTMLResponse(
|
|
120
|
-
content=_callback_html(success=False, error=result.get("error", "Token exchange failed")),
|
|
121
|
-
status_code=400,
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
email = result.get("email", "Unknown")
|
|
125
|
-
name = result.get("name", "")
|
|
126
|
-
access_token = result.get("access_token")
|
|
127
|
-
refresh_token = result.get("refresh_token")
|
|
128
|
-
|
|
129
|
-
# Store tokens via auth_service (single point of access for credentials)
|
|
130
|
-
auth_service = get_auth_service()
|
|
131
|
-
|
|
132
|
-
if mode == "customer" and customer_id:
|
|
133
|
-
# Customer mode: store encrypted tokens via auth_service
|
|
134
|
-
await auth_service.store_oauth_tokens(
|
|
135
|
-
provider="google",
|
|
136
|
-
access_token=access_token,
|
|
137
|
-
refresh_token=refresh_token,
|
|
138
|
-
email=email,
|
|
139
|
-
name=name,
|
|
140
|
-
scopes=",".join(result.get("scopes", [])),
|
|
141
|
-
customer_id=customer_id,
|
|
142
|
-
)
|
|
143
|
-
logger.info(f"Google OAuth successful for customer {customer_id}: {email}")
|
|
144
|
-
|
|
145
|
-
if redirect_after:
|
|
146
|
-
return RedirectResponse(url=f"{redirect_after}?google_connected=true&customer={customer_id}&email={email}")
|
|
147
|
-
else:
|
|
148
|
-
# Owner mode: store encrypted tokens via auth_service
|
|
149
|
-
await auth_service.store_oauth_tokens(
|
|
150
|
-
provider="google",
|
|
151
|
-
access_token=access_token,
|
|
152
|
-
refresh_token=refresh_token,
|
|
153
|
-
email=email,
|
|
154
|
-
name=name,
|
|
155
|
-
scopes=",".join(result.get("scopes", [])),
|
|
156
|
-
customer_id="owner",
|
|
157
|
-
)
|
|
158
|
-
logger.info(f"Google OAuth successful for {email}")
|
|
159
|
-
|
|
160
|
-
# Update persistent status and broadcast completion event
|
|
161
|
-
from services.status_broadcaster import get_status_broadcaster
|
|
162
|
-
broadcaster = get_status_broadcaster()
|
|
163
|
-
broadcaster._status["google"] = {
|
|
164
|
-
"connected": True,
|
|
165
|
-
"email": email,
|
|
166
|
-
"name": name,
|
|
167
|
-
}
|
|
168
|
-
await broadcaster.broadcast({
|
|
169
|
-
"type": "google_oauth_complete",
|
|
170
|
-
"data": {"success": True, "email": email, "name": name, "mode": mode, "customer_id": customer_id},
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
return HTMLResponse(content=_callback_html(success=True, email=email), status_code=200)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
@router.get("/status")
|
|
177
|
-
async def get_google_status():
|
|
178
|
-
"""Get Google connection status for owner mode."""
|
|
179
|
-
auth_service = get_auth_service()
|
|
180
|
-
tokens = await auth_service.get_oauth_tokens("google", customer_id="owner")
|
|
181
|
-
|
|
182
|
-
if not tokens:
|
|
183
|
-
return {"connected": False, "email": None}
|
|
184
|
-
|
|
185
|
-
return {
|
|
186
|
-
"connected": True,
|
|
187
|
-
"email": tokens.get("email"),
|
|
188
|
-
"name": tokens.get("name"),
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
@router.post("/logout")
|
|
193
|
-
async def google_logout():
|
|
194
|
-
"""Disconnect Google (owner mode)."""
|
|
195
|
-
auth_service = get_auth_service()
|
|
196
|
-
await auth_service.remove_oauth_tokens("google", customer_id="owner")
|
|
197
|
-
logger.info("Google disconnected")
|
|
198
|
-
return {"success": True, "message": "Google disconnected"}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
@router.post("/customer-auth-url")
|
|
202
|
-
async def generate_customer_auth_url(request: Request, customer_id: str, redirect_after: Optional[str] = None):
|
|
203
|
-
"""Generate OAuth URL for a customer to connect their Google account."""
|
|
204
|
-
from services.oauth_utils import get_redirect_uri
|
|
205
|
-
|
|
206
|
-
auth_service = get_auth_service()
|
|
207
|
-
client_id = await auth_service.get_api_key("google_client_id") or ""
|
|
208
|
-
client_secret = await auth_service.get_api_key("google_client_secret") or ""
|
|
209
|
-
|
|
210
|
-
if not client_id or not client_secret:
|
|
211
|
-
return {"success": False, "error": "Google not configured. Add Client ID and Secret."}
|
|
212
|
-
|
|
213
|
-
redirect_uri = get_redirect_uri(request, "google")
|
|
214
|
-
|
|
215
|
-
oauth = GoogleOAuth(
|
|
216
|
-
client_id=client_id,
|
|
217
|
-
client_secret=client_secret,
|
|
218
|
-
redirect_uri=redirect_uri,
|
|
219
|
-
)
|
|
220
|
-
result = oauth.generate_authorization_url(state_data={"customer_id": customer_id, "redirect_after": redirect_after, "mode": "customer"})
|
|
221
|
-
return {"success": True, "url": result["url"], "state": result["state"]}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
@router.get("/customer/{customer_id}/status")
|
|
225
|
-
async def get_customer_google_status(customer_id: str):
|
|
226
|
-
"""Get Google connection status for a customer."""
|
|
227
|
-
auth_service = get_auth_service()
|
|
228
|
-
tokens = await auth_service.get_oauth_tokens("google", customer_id=customer_id)
|
|
229
|
-
if not tokens:
|
|
230
|
-
return {"connected": False, "customer_id": customer_id}
|
|
231
|
-
return {
|
|
232
|
-
"connected": True,
|
|
233
|
-
"customer_id": customer_id,
|
|
234
|
-
"email": tokens.get("email"),
|
|
235
|
-
"name": tokens.get("name"),
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
@router.post("/customer/{customer_id}/disconnect")
|
|
240
|
-
async def disconnect_customer_google(customer_id: str):
|
|
241
|
-
"""Disconnect a customer's Google account."""
|
|
242
|
-
auth_service = get_auth_service()
|
|
243
|
-
await auth_service.remove_oauth_tokens("google", customer_id=customer_id)
|
|
244
|
-
logger.info(f"Google disconnected for customer {customer_id}")
|
|
245
|
-
return {"success": True, "customer_id": customer_id}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
def _callback_html(success: bool, email: str = None, error: str = None) -> str:
|
|
249
|
-
"""Generate callback HTML page."""
|
|
250
|
-
if success:
|
|
251
|
-
title, message, color = "Google Connected", f"Successfully connected as {email}!", "#34a853"
|
|
252
|
-
else:
|
|
253
|
-
title, message, color = "Connection Failed", error or "Failed to connect", "#ea4335"
|
|
254
|
-
|
|
255
|
-
escaped_error = error.replace("'", "\\'") if error else ""
|
|
256
|
-
return f"""<!DOCTYPE html>
|
|
257
|
-
<html>
|
|
258
|
-
<head><title>{title}</title>
|
|
259
|
-
<style>
|
|
260
|
-
*{{margin:0;padding:0;box-sizing:border-box}}
|
|
261
|
-
body{{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:linear-gradient(135deg,#15202b,#1a1a2e);min-height:100vh;display:flex;align-items:center;justify-content:center;color:#fff}}
|
|
262
|
-
.container{{text-align:center;padding:40px;background:rgba(255,255,255,0.05);border-radius:16px;border:1px solid rgba(255,255,255,0.1);max-width:400px}}
|
|
263
|
-
.icon{{width:64px;height:64px;margin-bottom:20px;color:{color}}}
|
|
264
|
-
h1{{font-size:24px;margin-bottom:12px;color:{color}}}
|
|
265
|
-
p{{font-size:16px;color:rgba(255,255,255,0.8);margin-bottom:20px}}
|
|
266
|
-
.close-text{{font-size:14px;color:rgba(255,255,255,0.5)}}
|
|
267
|
-
</style></head>
|
|
268
|
-
<body><div class="container">
|
|
269
|
-
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
270
|
-
{"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z'/>" if success else "<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z'/>"}
|
|
271
|
-
</svg>
|
|
272
|
-
<h1>{title}</h1><p>{message}</p><p class="close-text">This window will close automatically...</p>
|
|
273
|
-
</div>
|
|
274
|
-
<script>
|
|
275
|
-
if(window.opener){{window.opener.postMessage({{type:'google_oauth_callback',success:{str(success).lower()},{"email:'"+email+"'," if email else ""}{"error:'"+escaped_error+"'," if error else ""}}},'*')}}
|
|
276
|
-
setTimeout(function(){{window.close()}},2000);
|
|
277
|
-
</script></body></html>"""
|
package/server/routers/maps.py
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
"""Google Maps service routes."""
|
|
2
|
-
|
|
3
|
-
from fastapi import APIRouter, Depends, HTTPException
|
|
4
|
-
from pydantic import BaseModel
|
|
5
|
-
from typing import Dict, Any, Optional
|
|
6
|
-
import httpx
|
|
7
|
-
|
|
8
|
-
from core.container import container
|
|
9
|
-
from services.maps import MapsService
|
|
10
|
-
from services.status_broadcaster import get_status_broadcaster
|
|
11
|
-
from core.logging import get_logger
|
|
12
|
-
|
|
13
|
-
logger = get_logger(__name__)
|
|
14
|
-
router = APIRouter(prefix="/python", tags=["maps"])
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class GoogleMapsRequest(BaseModel):
|
|
18
|
-
node_id: str
|
|
19
|
-
node_type: str
|
|
20
|
-
parameters: Dict[str, Any]
|
|
21
|
-
api_key: Optional[str] = None
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class ApiKeyValidationRequest(BaseModel):
|
|
25
|
-
api_key: str
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@router.post("/maps/validate-key")
|
|
29
|
-
async def validate_google_maps_key(request: ApiKeyValidationRequest):
|
|
30
|
-
"""Validate Google Maps API key and broadcast status via WebSocket."""
|
|
31
|
-
broadcaster = get_status_broadcaster()
|
|
32
|
-
|
|
33
|
-
try:
|
|
34
|
-
api_key = request.api_key.strip()
|
|
35
|
-
if not api_key:
|
|
36
|
-
await broadcaster.update_api_key_status(
|
|
37
|
-
provider="google_maps",
|
|
38
|
-
valid=False,
|
|
39
|
-
message="API key is required"
|
|
40
|
-
)
|
|
41
|
-
raise HTTPException(status_code=400, detail="API key is required")
|
|
42
|
-
|
|
43
|
-
# Test the API key with a simple geocoding request
|
|
44
|
-
async with httpx.AsyncClient() as client:
|
|
45
|
-
response = await client.get(
|
|
46
|
-
"https://maps.googleapis.com/maps/api/geocode/json",
|
|
47
|
-
params={
|
|
48
|
-
"address": "1600 Amphitheatre Parkway, Mountain View, CA",
|
|
49
|
-
"key": api_key
|
|
50
|
-
},
|
|
51
|
-
timeout=10.0
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
data = response.json()
|
|
55
|
-
|
|
56
|
-
if data.get("status") == "OK":
|
|
57
|
-
await broadcaster.update_api_key_status(
|
|
58
|
-
provider="google_maps",
|
|
59
|
-
valid=True,
|
|
60
|
-
message="API key validated successfully"
|
|
61
|
-
)
|
|
62
|
-
return {
|
|
63
|
-
"success": True,
|
|
64
|
-
"valid": True,
|
|
65
|
-
"message": "Google Maps API key is valid"
|
|
66
|
-
}
|
|
67
|
-
elif data.get("status") == "REQUEST_DENIED":
|
|
68
|
-
error_msg = data.get("error_message", "Invalid API key")
|
|
69
|
-
await broadcaster.update_api_key_status(
|
|
70
|
-
provider="google_maps",
|
|
71
|
-
valid=False,
|
|
72
|
-
message=error_msg
|
|
73
|
-
)
|
|
74
|
-
return {
|
|
75
|
-
"success": True,
|
|
76
|
-
"valid": False,
|
|
77
|
-
"message": error_msg
|
|
78
|
-
}
|
|
79
|
-
else:
|
|
80
|
-
# Other statuses like ZERO_RESULTS still mean the key works
|
|
81
|
-
await broadcaster.update_api_key_status(
|
|
82
|
-
provider="google_maps",
|
|
83
|
-
valid=True,
|
|
84
|
-
message="API key validated"
|
|
85
|
-
)
|
|
86
|
-
return {
|
|
87
|
-
"success": True,
|
|
88
|
-
"valid": True,
|
|
89
|
-
"message": f"API key is valid (status: {data.get('status')})"
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
except httpx.TimeoutException:
|
|
93
|
-
await broadcaster.update_api_key_status(
|
|
94
|
-
provider="google_maps",
|
|
95
|
-
valid=False,
|
|
96
|
-
message="Validation request timed out"
|
|
97
|
-
)
|
|
98
|
-
raise HTTPException(status_code=504, detail="Validation request timed out")
|
|
99
|
-
except httpx.RequestError as e:
|
|
100
|
-
await broadcaster.update_api_key_status(
|
|
101
|
-
provider="google_maps",
|
|
102
|
-
valid=False,
|
|
103
|
-
message=f"Network error: {str(e)}"
|
|
104
|
-
)
|
|
105
|
-
raise HTTPException(status_code=503, detail=f"Network error: {str(e)}")
|
|
106
|
-
except HTTPException:
|
|
107
|
-
raise
|
|
108
|
-
except Exception as e:
|
|
109
|
-
logger.error(f"API key validation error: {e}")
|
|
110
|
-
await broadcaster.update_api_key_status(
|
|
111
|
-
provider="google_maps",
|
|
112
|
-
valid=False,
|
|
113
|
-
message=f"Validation failed: {str(e)}"
|
|
114
|
-
)
|
|
115
|
-
raise HTTPException(status_code=500, detail=f"Validation failed: {str(e)}")
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
@router.post("/createmap/execute")
|
|
119
|
-
async def execute_createmap_node(
|
|
120
|
-
request: GoogleMapsRequest,
|
|
121
|
-
maps_service: MapsService = Depends(lambda: container.maps_service())
|
|
122
|
-
):
|
|
123
|
-
"""Execute Create Map node - Google Maps initialization."""
|
|
124
|
-
return await maps_service.create_map(request.node_id, request.parameters)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
@router.post("/addlocations/execute")
|
|
128
|
-
async def execute_addlocations_node(
|
|
129
|
-
request: GoogleMapsRequest,
|
|
130
|
-
maps_service: MapsService = Depends(lambda: container.maps_service())
|
|
131
|
-
):
|
|
132
|
-
"""Execute Add Locations node - Google Maps Geocoding."""
|
|
133
|
-
return await maps_service.geocode_location(request.node_id, request.parameters)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
@router.post("/shownearbyplaces/execute")
|
|
137
|
-
async def execute_shownearbyplaces_node(
|
|
138
|
-
request: GoogleMapsRequest,
|
|
139
|
-
maps_service: MapsService = Depends(lambda: container.maps_service())
|
|
140
|
-
):
|
|
141
|
-
"""Execute Show Nearby Places node - Google Places API."""
|
|
142
|
-
return await maps_service.find_nearby_places(request.node_id, request.parameters)
|