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
@@ -3,7 +3,6 @@ import { Loader2, Save } from 'lucide-react';
3
3
  import { toast } from 'sonner';
4
4
  import { useMutation, useQueries, useQuery } from '@tanstack/react-query';
5
5
 
6
- import { Button } from '@/components/ui/button';
7
6
  import { Input } from '@/components/ui/input';
8
7
  import { Progress } from '@/components/ui/progress';
9
8
  import { Checkbox } from '@/components/ui/checkbox';
@@ -30,7 +29,6 @@ import { ActionButton } from '@/components/ui/action-button';
30
29
  import ParameterRenderer from '../ParameterRenderer';
31
30
  import ToolSchemaEditor from './ToolSchemaEditor';
32
31
  import MasterSkillEditor from './MasterSkillEditor';
33
- import { useAppTheme } from '../../hooks/useAppTheme';
34
32
  import { useAppStore } from '../../store/useAppStore';
35
33
  import { useWebSocket, CompactionStats } from '../../contexts/WebSocketContext';
36
34
  import { useUserSettingsQuery } from '../../hooks/useUserSettingsQuery';
@@ -44,7 +42,12 @@ import { ExecutionResult } from '../../services/executionService';
44
42
  import { Edge } from 'reactflow';
45
43
  import { shouldShowParameter } from '../../utils/parameterVisibility';
46
44
 
47
- import { resolveNodeDescription } from '../../lib/nodeSpec';
45
+ import { resolveNodeDescription, getCachedNodeSpec } from '../../lib/nodeSpec';
46
+
47
+ const isMasterSkillNodeType = (nodeType: string | undefined): boolean => {
48
+ if (!nodeType) return false;
49
+ return (getCachedNodeSpec(nodeType)?.uiHints as any)?.isMasterSkillEditor === true;
50
+ };
48
51
  // Wave 10.G.3: retired the three tribal arrays `SKILL_NODE_TYPES`,
49
52
  // `TOOL_NODE_TYPES`, and `AGENT_WITH_SKILLS_TYPES`. The parameter panel
50
53
  // now reads `uiHints.hasSkills` / `uiHints.isToolPanel` /
@@ -87,8 +90,7 @@ const MiddleSection: React.FC<MiddleSectionProps> = ({
87
90
  isLoadingParameters = false,
88
91
  executionResults = []
89
92
  }) => {
90
- const theme = useAppTheme();
91
- const { currentWorkflow } = useAppStore();
93
+ const currentWorkflow = useAppStore((s) => s.currentWorkflow);
92
94
  const { clearMemory, resetSkill, sendRequest, getNodeParameters, compactionStats: contextCompactionStats, updateCompactionStats } = useWebSocket();
93
95
 
94
96
  const [showClearMemoryDialog, setShowClearMemoryDialog] = useState(false);
@@ -297,7 +299,7 @@ const MiddleSection: React.FC<MiddleSectionProps> = ({
297
299
  const masterSkillEdgeSources = useMemo<string[]>(() => {
298
300
  const nodes = currentWorkflow?.nodes || [];
299
301
  return skillEdges
300
- .filter((edge) => nodes.find((n: any) => n.id === edge.source)?.type === 'masterSkill')
302
+ .filter((edge) => isMasterSkillNodeType(nodes.find((n: any) => n.id === edge.source)?.type))
301
303
  .map((edge) => edge.source);
302
304
  }, [skillEdges, currentWorkflow?.nodes]);
303
305
 
@@ -425,7 +427,7 @@ const MiddleSection: React.FC<MiddleSectionProps> = ({
425
427
  for (const edge of skillEdges) {
426
428
  const sourceNode = nodes.find((n: any) => n.id === edge.source);
427
429
  const nodeType = sourceNode?.type || '';
428
- if (nodeType === 'masterSkill') continue;
430
+ if (isMasterSkillNodeType(nodeType)) continue;
429
431
  const def = resolveNodeDescription(nodeType);
430
432
  skills.push({
431
433
  id: edge.source,
@@ -452,7 +454,7 @@ const MiddleSection: React.FC<MiddleSectionProps> = ({
452
454
 
453
455
  for (const edge of skillEdges) {
454
456
  const sourceNode = nodes.find((n: any) => n.id === edge.source);
455
- if (sourceNode?.type === 'masterSkill') {
457
+ if (isMasterSkillNodeType(sourceNode?.type)) {
456
458
  const params = masterSkillParams[edge.source];
457
459
  const skillsConfig = params?.skills_config || {};
458
460
 
@@ -510,38 +512,21 @@ const MiddleSection: React.FC<MiddleSectionProps> = ({
510
512
  const consoleOutput = isCodeExecutorNode ? getConsoleOutput() : '';
511
513
 
512
514
  return (
513
- <div style={{
514
- flex: 1,
515
- display: 'flex',
516
- flexDirection: 'column',
517
- height: '100%',
518
- overflow: 'hidden',
519
- position: 'relative'
520
- }}>
515
+ <div className="relative flex h-full flex-1 flex-col overflow-hidden">
521
516
  {/* Description - hide for code editor nodes (Python, Skill) and masterSkill */}
522
517
  {!needsCodeEditorLayout && !isMasterSkillNode && (
523
- <div style={{
524
- padding: `${theme.spacing.lg} ${theme.spacing.xl} ${theme.spacing.sm}`,
525
- borderBottom: `1px solid ${theme.colors.border}`,
526
- backgroundColor: theme.colors.backgroundAlt,
527
- flexShrink: 0
528
- }}>
529
- <p style={{
530
- margin: 0,
531
- fontSize: theme.fontSize.base,
532
- color: theme.colors.textSecondary,
533
- lineHeight: '1.5',
534
- }}>
518
+ <div className="shrink-0 border-b border-border-default bg-bg-panel px-6 pt-4 pb-2">
519
+ <p className="m-0 text-base leading-[1.5] text-fg-muted">
535
520
  {nodeDefinition.description}
536
521
  </p>
537
522
  </div>
538
523
  )}
539
524
 
540
525
  {/* Main Content Area - Flexible */}
541
- <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0, overflow: 'hidden' }}>
526
+ <div className="flex min-h-0 flex-1 flex-col overflow-hidden">
542
527
  {/* Master Skill Editor - Full panel for masterSkill nodes */}
543
528
  {isMasterSkillNode ? (
544
- <div style={{ flex: 1, display: 'flex', flexDirection: 'column', padding: theme.spacing.lg, overflow: 'hidden' }}>
529
+ <div className="flex flex-1 flex-col overflow-hidden p-4">
545
530
  <MasterSkillEditor
546
531
  skillsConfig={parameters.skills_config || {}}
547
532
  onConfigChange={(config) => onParameterChange('skills_config', config)}
@@ -553,45 +538,34 @@ const MiddleSection: React.FC<MiddleSectionProps> = ({
553
538
  ) : (
554
539
  <>
555
540
  {/* Parameters */}
556
- <div style={{
557
- padding: theme.spacing.xl,
558
- flex: needsCodeEditorLayout ? '3' : 1,
559
- overflowY: needsCodeEditorLayout ? 'hidden' : 'auto',
560
- overflowX: 'hidden',
561
- width: '100%',
562
- boxSizing: 'border-box',
563
- minHeight: 0,
564
- display: needsCodeEditorLayout ? 'flex' : 'block',
565
- flexDirection: 'column'
566
- }}>
541
+ <div
542
+ className={cn(
543
+ 'box-border min-h-0 w-full overflow-x-hidden p-6',
544
+ needsCodeEditorLayout
545
+ ? 'flex flex-[3] flex-col overflow-y-hidden'
546
+ : 'block flex-1 overflow-y-auto',
547
+ )}
548
+ >
567
549
  {/* Parameters Container */}
568
- <div style={{
569
- backgroundColor: theme.colors.background,
570
- border: `1px solid ${theme.colors.border}`,
571
- borderRadius: theme.borderRadius.md,
572
- padding: theme.spacing.lg,
573
- boxShadow: `0 1px 3px ${theme.colors.shadowLight}`,
574
- height: needsCodeEditorLayout ? '100%' : 'auto',
575
- display: needsCodeEditorLayout ? 'flex' : 'block',
576
- flexDirection: 'column',
577
- boxSizing: 'border-box'
578
- }}>
550
+ <div
551
+ className={cn(
552
+ 'box-border rounded-md border border-border-default bg-bg-elevated p-4 shadow-sm',
553
+ needsCodeEditorLayout ? 'flex h-full flex-col' : 'block h-auto',
554
+ )}
555
+ >
579
556
  {/* All Parameters - standard n8n style */}
580
557
  {visibleParams.map((param: INodeProperties, index: number) => {
581
558
  // Check if this parameter is a code editor - give it more flex space
582
559
  const isCodeParam = (param as any).typeOptions?.editor === 'code';
560
+ const isLast = index === visibleParams.length - 1;
583
561
  return (
584
562
  <div
585
563
  key={param.name}
586
- style={{
587
- paddingBottom: index < visibleParams.length - 1 ? theme.spacing.md : 0,
588
- marginBottom: index < visibleParams.length - 1 ? theme.spacing.md : 0,
589
- borderBottom: index < visibleParams.length - 1 ? `1px solid ${theme.colors.border}` : 'none',
590
- flex: needsCodeEditorLayout && isCodeParam ? 1 : 'none',
591
- display: needsCodeEditorLayout ? 'flex' : 'block',
592
- flexDirection: 'column',
593
- minHeight: needsCodeEditorLayout && isCodeParam ? '300px' : 0
594
- }}
564
+ className={cn(
565
+ needsCodeEditorLayout ? 'flex flex-col' : 'block',
566
+ !isLast && 'mb-3 border-b border-border-default pb-3',
567
+ needsCodeEditorLayout && isCodeParam ? 'min-h-[300px] flex-1' : 'min-h-0 flex-none',
568
+ )}
595
569
  >
596
570
  <ParameterRenderer
597
571
  parameter={param}
@@ -618,21 +592,21 @@ const MiddleSection: React.FC<MiddleSectionProps> = ({
618
592
 
619
593
  {/* Clear Memory Button - Only for memory nodes */}
620
594
  {isMemoryNode && (
621
- <div className="mt-3 flex justify-end border-t border-border pt-3">
622
- <Button
623
- variant="destructive"
624
- size="sm"
595
+ <div className="mt-3 flex justify-end border-t border-border-default pt-3">
596
+ <ActionButton
597
+ intent="stop"
625
598
  onClick={() => setShowClearMemoryDialog(true)}
599
+ className="h-8"
626
600
  >
627
601
  <Trash2 className="h-3.5 w-3.5" />
628
602
  Clear Memory
629
- </Button>
603
+ </ActionButton>
630
604
  </div>
631
605
  )}
632
606
 
633
607
  {/* Reset Skill Button - Only for built-in skill nodes */}
634
608
  {isSkillNode && (
635
- <div className="mt-3 flex justify-end border-t border-border pt-3">
609
+ <div className="mt-3 flex justify-end border-t border-border-default pt-3">
636
610
  <ActionButton
637
611
  intent="config"
638
612
  onClick={() => setShowResetSkillDialog(true)}
@@ -710,18 +684,18 @@ const MiddleSection: React.FC<MiddleSectionProps> = ({
710
684
 
711
685
  {/* Token Usage Section - Only for agent nodes with memory connected */}
712
686
  {isAgentWithSkills && connectedMemorySessionId && (
713
- <Accordion type="single" collapsible defaultValue="tokens" style={{ marginTop: 16 }}>
687
+ <Accordion type="single" collapsible defaultValue="tokens" className="mt-4">
714
688
  <AccordionItem value="tokens">
715
689
  <AccordionTrigger>
716
690
  {(() => {
717
691
  const ctxLen = compactionStats?.context_length || 0;
718
692
  const displayMax = ctxLen > 0 ? ctxLen : compactionStats?.threshold || 0;
719
693
  return (
720
- <span className="flex flex-1 items-center gap-2">
694
+ <span className="font-display tracking-[var(--type-tracking-display)] [text-transform:var(--type-uppercase)] text-fg-default flex flex-1 items-center gap-2">
721
695
  <Zap className="h-4 w-4" />
722
696
  Token Usage
723
697
  {compactionStats && displayMax > 0 && (
724
- <span className="ml-auto text-xs text-muted-foreground">
698
+ <span className="ml-auto text-xs text-fg-muted normal-case tracking-normal">
725
699
  {Math.round(compactionStats.total / 1000)}K / {Math.round(displayMax / 1000)}K{ctxLen > 0 ? ' context' : ''}
726
700
  </span>
727
701
  )}
@@ -769,8 +743,9 @@ const MiddleSection: React.FC<MiddleSectionProps> = ({
769
743
  step={10000}
770
744
  className="h-7 w-28 text-xs"
771
745
  />
772
- <Button
773
- size="icon-sm"
746
+ <ActionButton
747
+ intent="save"
748
+ className="h-7 w-7 px-0"
774
749
  disabled={savingThreshold || !connectedMemorySessionId}
775
750
  onClick={() => {
776
751
  if (!connectedMemorySessionId) return;
@@ -781,7 +756,7 @@ const MiddleSection: React.FC<MiddleSectionProps> = ({
781
756
  }}
782
757
  >
783
758
  {savingThreshold ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Save className="h-3.5 w-3.5" />}
784
- </Button>
759
+ </ActionButton>
785
760
  </div>
786
761
  </div>
787
762
  ) : (
@@ -829,12 +804,12 @@ const MiddleSection: React.FC<MiddleSectionProps> = ({
829
804
  >
830
805
  <AccordionItem value="skills">
831
806
  <AccordionTrigger>
832
- <span className="flex flex-1 items-center gap-2">
807
+ <span className="font-display tracking-[var(--type-tracking-display)] [text-transform:var(--type-uppercase)] text-fg-default flex flex-1 items-center gap-2">
833
808
  <Sparkles className="h-4 w-4" />
834
809
  Connected Skills
835
810
  <Badge
836
811
  variant={expandedConnectedSkills.length > 0 ? 'default' : 'outline'}
837
- className="ml-auto"
812
+ className="ml-auto normal-case tracking-normal"
838
813
  >
839
814
  {expandedConnectedSkills.length}
840
815
  </Badge>
@@ -894,20 +869,22 @@ const MiddleSection: React.FC<MiddleSectionProps> = ({
894
869
  <Accordion type="single" collapsible defaultValue="console" className="flex min-h-0 flex-1 flex-col">
895
870
  <AccordionItem value="console" className="flex min-h-0 flex-1 flex-col">
896
871
  <AccordionTrigger>
897
- <span className="flex flex-1 items-center gap-2">
872
+ <span className="font-display tracking-[var(--type-tracking-display)] [text-transform:var(--type-uppercase)] text-fg-default flex flex-1 items-center gap-2">
898
873
  <TerminalSquare className="h-4 w-4" />
899
874
  Console
900
875
  {consoleOutput && (
901
- <Badge variant="success" className="ml-auto">Output</Badge>
876
+ <Badge variant="success" className="ml-auto normal-case tracking-normal">Output</Badge>
902
877
  )}
903
878
  </span>
904
879
  </AccordionTrigger>
905
880
  <AccordionContent className="flex min-h-0 flex-1 flex-col">
906
- <div className="min-h-0 flex-1 overflow-y-auto rounded-md bg-[#1a1a2e] p-2 font-mono text-sm leading-relaxed">
881
+ <div className="min-h-0 flex-1 overflow-y-auto rounded-md bg-bg-app p-2 font-mono text-sm leading-relaxed">
907
882
  {consoleOutput ? (
908
883
  <pre
909
- className="m-0 whitespace-pre-wrap break-words"
910
- style={{ color: consoleOutput.startsWith('Error') ? theme.dracula.red : theme.dracula.green }}
884
+ className={cn(
885
+ 'm-0 whitespace-pre-wrap break-words',
886
+ consoleOutput.startsWith('Error') ? 'text-destructive' : 'text-success',
887
+ )}
911
888
  >
912
889
  {consoleOutput}
913
890
  </pre>
@@ -33,7 +33,7 @@ import {
33
33
  SelectTrigger,
34
34
  SelectValue,
35
35
  } from '@/components/ui/select';
36
- import { Button } from '@/components/ui/button';
36
+ import { ActionButton } from '@/components/ui/action-button';
37
37
  import { Checkbox } from '@/components/ui/checkbox';
38
38
 
39
39
  import { resolveNodeDescription } from '../../lib/nodeSpec';
@@ -122,10 +122,12 @@ function formValuesToConfig(values: ToolSchemaFormValues): ToolSchemaConfig {
122
122
  // Editor
123
123
  // ---------------------------------------------------------------------------
124
124
 
125
- const ToolSchemaEditor: React.FC<ToolSchemaEditorProps> = ({ nodeId }) => {
126
- const { getToolSchema, saveToolSchema, deleteToolSchema, isLoading } = useToolSchema();
127
- const { isConnected } = useWebSocket();
128
- const { currentWorkflow } = useAppStore();
125
+ // Outer component is the gate: it only calls the hooks needed to decide
126
+ // whether the editor should render at all. The full hook surface (form,
127
+ // effects, field array) lives in <ToolSchemaEditorBody/> so React's
128
+ // rules-of-hooks invariants hold whether the gate is open or closed.
129
+ const ToolSchemaEditor: React.FC<ToolSchemaEditorProps> = ({ nodeId, toolName, toolDescription }) => {
130
+ const currentWorkflow = useAppStore((s) => s.currentWorkflow);
129
131
 
130
132
  const currentNode = useMemo(() => {
131
133
  if (!currentWorkflow?.nodes) return null;
@@ -139,6 +141,14 @@ const ToolSchemaEditor: React.FC<ToolSchemaEditorProps> = ({ nodeId }) => {
139
141
  const isAndroidTool = (currentNodeDef?.uiHints as any)?.isAndroidToolkit === true;
140
142
  if (!isAndroidTool) return null;
141
143
 
144
+ return <ToolSchemaEditorBody nodeId={nodeId} toolName={toolName} toolDescription={toolDescription} />;
145
+ };
146
+
147
+ const ToolSchemaEditorBody: React.FC<ToolSchemaEditorProps> = ({ nodeId }) => {
148
+ const { getToolSchema, saveToolSchema, deleteToolSchema, isLoading } = useToolSchema();
149
+ const { isConnected } = useWebSocket();
150
+ const currentWorkflow = useAppStore((s) => s.currentWorkflow);
151
+
142
152
  const connectedServices = useMemo(() => {
143
153
  if (!currentWorkflow?.edges || !currentWorkflow?.nodes) return [];
144
154
  const incomingEdges = currentWorkflow.edges.filter((edge) => edge.target === nodeId);
@@ -244,7 +254,7 @@ const ToolSchemaEditor: React.FC<ToolSchemaEditorProps> = ({ nodeId }) => {
244
254
  !isExpanded && '-rotate-90'
245
255
  )}
246
256
  />
247
- <span className="font-semibold text-foreground">Connected Services</span>
257
+ <span className="font-display tracking-[var(--type-tracking-display)] [text-transform:var(--type-uppercase)] text-sm font-semibold text-fg-default">Connected Services</span>
248
258
  </div>
249
259
  <span className="text-sm text-muted-foreground">
250
260
  {connectedServices.length} service(s)
@@ -279,18 +289,16 @@ const ToolSchemaEditor: React.FC<ToolSchemaEditorProps> = ({ nodeId }) => {
279
289
  )}
280
290
  </div>
281
291
  ) : (
282
- <div className="mb-3 rounded border border-dracula-orange/30 bg-dracula-orange/10 p-2 text-sm text-dracula-orange">
292
+ <div className="mb-3 rounded border border-warning/30 bg-warning/10 p-2 text-sm text-warning">
283
293
  Connect Android nodes to the input handle
284
294
  </div>
285
295
  )}
286
296
 
287
297
  <div className="mb-2 flex items-center justify-between">
288
- <label className="text-sm text-muted-foreground">Schema Fields</label>
289
- <Button
290
- type="button"
291
- variant="outline"
292
- size="sm"
293
- className="h-7 border-dracula-cyan/40 bg-dracula-cyan/15 text-dracula-cyan hover:bg-dracula-cyan/25"
298
+ <label className="font-display tracking-[var(--type-tracking-display)] [text-transform:var(--type-uppercase)] text-sm text-fg-muted">Schema Fields</label>
299
+ <ActionButton
300
+ intent="tools"
301
+ className="h-7"
294
302
  onClick={() =>
295
303
  append({
296
304
  name: `field_${fields.length + 1}`,
@@ -301,7 +309,7 @@ const ToolSchemaEditor: React.FC<ToolSchemaEditorProps> = ({ nodeId }) => {
301
309
  }
302
310
  >
303
311
  + Add
304
- </Button>
312
+ </ActionButton>
305
313
  </div>
306
314
 
307
315
  <div className="flex flex-col gap-1">
@@ -312,17 +320,16 @@ const ToolSchemaEditor: React.FC<ToolSchemaEditorProps> = ({ nodeId }) => {
312
320
 
313
321
  {hasChanges && (
314
322
  <div className="mt-3 flex justify-end gap-2">
315
- <Button type="button" variant="outline" size="sm" onClick={handleReset}>
323
+ <ActionButton intent="config" type="button" onClick={handleReset}>
316
324
  Reset
317
- </Button>
318
- <Button
325
+ </ActionButton>
326
+ <ActionButton
327
+ intent="save"
319
328
  type="submit"
320
- size="sm"
321
329
  disabled={isLoading || saveStatus === 'saving'}
322
- className="border-dracula-green/40 bg-dracula-green/15 text-dracula-green hover:bg-dracula-green/25"
323
330
  >
324
331
  {saveStatus === 'saving' ? 'Saving...' : saveStatus === 'saved' ? 'Saved!' : 'Save'}
325
- </Button>
332
+ </ActionButton>
326
333
  </div>
327
334
  )}
328
335
  </form>
@@ -391,14 +398,13 @@ const FieldRow: React.FC<{ index: number; onRemove: () => void }> = ({ index, on
391
398
  </FormItem>
392
399
  )}
393
400
  />
394
- <Button
395
- type="button"
396
- size="sm"
401
+ <ActionButton
402
+ intent="stop"
397
403
  onClick={onRemove}
398
- className="h-7 border-none bg-dracula-red/15 px-2 text-xs text-dracula-red hover:bg-dracula-red/25"
404
+ className="h-7 px-2 text-xs"
399
405
  >
400
406
  X
401
- </Button>
407
+ </ActionButton>
402
408
  </div>
403
409
  <FormField
404
410
  control={control}
@@ -11,13 +11,18 @@
11
11
  */
12
12
 
13
13
  import { describe, it, expect, vi, beforeEach } from 'vitest';
14
- import { render, screen, waitFor } from '@testing-library/react';
14
+ import { screen, waitFor } from '@testing-library/react';
15
+ import { renderWithProviders as render } from '../../../test/providers';
15
16
 
16
17
  // --- Mocks -----------------------------------------------------------------
17
18
 
18
19
  const storeState: { currentWorkflow: any } = { currentWorkflow: null };
20
+ // Threads the slice selector so `useAppStore((s) => s.currentWorkflow)`
21
+ // returns the right slice. A no-arg `useAppStore()` returns the whole
22
+ // state for legacy whole-store consumers.
19
23
  vi.mock('../../../store/useAppStore', () => ({
20
- useAppStore: () => storeState,
24
+ useAppStore: <T,>(selector?: (state: typeof storeState) => T): T | typeof storeState =>
25
+ selector ? selector(storeState) : storeState,
21
26
  }));
22
27
 
23
28
  const wsMock = {
@@ -51,7 +51,7 @@ const AIResultModal: React.FC<AIResultModalProps> = ({ isOpen, onClose, result }
51
51
  <div className="border-b border-border bg-muted p-4">
52
52
  <div className="mb-2 flex items-start justify-between">
53
53
  <div>
54
- <h3 className="m-0 text-base font-semibold text-foreground">
54
+ <h3 className="m-0 font-display text-base font-semibold tracking-[var(--type-tracking-display)] text-fg-default [text-transform:var(--type-uppercase)]">
55
55
  {result.nodeName} Result
56
56
  </h3>
57
57
  <p className="mt-1 mb-0 text-sm text-muted-foreground">
@@ -23,22 +23,26 @@ const CollapsibleSection: React.FC<CollapsibleSectionProps> = ({
23
23
  const open = !isCollapsed;
24
24
  return (
25
25
  <Collapsible open={open} onOpenChange={() => onToggle()}>
26
- <div className="overflow-hidden rounded-lg border border-border bg-background">
27
- <CollapsibleTrigger className="flex w-full cursor-pointer items-center justify-between gap-2 border-none bg-muted px-4 py-3 text-base text-foreground transition-colors hover:bg-card">
26
+ {/* bg-bg-app + border-default for the outer card; bg-bg-elevated
27
+ on the trigger so the head sits above the body matches the
28
+ handoff `.cat` / `.cat-head` two-tier surface. `cat` /
29
+ `cat-head` co-classes activate per-theme decorations. */}
30
+ <div className={cn('cat overflow-hidden rounded-lg border border-border-default bg-bg-app', isCollapsed && 'collapsed')}>
31
+ <CollapsibleTrigger className="cat-head flex w-full cursor-pointer items-center justify-between gap-2 border-none bg-bg-elevated px-4 py-3 text-base text-fg-default transition-colors hover:bg-bg-hover">
28
32
  {typeof title === 'string' ? (
29
- <span className="font-medium">{title}</span>
33
+ <span className="font-display font-medium">{title}</span>
30
34
  ) : (
31
35
  <div className="flex flex-1 items-center">{title}</div>
32
36
  )}
33
37
  <ChevronDown
34
38
  className={cn(
35
- 'h-3 w-3 shrink-0 text-muted-foreground transition-transform',
39
+ 'h-3 w-3 shrink-0 text-fg-muted transition-transform',
36
40
  isCollapsed && '-rotate-90'
37
41
  )}
38
42
  />
39
43
  </CollapsibleTrigger>
40
44
 
41
- <CollapsibleContent className={cn('transition-[padding]', open && 'p-3')}>
45
+ <CollapsibleContent className={cn('cat-body transition-[padding]', open && 'p-3')}>
42
46
  {children}
43
47
  </CollapsibleContent>
44
48
  </div>
@@ -0,0 +1,147 @@
1
+ /**
2
+ * CommandPalette — global ⌘K (Ctrl+K) command launcher.
3
+ *
4
+ * Lightweight shell action surface inspired by the design handoff's
5
+ * `.cmdk` panel. Keeps a small registered command set in state; the
6
+ * Dashboard wires the actual handlers (`onOpenSettings`, etc.) via the
7
+ * `commands` prop. Composes on top of the cmdk library that ships with
8
+ * the codebase (CredentialsPalette uses the same dependency).
9
+ *
10
+ * Token-driven: chrome reads bg-bg-elevated + border-border-strong;
11
+ * active row reads bg-bg-active + text-accent. Under Renaissance the
12
+ * panel becomes an "open scroll" via the per-theme background image
13
+ * (declared in renaissance.css `.cmdk` block — wired through here by
14
+ * applying the `cmdk` class on the outer wrapper). Under Cyber it
15
+ * becomes a "root terminal" with neon-cyan border + scanlines.
16
+ */
17
+
18
+ import * as React from 'react';
19
+ import { useEffect } from 'react';
20
+ import { Command } from 'cmdk';
21
+ import { Search } from 'lucide-react';
22
+ import { Dialog, DialogPortal, DialogOverlay, DialogTitle, DialogDescription } from '@/components/ui/dialog';
23
+ import { Dialog as DialogPrimitive } from 'radix-ui';
24
+ import { cn } from '@/lib/utils';
25
+
26
+ export interface CommandItem {
27
+ id: string;
28
+ label: string;
29
+ hint?: string;
30
+ shortcut?: string;
31
+ /** lucide-react icon component */
32
+ icon?: React.ComponentType<{ className?: string }>;
33
+ /** Group label used to bucket items in the list. */
34
+ group?: string;
35
+ /** Invoked when the user presses Enter / clicks the row. */
36
+ onRun: () => void;
37
+ }
38
+
39
+ interface CommandPaletteProps {
40
+ open: boolean;
41
+ onOpenChange: (open: boolean) => void;
42
+ commands: CommandItem[];
43
+ }
44
+
45
+ export const CommandPalette: React.FC<CommandPaletteProps> = ({ open, onOpenChange, commands }) => {
46
+ // ⌘K / Ctrl+K toggles the palette globally.
47
+ useEffect(() => {
48
+ const handler = (e: KeyboardEvent) => {
49
+ if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
50
+ e.preventDefault();
51
+ onOpenChange(!open);
52
+ } else if (e.key === 'Escape' && open) {
53
+ onOpenChange(false);
54
+ }
55
+ };
56
+ window.addEventListener('keydown', handler);
57
+ return () => window.removeEventListener('keydown', handler);
58
+ }, [open, onOpenChange]);
59
+
60
+ // Bucket commands by group so the list renders Command.Group sections.
61
+ const groups = React.useMemo(() => {
62
+ const buckets: Record<string, CommandItem[]> = {};
63
+ for (const cmd of commands) {
64
+ const key = cmd.group ?? 'Actions';
65
+ (buckets[key] ??= []).push(cmd);
66
+ }
67
+ return buckets;
68
+ }, [commands]);
69
+
70
+ return (
71
+ <Dialog open={open} onOpenChange={onOpenChange}>
72
+ <DialogPortal>
73
+ <DialogOverlay className="bg-bg-overlay" />
74
+ <DialogPrimitive.Content
75
+ data-slot="command-palette"
76
+ className={cn(
77
+ // `cmdk` class is the per-theme decorative hook (renaissance
78
+ // and cyber CSS apply backgrounds + borders + scanlines via
79
+ // `:root[data-theme="..."] .cmdk`).
80
+ 'cmdk fixed left-1/2 top-[14%] z-50 w-[min(560px,92vw)] -translate-x-1/2 overflow-hidden rounded-md border border-border-strong bg-bg-elevated shadow-2xl outline-none',
81
+ 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 duration-100',
82
+ )}
83
+ >
84
+ <DialogTitle className="sr-only">Command palette</DialogTitle>
85
+ <DialogDescription className="sr-only">Run a command from the keyboard</DialogDescription>
86
+
87
+ <Command label="Command palette" className="flex flex-col" shouldFilter>
88
+ <div className="relative border-b border-border-default px-4">
89
+ <Search className="pointer-events-none absolute left-4 top-1/2 h-4 w-4 -translate-y-1/2 text-fg-muted" />
90
+ <Command.Input
91
+ placeholder="Type a command or search..."
92
+ autoFocus
93
+ className="h-12 w-full bg-transparent pl-7 font-display text-[15px] tracking-[var(--type-tracking-display)] text-fg-default placeholder:text-fg-faint focus:outline-none [text-transform:var(--type-uppercase)]"
94
+ />
95
+ </div>
96
+
97
+ <Command.List className="max-h-[360px] overflow-y-auto p-1.5">
98
+ <Command.Empty className="px-3 py-6 text-center text-sm text-fg-muted">
99
+ No matching commands
100
+ </Command.Empty>
101
+
102
+ {Object.entries(groups).map(([groupName, items]) => (
103
+ <Command.Group
104
+ key={groupName}
105
+ heading={groupName}
106
+ className="px-1 py-1 text-[10px] font-semibold uppercase tracking-wider text-fg-faint"
107
+ >
108
+ {items.map((cmd) => {
109
+ const Icon = cmd.icon;
110
+ return (
111
+ <Command.Item
112
+ key={cmd.id}
113
+ value={`${cmd.label} ${cmd.hint ?? ''}`}
114
+ onSelect={() => {
115
+ onOpenChange(false);
116
+ // Defer the action one frame so the dialog
117
+ // closes cleanly before the handler fires
118
+ // (avoids focus-trap clashes when the action
119
+ // opens another modal).
120
+ requestAnimationFrame(() => cmd.onRun());
121
+ }}
122
+ className="flex cursor-pointer items-center gap-3 rounded-sm px-3 py-2 text-sm text-fg-default data-[selected=true]:bg-bg-active data-[selected=true]:text-accent"
123
+ >
124
+ {Icon && <Icon className="h-4 w-4 shrink-0" />}
125
+ <span className="flex-1 truncate font-display">{cmd.label}</span>
126
+ {cmd.hint && (
127
+ <span className="font-mono text-[11px] text-fg-faint">{cmd.hint}</span>
128
+ )}
129
+ {cmd.shortcut && (
130
+ <kbd className="ml-auto rounded-sm border border-border-default bg-bg-app px-1.5 py-0.5 font-mono text-[10px] text-fg-muted">
131
+ {cmd.shortcut}
132
+ </kbd>
133
+ )}
134
+ </Command.Item>
135
+ );
136
+ })}
137
+ </Command.Group>
138
+ ))}
139
+ </Command.List>
140
+ </Command>
141
+ </DialogPrimitive.Content>
142
+ </DialogPortal>
143
+ </Dialog>
144
+ );
145
+ };
146
+
147
+ export default CommandPalette;