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.
Files changed (393) hide show
  1. package/README.md +143 -107
  2. package/client/dist/assets/ActionBar-Du2MSFSz.js +1 -0
  3. package/client/dist/assets/ApiKeyInput-k2LBmBjb.js +1 -0
  4. package/client/dist/assets/ApiKeyPanel-C_bV9U0X.js +1 -0
  5. package/client/dist/assets/ApiUsageSection-CmVfwZzL.js +1 -0
  6. package/client/dist/assets/EmailPanel-CeKIMGu-.js +1 -0
  7. package/client/dist/assets/OAuthPanel-KA3t3Q2K.js +1 -0
  8. package/client/dist/assets/QrPairingPanel-NgNpJNuk.js +1 -0
  9. package/client/dist/assets/RateLimitSection-Du5YNVIA.js +1 -0
  10. package/client/dist/assets/StatusCard-DNLyayXc.js +1 -0
  11. package/client/dist/assets/index-DQ0nwhec.js +257 -0
  12. package/client/dist/assets/index-DxmbVskS.css +1 -0
  13. package/client/dist/assets/vendor-flow-CZmBvHRo.js +1 -0
  14. package/client/dist/assets/vendor-icons-CVrPjN2Q.js +22 -0
  15. package/client/dist/assets/vendor-markdown-CRou3yQ5.js +62 -0
  16. package/client/dist/assets/vendor-misc-C4VxKHs5.js +1 -0
  17. package/client/dist/assets/vendor-query-SzWcOU0G.js +1 -0
  18. package/client/dist/assets/vendor-radix-Dnos29jG.js +56 -0
  19. package/client/dist/assets/vendor-react-DvWIbVx0.js +1 -0
  20. package/client/dist/index.html +37 -3
  21. package/client/index.html +28 -1
  22. package/client/package.json +44 -40
  23. package/client/src/App.tsx +2 -0
  24. package/client/src/Dashboard.tsx +157 -45
  25. package/client/src/ParameterPanel.tsx +3 -5
  26. package/client/src/adapters/nodeSpecToDescription.ts +1 -0
  27. package/client/src/assets/icons/NodeIcon.tsx +32 -0
  28. package/client/src/assets/icons/index.ts +4 -0
  29. package/client/src/assets/icons/stripe.svg +1 -0
  30. package/client/src/assets/icons/themedGlyphs.ts +404 -0
  31. package/client/src/components/AIAgentNode.tsx +77 -53
  32. package/client/src/components/GenericNode.tsx +34 -52
  33. package/client/src/components/OutputPanel.tsx +64 -147
  34. package/client/src/components/ParameterRenderer.tsx +5 -3
  35. package/client/src/components/SkillEditorModal.tsx +9 -18
  36. package/client/src/components/SquareNode.tsx +97 -115
  37. package/client/src/components/StartNode.tsx +32 -42
  38. package/client/src/components/SvgFilterDefs.tsx +54 -0
  39. package/client/src/components/TeamMonitorNode.tsx +12 -14
  40. package/client/src/components/ToolkitNode.tsx +35 -60
  41. package/client/src/components/TriggerNode.tsx +43 -77
  42. package/client/src/components/__tests__/CredentialsModal.test.tsx +49 -45
  43. package/client/src/components/credentials/CredentialsModal.tsx +98 -30
  44. package/client/src/components/credentials/CredentialsPalette.tsx +73 -5
  45. package/client/src/components/credentials/catalogueAdapter.ts +17 -1
  46. package/client/src/components/credentials/panels/ApiKeyPanel.tsx +102 -37
  47. package/client/src/components/credentials/panels/EmailPanel.tsx +7 -19
  48. package/client/src/components/credentials/panels/OAuthPanel.tsx +5 -1
  49. package/client/src/components/credentials/panels/QrPairingPanel.tsx +1 -3
  50. package/client/src/components/credentials/primitives/ActionBar.tsx +7 -11
  51. package/client/src/components/credentials/primitives/OAuthConnect.tsx +19 -28
  52. package/client/src/components/credentials/sections/ProviderDefaultsSection.tsx +24 -3
  53. package/client/src/components/credentials/types.ts +12 -2
  54. package/client/src/components/credentials/useCredentialPanel.ts +43 -19
  55. package/client/src/components/icons/AIProviderIcons.tsx +16 -0
  56. package/client/src/components/onboarding/OnboardingWizard.tsx +23 -63
  57. package/client/src/components/onboarding/nodeRoleClasses.ts +23 -0
  58. package/client/src/components/onboarding/steps/CanvasStep.tsx +15 -21
  59. package/client/src/components/onboarding/steps/ConceptsStep.tsx +2 -11
  60. package/client/src/components/onboarding/steps/GetStartedStep.tsx +2 -10
  61. package/client/src/components/parameterPanel/InputSection.tsx +9 -7
  62. package/client/src/components/parameterPanel/MasterSkillEditor.tsx +84 -198
  63. package/client/src/components/parameterPanel/MiddleSection.tsx +57 -80
  64. package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +31 -25
  65. package/client/src/components/parameterPanel/__tests__/InputSection.test.tsx +7 -2
  66. package/client/src/components/ui/AIResultModal.tsx +1 -1
  67. package/client/src/components/ui/CollapsibleSection.tsx +9 -5
  68. package/client/src/components/ui/CommandPalette.tsx +147 -0
  69. package/client/src/components/ui/CommandPaletteHost.tsx +189 -0
  70. package/client/src/components/ui/ComponentItem.tsx +13 -7
  71. package/client/src/components/ui/ComponentPalette.tsx +24 -13
  72. package/client/src/components/ui/ConsolePanel.tsx +19 -11
  73. package/client/src/components/ui/DropCap.tsx +28 -0
  74. package/client/src/components/ui/EditableNodeLabel.tsx +10 -2
  75. package/client/src/components/ui/InputNodesPanel.tsx +1 -1
  76. package/client/src/components/ui/Modal.tsx +38 -6
  77. package/client/src/components/ui/OutputDisplayPanel.tsx +1 -1
  78. package/client/src/components/ui/SettingsPanel.tsx +42 -13
  79. package/client/src/components/ui/StatusBar.tsx +108 -0
  80. package/client/src/components/ui/ThemeSwitcher.tsx +109 -0
  81. package/client/src/components/ui/TopToolbar.tsx +42 -25
  82. package/client/src/components/ui/WorkflowSidebar.tsx +32 -16
  83. package/client/src/components/ui/action-button.tsx +40 -15
  84. package/client/src/components/ui/button.tsx +24 -1
  85. package/client/src/components/ui/dropdown-menu.tsx +24 -2
  86. package/client/src/components/ui/input.tsx +19 -2
  87. package/client/src/components/ui/select.tsx +15 -0
  88. package/client/src/components/ui/textarea.tsx +15 -2
  89. package/client/src/contexts/AuthContext.tsx +148 -109
  90. package/client/src/contexts/ThemeContext.tsx +93 -17
  91. package/client/src/contexts/WebSocketContext.tsx +373 -206
  92. package/client/src/contexts/__tests__/AuthContext.test.tsx +221 -0
  93. package/client/src/hooks/__tests__/useDragVariable.test.ts +7 -1
  94. package/client/src/hooks/__tests__/useWorkflowOpsListener.test.ts +142 -0
  95. package/client/src/hooks/useAppTheme.ts +209 -7
  96. package/client/src/hooks/useAutoSkillEdges.ts +7 -2
  97. package/client/src/hooks/useCatalogueQuery.ts +67 -1
  98. package/client/src/hooks/useDragVariable.ts +1 -1
  99. package/client/src/hooks/useNodeAllowlist.ts +115 -8
  100. package/client/src/hooks/useOnboarding.ts +20 -8
  101. package/client/src/hooks/useParameterPanel.ts +2 -1
  102. package/client/src/hooks/useReactFlowNodes.ts +2 -1
  103. package/client/src/hooks/useSound.ts +185 -0
  104. package/client/src/hooks/useWorkflowManagement.ts +6 -8
  105. package/client/src/hooks/useWorkflowOpsListener.ts +90 -0
  106. package/client/src/index.css +65 -3
  107. package/client/src/lib/__tests__/connectionConfig.test.ts +91 -0
  108. package/client/src/lib/aiModelProviders.ts +8 -0
  109. package/client/src/lib/connectionConfig.ts +107 -0
  110. package/client/src/lib/queryPersist.ts +13 -5
  111. package/client/src/lib/sound.ts +393 -0
  112. package/client/src/main.tsx +20 -0
  113. package/client/src/store/useAppStore.ts +26 -0
  114. package/client/src/styles/canvasAnimations.ts +37 -36
  115. package/client/src/styles/theme.ts +36 -20
  116. package/client/src/test/setup.ts +1 -0
  117. package/client/src/themes/atomic.css +253 -0
  118. package/client/src/themes/base.css +373 -0
  119. package/client/src/themes/cyber.css +890 -0
  120. package/client/src/themes/dark.css +70 -0
  121. package/client/src/themes/edo.css +246 -0
  122. package/client/src/themes/greek.css +293 -0
  123. package/client/src/themes/light.css +78 -0
  124. package/client/src/themes/plague.css +253 -0
  125. package/client/src/themes/renaissance.css +727 -0
  126. package/client/src/themes/rot.css +249 -0
  127. package/client/src/themes/steampunk.css +272 -0
  128. package/client/src/themes/surveillance.css +289 -0
  129. package/client/src/themes/wasteland.css +250 -0
  130. package/client/src/types/INodeProperties.ts +5 -0
  131. package/client/src/types/NodeTypes.ts +11 -1
  132. package/client/src/types/__tests__/cloudEvents.test.ts +99 -0
  133. package/client/src/types/cloudEvents.ts +78 -0
  134. package/client/src/vite-env.d.ts +7 -0
  135. package/client/tsconfig.json +1 -1
  136. package/client/vite.config.js +62 -2
  137. package/install.ps1 +1 -1
  138. package/install.sh +1 -1
  139. package/machina/commands/build.py +51 -7
  140. package/machina/pyproject.toml +4 -0
  141. package/machina/supervisor.py +12 -2
  142. package/machina/tree.py +71 -21
  143. package/package.json +4 -4
  144. package/scripts/install.js +16 -1
  145. package/server/config/ai_cli_providers.json +54 -0
  146. package/server/config/credential_providers.json +109 -2
  147. package/server/config/llm_defaults.json +24 -0
  148. package/server/config/model_registry.json +338 -499
  149. package/server/config/node_allowlist.json +16 -1
  150. package/server/config/pricing.json +8 -0
  151. package/server/constants.py +38 -15
  152. package/server/core/container.py +2 -2
  153. package/server/core/credentials_database.py +35 -2
  154. package/server/core/logging.py +4 -3
  155. package/server/main.py +99 -13
  156. package/server/models/node_metadata.py +1 -0
  157. package/server/nodejs/package.json +8 -6
  158. package/server/nodejs/src/index.ts +22 -5
  159. package/server/nodes/README.md +31 -4
  160. package/server/nodes/agent/_inline.py +2 -0
  161. package/server/nodes/agent/_specialized.py +6 -3
  162. package/server/nodes/agent/ai_agent.py +13 -3
  163. package/server/nodes/agent/chat_agent.py +6 -3
  164. package/server/nodes/agent/claude_code_agent.py +287 -75
  165. package/server/nodes/agent/codex_agent.py +239 -0
  166. package/server/nodes/agent/deep_agent.py +3 -3
  167. package/server/nodes/agent/rlm_agent.py +3 -3
  168. package/server/nodes/android/__init__.py +31 -1
  169. package/server/nodes/android/_base.py +9 -5
  170. package/server/{services/android_service.py → nodes/android/_dispatcher.py} +2 -2
  171. package/server/nodes/android/_handlers.py +154 -0
  172. package/server/nodes/android/_option_loaders.py +44 -0
  173. package/server/nodes/android/_refresh.py +127 -0
  174. package/server/{services/android → nodes/android/_relay}/client.py +4 -4
  175. package/server/{routers/android.py → nodes/android/_router.py} +27 -8
  176. package/server/nodes/browser/browser.py +2 -2
  177. package/server/nodes/code/_base.py +6 -2
  178. package/server/nodes/code/_claude_code.py +134 -0
  179. package/server/nodes/document/embedding_generator.py +3 -3
  180. package/server/nodes/document/http_scraper.py +3 -3
  181. package/server/nodes/document/vector_store.py +5 -5
  182. package/server/nodes/email/__init__.py +11 -1
  183. package/server/nodes/email/_filters.py +21 -0
  184. package/server/{services/himalaya_service.py → nodes/email/_himalaya.py} +6 -10
  185. package/server/{services/email_service.py → nodes/email/_service.py} +9 -13
  186. package/server/nodes/email/email_read.py +1 -1
  187. package/server/nodes/email/email_receive.py +54 -5
  188. package/server/nodes/email/email_send.py +1 -1
  189. package/server/nodes/filesystem/shell.py +24 -1
  190. package/server/nodes/google/__init__.py +55 -1
  191. package/server/{services/handlers/google_auth.py → nodes/google/_auth_helper.py} +8 -5
  192. package/server/nodes/google/_base.py +2 -2
  193. package/server/nodes/google/_credentials.py +5 -5
  194. package/server/nodes/google/_filters.py +25 -0
  195. package/server/nodes/google/_handlers.py +57 -0
  196. package/server/{services/google_oauth.py → nodes/google/_oauth.py} +195 -162
  197. package/server/nodes/google/_option_loaders.py +107 -0
  198. package/server/nodes/google/_refresh.py +66 -0
  199. package/server/nodes/google/_router.py +131 -0
  200. package/server/nodes/google/gmail_receive.py +41 -4
  201. package/server/nodes/groups.py +1 -0
  202. package/server/nodes/location/_credentials.py +45 -1
  203. package/server/{services/maps.py → nodes/location/_service.py} +18 -3
  204. package/server/nodes/location/gmaps_create.py +4 -4
  205. package/server/nodes/location/gmaps_locations.py +4 -4
  206. package/server/nodes/location/gmaps_nearby_places.py +4 -4
  207. package/server/nodes/model/_base.py +8 -3
  208. package/server/nodes/model/_credentials.py +96 -8
  209. package/server/nodes/model/_local_validator.py +345 -0
  210. package/server/nodes/model/lmstudio_chat_model.py +23 -0
  211. package/server/nodes/model/ollama_chat_model.py +25 -0
  212. package/server/nodes/proxy/_usage.py +2 -2
  213. package/server/nodes/proxy/proxy_config.py +14 -14
  214. package/server/nodes/proxy/proxy_request.py +4 -4
  215. package/server/nodes/scraper/_credentials.py +29 -1
  216. package/server/nodes/scraper/apify_actor.py +9 -9
  217. package/server/nodes/scraper/crawlee_scraper.py +5 -5
  218. package/server/nodes/search/brave_search.py +4 -0
  219. package/server/nodes/search/perplexity_search.py +9 -0
  220. package/server/nodes/search/serper_search.py +3 -0
  221. package/server/nodes/skill/simple_memory.py +12 -0
  222. package/server/nodes/social/_base.py +2 -2
  223. package/server/nodes/stripe/__init__.py +46 -0
  224. package/server/nodes/stripe/_credentials.py +33 -0
  225. package/server/nodes/stripe/_handlers.py +270 -0
  226. package/server/nodes/stripe/_install.py +127 -0
  227. package/server/nodes/stripe/_source.py +174 -0
  228. package/server/nodes/stripe/stripe_action.py +81 -0
  229. package/server/nodes/stripe/stripe_receive.py +92 -0
  230. package/server/nodes/telegram/_credentials.py +52 -1
  231. package/server/nodes/telegram/_handlers.py +19 -18
  232. package/server/nodes/telegram/_service.py +134 -32
  233. package/server/nodes/telegram/telegram_send.py +5 -6
  234. package/server/nodes/text/file_handler.py +2 -2
  235. package/server/nodes/text/text_generator.py +2 -2
  236. package/server/nodes/tool/agent_builder.py +630 -0
  237. package/server/nodes/tool/task_manager.py +144 -2
  238. package/server/nodes/twitter/__init__.py +38 -1
  239. package/server/nodes/twitter/_base.py +7 -7
  240. package/server/nodes/twitter/_credentials.py +1 -1
  241. package/server/nodes/twitter/_filters.py +37 -0
  242. package/server/nodes/twitter/_handlers.py +77 -0
  243. package/server/nodes/twitter/_oauth.py +124 -0
  244. package/server/nodes/twitter/_refresh.py +78 -0
  245. package/server/nodes/twitter/_router.py +29 -0
  246. package/server/nodes/twitter/twitter_receive.py +4 -0
  247. package/server/nodes/visuals.json +64 -19
  248. package/server/nodes/whatsapp/__init__.py +45 -5
  249. package/server/nodes/whatsapp/_base.py +3 -3
  250. package/server/nodes/whatsapp/_filters.py +137 -0
  251. package/server/nodes/whatsapp/_handlers.py +167 -0
  252. package/server/nodes/whatsapp/_option_loaders.py +68 -0
  253. package/server/nodes/whatsapp/_refresh.py +62 -0
  254. package/server/nodes/whatsapp/_runtime.py +1 -1
  255. package/server/pyproject.toml +29 -7
  256. package/server/routers/schemas.py +2 -2
  257. package/server/routers/webhook.py +26 -9
  258. package/server/routers/websocket.py +149 -810
  259. package/server/services/ai.py +89 -8
  260. package/server/services/auth.py +220 -43
  261. package/server/services/claude_oauth.py +126 -100
  262. package/server/services/cli_agent/__init__.py +78 -0
  263. package/server/services/cli_agent/_handlers.py +237 -0
  264. package/server/services/cli_agent/config.py +112 -0
  265. package/server/services/cli_agent/factory.py +48 -0
  266. package/server/services/cli_agent/lockfile.py +141 -0
  267. package/server/services/cli_agent/mcp_server.py +482 -0
  268. package/server/services/cli_agent/protocol.py +173 -0
  269. package/server/services/cli_agent/providers/__init__.py +9 -0
  270. package/server/services/cli_agent/providers/anthropic_claude.py +419 -0
  271. package/server/services/cli_agent/providers/google_gemini.py +80 -0
  272. package/server/services/cli_agent/providers/openai_codex.py +310 -0
  273. package/server/services/cli_agent/service.py +607 -0
  274. package/server/services/cli_agent/session.py +618 -0
  275. package/server/services/cli_agent/types.py +227 -0
  276. package/server/services/cli_agent/workflow_tools.py +233 -0
  277. package/server/services/credential_registry.py +26 -1
  278. package/server/services/deployment/manager.py +26 -145
  279. package/server/services/deployment/poll_registry.py +59 -0
  280. package/server/services/event_waiter.py +76 -246
  281. package/server/services/events/__init__.py +54 -0
  282. package/server/services/events/cli.py +78 -0
  283. package/server/services/events/daemon.py +163 -0
  284. package/server/services/events/envelope.py +281 -0
  285. package/server/services/events/lifecycle.py +99 -0
  286. package/server/services/events/oauth_lifecycle.py +534 -0
  287. package/server/services/events/polling.py +60 -0
  288. package/server/services/events/push.py +36 -0
  289. package/server/services/events/source.py +63 -0
  290. package/server/services/events/triggers.py +118 -0
  291. package/server/services/events/verifiers/__init__.py +25 -0
  292. package/server/services/events/verifiers/base.py +28 -0
  293. package/server/services/events/verifiers/github.py +25 -0
  294. package/server/services/events/verifiers/hmac_basic.py +32 -0
  295. package/server/services/events/verifiers/standard_webhooks.py +47 -0
  296. package/server/services/events/verifiers/stripe.py +42 -0
  297. package/server/services/events/webhook.py +105 -0
  298. package/server/services/handlers/tools.py +28 -186
  299. package/server/services/llm/config.py +7 -0
  300. package/server/services/llm/factory.py +8 -2
  301. package/server/services/memory/__init__.py +52 -0
  302. package/server/services/memory/jsonl.py +80 -0
  303. package/server/services/memory/markdown.py +65 -0
  304. package/server/services/memory/state.py +112 -0
  305. package/server/services/memory/vector_store.py +40 -0
  306. package/server/services/model_registry.py +76 -0
  307. package/server/services/node_allowlist.py +71 -15
  308. package/server/services/node_executor.py +2 -2
  309. package/server/services/node_output_schemas.py +21 -10
  310. package/server/services/node_spec.py +1 -1
  311. package/server/services/oauth_utils.py +1 -1
  312. package/server/services/plugin/__init__.py +2 -0
  313. package/server/services/plugin/base.py +44 -2
  314. package/server/services/plugin/credential.py +288 -1
  315. package/server/services/plugin/deps.py +105 -0
  316. package/server/services/plugin/edge_walker.py +12 -4
  317. package/server/services/plugin/oauth.py +381 -0
  318. package/server/services/plugin/polling.py +247 -0
  319. package/server/services/plugin/registry.py +145 -0
  320. package/server/services/plugin/singleton.py +65 -0
  321. package/server/services/plugin/ws.py +81 -0
  322. package/server/services/process_service.py +31 -2
  323. package/server/services/status_broadcaster.py +155 -238
  324. package/server/services/temporal/workflow.py +7 -7
  325. package/server/services/workflow.py +21 -3
  326. package/server/services/ws_handler_registry.py +111 -28
  327. package/server/skills/GUIDE.md +16 -1
  328. package/server/skills/assistant/agent-builder-skill/SKILL.md +166 -0
  329. package/server/skills/payments_agent/stripe-skill/SKILL.md +306 -0
  330. package/server/tests/credentials/test_auth_service.py +16 -9
  331. package/server/tests/credentials/test_credential_broadcasts.py +219 -0
  332. package/server/tests/credentials/test_google_oauth.py +6 -6
  333. package/server/tests/credentials/test_oauth_utils.py +1 -1
  334. package/server/tests/credentials/test_twitter_oauth.py +2 -2
  335. package/server/tests/credentials/test_websocket_handlers.py +44 -20
  336. package/server/tests/llm/test_factory.py +1 -0
  337. package/server/tests/llm/test_wiring.py +5 -1
  338. package/server/tests/nodes/_compat.py +24 -24
  339. package/server/tests/nodes/test_agent_builder.py +439 -0
  340. package/server/tests/nodes/test_ai_tools.py +18 -14
  341. package/server/tests/nodes/test_code_fs_process.py +17 -8
  342. package/server/tests/nodes/test_email.py +10 -9
  343. package/server/tests/nodes/test_google_workspace.py +2 -2
  344. package/server/tests/nodes/test_specialized_agents.py +100 -53
  345. package/server/tests/nodes/test_stripe_plugin.py +293 -0
  346. package/server/tests/nodes/test_telegram_social.py +4 -4
  347. package/server/tests/nodes/test_twitter.py +1 -1
  348. package/server/tests/nodes/test_web_automation.py +2 -2
  349. package/server/tests/nodes/test_whatsapp.py +9 -9
  350. package/server/tests/services/cli_agent/__init__.py +0 -0
  351. package/server/tests/services/cli_agent/test_mcp_server.py +432 -0
  352. package/server/tests/services/cli_agent/test_providers.py +358 -0
  353. package/server/tests/services/cli_agent/test_service.py +298 -0
  354. package/server/tests/services/memory/__init__.py +0 -0
  355. package/server/tests/services/memory/test_jsonl.py +188 -0
  356. package/server/tests/services/test_events.py +333 -0
  357. package/server/tests/test_node_spec.py +56 -16
  358. package/server/tests/test_plugin_helpers.py +116 -0
  359. package/server/tests/test_plugin_self_containment.py +486 -0
  360. package/server/tests/test_status_broadcasts.py +425 -0
  361. package/workflows/{AI Assistant_workflow-1777421105154-0m4snkzjf.json → AI Assistant_workflow-1778504793388-ou1m1tz2x.json } +70 -266
  362. package/workflows/{AI Employee_workflow-1777720598005-u4cm858dv.json → AI Employee_example_workflow-1777720598005-u4cm858dv.json } +112 -112
  363. package/workflows/Claude Assistant_workflow-1778380124051-mdibn807c.json +709 -0
  364. package/client/dist/assets/ActionBar-vzPpSR77.js +0 -1
  365. package/client/dist/assets/ApiKeyInput-Ds7AKFe8.js +0 -1
  366. package/client/dist/assets/ApiKeyPanel-gfblELep.js +0 -1
  367. package/client/dist/assets/ApiUsageSection-BMNWTe2r.js +0 -1
  368. package/client/dist/assets/EmailPanel-B1Om64p5.js +0 -1
  369. package/client/dist/assets/OAuthPanel-CXyQYGBz.js +0 -1
  370. package/client/dist/assets/QrPairingPanel-BgNuI1we.js +0 -1
  371. package/client/dist/assets/RateLimitSection-YYK8sx1T.js +0 -1
  372. package/client/dist/assets/StatusCard-DuYA5hJR.js +0 -1
  373. package/client/dist/assets/index-D9tZfgvi.js +0 -363
  374. package/client/dist/assets/index-al7snTkG.css +0 -1
  375. package/client/src/components/credentials/providers.tsx +0 -177
  376. package/server/routers/google.py +0 -277
  377. package/server/routers/maps.py +0 -142
  378. package/server/routers/twitter.py +0 -365
  379. package/server/services/claude_code_service.py +0 -106
  380. package/server/services/memory.py +0 -159
  381. package/server/services/node_option_loaders/__init__.py +0 -77
  382. package/server/services/node_option_loaders/android_loaders.py +0 -55
  383. package/server/services/node_option_loaders/google_loaders.py +0 -97
  384. package/server/services/node_option_loaders/whatsapp_loaders.py +0 -69
  385. package/server/services/twitter_oauth.py +0 -411
  386. package/server/services/websocket_client.py +0 -29
  387. /package/server/{services/android → nodes/android/_relay}/__init__.py +0 -0
  388. /package/server/{services/android → nodes/android/_relay}/broadcaster.py +0 -0
  389. /package/server/{services/android → nodes/android/_relay}/manager.py +0 -0
  390. /package/server/{services/android → nodes/android/_relay}/protocol.py +0 -0
  391. /package/server/{services/browser_service.py → nodes/browser/_service.py} +0 -0
  392. /package/server/{services/whatsapp_service.py → nodes/whatsapp/_service.py} +0 -0
  393. /package/server/skills/{task_agent → assistant}/write-todos-skill/SKILL.md +0 -0
@@ -1,4 +1,26 @@
1
- """Claude OAuth Service - Isolated login that doesn't affect user's main session."""
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 Dict, Any
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
- # Isolated directories for MachinaOs
16
- MACHINA_CLAUDE_DIR = Path.home() / ".claude-machina"
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
- def _get_claude_cmd() -> str:
21
- """Get path to isolated claude CLI, installing if needed."""
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
- claude_path = MACHINA_NPM_DIR / "claude.cmd"
58
+ bin_path = MACHINA_NPM_DIR / "node_modules" / ".bin" / "claude.cmd"
24
59
  else:
25
- claude_path = MACHINA_NPM_DIR / "bin" / "claude"
60
+ bin_path = MACHINA_NPM_DIR / "node_modules" / ".bin" / "claude"
26
61
 
27
- if claude_path.exists():
28
- return str(claude_path)
62
+ if bin_path.exists():
63
+ return str(bin_path)
29
64
 
30
- # Install claude-code in isolated location
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
- # Find the installed binary
49
- if sys.platform == "win32":
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
- if not claude_path.exists():
55
- raise FileNotFoundError(f"Claude CLI not found at {claude_path} after install")
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
- async def initiate_claude_oauth() -> Dict[str, Any]:
62
- """Run claude login in isolated environment with auto-filled prompts."""
63
- try:
64
- MACHINA_CLAUDE_DIR.mkdir(exist_ok=True)
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
- creds_path = MACHINA_CLAUDE_DIR / ".credentials.json"
125
- if not creds_path.exists():
126
- return {"success": False, "has_token": False}
127
-
128
- with open(creds_path) as f:
129
- creds = json.load(f)
130
-
131
- return {
132
- "success": True,
133
- "has_token": bool(creds.get("accessToken")),
134
- "access_token": creds.get("accessToken"),
135
- "expires_at": creds.get("expiresAt"),
136
- }
137
- except Exception as e:
138
- return {"success": False, "error": str(e)}
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
+ }