machinaos 0.0.1

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 (288) hide show
  1. package/.env.template +71 -0
  2. package/LICENSE +21 -0
  3. package/README.md +87 -0
  4. package/bin/cli.js +159 -0
  5. package/client/.dockerignore +45 -0
  6. package/client/Dockerfile +68 -0
  7. package/client/eslint.config.js +29 -0
  8. package/client/index.html +13 -0
  9. package/client/nginx.conf +66 -0
  10. package/client/package.json +48 -0
  11. package/client/src/App.tsx +27 -0
  12. package/client/src/Dashboard.tsx +1173 -0
  13. package/client/src/ParameterPanel.tsx +301 -0
  14. package/client/src/components/AIAgentNode.tsx +321 -0
  15. package/client/src/components/APIKeyValidator.tsx +118 -0
  16. package/client/src/components/ClaudeChatModelNode.tsx +18 -0
  17. package/client/src/components/ConditionalEdge.tsx +189 -0
  18. package/client/src/components/CredentialsModal.tsx +306 -0
  19. package/client/src/components/EdgeConditionEditor.tsx +443 -0
  20. package/client/src/components/GeminiChatModelNode.tsx +18 -0
  21. package/client/src/components/GenericNode.tsx +357 -0
  22. package/client/src/components/LocationParameterPanel.tsx +154 -0
  23. package/client/src/components/ModelNode.tsx +286 -0
  24. package/client/src/components/OpenAIChatModelNode.tsx +18 -0
  25. package/client/src/components/OutputPanel.tsx +471 -0
  26. package/client/src/components/ParameterRenderer.tsx +1874 -0
  27. package/client/src/components/SkillEditorModal.tsx +417 -0
  28. package/client/src/components/SquareNode.tsx +797 -0
  29. package/client/src/components/StartNode.tsx +250 -0
  30. package/client/src/components/ToolkitNode.tsx +365 -0
  31. package/client/src/components/TriggerNode.tsx +463 -0
  32. package/client/src/components/auth/LoginPage.tsx +247 -0
  33. package/client/src/components/auth/ProtectedRoute.tsx +59 -0
  34. package/client/src/components/base/BaseChatModelNode.tsx +271 -0
  35. package/client/src/components/icons/AIProviderIcons.tsx +50 -0
  36. package/client/src/components/maps/GoogleMapsPicker.tsx +137 -0
  37. package/client/src/components/maps/MapsPreviewPanel.tsx +110 -0
  38. package/client/src/components/maps/index.ts +26 -0
  39. package/client/src/components/parameterPanel/InputSection.tsx +1094 -0
  40. package/client/src/components/parameterPanel/LocationPanelLayout.tsx +65 -0
  41. package/client/src/components/parameterPanel/MapsSection.tsx +92 -0
  42. package/client/src/components/parameterPanel/MiddleSection.tsx +571 -0
  43. package/client/src/components/parameterPanel/OutputSection.tsx +81 -0
  44. package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +82 -0
  45. package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +436 -0
  46. package/client/src/components/parameterPanel/index.ts +42 -0
  47. package/client/src/components/shared/DataPanel.tsx +142 -0
  48. package/client/src/components/shared/JSONTreeRenderer.tsx +106 -0
  49. package/client/src/components/ui/AIResultModal.tsx +204 -0
  50. package/client/src/components/ui/AndroidSettingsPanel.tsx +401 -0
  51. package/client/src/components/ui/CodeEditor.tsx +81 -0
  52. package/client/src/components/ui/CollapsibleSection.tsx +88 -0
  53. package/client/src/components/ui/ComponentItem.tsx +154 -0
  54. package/client/src/components/ui/ComponentPalette.tsx +321 -0
  55. package/client/src/components/ui/ConsolePanel.tsx +1074 -0
  56. package/client/src/components/ui/ErrorBoundary.tsx +196 -0
  57. package/client/src/components/ui/InputNodesPanel.tsx +204 -0
  58. package/client/src/components/ui/MapSelector.tsx +314 -0
  59. package/client/src/components/ui/Modal.tsx +149 -0
  60. package/client/src/components/ui/NodeContextMenu.tsx +192 -0
  61. package/client/src/components/ui/NodeOutputPanel.tsx +1150 -0
  62. package/client/src/components/ui/OutputDisplayPanel.tsx +381 -0
  63. package/client/src/components/ui/SettingsPanel.tsx +243 -0
  64. package/client/src/components/ui/TopToolbar.tsx +736 -0
  65. package/client/src/components/ui/WhatsAppSettingsPanel.tsx +345 -0
  66. package/client/src/components/ui/WorkflowSidebar.tsx +294 -0
  67. package/client/src/config/antdTheme.ts +186 -0
  68. package/client/src/config/api.ts +54 -0
  69. package/client/src/contexts/AuthContext.tsx +221 -0
  70. package/client/src/contexts/ThemeContext.tsx +42 -0
  71. package/client/src/contexts/WebSocketContext.tsx +1971 -0
  72. package/client/src/factories/baseChatModelFactory.ts +256 -0
  73. package/client/src/hooks/useAndroidOperations.ts +164 -0
  74. package/client/src/hooks/useApiKeyValidation.ts +107 -0
  75. package/client/src/hooks/useApiKeys.ts +238 -0
  76. package/client/src/hooks/useAppTheme.ts +17 -0
  77. package/client/src/hooks/useComponentPalette.ts +51 -0
  78. package/client/src/hooks/useCopyPaste.ts +155 -0
  79. package/client/src/hooks/useDragAndDrop.ts +124 -0
  80. package/client/src/hooks/useDragVariable.ts +88 -0
  81. package/client/src/hooks/useExecution.ts +313 -0
  82. package/client/src/hooks/useParameterPanel.ts +176 -0
  83. package/client/src/hooks/useReactFlowNodes.ts +189 -0
  84. package/client/src/hooks/useToolSchema.ts +209 -0
  85. package/client/src/hooks/useWhatsApp.ts +196 -0
  86. package/client/src/hooks/useWorkflowManagement.ts +46 -0
  87. package/client/src/index.css +315 -0
  88. package/client/src/main.tsx +19 -0
  89. package/client/src/nodeDefinitions/aiAgentNodes.ts +336 -0
  90. package/client/src/nodeDefinitions/aiModelNodes.ts +340 -0
  91. package/client/src/nodeDefinitions/androidDeviceNodes.ts +140 -0
  92. package/client/src/nodeDefinitions/androidServiceNodes.ts +383 -0
  93. package/client/src/nodeDefinitions/chatNodes.ts +135 -0
  94. package/client/src/nodeDefinitions/codeNodes.ts +54 -0
  95. package/client/src/nodeDefinitions/documentNodes.ts +379 -0
  96. package/client/src/nodeDefinitions/index.ts +15 -0
  97. package/client/src/nodeDefinitions/locationNodes.ts +463 -0
  98. package/client/src/nodeDefinitions/schedulerNodes.ts +220 -0
  99. package/client/src/nodeDefinitions/skillNodes.ts +211 -0
  100. package/client/src/nodeDefinitions/toolNodes.ts +198 -0
  101. package/client/src/nodeDefinitions/utilityNodes.ts +284 -0
  102. package/client/src/nodeDefinitions/whatsappNodes.ts +865 -0
  103. package/client/src/nodeDefinitions/workflowNodes.ts +41 -0
  104. package/client/src/nodeDefinitions.ts +104 -0
  105. package/client/src/schemas/workflowSchema.ts +264 -0
  106. package/client/src/services/dynamicParameterService.ts +96 -0
  107. package/client/src/services/execution/aiAgentExecutionService.ts +35 -0
  108. package/client/src/services/executionService.ts +232 -0
  109. package/client/src/services/workflowApi.ts +91 -0
  110. package/client/src/store/useAppStore.ts +582 -0
  111. package/client/src/styles/theme.ts +508 -0
  112. package/client/src/styles/zIndex.ts +17 -0
  113. package/client/src/types/ComponentTypes.ts +39 -0
  114. package/client/src/types/EdgeCondition.ts +231 -0
  115. package/client/src/types/INodeProperties.ts +288 -0
  116. package/client/src/types/NodeTypes.ts +28 -0
  117. package/client/src/utils/formatters.ts +33 -0
  118. package/client/src/utils/googleMapsLoader.ts +140 -0
  119. package/client/src/utils/locationUtils.ts +85 -0
  120. package/client/src/utils/nodeUtils.ts +31 -0
  121. package/client/src/utils/workflow.ts +30 -0
  122. package/client/src/utils/workflowExport.ts +120 -0
  123. package/client/src/vite-env.d.ts +12 -0
  124. package/client/tailwind.config.js +60 -0
  125. package/client/tsconfig.json +25 -0
  126. package/client/tsconfig.node.json +11 -0
  127. package/client/vite.config.js +35 -0
  128. package/docker-compose.prod.yml +107 -0
  129. package/docker-compose.yml +104 -0
  130. package/docs-MachinaOs/README.md +85 -0
  131. package/docs-MachinaOs/deployment/docker.mdx +228 -0
  132. package/docs-MachinaOs/deployment/production.mdx +345 -0
  133. package/docs-MachinaOs/docs.json +75 -0
  134. package/docs-MachinaOs/faq.mdx +309 -0
  135. package/docs-MachinaOs/favicon.svg +5 -0
  136. package/docs-MachinaOs/installation.mdx +160 -0
  137. package/docs-MachinaOs/introduction.mdx +114 -0
  138. package/docs-MachinaOs/logo/dark.svg +6 -0
  139. package/docs-MachinaOs/logo/light.svg +6 -0
  140. package/docs-MachinaOs/nodes/ai-agent.mdx +216 -0
  141. package/docs-MachinaOs/nodes/ai-models.mdx +240 -0
  142. package/docs-MachinaOs/nodes/android.mdx +411 -0
  143. package/docs-MachinaOs/nodes/overview.mdx +181 -0
  144. package/docs-MachinaOs/nodes/schedulers.mdx +316 -0
  145. package/docs-MachinaOs/nodes/webhooks.mdx +330 -0
  146. package/docs-MachinaOs/nodes/whatsapp.mdx +305 -0
  147. package/docs-MachinaOs/quickstart.mdx +119 -0
  148. package/docs-MachinaOs/tutorials/ai-agent-workflow.mdx +177 -0
  149. package/docs-MachinaOs/tutorials/android-automation.mdx +242 -0
  150. package/docs-MachinaOs/tutorials/first-workflow.mdx +134 -0
  151. package/docs-MachinaOs/tutorials/whatsapp-automation.mdx +185 -0
  152. package/nul +0 -0
  153. package/package.json +70 -0
  154. package/scripts/build.js +158 -0
  155. package/scripts/check-ports.ps1 +33 -0
  156. package/scripts/clean.js +40 -0
  157. package/scripts/docker.js +93 -0
  158. package/scripts/kill-port.ps1 +154 -0
  159. package/scripts/start.js +210 -0
  160. package/scripts/stop.js +325 -0
  161. package/server/.dockerignore +44 -0
  162. package/server/Dockerfile +45 -0
  163. package/server/constants.py +249 -0
  164. package/server/core/__init__.py +1 -0
  165. package/server/core/cache.py +461 -0
  166. package/server/core/config.py +128 -0
  167. package/server/core/container.py +99 -0
  168. package/server/core/database.py +1211 -0
  169. package/server/core/logging.py +314 -0
  170. package/server/main.py +289 -0
  171. package/server/middleware/__init__.py +5 -0
  172. package/server/middleware/auth.py +89 -0
  173. package/server/models/__init__.py +1 -0
  174. package/server/models/auth.py +52 -0
  175. package/server/models/cache.py +24 -0
  176. package/server/models/database.py +211 -0
  177. package/server/models/nodes.py +455 -0
  178. package/server/package.json +9 -0
  179. package/server/pyproject.toml +72 -0
  180. package/server/requirements.txt +83 -0
  181. package/server/routers/__init__.py +1 -0
  182. package/server/routers/android.py +294 -0
  183. package/server/routers/auth.py +203 -0
  184. package/server/routers/database.py +151 -0
  185. package/server/routers/maps.py +142 -0
  186. package/server/routers/nodejs_compat.py +289 -0
  187. package/server/routers/webhook.py +90 -0
  188. package/server/routers/websocket.py +2127 -0
  189. package/server/routers/whatsapp.py +761 -0
  190. package/server/routers/workflow.py +200 -0
  191. package/server/services/__init__.py +1 -0
  192. package/server/services/ai.py +2415 -0
  193. package/server/services/android/__init__.py +27 -0
  194. package/server/services/android/broadcaster.py +114 -0
  195. package/server/services/android/client.py +608 -0
  196. package/server/services/android/manager.py +78 -0
  197. package/server/services/android/protocol.py +165 -0
  198. package/server/services/android_service.py +588 -0
  199. package/server/services/auth.py +131 -0
  200. package/server/services/chat_client.py +160 -0
  201. package/server/services/deployment/__init__.py +12 -0
  202. package/server/services/deployment/manager.py +706 -0
  203. package/server/services/deployment/state.py +47 -0
  204. package/server/services/deployment/triggers.py +275 -0
  205. package/server/services/event_waiter.py +785 -0
  206. package/server/services/execution/__init__.py +77 -0
  207. package/server/services/execution/cache.py +769 -0
  208. package/server/services/execution/conditions.py +373 -0
  209. package/server/services/execution/dlq.py +132 -0
  210. package/server/services/execution/executor.py +1351 -0
  211. package/server/services/execution/models.py +531 -0
  212. package/server/services/execution/recovery.py +235 -0
  213. package/server/services/handlers/__init__.py +126 -0
  214. package/server/services/handlers/ai.py +355 -0
  215. package/server/services/handlers/android.py +260 -0
  216. package/server/services/handlers/code.py +278 -0
  217. package/server/services/handlers/document.py +598 -0
  218. package/server/services/handlers/http.py +193 -0
  219. package/server/services/handlers/polyglot.py +105 -0
  220. package/server/services/handlers/tools.py +845 -0
  221. package/server/services/handlers/triggers.py +107 -0
  222. package/server/services/handlers/utility.py +822 -0
  223. package/server/services/handlers/whatsapp.py +476 -0
  224. package/server/services/maps.py +289 -0
  225. package/server/services/memory_store.py +103 -0
  226. package/server/services/node_executor.py +375 -0
  227. package/server/services/parameter_resolver.py +218 -0
  228. package/server/services/polyglot_client.py +169 -0
  229. package/server/services/scheduler.py +155 -0
  230. package/server/services/skill_loader.py +417 -0
  231. package/server/services/status_broadcaster.py +826 -0
  232. package/server/services/temporal/__init__.py +23 -0
  233. package/server/services/temporal/activities.py +344 -0
  234. package/server/services/temporal/client.py +76 -0
  235. package/server/services/temporal/executor.py +147 -0
  236. package/server/services/temporal/worker.py +251 -0
  237. package/server/services/temporal/workflow.py +355 -0
  238. package/server/services/temporal/ws_client.py +236 -0
  239. package/server/services/text.py +111 -0
  240. package/server/services/user_auth.py +172 -0
  241. package/server/services/websocket_client.py +29 -0
  242. package/server/services/workflow.py +597 -0
  243. package/server/skills/android-skill/SKILL.md +82 -0
  244. package/server/skills/assistant-personality/SKILL.md +45 -0
  245. package/server/skills/code-skill/SKILL.md +140 -0
  246. package/server/skills/http-skill/SKILL.md +161 -0
  247. package/server/skills/maps-skill/SKILL.md +170 -0
  248. package/server/skills/memory-skill/SKILL.md +154 -0
  249. package/server/skills/scheduler-skill/SKILL.md +84 -0
  250. package/server/skills/whatsapp-skill/SKILL.md +283 -0
  251. package/server/uv.lock +2916 -0
  252. package/server/whatsapp-rpc/.dockerignore +30 -0
  253. package/server/whatsapp-rpc/Dockerfile +44 -0
  254. package/server/whatsapp-rpc/Dockerfile.web +17 -0
  255. package/server/whatsapp-rpc/README.md +139 -0
  256. package/server/whatsapp-rpc/cli.js +95 -0
  257. package/server/whatsapp-rpc/configs/config.yaml +7 -0
  258. package/server/whatsapp-rpc/docker-compose.yml +35 -0
  259. package/server/whatsapp-rpc/docs/API.md +410 -0
  260. package/server/whatsapp-rpc/go.mod +67 -0
  261. package/server/whatsapp-rpc/go.sum +203 -0
  262. package/server/whatsapp-rpc/package.json +30 -0
  263. package/server/whatsapp-rpc/schema.json +1294 -0
  264. package/server/whatsapp-rpc/scripts/clean.cjs +66 -0
  265. package/server/whatsapp-rpc/scripts/cli.js +162 -0
  266. package/server/whatsapp-rpc/src/go/cmd/server/main.go +91 -0
  267. package/server/whatsapp-rpc/src/go/config/config.go +49 -0
  268. package/server/whatsapp-rpc/src/go/rpc/rpc.go +446 -0
  269. package/server/whatsapp-rpc/src/go/rpc/server.go +112 -0
  270. package/server/whatsapp-rpc/src/go/whatsapp/history.go +166 -0
  271. package/server/whatsapp-rpc/src/go/whatsapp/messages.go +390 -0
  272. package/server/whatsapp-rpc/src/go/whatsapp/service.go +2130 -0
  273. package/server/whatsapp-rpc/src/go/whatsapp/types.go +261 -0
  274. package/server/whatsapp-rpc/src/python/pyproject.toml +15 -0
  275. package/server/whatsapp-rpc/src/python/whatsapp_rpc/__init__.py +4 -0
  276. package/server/whatsapp-rpc/src/python/whatsapp_rpc/client.py +427 -0
  277. package/server/whatsapp-rpc/web/app.py +609 -0
  278. package/server/whatsapp-rpc/web/requirements.txt +6 -0
  279. package/server/whatsapp-rpc/web/rpc_client.py +427 -0
  280. package/server/whatsapp-rpc/web/static/openapi.yaml +59 -0
  281. package/server/whatsapp-rpc/web/templates/base.html +150 -0
  282. package/server/whatsapp-rpc/web/templates/contacts.html +240 -0
  283. package/server/whatsapp-rpc/web/templates/dashboard.html +320 -0
  284. package/server/whatsapp-rpc/web/templates/groups.html +328 -0
  285. package/server/whatsapp-rpc/web/templates/messages.html +465 -0
  286. package/server/whatsapp-rpc/web/templates/messaging.html +681 -0
  287. package/server/whatsapp-rpc/web/templates/send.html +259 -0
  288. package/server/whatsapp-rpc/web/templates/settings.html +459 -0
@@ -0,0 +1,294 @@
1
+ """Android System Services routes."""
2
+
3
+ from fastapi import APIRouter, Depends
4
+ from pydantic import BaseModel, Field
5
+ from typing import Dict, Any, Optional
6
+
7
+ from core.container import container
8
+ from services.android_service import AndroidService
9
+ from core.logging import get_logger
10
+
11
+ logger = get_logger(__name__)
12
+ router = APIRouter(prefix="/api/android", tags=["android"])
13
+
14
+
15
+ class AndroidServiceRequest(BaseModel):
16
+ """Request model for Android service execution."""
17
+ service_id: str = Field(..., description="Android service ID (e.g., 'battery', 'network', 'app_launcher')")
18
+ action: str = Field(..., description="Service action to perform (e.g., 'status', 'launch', 'list')")
19
+ parameters: Dict[str, Any] = Field(default_factory=dict, description="Action-specific parameters")
20
+ android_host: str = Field(default="localhost", description="Android device API host")
21
+ android_port: int = Field(default=8888, description="Android device API port")
22
+
23
+
24
+ @router.post("/execute")
25
+ async def execute_android_service(
26
+ request: AndroidServiceRequest,
27
+ android_service: AndroidService = Depends(lambda: container.android_service())
28
+ ):
29
+ """Execute an Android system service action.
30
+
31
+ Examples:
32
+ - Battery status: {"service_id": "battery", "action": "status"}
33
+ - Launch app: {"service_id": "app_launcher", "action": "launch", "parameters": {"package_name": "com.android.settings"}}
34
+ - List apps: {"service_id": "app_list", "action": "list", "parameters": {"include_system": false}}
35
+ """
36
+ logger.info(
37
+ "[Android API] Executing service",
38
+ service_id=request.service_id,
39
+ action=request.action,
40
+ android_host=request.android_host,
41
+ android_port=request.android_port
42
+ )
43
+
44
+ result = await android_service.execute_service(
45
+ node_id="api_call",
46
+ service_id=request.service_id,
47
+ action=request.action,
48
+ parameters=request.parameters,
49
+ android_host=request.android_host,
50
+ android_port=request.android_port
51
+ )
52
+
53
+ return result
54
+
55
+
56
+ @router.get("/status")
57
+ async def check_device_status(
58
+ android_host: str = "localhost",
59
+ android_port: int = 8888,
60
+ android_service: AndroidService = Depends(lambda: container.android_service())
61
+ ):
62
+ """Check if Android device API is reachable."""
63
+ logger.info(
64
+ "[Android API] Checking device status",
65
+ android_host=android_host,
66
+ android_port=android_port
67
+ )
68
+
69
+ status = await android_service.check_device_status(
70
+ android_host=android_host,
71
+ android_port=android_port
72
+ )
73
+
74
+ return status
75
+
76
+
77
+ @router.get("/services/{service_id}/actions")
78
+ async def get_service_actions(
79
+ service_id: str,
80
+ android_service: AndroidService = Depends(lambda: container.android_service())
81
+ ):
82
+ """Get available actions for a specific Android service.
83
+
84
+ Returns list of action options that can be performed on the service.
85
+ """
86
+ logger.info(f"[Android API] Getting actions for service: {service_id}")
87
+
88
+ actions = android_service.get_service_actions(service_id)
89
+
90
+ if not actions:
91
+ return {
92
+ "success": False,
93
+ "error": f"Unknown service: {service_id}",
94
+ "actions": []
95
+ }
96
+
97
+ return {
98
+ "success": True,
99
+ "service_id": service_id,
100
+ "actions": actions
101
+ }
102
+
103
+
104
+ @router.get("/services/{service_id}/actions/{action}/parameters")
105
+ async def get_action_parameters(
106
+ service_id: str,
107
+ action: str,
108
+ android_service: AndroidService = Depends(lambda: container.android_service())
109
+ ):
110
+ """Get default parameters for a specific service action.
111
+
112
+ Returns default parameter template that can be used as a starting point.
113
+ """
114
+ logger.info(f"[Android API] Getting parameters for: {service_id}/{action}")
115
+
116
+ default_params = android_service.get_default_parameters(service_id, action)
117
+
118
+ return {
119
+ "success": True,
120
+ "service_id": service_id,
121
+ "action": action,
122
+ "default_parameters": default_params
123
+ }
124
+
125
+
126
+ @router.get("/devices")
127
+ async def list_android_devices():
128
+ """List all connected Android devices via ADB."""
129
+ import subprocess
130
+ try:
131
+ # Run adb devices command
132
+ result = subprocess.run(
133
+ ["adb", "devices", "-l"],
134
+ capture_output=True,
135
+ text=True,
136
+ encoding="utf-8",
137
+ errors="replace",
138
+ timeout=5
139
+ )
140
+
141
+ devices = []
142
+ lines = result.stdout.strip().split('\n')[1:] # Skip header "List of devices attached"
143
+
144
+ for line in lines:
145
+ if not line.strip():
146
+ continue
147
+
148
+ parts = line.split()
149
+ if len(parts) >= 2:
150
+ device_id = parts[0]
151
+ state = parts[1]
152
+
153
+ # Extract model and other info
154
+ model = "Unknown"
155
+ android_version = None
156
+
157
+ for part in parts[2:]:
158
+ if part.startswith("model:"):
159
+ model = part.split(":")[1]
160
+ elif part.startswith("device:"):
161
+ pass # Can use this for device codename
162
+
163
+ devices.append({
164
+ "id": device_id,
165
+ "state": state,
166
+ "model": model,
167
+ "android_version": android_version
168
+ })
169
+
170
+ return {
171
+ "success": True,
172
+ "devices": devices,
173
+ "count": len(devices)
174
+ }
175
+ except FileNotFoundError:
176
+ logger.error("[Android] ADB not found in PATH")
177
+ return {
178
+ "success": False,
179
+ "error": "ADB not found. Please install Android SDK Platform Tools",
180
+ "devices": []
181
+ }
182
+ except Exception as e:
183
+ logger.error(f"[Android] Failed to list devices: {e}")
184
+ return {
185
+ "success": False,
186
+ "error": str(e),
187
+ "devices": []
188
+ }
189
+
190
+
191
+ @router.post("/port-forward")
192
+ async def setup_port_forwarding(
193
+ device_id: str,
194
+ local_port: int = 8888,
195
+ device_port: int = 8888
196
+ ):
197
+ """Setup ADB port forwarding for Android device communication."""
198
+ import subprocess
199
+ try:
200
+ # Setup port forwarding: adb -s device_id forward tcp:local_port tcp:device_port
201
+ cmd = ["adb", "-s", device_id, "forward", f"tcp:{local_port}", f"tcp:{device_port}"]
202
+
203
+ result = subprocess.run(
204
+ cmd,
205
+ capture_output=True,
206
+ text=True,
207
+ encoding="utf-8",
208
+ errors="replace",
209
+ timeout=5
210
+ )
211
+
212
+ if result.returncode == 0:
213
+ logger.info(
214
+ f"[Android] Port forwarding setup: {device_id} tcp:{local_port} -> tcp:{device_port}"
215
+ )
216
+ return {
217
+ "success": True,
218
+ "device_id": device_id,
219
+ "local_port": local_port,
220
+ "device_port": device_port,
221
+ "message": f"Port forwarding active: localhost:{local_port} -> device:{device_port}"
222
+ }
223
+ else:
224
+ error_msg = result.stderr.strip() or result.stdout.strip()
225
+ logger.error(f"[Android] Port forwarding failed: {error_msg}")
226
+ return {
227
+ "success": False,
228
+ "error": error_msg
229
+ }
230
+ except FileNotFoundError:
231
+ logger.error("[Android] ADB not found in PATH")
232
+ return {
233
+ "success": False,
234
+ "error": "ADB not found. Please install Android SDK Platform Tools"
235
+ }
236
+ except Exception as e:
237
+ logger.error(f"[Android] Port forwarding error: {e}")
238
+ return {
239
+ "success": False,
240
+ "error": str(e)
241
+ }
242
+
243
+
244
+ @router.get("/health")
245
+ async def android_health_check():
246
+ """Android service health check."""
247
+ return {
248
+ "status": "OK",
249
+ "service": "android"
250
+ }
251
+
252
+
253
+ @router.get("/relay-status")
254
+ async def get_relay_connection_status():
255
+ """Get relay connection status for remote Android devices.
256
+
257
+ Returns connection status including whether a relay connection is active
258
+ and the paired Android device.
259
+ """
260
+ try:
261
+ from services.android import get_current_relay_client
262
+
263
+ relay_client = get_current_relay_client()
264
+
265
+ if relay_client and relay_client.is_connected():
266
+ return {
267
+ "success": True,
268
+ "connected": True,
269
+ "paired": relay_client.is_paired(),
270
+ "connection_type": "relay",
271
+ "device_id": relay_client.paired_device_id,
272
+ "device_name": relay_client.paired_device_name,
273
+ "session_token": relay_client.session_token,
274
+ "qr_data": relay_client.qr_data if not relay_client.is_paired() else None,
275
+ "status": "paired" if relay_client.is_paired() else "waiting_for_pairing"
276
+ }
277
+ else:
278
+ return {
279
+ "success": True,
280
+ "connected": False,
281
+ "paired": False,
282
+ "connection_type": None,
283
+ "device_id": None,
284
+ "device_name": None,
285
+ "status": "disconnected"
286
+ }
287
+ except Exception as e:
288
+ logger.error(f"[Android] Failed to get relay status: {e}")
289
+ return {
290
+ "success": False,
291
+ "connected": False,
292
+ "error": str(e),
293
+ "status": "error"
294
+ }
@@ -0,0 +1,203 @@
1
+ """Authentication routes for user login, registration, and session management."""
2
+
3
+ from fastapi import APIRouter, Depends, HTTPException, Response, Request
4
+ from pydantic import BaseModel, EmailStr
5
+ from typing import Optional
6
+
7
+ from core.container import container
8
+ from core.config import Settings
9
+ from core.logging import get_logger
10
+ from services.user_auth import UserAuthService
11
+
12
+ logger = get_logger(__name__)
13
+ router = APIRouter(prefix="/api/auth", tags=["auth"])
14
+
15
+
16
+ class RegisterRequest(BaseModel):
17
+ email: EmailStr
18
+ password: str
19
+ display_name: str
20
+
21
+
22
+ class LoginRequest(BaseModel):
23
+ email: EmailStr
24
+ password: str
25
+
26
+
27
+ class UserResponse(BaseModel):
28
+ id: int
29
+ email: str
30
+ display_name: str
31
+ is_owner: bool
32
+
33
+
34
+ def get_user_auth_service() -> UserAuthService:
35
+ return container.user_auth_service()
36
+
37
+
38
+ def get_settings() -> Settings:
39
+ return container.settings()
40
+
41
+
42
+ @router.get("/status")
43
+ async def get_auth_status(
44
+ request: Request,
45
+ user_auth: UserAuthService = Depends(get_user_auth_service),
46
+ settings: Settings = Depends(get_settings)
47
+ ):
48
+ """
49
+ Get authentication status.
50
+ Returns auth mode and current user if authenticated.
51
+ """
52
+ status = user_auth.get_auth_status()
53
+
54
+ # Check if user has a valid session
55
+ token = request.cookies.get(settings.jwt_cookie_name)
56
+ current_user = None
57
+
58
+ if token:
59
+ user = await user_auth.get_current_user(token)
60
+ if user:
61
+ current_user = {
62
+ "id": user.id,
63
+ "email": user.email,
64
+ "display_name": user.display_name,
65
+ "is_owner": user.is_owner
66
+ }
67
+
68
+ # Check if registration is available
69
+ can_register = await user_auth.can_register()
70
+
71
+ return {
72
+ "auth_mode": status["auth_mode"],
73
+ "authenticated": current_user is not None,
74
+ "user": current_user,
75
+ "can_register": can_register
76
+ }
77
+
78
+
79
+ @router.post("/register")
80
+ async def register(
81
+ request: RegisterRequest,
82
+ response: Response,
83
+ user_auth: UserAuthService = Depends(get_user_auth_service),
84
+ settings: Settings = Depends(get_settings)
85
+ ):
86
+ """
87
+ Register a new user.
88
+ In single-owner mode, only the first user can register.
89
+ In multi-user mode, anyone can register.
90
+ """
91
+ user, error = await user_auth.register(
92
+ email=request.email,
93
+ password=request.password,
94
+ display_name=request.display_name
95
+ )
96
+
97
+ if error:
98
+ raise HTTPException(status_code=400, detail=error)
99
+
100
+ # Create token and set cookie
101
+ token = user_auth.create_access_token(user)
102
+ response.set_cookie(
103
+ key=settings.jwt_cookie_name,
104
+ value=token,
105
+ httponly=True,
106
+ secure=settings.jwt_cookie_secure,
107
+ samesite=settings.jwt_cookie_samesite,
108
+ max_age=settings.jwt_expire_minutes * 60
109
+ )
110
+
111
+ return {
112
+ "success": True,
113
+ "user": {
114
+ "id": user.id,
115
+ "email": user.email,
116
+ "display_name": user.display_name,
117
+ "is_owner": user.is_owner
118
+ }
119
+ }
120
+
121
+
122
+ @router.post("/login")
123
+ async def login(
124
+ request: LoginRequest,
125
+ response: Response,
126
+ user_auth: UserAuthService = Depends(get_user_auth_service),
127
+ settings: Settings = Depends(get_settings)
128
+ ):
129
+ """
130
+ Login with email and password.
131
+ Sets HttpOnly cookie with JWT token.
132
+ """
133
+ user, error = await user_auth.login(
134
+ email=request.email,
135
+ password=request.password
136
+ )
137
+
138
+ if error:
139
+ raise HTTPException(status_code=401, detail=error)
140
+
141
+ # Create token and set cookie
142
+ token = user_auth.create_access_token(user)
143
+ response.set_cookie(
144
+ key=settings.jwt_cookie_name,
145
+ value=token,
146
+ httponly=True,
147
+ secure=settings.jwt_cookie_secure,
148
+ samesite=settings.jwt_cookie_samesite,
149
+ max_age=settings.jwt_expire_minutes * 60
150
+ )
151
+
152
+ return {
153
+ "success": True,
154
+ "user": {
155
+ "id": user.id,
156
+ "email": user.email,
157
+ "display_name": user.display_name,
158
+ "is_owner": user.is_owner
159
+ }
160
+ }
161
+
162
+
163
+ @router.post("/logout")
164
+ async def logout(
165
+ response: Response,
166
+ settings: Settings = Depends(get_settings)
167
+ ):
168
+ """
169
+ Logout by clearing the auth cookie.
170
+ """
171
+ response.delete_cookie(
172
+ key=settings.jwt_cookie_name,
173
+ httponly=True,
174
+ secure=settings.jwt_cookie_secure,
175
+ samesite=settings.jwt_cookie_samesite
176
+ )
177
+ return {"success": True}
178
+
179
+
180
+ @router.get("/me")
181
+ async def get_current_user(
182
+ request: Request,
183
+ user_auth: UserAuthService = Depends(get_user_auth_service),
184
+ settings: Settings = Depends(get_settings)
185
+ ):
186
+ """
187
+ Get current authenticated user.
188
+ Requires valid session cookie.
189
+ """
190
+ token = request.cookies.get(settings.jwt_cookie_name)
191
+ if not token:
192
+ raise HTTPException(status_code=401, detail="Not authenticated")
193
+
194
+ user = await user_auth.get_current_user(token)
195
+ if not user:
196
+ raise HTTPException(status_code=401, detail="Invalid or expired session")
197
+
198
+ return {
199
+ "id": user.id,
200
+ "email": user.email,
201
+ "display_name": user.display_name,
202
+ "is_owner": user.is_owner
203
+ }
@@ -0,0 +1,151 @@
1
+ """Database operations routes (replaces frontend storage)."""
2
+
3
+ from fastapi import APIRouter, Depends
4
+ from pydantic import BaseModel
5
+ from typing import Dict, Any, Optional, List
6
+
7
+ from core.container import container
8
+ from core.database import Database
9
+ from core.logging import get_logger
10
+
11
+ logger = get_logger(__name__)
12
+ router = APIRouter(prefix="/api/database", tags=["database"])
13
+
14
+
15
+ class NodeParameterRequest(BaseModel):
16
+ node_id: str
17
+ parameters: Dict[str, Any]
18
+
19
+
20
+ class WorkflowSaveRequest(BaseModel):
21
+ workflow_id: str
22
+ name: str
23
+ data: Dict[str, Any]
24
+
25
+
26
+ @router.post("/node-parameters")
27
+ async def save_node_parameters(
28
+ request: NodeParameterRequest,
29
+ database: Database = Depends(lambda: container.database())
30
+ ):
31
+ """Save node parameters (replaces frontend Dexie)."""
32
+ try:
33
+ success = await database.save_node_parameters(request.node_id, request.parameters)
34
+ return {"success": success}
35
+ except Exception as e:
36
+ logger.error("Failed to save node parameters", error=str(e))
37
+ return {"success": False, "error": str(e)}
38
+
39
+
40
+ @router.get("/node-parameters/{node_id}")
41
+ async def get_node_parameters(
42
+ node_id: str,
43
+ database: Database = Depends(lambda: container.database())
44
+ ):
45
+ """Get node parameters (replaces frontend Dexie)."""
46
+ try:
47
+ parameters = await database.get_node_parameters(node_id)
48
+ return {"success": True, "parameters": parameters}
49
+ except Exception as e:
50
+ logger.error("Failed to get node parameters", error=str(e))
51
+ return {"success": False, "error": str(e)}
52
+
53
+
54
+ @router.delete("/node-parameters/{node_id}")
55
+ async def delete_node_parameters(
56
+ node_id: str,
57
+ database: Database = Depends(lambda: container.database())
58
+ ):
59
+ """Delete node parameters (replaces frontend Dexie)."""
60
+ try:
61
+ success = await database.delete_node_parameters(node_id)
62
+ return {"success": success}
63
+ except Exception as e:
64
+ logger.error("Failed to delete node parameters", error=str(e))
65
+ return {"success": False, "error": str(e)}
66
+
67
+
68
+ # ============================================================================
69
+ # Workflow Operations
70
+ # ============================================================================
71
+
72
+ @router.post("/workflows")
73
+ async def save_workflow(
74
+ request: WorkflowSaveRequest,
75
+ database: Database = Depends(lambda: container.database())
76
+ ):
77
+ """Save workflow to database."""
78
+ try:
79
+ success = await database.save_workflow(
80
+ workflow_id=request.workflow_id,
81
+ name=request.name,
82
+ data=request.data
83
+ )
84
+ return {"success": success, "workflow_id": request.workflow_id}
85
+ except Exception as e:
86
+ logger.error("Failed to save workflow", error=str(e))
87
+ return {"success": False, "error": str(e)}
88
+
89
+
90
+ @router.get("/workflows")
91
+ async def get_all_workflows(
92
+ database: Database = Depends(lambda: container.database())
93
+ ):
94
+ """Get all workflows."""
95
+ try:
96
+ workflows = await database.get_all_workflows()
97
+ return {
98
+ "success": True,
99
+ "workflows": [
100
+ {
101
+ "id": w.id,
102
+ "name": w.name,
103
+ "nodeCount": len(w.data.get("nodes", [])) if w.data else 0,
104
+ "createdAt": w.created_at.isoformat() if w.created_at else None,
105
+ "lastModified": w.updated_at.isoformat() if w.updated_at else None
106
+ }
107
+ for w in workflows
108
+ ]
109
+ }
110
+ except Exception as e:
111
+ logger.error("Failed to get workflows", error=str(e))
112
+ return {"success": False, "error": str(e)}
113
+
114
+
115
+ @router.get("/workflows/{workflow_id}")
116
+ async def get_workflow(
117
+ workflow_id: str,
118
+ database: Database = Depends(lambda: container.database())
119
+ ):
120
+ """Get workflow by ID."""
121
+ try:
122
+ workflow = await database.get_workflow(workflow_id)
123
+ if workflow:
124
+ return {
125
+ "success": True,
126
+ "workflow": {
127
+ "id": workflow.id,
128
+ "name": workflow.name,
129
+ "data": workflow.data,
130
+ "createdAt": workflow.created_at.isoformat() if workflow.created_at else None,
131
+ "lastModified": workflow.updated_at.isoformat() if workflow.updated_at else None
132
+ }
133
+ }
134
+ return {"success": False, "error": "Workflow not found"}
135
+ except Exception as e:
136
+ logger.error("Failed to get workflow", error=str(e))
137
+ return {"success": False, "error": str(e)}
138
+
139
+
140
+ @router.delete("/workflows/{workflow_id}")
141
+ async def delete_workflow(
142
+ workflow_id: str,
143
+ database: Database = Depends(lambda: container.database())
144
+ ):
145
+ """Delete workflow."""
146
+ try:
147
+ success = await database.delete_workflow(workflow_id)
148
+ return {"success": success, "workflow_id": workflow_id}
149
+ except Exception as e:
150
+ logger.error("Failed to delete workflow", error=str(e))
151
+ return {"success": False, "error": str(e)}