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
|
@@ -15,7 +15,7 @@ from urllib.parse import urljoin
|
|
|
15
15
|
from pydantic import BaseModel, ConfigDict, Field
|
|
16
16
|
|
|
17
17
|
from core.logging import get_logger
|
|
18
|
-
from services.plugin import ActionNode, NodeContext, Operation, TaskQueue
|
|
18
|
+
from services.plugin import ActionNode, NodeContext, NodeUserError, Operation, TaskQueue
|
|
19
19
|
|
|
20
20
|
logger = get_logger(__name__)
|
|
21
21
|
|
|
@@ -296,7 +296,7 @@ class CrawleeScraperNode(ActionNode):
|
|
|
296
296
|
async def scrape(self, ctx: NodeContext, params: CrawleeScraperParams) -> CrawleeScraperOutput:
|
|
297
297
|
url = (params.url or "").strip()
|
|
298
298
|
if not url:
|
|
299
|
-
raise
|
|
299
|
+
raise NodeUserError("URL is required")
|
|
300
300
|
|
|
301
301
|
crawler_type = params.crawler_type
|
|
302
302
|
mode = params.mode
|
|
@@ -326,16 +326,16 @@ class CrawleeScraperNode(ActionNode):
|
|
|
326
326
|
max_concurrency, timeout_secs,
|
|
327
327
|
)
|
|
328
328
|
else:
|
|
329
|
-
raise
|
|
329
|
+
raise NodeUserError(f"Unknown crawler type: {crawler_type}")
|
|
330
330
|
except ImportError as e:
|
|
331
331
|
msg = str(e).lower()
|
|
332
332
|
if 'playwright' in msg:
|
|
333
|
-
raise
|
|
333
|
+
raise NodeUserError(
|
|
334
334
|
"Playwright not installed. Run: "
|
|
335
335
|
"pip install 'crawlee[playwright]' && playwright install chromium",
|
|
336
336
|
)
|
|
337
337
|
if 'crawlee' in msg:
|
|
338
|
-
raise
|
|
338
|
+
raise NodeUserError(
|
|
339
339
|
"Crawlee not installed. Run: pip install 'crawlee[beautifulsoup]'",
|
|
340
340
|
)
|
|
341
341
|
raise
|
|
@@ -36,6 +36,10 @@ class BraveSearchCredential(ApiKeyCredential):
|
|
|
36
36
|
key_name = "X-Subscription-Token"
|
|
37
37
|
key_location = "header"
|
|
38
38
|
docs_url = "https://api.search.brave.com/app/keys"
|
|
39
|
+
# Lightweight probe — minimal query, count=1 just to confirm the
|
|
40
|
+
# token authenticates against the web-search endpoint.
|
|
41
|
+
probe_url = "https://api.search.brave.com/res/v1/web/search"
|
|
42
|
+
probe_params = {"q": "ping", "count": 1}
|
|
39
43
|
|
|
40
44
|
|
|
41
45
|
class BraveSearchResult(BaseModel):
|
|
@@ -30,6 +30,15 @@ class PerplexityCredential(ApiKeyCredential):
|
|
|
30
30
|
key_name = "Authorization"
|
|
31
31
|
key_location = "bearer"
|
|
32
32
|
docs_url = "https://docs.perplexity.ai/guides/getting-started"
|
|
33
|
+
# Cheapest valid request: minimal completion, max_tokens=1.
|
|
34
|
+
# Sonar is the always-available default model -- no entitlement gate.
|
|
35
|
+
probe_url = "https://api.perplexity.ai/chat/completions"
|
|
36
|
+
probe_method = "POST"
|
|
37
|
+
probe_json = {
|
|
38
|
+
"model": "sonar",
|
|
39
|
+
"messages": [{"role": "user", "content": "ping"}],
|
|
40
|
+
"max_tokens": 1,
|
|
41
|
+
}
|
|
33
42
|
|
|
34
43
|
|
|
35
44
|
class PerplexityResult(BaseModel):
|
|
@@ -23,6 +23,9 @@ class SerperCredential(ApiKeyCredential):
|
|
|
23
23
|
key_name = "X-API-KEY"
|
|
24
24
|
key_location = "header"
|
|
25
25
|
docs_url = "https://serper.dev/api-key"
|
|
26
|
+
probe_url = "https://google.serper.dev/search"
|
|
27
|
+
probe_method = "POST"
|
|
28
|
+
probe_json = {"q": "ping", "num": 1}
|
|
26
29
|
|
|
27
30
|
|
|
28
31
|
class SerperSearchResult(BaseModel):
|
|
@@ -68,6 +68,18 @@ class SimpleMemoryParams(BaseModel):
|
|
|
68
68
|
description="Number of relevant memories to retrieve from long-term storage",
|
|
69
69
|
json_schema_extra={"displayOptions": {"show": {"long_term_enabled": [True]}}},
|
|
70
70
|
)
|
|
71
|
+
last_session_id: Optional[str] = Field(
|
|
72
|
+
default=None,
|
|
73
|
+
title="Last Claude Session ID",
|
|
74
|
+
description=(
|
|
75
|
+
"Internal: the session UUID claude returned on the most "
|
|
76
|
+
"recent successful run. Drives `--resume <UUID>` on the "
|
|
77
|
+
"next spawn so claude finds and continues its own JSONL "
|
|
78
|
+
"transcript on disk. Hidden from the UI; clearing the "
|
|
79
|
+
"memory wipes this too."
|
|
80
|
+
),
|
|
81
|
+
json_schema_extra={"hidden": True},
|
|
82
|
+
)
|
|
71
83
|
|
|
72
84
|
model_config = ConfigDict(extra="ignore")
|
|
73
85
|
|
|
@@ -7,7 +7,7 @@ Email, Matrix, Teams).
|
|
|
7
7
|
|
|
8
8
|
Imported by :class:`nodes.social.social_receive.SocialReceiveNode` and
|
|
9
9
|
:class:`nodes.social.social_send.SocialSendNode`. Calls into
|
|
10
|
-
``
|
|
10
|
+
``nodes.whatsapp._service`` for the WhatsApp bridge stay unchanged — moving
|
|
11
11
|
them out is a separate refactor.
|
|
12
12
|
"""
|
|
13
13
|
|
|
@@ -475,7 +475,7 @@ async def _send_via_whatsapp(
|
|
|
475
475
|
|
|
476
476
|
Maps socialSend parameters to whatsappSend parameters.
|
|
477
477
|
"""
|
|
478
|
-
from
|
|
478
|
+
from nodes.whatsapp._service import handle_whatsapp_send as whatsapp_send_handler
|
|
479
479
|
|
|
480
480
|
# Map socialSend params to whatsappSend format
|
|
481
481
|
whatsapp_params = {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Plugins for the 'payments' palette group — Stripe.
|
|
2
|
+
|
|
3
|
+
Self-contained Wave 12 plugin. All boilerplate (lifecycle WS handlers,
|
|
4
|
+
status refresh, signature verification, CLI subprocess) lives in
|
|
5
|
+
``services.events``. This package contributes only the Stripe-specific
|
|
6
|
+
shapes: command builder, secret-capture regex, output reshape, the
|
|
7
|
+
livemode filter, and the credential class.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from services.events import make_status_refresh, register_webhook_source
|
|
13
|
+
from services.node_output_schemas import register_output_schema
|
|
14
|
+
from services.status_broadcaster import register_service_refresh
|
|
15
|
+
from services.ws_handler_registry import register_ws_handlers
|
|
16
|
+
|
|
17
|
+
from ._credentials import StripeCredential
|
|
18
|
+
from ._handlers import WS_HANDLERS
|
|
19
|
+
from ._source import (
|
|
20
|
+
StripeListenSource,
|
|
21
|
+
StripeWebhookSource,
|
|
22
|
+
get_listen_source,
|
|
23
|
+
get_webhook_source,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from .stripe_action import StripeActionNode, StripeActionOutput
|
|
27
|
+
from .stripe_receive import StripeReceiveNode, StripeReceiveOutput
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
register_ws_handlers(WS_HANDLERS)
|
|
31
|
+
register_webhook_source(get_webhook_source())
|
|
32
|
+
register_service_refresh(make_status_refresh(
|
|
33
|
+
get_listen_source(), status_key="stripe", broadcast_type="stripe_status",
|
|
34
|
+
))
|
|
35
|
+
register_output_schema("stripeReceive", StripeReceiveOutput)
|
|
36
|
+
register_output_schema("stripeAction", StripeActionOutput)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"StripeCredential",
|
|
41
|
+
"StripeListenSource",
|
|
42
|
+
"StripeWebhookSource",
|
|
43
|
+
"WS_HANDLERS",
|
|
44
|
+
"get_listen_source",
|
|
45
|
+
"get_webhook_source",
|
|
46
|
+
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Stripe credential — thin marker.
|
|
2
|
+
|
|
3
|
+
The Stripe CLI manages its own auth state at
|
|
4
|
+
``~/.config/stripe/config.toml`` (populated by ``stripe login`` and
|
|
5
|
+
cleared by ``stripe logout``). MachinaOs doesn't store an API key
|
|
6
|
+
itself — only the captured webhook signing secret rides along as an
|
|
7
|
+
extra field so :class:`StripeWebhookSource` can verify
|
|
8
|
+
``Stripe-Signature`` on incoming events.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Any, Dict
|
|
14
|
+
|
|
15
|
+
from services.plugin.credential import Credential
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class StripeCredential(Credential):
|
|
19
|
+
id = "stripe"
|
|
20
|
+
display_name = "Stripe"
|
|
21
|
+
category = "Payments"
|
|
22
|
+
icon = "asset:stripe"
|
|
23
|
+
auth = "custom"
|
|
24
|
+
docs_url = "https://stripe.com/docs/cli"
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
async def resolve(cls, *, user_id: str = "owner") -> Dict[str, Any]:
|
|
28
|
+
"""Return only the captured webhook signing secret. There is
|
|
29
|
+
no api_key — auth lives in the Stripe CLI's config file."""
|
|
30
|
+
from services.plugin.deps import get_auth_service
|
|
31
|
+
|
|
32
|
+
secret = await get_auth_service().get_api_key("stripe_webhook_secret")
|
|
33
|
+
return {"stripe_webhook_secret": secret} if secret else {}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""Stripe WebSocket handlers.
|
|
2
|
+
|
|
3
|
+
Login is a thin wrap around the Stripe CLI's two machine-friendly
|
|
4
|
+
flags:
|
|
5
|
+
|
|
6
|
+
* ``stripe login --non-interactive`` prints
|
|
7
|
+
``{browser_url, verification_code, next_step}`` JSON and exits.
|
|
8
|
+
* ``stripe login --complete <next_step>`` polls Stripe until the
|
|
9
|
+
user authorises in the browser, then writes credentials to
|
|
10
|
+
``~/.config/stripe/config.toml`` and exits 0.
|
|
11
|
+
|
|
12
|
+
The ``stripe_login`` handler runs step 1 synchronously, returns the
|
|
13
|
+
URL and verification code to the frontend (same shape as Twitter /
|
|
14
|
+
Google ``oauth_login`` handlers), then fires step 2 as a background
|
|
15
|
+
task. When step 2 finishes, we kick the broadcaster's stripe-status
|
|
16
|
+
refresh callback so the modal updates reactively.
|
|
17
|
+
|
|
18
|
+
Every other lifecycle command (connect/disconnect/reconnect/status)
|
|
19
|
+
comes from :func:`services.events.make_lifecycle_handlers`.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import asyncio
|
|
25
|
+
import json
|
|
26
|
+
import shlex
|
|
27
|
+
from typing import Any, Dict
|
|
28
|
+
|
|
29
|
+
from fastapi import WebSocket
|
|
30
|
+
|
|
31
|
+
from core.logging import get_logger
|
|
32
|
+
from services.events import make_lifecycle_handlers, run_cli_command
|
|
33
|
+
|
|
34
|
+
from ._install import ensure_stripe_cli, stripe_cli_path
|
|
35
|
+
from ._source import (
|
|
36
|
+
get_listen_source,
|
|
37
|
+
is_logged_in,
|
|
38
|
+
stripe_config_path,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
logger = get_logger(__name__)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
_LOGIN_TIMEOUT_SECONDS = 600
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
async def _resolved_binary() -> str | None:
|
|
48
|
+
"""Resolve the stripe binary path, downloading on first use.
|
|
49
|
+
Returns None on install failure (caller should surface error)."""
|
|
50
|
+
try:
|
|
51
|
+
return str(await ensure_stripe_cli())
|
|
52
|
+
except Exception as e:
|
|
53
|
+
logger.warning("[Stripe] CLI install failed: %s", e)
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
async def _status_snapshot() -> Dict[str, Any]:
|
|
58
|
+
"""Compose the daemon-status + login-state dict the modal renders."""
|
|
59
|
+
src = get_listen_source()
|
|
60
|
+
status = await src.status()
|
|
61
|
+
status["logged_in"] = is_logged_in()
|
|
62
|
+
status["connected"] = bool(status.get("running")) and status["logged_in"]
|
|
63
|
+
return status
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# --- Catalogue-stored marker -------------------------------------------------
|
|
67
|
+
#
|
|
68
|
+
# The catalogue handler in routers/websocket.py keys its "stored" check off
|
|
69
|
+
# `auth_service.get_oauth_tokens(status_hook)` for any provider with
|
|
70
|
+
# `status_hook` set (the Google / Twitter pattern). The Stripe CLI manages
|
|
71
|
+
# its own auth at ~/.config/stripe/config.toml — there are no real OAuth
|
|
72
|
+
# tokens for us to store. We persist a synthetic marker via the same API
|
|
73
|
+
# Google/Twitter use, so the catalogue's existing logic flips
|
|
74
|
+
# `stored: true` after login without any node-specific code in the
|
|
75
|
+
# catalogue handler.
|
|
76
|
+
|
|
77
|
+
_MARKER_TOKEN = "cli-managed"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def _mark_logged_in() -> None:
|
|
81
|
+
from services.plugin.deps import get_auth_service
|
|
82
|
+
await get_auth_service().store_oauth_tokens(
|
|
83
|
+
provider="stripe",
|
|
84
|
+
access_token=_MARKER_TOKEN,
|
|
85
|
+
refresh_token=_MARKER_TOKEN,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
async def _mark_logged_out() -> None:
|
|
90
|
+
from services.plugin.deps import get_auth_service
|
|
91
|
+
await get_auth_service().remove_oauth_tokens("stripe")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
async def _broadcast_credential_event(event_type: str) -> None:
|
|
95
|
+
"""Emit a CloudEvents-shaped credential mutation broadcast.
|
|
96
|
+
|
|
97
|
+
Wraps :class:`services.events.envelope.WorkflowEvent` via the canonical
|
|
98
|
+
helper :func:`StatusBroadcaster.broadcast_credential_event` — same
|
|
99
|
+
invariant that ``handle_save_api_key`` / ``handle_twitter_logout`` are
|
|
100
|
+
locked to in ``tests/credentials/test_credential_broadcasts.py``. The
|
|
101
|
+
frontend listens on ``case 'credential_catalogue_updated'`` (the
|
|
102
|
+
helper's outer wire-format type) and invalidates the catalogue query.
|
|
103
|
+
"""
|
|
104
|
+
from services.status_broadcaster import get_status_broadcaster
|
|
105
|
+
await get_status_broadcaster().broadcast_credential_event(
|
|
106
|
+
event_type, provider="stripe",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
async def handle_stripe_trigger(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
111
|
+
"""Run ``stripe trigger <event>`` for synthetic test events."""
|
|
112
|
+
event = data.get("event")
|
|
113
|
+
if not event:
|
|
114
|
+
return {"success": False, "error": "event required (e.g. 'charge.succeeded')"}
|
|
115
|
+
binary = await _resolved_binary()
|
|
116
|
+
if not binary:
|
|
117
|
+
return {"success": False, "error": "Stripe CLI install failed"}
|
|
118
|
+
return await run_cli_command(binary=binary, argv=["trigger", event])
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
async def handle_stripe_login(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
122
|
+
"""Step 1 of CLI OAuth: get the browser URL + verification code."""
|
|
123
|
+
logger.info("[Stripe] login flow: step 1/2 (--non-interactive) starting")
|
|
124
|
+
binary = await _resolved_binary()
|
|
125
|
+
if not binary:
|
|
126
|
+
logger.warning("[Stripe] login failed: CLI binary unavailable")
|
|
127
|
+
return {
|
|
128
|
+
"success": False,
|
|
129
|
+
"error": "Stripe CLI install failed. Manual install: https://stripe.com/docs/stripe-cli#install",
|
|
130
|
+
}
|
|
131
|
+
logger.info("[Stripe] using binary: %s", binary)
|
|
132
|
+
result = await run_cli_command(
|
|
133
|
+
binary=binary, argv=["login", "--non-interactive"], timeout=10.0,
|
|
134
|
+
)
|
|
135
|
+
if not result["success"]:
|
|
136
|
+
logger.warning(
|
|
137
|
+
"[Stripe] login step 1 CLI failure: %s | stderr=%r",
|
|
138
|
+
result.get("error"), (result.get("stderr") or "")[:300],
|
|
139
|
+
)
|
|
140
|
+
return result
|
|
141
|
+
try:
|
|
142
|
+
info = json.loads(result["stdout"])
|
|
143
|
+
except json.JSONDecodeError as e:
|
|
144
|
+
logger.warning("[Stripe] login step 1 unparseable stdout=%r", result["stdout"][:300])
|
|
145
|
+
return {"success": False, "error": f"unparseable stripe login response: {e}"}
|
|
146
|
+
|
|
147
|
+
next_step_raw = info.get("next_step")
|
|
148
|
+
url = info.get("browser_url") or info.get("url")
|
|
149
|
+
if not (url and next_step_raw):
|
|
150
|
+
logger.warning("[Stripe] login response missing url/next_step: keys=%s", list(info.keys()))
|
|
151
|
+
return {"success": False, "error": "stripe login response missing browser_url / next_step"}
|
|
152
|
+
|
|
153
|
+
# ``next_step`` is the LITERAL shell command the user would otherwise
|
|
154
|
+
# type, e.g.:
|
|
155
|
+
# stripe login --complete 'https://dashboard.stripe.com/stripecli/auth/…?secret=…'
|
|
156
|
+
# Feeding the whole string into ``--complete`` makes the CLI try to
|
|
157
|
+
# URL-parse it and bail with "first path segment in URL cannot contain
|
|
158
|
+
# colon". Tokenise it and pass just the auth URL (the last argument)
|
|
159
|
+
# to ``--complete``.
|
|
160
|
+
try:
|
|
161
|
+
complete_url = shlex.split(next_step_raw)[-1]
|
|
162
|
+
except (ValueError, IndexError):
|
|
163
|
+
complete_url = next_step_raw
|
|
164
|
+
if not complete_url.startswith("http"):
|
|
165
|
+
logger.warning(
|
|
166
|
+
"[Stripe] could not extract auth URL from next_step=%r — falling back to raw value",
|
|
167
|
+
next_step_raw,
|
|
168
|
+
)
|
|
169
|
+
complete_url = next_step_raw
|
|
170
|
+
|
|
171
|
+
logger.info(
|
|
172
|
+
"[Stripe] login step 1 ok: code=%s, browser_url issued — opening on frontend; "
|
|
173
|
+
"spawning step 2 (--complete) in background (timeout=%ss)",
|
|
174
|
+
info.get("verification_code"), _LOGIN_TIMEOUT_SECONDS,
|
|
175
|
+
)
|
|
176
|
+
asyncio.create_task(_complete_login(binary, complete_url))
|
|
177
|
+
return {
|
|
178
|
+
"success": True,
|
|
179
|
+
"url": url,
|
|
180
|
+
"verification_code": info.get("verification_code"),
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
async def _complete_login(binary: str, next_step: str) -> None:
|
|
185
|
+
"""Step 2: block on ``stripe login --complete`` until the user
|
|
186
|
+
authorises (or the 10-min timeout fires). On success, write the
|
|
187
|
+
same kind of marker the Google / Twitter callbacks write so the
|
|
188
|
+
catalogue's stored-check flips, then auto-start the listen
|
|
189
|
+
daemon and trigger the generic catalogue refresh on the frontend."""
|
|
190
|
+
logger.info("[Stripe] login step 2/2 polling for browser confirmation")
|
|
191
|
+
try:
|
|
192
|
+
result = await run_cli_command(
|
|
193
|
+
binary=binary, argv=["login", "--complete", next_step],
|
|
194
|
+
timeout=_LOGIN_TIMEOUT_SECONDS,
|
|
195
|
+
)
|
|
196
|
+
except Exception as e:
|
|
197
|
+
# ``asyncio.create_task`` swallows exceptions silently — log them.
|
|
198
|
+
# Nothing was persisted, so no broadcast: catalogue state is unchanged.
|
|
199
|
+
logger.exception("[Stripe] login step 2 raised unexpectedly: %s", e)
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
if not result.get("success"):
|
|
203
|
+
logger.warning(
|
|
204
|
+
"[Stripe] login step 2 CLI failure: %s | stderr=%r",
|
|
205
|
+
result.get("error"), (result.get("stderr") or "")[:300],
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if not is_logged_in():
|
|
209
|
+
logger.warning(
|
|
210
|
+
"[Stripe] step 2 finished but ``is_logged_in()`` is False — config file %s missing/empty; "
|
|
211
|
+
"user likely closed the browser before authorising",
|
|
212
|
+
stripe_config_path(),
|
|
213
|
+
)
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
logger.info(
|
|
217
|
+
"[Stripe] auth successful — credentials written to %s; persisting catalogue marker + starting listen daemon",
|
|
218
|
+
stripe_config_path(),
|
|
219
|
+
)
|
|
220
|
+
await _mark_logged_in()
|
|
221
|
+
logger.info("[Stripe] catalogue marker token persisted (auth_service.store_oauth_tokens)")
|
|
222
|
+
start_result = await get_listen_source().start()
|
|
223
|
+
if start_result.get("success"):
|
|
224
|
+
logger.info("[Stripe] listen daemon started (pid=%s)", start_result.get("status", {}).get("pid"))
|
|
225
|
+
else:
|
|
226
|
+
logger.warning("[Stripe] listen daemon failed to start: %s", start_result.get("error"))
|
|
227
|
+
await _broadcast_credential_event("credential.oauth.connected")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
async def handle_stripe_logout(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
231
|
+
"""Stop the daemon (if running) and run ``stripe logout --all`` to
|
|
232
|
+
clear ``~/.config/stripe/config.toml``. Mirror the Google/Twitter
|
|
233
|
+
logout shape: drop the catalogue marker token + broadcast the
|
|
234
|
+
generic catalogue invalidation so the modal flips immediately."""
|
|
235
|
+
logger.info("[Stripe] logout starting: stopping daemon + running 'stripe logout --all'")
|
|
236
|
+
await get_listen_source().stop()
|
|
237
|
+
cached = stripe_cli_path()
|
|
238
|
+
if cached is None:
|
|
239
|
+
cfg = stripe_config_path()
|
|
240
|
+
if cfg.exists():
|
|
241
|
+
cfg.unlink(missing_ok=True)
|
|
242
|
+
result: Dict[str, Any] = {"success": True, "message": "Logged out (CLI not yet installed; cleared config file)"}
|
|
243
|
+
logger.info("[Stripe] logout fallback: CLI not installed; deleted %s", cfg)
|
|
244
|
+
else:
|
|
245
|
+
result = await run_cli_command(binary=str(cached), argv=["logout", "--all"], timeout=10.0)
|
|
246
|
+
logger.info(
|
|
247
|
+
"[Stripe] logout CLI result: success=%s err=%s",
|
|
248
|
+
result.get("success"), result.get("error"),
|
|
249
|
+
)
|
|
250
|
+
await _mark_logged_out()
|
|
251
|
+
await _broadcast_credential_event("credential.oauth.disconnected")
|
|
252
|
+
logger.info("[Stripe] logout complete: marker token removed + catalogue broadcast sent")
|
|
253
|
+
return result
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
async def handle_stripe_status(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
257
|
+
"""Augments the stock daemon-status with login-state."""
|
|
258
|
+
return {"success": True, "status": await _status_snapshot()}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
WS_HANDLERS = make_lifecycle_handlers(
|
|
262
|
+
prefix="stripe",
|
|
263
|
+
source=get_listen_source(),
|
|
264
|
+
extra={
|
|
265
|
+
"stripe_login": handle_stripe_login,
|
|
266
|
+
"stripe_logout": handle_stripe_logout,
|
|
267
|
+
"stripe_trigger": handle_stripe_trigger,
|
|
268
|
+
},
|
|
269
|
+
)
|
|
270
|
+
WS_HANDLERS["stripe_status"] = handle_stripe_status
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Stripe CLI auto-installer.
|
|
2
|
+
|
|
3
|
+
On first use, downloads the official Stripe CLI binary from GitHub
|
|
4
|
+
releases (https://github.com/stripe/stripe-cli/releases) and caches
|
|
5
|
+
it under the workspace dir at ``_stripe/bin/stripe[.exe]``. A system
|
|
6
|
+
install on PATH (brew / scoop / apt / direct binary) is preferred —
|
|
7
|
+
the download path only fires when no system binary is found.
|
|
8
|
+
|
|
9
|
+
Pin a version here when bumping; pre-built archives are signed by
|
|
10
|
+
Stripe and served over GitHub's CDN.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import io
|
|
17
|
+
import platform
|
|
18
|
+
import shutil
|
|
19
|
+
import stat
|
|
20
|
+
import tarfile
|
|
21
|
+
import zipfile
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Optional, Tuple
|
|
24
|
+
|
|
25
|
+
import httpx
|
|
26
|
+
|
|
27
|
+
from core.logging import get_logger
|
|
28
|
+
|
|
29
|
+
logger = get_logger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_VERSION = "1.40.9"
|
|
33
|
+
_RELEASE_BASE = f"https://github.com/stripe/stripe-cli/releases/download/v{_VERSION}"
|
|
34
|
+
|
|
35
|
+
# (system, machine) → (asset filename, archive type, member name to extract)
|
|
36
|
+
_ASSETS: dict[Tuple[str, str], Tuple[str, str, str]] = {
|
|
37
|
+
("Windows", "AMD64"): (f"stripe_{_VERSION}_windows_x86_64.zip", "zip", "stripe.exe"),
|
|
38
|
+
("Linux", "x86_64"): (f"stripe_{_VERSION}_linux_x86_64.tar.gz", "tar", "stripe"),
|
|
39
|
+
("Linux", "aarch64"): (f"stripe_{_VERSION}_linux_arm64.tar.gz", "tar", "stripe"),
|
|
40
|
+
("Linux", "arm64"): (f"stripe_{_VERSION}_linux_arm64.tar.gz", "tar", "stripe"),
|
|
41
|
+
("Darwin", "x86_64"): (f"stripe_{_VERSION}_mac-os_x86_64.tar.gz", "tar", "stripe"),
|
|
42
|
+
("Darwin", "arm64"): (f"stripe_{_VERSION}_mac-os_arm64.tar.gz", "tar", "stripe"),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_cached_path: Optional[Path] = None
|
|
47
|
+
_install_lock = asyncio.Lock()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _bin_dir() -> Path:
|
|
51
|
+
from core.config import Settings
|
|
52
|
+
p = Path(Settings().workspace_base_resolved).resolve() / "_stripe" / "bin"
|
|
53
|
+
p.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
return p
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def stripe_cli_path() -> Optional[Path]:
|
|
58
|
+
"""Sync getter for the resolved binary path. Returns ``None`` if
|
|
59
|
+
:func:`ensure_stripe_cli` hasn't run yet (or never resolved)."""
|
|
60
|
+
return _cached_path
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def ensure_stripe_cli() -> Path:
|
|
64
|
+
"""Return absolute path to the stripe binary, downloading it if
|
|
65
|
+
necessary. Idempotent + concurrent-safe.
|
|
66
|
+
|
|
67
|
+
Resolution order:
|
|
68
|
+
1. Cached path from a prior call (in-process).
|
|
69
|
+
2. ``shutil.which("stripe")`` — system install on PATH.
|
|
70
|
+
3. Workspace-local copy at ``_stripe/bin/stripe[.exe]``.
|
|
71
|
+
4. Fresh download from GitHub releases under
|
|
72
|
+
:data:`_VERSION`.
|
|
73
|
+
"""
|
|
74
|
+
global _cached_path
|
|
75
|
+
if _cached_path and _cached_path.exists():
|
|
76
|
+
return _cached_path
|
|
77
|
+
|
|
78
|
+
sys_path = shutil.which("stripe")
|
|
79
|
+
if sys_path:
|
|
80
|
+
_cached_path = Path(sys_path)
|
|
81
|
+
logger.info("[Stripe] using system CLI at %s", _cached_path)
|
|
82
|
+
return _cached_path
|
|
83
|
+
|
|
84
|
+
binary_name = "stripe.exe" if platform.system() == "Windows" else "stripe"
|
|
85
|
+
target = _bin_dir() / binary_name
|
|
86
|
+
|
|
87
|
+
async with _install_lock:
|
|
88
|
+
if _cached_path and _cached_path.exists():
|
|
89
|
+
return _cached_path
|
|
90
|
+
if target.exists():
|
|
91
|
+
_cached_path = target
|
|
92
|
+
return target
|
|
93
|
+
await _download_release(target)
|
|
94
|
+
_cached_path = target
|
|
95
|
+
return target
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
async def _download_release(target: Path) -> None:
|
|
99
|
+
key = (platform.system(), platform.machine())
|
|
100
|
+
asset = _ASSETS.get(key)
|
|
101
|
+
if asset is None:
|
|
102
|
+
raise RuntimeError(
|
|
103
|
+
f"No prebuilt Stripe CLI for {key}. Install manually from "
|
|
104
|
+
"https://stripe.com/docs/stripe-cli#install"
|
|
105
|
+
)
|
|
106
|
+
asset_name, kind, member = asset
|
|
107
|
+
url = f"{_RELEASE_BASE}/{asset_name}"
|
|
108
|
+
logger.info("[Stripe] downloading CLI v%s from %s", _VERSION, url)
|
|
109
|
+
|
|
110
|
+
async with httpx.AsyncClient(follow_redirects=True, timeout=120) as client:
|
|
111
|
+
resp = await client.get(url)
|
|
112
|
+
resp.raise_for_status()
|
|
113
|
+
archive = resp.content
|
|
114
|
+
|
|
115
|
+
if kind == "zip":
|
|
116
|
+
with zipfile.ZipFile(io.BytesIO(archive)) as z:
|
|
117
|
+
target.write_bytes(z.read(member))
|
|
118
|
+
else:
|
|
119
|
+
with tarfile.open(fileobj=io.BytesIO(archive), mode="r:gz") as t:
|
|
120
|
+
f = t.extractfile(member)
|
|
121
|
+
if f is None:
|
|
122
|
+
raise RuntimeError(f"Member {member!r} missing from {asset_name}")
|
|
123
|
+
target.write_bytes(f.read())
|
|
124
|
+
|
|
125
|
+
if platform.system() != "Windows":
|
|
126
|
+
target.chmod(target.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
127
|
+
logger.info("[Stripe] CLI installed to %s", target)
|