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
|
@@ -1,4 +1,26 @@
|
|
|
1
|
-
"""Claude OAuth
|
|
1
|
+
"""Claude OAuth — project-local install + the documented `claude auth` subcommands.
|
|
2
|
+
|
|
3
|
+
The Claude Code CLI lives at ``<repo>/data/claude-machina/npm/`` (on-demand
|
|
4
|
+
``npm install`` on first use, mirroring the WhatsApp project-local layout).
|
|
5
|
+
``CLAUDE_CONFIG_DIR`` points at ``<repo>/data/claude-machina/`` so the CLI
|
|
6
|
+
manages its own credentials inside the project tree, isolated from the
|
|
7
|
+
user's own ``~/.claude/`` session.
|
|
8
|
+
|
|
9
|
+
Every subprocess call goes through ``services.events.cli.run_cli_command``
|
|
10
|
+
— the canonical one-shot CLI helper used by Stripe / future plugins. Auth
|
|
11
|
+
surface follows https://code.claude.com/docs/en/cli-reference verbatim:
|
|
12
|
+
|
|
13
|
+
- ``claude auth login`` — opens the browser, writes credentials.
|
|
14
|
+
- ``claude auth status`` — prints JSON; exits 0 when logged in / 1 otherwise.
|
|
15
|
+
- ``claude auth logout`` — log out (CLI clears its own credentials).
|
|
16
|
+
|
|
17
|
+
The CLI owns its own credentials file; we never read or write it.
|
|
18
|
+
``login`` is a long-running one-shot (waits for the browser flow to
|
|
19
|
+
complete) so callers schedule it as ``asyncio.create_task`` — same shape
|
|
20
|
+
Stripe uses for ``stripe login --complete``.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
2
24
|
|
|
3
25
|
import json
|
|
4
26
|
import os
|
|
@@ -6,133 +28,137 @@ import shutil
|
|
|
6
28
|
import subprocess
|
|
7
29
|
import sys
|
|
8
30
|
from pathlib import Path
|
|
9
|
-
from typing import
|
|
31
|
+
from typing import Any, Dict
|
|
10
32
|
|
|
11
33
|
from core.logging import get_logger
|
|
34
|
+
from services.events.cli import run_cli_command
|
|
12
35
|
|
|
13
36
|
logger = get_logger(__name__)
|
|
14
37
|
|
|
15
|
-
#
|
|
16
|
-
|
|
38
|
+
# server/services/claude_oauth.py -> parents[2] is the repo root.
|
|
39
|
+
_PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
40
|
+
|
|
41
|
+
MACHINA_CLAUDE_DIR = (_PROJECT_ROOT / "data" / "claude-machina").resolve()
|
|
17
42
|
MACHINA_NPM_DIR = MACHINA_CLAUDE_DIR / "npm"
|
|
18
43
|
|
|
44
|
+
# Generous timeout for browser-flow login; Stripe uses the same window.
|
|
45
|
+
LOGIN_TIMEOUT_SECONDS = 600.0
|
|
46
|
+
# One-shot status / logout return immediately.
|
|
47
|
+
ONESHOT_TIMEOUT_SECONDS = 30.0
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def claude_binary_path() -> str:
|
|
51
|
+
"""Return path to the project-local claude CLI, installing on miss.
|
|
19
52
|
|
|
20
|
-
|
|
21
|
-
|
|
53
|
+
Single source of truth shared by the auth handler
|
|
54
|
+
(``services/cli_agent/_handlers.py``) AND the agent spawn
|
|
55
|
+
(``services/cli_agent/providers/anthropic_claude.py``) so both surfaces
|
|
56
|
+
use the same binary + ``CLAUDE_CONFIG_DIR``-isolated credentials."""
|
|
22
57
|
if sys.platform == "win32":
|
|
23
|
-
|
|
58
|
+
bin_path = MACHINA_NPM_DIR / "node_modules" / ".bin" / "claude.cmd"
|
|
24
59
|
else:
|
|
25
|
-
|
|
60
|
+
bin_path = MACHINA_NPM_DIR / "node_modules" / ".bin" / "claude"
|
|
26
61
|
|
|
27
|
-
if
|
|
28
|
-
return str(
|
|
62
|
+
if bin_path.exists():
|
|
63
|
+
return str(bin_path)
|
|
29
64
|
|
|
30
|
-
|
|
31
|
-
logger.info("Installing Claude Code CLI in isolated environment...")
|
|
65
|
+
logger.info("Installing Claude Code CLI in project-local environment...")
|
|
32
66
|
MACHINA_NPM_DIR.mkdir(parents=True, exist_ok=True)
|
|
33
67
|
|
|
34
68
|
npm_cmd = shutil.which("npm")
|
|
35
69
|
if not npm_cmd:
|
|
36
|
-
raise FileNotFoundError("npm not found")
|
|
70
|
+
raise FileNotFoundError("npm not found on PATH")
|
|
37
71
|
|
|
38
72
|
result = subprocess.run(
|
|
39
73
|
[npm_cmd, "install", "@anthropic-ai/claude-code", "--prefix", str(MACHINA_NPM_DIR)],
|
|
40
74
|
capture_output=True,
|
|
41
75
|
text=True,
|
|
42
76
|
)
|
|
43
|
-
|
|
44
77
|
if result.returncode != 0:
|
|
45
78
|
logger.error(f"npm install failed: {result.stderr}")
|
|
46
79
|
raise RuntimeError(f"Failed to install claude-code: {result.stderr}")
|
|
47
80
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
claude_path = MACHINA_NPM_DIR / "node_modules" / ".bin" / "claude.cmd"
|
|
51
|
-
else:
|
|
52
|
-
claude_path = MACHINA_NPM_DIR / "node_modules" / ".bin" / "claude"
|
|
81
|
+
if not bin_path.exists():
|
|
82
|
+
raise FileNotFoundError(f"Claude CLI not found at {bin_path} after install")
|
|
53
83
|
|
|
54
|
-
|
|
55
|
-
|
|
84
|
+
logger.info(f"Claude Code CLI installed at: {bin_path}")
|
|
85
|
+
return str(bin_path)
|
|
56
86
|
|
|
57
|
-
logger.info(f"Claude Code CLI installed at: {claude_path}")
|
|
58
|
-
return str(claude_path)
|
|
59
87
|
|
|
88
|
+
def _claude_env() -> Dict[str, str]:
|
|
89
|
+
env = os.environ.copy()
|
|
90
|
+
env["CLAUDE_CONFIG_DIR"] = str(MACHINA_CLAUDE_DIR)
|
|
91
|
+
return env
|
|
60
92
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
# Get or install claude CLI
|
|
67
|
-
claude_cmd = _get_claude_cmd()
|
|
68
|
-
|
|
69
|
-
env = os.environ.copy()
|
|
70
|
-
env["CLAUDE_CONFIG_DIR"] = str(MACHINA_CLAUDE_DIR)
|
|
71
|
-
|
|
72
|
-
# Run claude login with stdin to auto-fill prompts
|
|
73
|
-
# Claude login asks: 1) "Do you agree?" (yes) 2) "Open browser?" (yes)
|
|
74
|
-
if sys.platform == "win32":
|
|
75
|
-
# Windows: pipe inputs and let it open browser
|
|
76
|
-
process = subprocess.Popen(
|
|
77
|
-
[claude_cmd, "login"],
|
|
78
|
-
env=env,
|
|
79
|
-
stdin=subprocess.PIPE,
|
|
80
|
-
stdout=subprocess.PIPE,
|
|
81
|
-
stderr=subprocess.PIPE,
|
|
82
|
-
creationflags=subprocess.CREATE_NO_WINDOW,
|
|
83
|
-
)
|
|
84
|
-
# Send "yes" responses for the prompts
|
|
85
|
-
try:
|
|
86
|
-
process.stdin.write(b"yes\nyes\n")
|
|
87
|
-
process.stdin.flush()
|
|
88
|
-
except Exception:
|
|
89
|
-
pass
|
|
90
|
-
else:
|
|
91
|
-
# Unix: pipe inputs
|
|
92
|
-
process = subprocess.Popen(
|
|
93
|
-
[claude_cmd, "login"],
|
|
94
|
-
env=env,
|
|
95
|
-
stdin=subprocess.PIPE,
|
|
96
|
-
stdout=subprocess.PIPE,
|
|
97
|
-
stderr=subprocess.PIPE,
|
|
98
|
-
)
|
|
99
|
-
try:
|
|
100
|
-
process.stdin.write(b"yes\nyes\n")
|
|
101
|
-
process.stdin.flush()
|
|
102
|
-
except Exception:
|
|
103
|
-
pass
|
|
104
|
-
|
|
105
|
-
logger.info(f"Claude OAuth login started with PID {process.pid}")
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
"success": True,
|
|
109
|
-
"message": "OAuth login started. Browser should open for authentication.",
|
|
110
|
-
"config_dir": str(MACHINA_CLAUDE_DIR),
|
|
111
|
-
"pid": process.pid,
|
|
112
|
-
}
|
|
113
|
-
except FileNotFoundError as e:
|
|
114
|
-
logger.error(f"CLI not found: {e}")
|
|
115
|
-
return {"success": False, "error": str(e)}
|
|
116
|
-
except Exception as e:
|
|
117
|
-
logger.error(f"Failed to start Claude OAuth: {e}")
|
|
118
|
-
return {"success": False, "error": str(e)}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def get_claude_credentials() -> Dict[str, Any]:
|
|
122
|
-
"""Read credentials from isolated config."""
|
|
93
|
+
|
|
94
|
+
async def _run_auth(subcommand: str, *, timeout: float) -> Dict[str, Any]:
|
|
95
|
+
"""Run ``claude auth <subcommand>`` via the canonical helper. Returns
|
|
96
|
+
the ``run_cli_command`` envelope (``{success, result, stdout, stderr,
|
|
97
|
+
error}``)."""
|
|
123
98
|
try:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
99
|
+
binary = claude_binary_path()
|
|
100
|
+
except (FileNotFoundError, RuntimeError) as e:
|
|
101
|
+
return {"success": False, "error": str(e), "stdout": "", "stderr": ""}
|
|
102
|
+
|
|
103
|
+
MACHINA_CLAUDE_DIR.mkdir(parents=True, exist_ok=True)
|
|
104
|
+
envelope = await run_cli_command(
|
|
105
|
+
binary=binary,
|
|
106
|
+
argv=["auth", subcommand],
|
|
107
|
+
timeout=timeout,
|
|
108
|
+
env=_claude_env(),
|
|
109
|
+
)
|
|
110
|
+
logger.info(
|
|
111
|
+
"[claude auth %s] success=%s stdout=%r stderr=%r",
|
|
112
|
+
subcommand,
|
|
113
|
+
envelope.get("success"),
|
|
114
|
+
(envelope.get("stdout") or "")[:512],
|
|
115
|
+
(envelope.get("stderr") or "")[:512],
|
|
116
|
+
)
|
|
117
|
+
return envelope
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
async def claude_auth_status_info() -> Dict[str, Any]:
|
|
121
|
+
"""Return parsed JSON from ``claude auth status``. Always includes
|
|
122
|
+
``loggedIn: bool``; populates ``email``/``orgName``/``subscriptionType``
|
|
123
|
+
when logged in."""
|
|
124
|
+
envelope = await _run_auth("status", timeout=ONESHOT_TIMEOUT_SECONDS)
|
|
125
|
+
parsed = envelope.get("result")
|
|
126
|
+
if isinstance(parsed, dict):
|
|
127
|
+
parsed.setdefault("loggedIn", bool(envelope.get("success")))
|
|
128
|
+
return parsed
|
|
129
|
+
|
|
130
|
+
# Status returns exit 1 when not logged in; the JSON still parses but
|
|
131
|
+
# run_cli_command treats non-zero exits as failure and skips parsing.
|
|
132
|
+
# Fall back to parsing stdout directly so we still surface the body.
|
|
133
|
+
stdout = envelope.get("stdout") or ""
|
|
134
|
+
if stdout:
|
|
135
|
+
try:
|
|
136
|
+
data = json.loads(stdout)
|
|
137
|
+
if isinstance(data, dict):
|
|
138
|
+
data.setdefault("loggedIn", False)
|
|
139
|
+
return data
|
|
140
|
+
except json.JSONDecodeError:
|
|
141
|
+
pass
|
|
142
|
+
return {"loggedIn": bool(envelope.get("success"))}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
async def claude_auth_status() -> bool:
|
|
146
|
+
"""True iff ``claude auth status`` reports loggedIn=True."""
|
|
147
|
+
info = await claude_auth_status_info()
|
|
148
|
+
return bool(info.get("loggedIn"))
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
async def run_claude_login() -> Dict[str, Any]:
|
|
152
|
+
"""Run ``claude auth login`` to completion. The CLI opens the user's
|
|
153
|
+
browser, runs its own callback server, and exits when the flow ends.
|
|
154
|
+
|
|
155
|
+
Long-running: callers should schedule this via ``asyncio.create_task``
|
|
156
|
+
(Stripe ``stripe login --complete`` precedent). Returns the envelope
|
|
157
|
+
from ``run_cli_command``."""
|
|
158
|
+
return await _run_auth("login", timeout=LOGIN_TIMEOUT_SECONDS)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
async def claude_auth_logout() -> bool:
|
|
162
|
+
"""Run ``claude auth logout``. True on exit 0."""
|
|
163
|
+
envelope = await _run_auth("logout", timeout=ONESHOT_TIMEOUT_SECONDS)
|
|
164
|
+
return bool(envelope.get("success"))
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""AI CLI agent framework — multi-provider, multi-instance, VSCode-pattern.
|
|
2
|
+
|
|
3
|
+
Spawns N parallel Claude Code / Codex CLI sessions per workflow node,
|
|
4
|
+
each isolated in its own git worktree, with a shared MCP server hosting
|
|
5
|
+
MachinaOs tools (``mcp__machina__*``) discovered via VSCode-style
|
|
6
|
+
lockfile.
|
|
7
|
+
|
|
8
|
+
Self-registers per-provider WebSocket handlers
|
|
9
|
+
(``claude_code_login``, ``claude_code_logout``, ``codex_cli_login``,
|
|
10
|
+
``codex_cli_logout``) into ``services.ws_handler_registry`` on import,
|
|
11
|
+
mirroring the telegram / whatsapp / stripe plugin folder pattern. The
|
|
12
|
+
router does not need to know about us by name.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging as _logging
|
|
18
|
+
|
|
19
|
+
# Pin every `[CC-Agent ...]` logger in this package at INFO so live
|
|
20
|
+
# debugging surfaces without flipping the global LOG_LEVEL. Override with
|
|
21
|
+
# ``logging.getLogger("services.cli_agent").setLevel(...)`` for quieter
|
|
22
|
+
# runs.
|
|
23
|
+
_logging.getLogger("services.cli_agent").setLevel(_logging.INFO)
|
|
24
|
+
|
|
25
|
+
from services.cli_agent.factory import (
|
|
26
|
+
ALL_PROVIDERS,
|
|
27
|
+
SUPPORTED_PROVIDERS,
|
|
28
|
+
create_cli_provider,
|
|
29
|
+
is_supported,
|
|
30
|
+
)
|
|
31
|
+
from services.cli_agent.protocol import (
|
|
32
|
+
AICliProvider,
|
|
33
|
+
BatchResult,
|
|
34
|
+
CanonicalUsage,
|
|
35
|
+
SessionResult,
|
|
36
|
+
)
|
|
37
|
+
from services.cli_agent.types import (
|
|
38
|
+
AICliTaskSpec,
|
|
39
|
+
BaseAICliTaskSpec,
|
|
40
|
+
BatchResultModel,
|
|
41
|
+
BatchSummary,
|
|
42
|
+
ClaudeTaskSpec,
|
|
43
|
+
CodexTaskSpec,
|
|
44
|
+
GeminiTaskSpec,
|
|
45
|
+
SessionResultModel,
|
|
46
|
+
session_result_to_model,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# --- self-registration on import -------------------------------------------
|
|
50
|
+
from services.ws_handler_registry import register_ws_handlers
|
|
51
|
+
from services.cli_agent._handlers import WS_HANDLERS
|
|
52
|
+
|
|
53
|
+
register_ws_handlers(WS_HANDLERS)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
# Factory
|
|
58
|
+
"create_cli_provider",
|
|
59
|
+
"is_supported",
|
|
60
|
+
"SUPPORTED_PROVIDERS",
|
|
61
|
+
"ALL_PROVIDERS",
|
|
62
|
+
# Protocol + dataclasses
|
|
63
|
+
"AICliProvider",
|
|
64
|
+
"CanonicalUsage",
|
|
65
|
+
"SessionResult",
|
|
66
|
+
"BatchResult",
|
|
67
|
+
# Pydantic specs
|
|
68
|
+
"BaseAICliTaskSpec",
|
|
69
|
+
"ClaudeTaskSpec",
|
|
70
|
+
"CodexTaskSpec",
|
|
71
|
+
"GeminiTaskSpec",
|
|
72
|
+
"AICliTaskSpec",
|
|
73
|
+
# Pydantic result models
|
|
74
|
+
"SessionResultModel",
|
|
75
|
+
"BatchSummary",
|
|
76
|
+
"BatchResultModel",
|
|
77
|
+
"session_result_to_model",
|
|
78
|
+
]
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""Per-provider WebSocket handlers for CLI-managed OAuth.
|
|
2
|
+
|
|
3
|
+
Self-registered into ``services.ws_handler_registry`` from
|
|
4
|
+
``services/cli_agent/__init__.py``. Naming convention matches Twitter /
|
|
5
|
+
Google / Stripe — each provider gets its own message type
|
|
6
|
+
(``claude_code_login`` / ``claude_code_logout`` / ``codex_cli_login`` /
|
|
7
|
+
``codex_cli_logout``); the frontend dispatches with an empty payload.
|
|
8
|
+
|
|
9
|
+
Claude flow uses the documented CLI subcommands from
|
|
10
|
+
https://code.claude.com/docs/en/cli-reference (``claude auth login`` /
|
|
11
|
+
``status`` / ``logout``). Every subprocess invocation goes through
|
|
12
|
+
``services.events.cli.run_cli_command`` (Stripe precedent — see
|
|
13
|
+
``nodes/stripe/_handlers.py``); the CLI owns its own credentials file
|
|
14
|
+
and we never touch it.
|
|
15
|
+
|
|
16
|
+
Login lifecycle:
|
|
17
|
+
|
|
18
|
+
1. ``handle_claude_code_login`` schedules ``run_claude_login`` as an
|
|
19
|
+
``asyncio.create_task`` (mirrors Stripe's ``stripe login --complete``)
|
|
20
|
+
and returns immediately. The CLI opens the user's browser; up to
|
|
21
|
+
10 minutes are allowed for the flow to complete.
|
|
22
|
+
2. When the task resolves, ``_finalize_claude_login`` reads
|
|
23
|
+
``claude auth status``, stores the synthetic ``"cli-managed"`` marker
|
|
24
|
+
along with the user's ``email``/``orgName`` via
|
|
25
|
+
``auth_service.store_oauth_tokens()``, and fires
|
|
26
|
+
``broadcast_credential_event("credential.oauth.connected", ...)`` —
|
|
27
|
+
the frontend's ``WebSocketContext`` re-fetches the catalogue.
|
|
28
|
+
|
|
29
|
+
Logout runs ``claude auth logout`` (CLI clears its own credentials),
|
|
30
|
+
drops the catalogue marker, and broadcasts ``.disconnected``.
|
|
31
|
+
|
|
32
|
+
Codex login is not yet wired (no ``codex_oauth.py``); the handler returns
|
|
33
|
+
a graceful error pointing the user at the manual flow.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import asyncio
|
|
39
|
+
from typing import Any, Awaitable, Callable, Dict, Optional
|
|
40
|
+
|
|
41
|
+
from fastapi import WebSocket
|
|
42
|
+
|
|
43
|
+
from core.logging import get_logger
|
|
44
|
+
|
|
45
|
+
logger = get_logger(__name__)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Synthetic marker stored in `auth_service` after a successful CLI login,
|
|
49
|
+
# matching `nodes/stripe/_handlers.py:_MARKER_TOKEN`. Lets the catalogue's
|
|
50
|
+
# generic `stored` check flip without per-provider code in the catalogue
|
|
51
|
+
# handler.
|
|
52
|
+
_MARKER_TOKEN = "cli-managed"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# Marker-token + credential-event broadcast (Stripe pattern)
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
async def _mark_logged_in(
|
|
60
|
+
catalogue_key: str,
|
|
61
|
+
*,
|
|
62
|
+
email: Optional[str] = None,
|
|
63
|
+
name: Optional[str] = None,
|
|
64
|
+
) -> None:
|
|
65
|
+
from core.container import container
|
|
66
|
+
await container.auth_service().store_oauth_tokens(
|
|
67
|
+
provider=catalogue_key,
|
|
68
|
+
access_token=_MARKER_TOKEN,
|
|
69
|
+
refresh_token=_MARKER_TOKEN,
|
|
70
|
+
email=email,
|
|
71
|
+
name=name,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def _mark_logged_out(catalogue_key: str) -> None:
|
|
76
|
+
from core.container import container
|
|
77
|
+
await container.auth_service().remove_oauth_tokens(catalogue_key)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def _broadcast_credential_event(event_type: str, provider: str) -> None:
|
|
81
|
+
"""Fire a CloudEvents-shaped catalogue-invalidation. Frontend listens
|
|
82
|
+
via ``WebSocketContext`` and re-fetches the catalogue."""
|
|
83
|
+
from services.status_broadcaster import get_status_broadcaster
|
|
84
|
+
await get_status_broadcaster().broadcast_credential_event(
|
|
85
|
+
event_type, provider=provider,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
# Claude — `claude auth login` / `auth status` / `auth logout`
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
async def _finalize_claude_login() -> None:
|
|
94
|
+
"""Run ``claude auth login`` to completion, then store user info +
|
|
95
|
+
broadcast on success."""
|
|
96
|
+
from services.claude_oauth import claude_auth_status_info, run_claude_login
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
envelope = await run_claude_login()
|
|
100
|
+
if not envelope.get("success"):
|
|
101
|
+
logger.warning(
|
|
102
|
+
"[claude_code_login] CLI exited unsuccessfully: %s",
|
|
103
|
+
envelope.get("error") or envelope.get("stderr"),
|
|
104
|
+
)
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
info = await claude_auth_status_info()
|
|
108
|
+
if not info.get("loggedIn"):
|
|
109
|
+
logger.warning(
|
|
110
|
+
"[claude_code_login] CLI exited cleanly but auth status "
|
|
111
|
+
"reports not logged in: %s", info,
|
|
112
|
+
)
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
email = info.get("email")
|
|
116
|
+
org_name = info.get("orgName")
|
|
117
|
+
await _mark_logged_in("claude_code", email=email, name=org_name)
|
|
118
|
+
await _broadcast_credential_event(
|
|
119
|
+
"credential.oauth.connected", provider="claude_code",
|
|
120
|
+
)
|
|
121
|
+
logger.info(
|
|
122
|
+
"[claude_code_login] connected as %s (%s · %s)",
|
|
123
|
+
email or "unknown",
|
|
124
|
+
org_name or "unknown org",
|
|
125
|
+
info.get("subscriptionType") or "unknown plan",
|
|
126
|
+
)
|
|
127
|
+
except asyncio.CancelledError:
|
|
128
|
+
raise
|
|
129
|
+
except Exception as exc: # pragma: no cover — defensive
|
|
130
|
+
logger.exception("[claude_code_login] finalize failed: %s", exc)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
async def handle_claude_code_login(
|
|
134
|
+
data: Dict[str, Any], # noqa: ARG001 — frontend sends {}
|
|
135
|
+
websocket: WebSocket, # noqa: ARG001 — registry signature
|
|
136
|
+
) -> Dict[str, Any]:
|
|
137
|
+
"""Spawn ``claude auth login`` in the background; let the CLI open
|
|
138
|
+
the user's browser. Idempotent re-click syncs the marker without
|
|
139
|
+
re-running the flow."""
|
|
140
|
+
from services.claude_oauth import claude_auth_status_info
|
|
141
|
+
|
|
142
|
+
info = await claude_auth_status_info()
|
|
143
|
+
if info.get("loggedIn"):
|
|
144
|
+
try:
|
|
145
|
+
await _mark_logged_in(
|
|
146
|
+
"claude_code",
|
|
147
|
+
email=info.get("email"),
|
|
148
|
+
name=info.get("orgName"),
|
|
149
|
+
)
|
|
150
|
+
await _broadcast_credential_event(
|
|
151
|
+
"credential.oauth.connected", provider="claude_code",
|
|
152
|
+
)
|
|
153
|
+
except Exception as exc:
|
|
154
|
+
logger.warning("[claude_code_login] mark/broadcast failed: %s", exc)
|
|
155
|
+
return {
|
|
156
|
+
"success": True,
|
|
157
|
+
"already_logged_in": True,
|
|
158
|
+
"email": info.get("email"),
|
|
159
|
+
"org_name": info.get("orgName"),
|
|
160
|
+
"subscription_type": info.get("subscriptionType"),
|
|
161
|
+
"message": "Already authenticated; refreshed status.",
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
asyncio.create_task(_finalize_claude_login(), name="claude_code_login")
|
|
165
|
+
return {
|
|
166
|
+
"success": True,
|
|
167
|
+
"message": "Claude is opening your browser to authenticate.",
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
async def handle_claude_code_logout(
|
|
172
|
+
data: Dict[str, Any], # noqa: ARG001
|
|
173
|
+
websocket: WebSocket, # noqa: ARG001
|
|
174
|
+
) -> Dict[str, Any]:
|
|
175
|
+
"""Run ``claude auth logout`` (CLI clears its own credentials), drop
|
|
176
|
+
the catalogue marker, broadcast ``.disconnected``."""
|
|
177
|
+
from services.claude_oauth import claude_auth_logout
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
await claude_auth_logout()
|
|
181
|
+
await _mark_logged_out("claude_code")
|
|
182
|
+
await _broadcast_credential_event(
|
|
183
|
+
"credential.oauth.disconnected", provider="claude_code",
|
|
184
|
+
)
|
|
185
|
+
except Exception as exc:
|
|
186
|
+
logger.warning("[claude_code_logout] failed: %s", exc)
|
|
187
|
+
return {"success": False, "error": str(exc)}
|
|
188
|
+
return {"success": True}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# ---------------------------------------------------------------------------
|
|
192
|
+
# Codex — login flow not yet wired; logout works for marker cleanup
|
|
193
|
+
# ---------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
async def handle_codex_cli_login(
|
|
196
|
+
data: Dict[str, Any], # noqa: ARG001
|
|
197
|
+
websocket: WebSocket, # noqa: ARG001
|
|
198
|
+
) -> Dict[str, Any]:
|
|
199
|
+
return {
|
|
200
|
+
"success": False,
|
|
201
|
+
"error": (
|
|
202
|
+
"Codex login is not yet wired in MachinaOs. "
|
|
203
|
+
"Install with `npm install -g @openai/codex` and run "
|
|
204
|
+
"`codex login` in your terminal — then click Login again "
|
|
205
|
+
"to mark connected."
|
|
206
|
+
),
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
async def handle_codex_cli_logout(
|
|
211
|
+
data: Dict[str, Any], # noqa: ARG001
|
|
212
|
+
websocket: WebSocket, # noqa: ARG001
|
|
213
|
+
) -> Dict[str, Any]:
|
|
214
|
+
try:
|
|
215
|
+
await _mark_logged_out("codex_cli")
|
|
216
|
+
await _broadcast_credential_event(
|
|
217
|
+
"credential.oauth.disconnected", provider="codex_cli",
|
|
218
|
+
)
|
|
219
|
+
except Exception as exc:
|
|
220
|
+
logger.warning("[codex_cli_logout] failed: %s", exc)
|
|
221
|
+
return {"success": False, "error": str(exc)}
|
|
222
|
+
return {"success": True}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# ---------------------------------------------------------------------------
|
|
226
|
+
# Registry payload — `services/cli_agent/__init__.py` registers these
|
|
227
|
+
# into `services.ws_handler_registry` on package import.
|
|
228
|
+
# ---------------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
WSHandler = Callable[[Dict[str, Any], WebSocket], Awaitable[Dict[str, Any]]]
|
|
231
|
+
|
|
232
|
+
WS_HANDLERS: Dict[str, WSHandler] = {
|
|
233
|
+
"claude_code_login": handle_claude_code_login,
|
|
234
|
+
"claude_code_logout": handle_claude_code_logout,
|
|
235
|
+
"codex_cli_login": handle_codex_cli_login,
|
|
236
|
+
"codex_cli_logout": handle_codex_cli_logout,
|
|
237
|
+
}
|