android-midscene-automation 0.2.4 → 1.1.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 (525) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +159 -29
  3. package/artemis-main/.dockerignore +15 -0
  4. package/artemis-main/.env.example +34 -0
  5. package/artemis-main/CONTRIBUTING.md +35 -0
  6. package/artemis-main/Dockerfile +68 -0
  7. package/artemis-main/LICENSE +202 -0
  8. package/artemis-main/Makefile +115 -0
  9. package/artemis-main/README.md +322 -0
  10. package/artemis-main/README_CN.md +320 -0
  11. package/artemis-main/apps/__init__.py +15 -0
  12. package/artemis-main/apps/admin_console/__init__.py +19 -0
  13. package/artemis-main/apps/admin_console/core/__init__.py +13 -0
  14. package/artemis-main/apps/admin_console/core/config.py +43 -0
  15. package/artemis-main/apps/admin_console/core/security.py +178 -0
  16. package/artemis-main/apps/admin_console/core/state.py +243 -0
  17. package/artemis-main/apps/admin_console/database/__init__.py +13 -0
  18. package/artemis-main/apps/admin_console/database/connection.py +60 -0
  19. package/artemis-main/apps/admin_console/database/repositories/__init__.py +13 -0
  20. package/artemis-main/apps/admin_console/database/repositories/session_repository.py +564 -0
  21. package/artemis-main/apps/admin_console/database/repositories/step_repository.py +512 -0
  22. package/artemis-main/apps/admin_console/database/repositories/trace_repository.py +133 -0
  23. package/artemis-main/apps/admin_console/index.html +3787 -0
  24. package/artemis-main/apps/admin_console/replay_manager.py +2585 -0
  25. package/artemis-main/apps/admin_console/routers/__init__.py +13 -0
  26. package/artemis-main/apps/admin_console/routers/media.py +198 -0
  27. package/artemis-main/apps/admin_console/routers/replay.py +91 -0
  28. package/artemis-main/apps/admin_console/routers/sessions.py +239 -0
  29. package/artemis-main/apps/admin_console/routers/steps.py +109 -0
  30. package/artemis-main/apps/admin_console/routers/stream.py +56 -0
  31. package/artemis-main/apps/admin_console/routers/system.py +761 -0
  32. package/artemis-main/apps/admin_console/routers/tasks.py +517 -0
  33. package/artemis-main/apps/admin_console/schemas/__init__.py +13 -0
  34. package/artemis-main/apps/admin_console/schemas/task_schema.py +47 -0
  35. package/artemis-main/apps/admin_console/server.py +508 -0
  36. package/artemis-main/apps/admin_console/services/__init__.py +13 -0
  37. package/artemis-main/apps/admin_console/services/device_stream_service.py +137 -0
  38. package/artemis-main/apps/admin_console/services/ipc_service.py +262 -0
  39. package/artemis-main/apps/admin_console/services/media_service.py +623 -0
  40. package/artemis-main/apps/admin_console/services/model_service.py +148 -0
  41. package/artemis-main/apps/admin_console/services/task_preset_catalog.py +514 -0
  42. package/artemis-main/apps/admin_console/services/task_queue_service.py +1719 -0
  43. package/artemis-main/apps/admin_console/services/worker_process_io.py +144 -0
  44. package/artemis-main/apps/showcase_ui/README.md +31 -0
  45. package/artemis-main/apps/showcase_ui/angular.json +132 -0
  46. package/artemis-main/apps/showcase_ui/package-lock.json +9472 -0
  47. package/artemis-main/apps/showcase_ui/package.json +39 -0
  48. package/artemis-main/apps/showcase_ui/proxy.conf.json +23 -0
  49. package/artemis-main/apps/showcase_ui/public/favicon.ico +0 -0
  50. package/artemis-main/apps/showcase_ui/public/logo.png +0 -0
  51. package/artemis-main/apps/showcase_ui/public/logo.svg +56 -0
  52. package/artemis-main/apps/showcase_ui/src/app/app.component.html +24 -0
  53. package/artemis-main/apps/showcase_ui/src/app/app.component.scss +49 -0
  54. package/artemis-main/apps/showcase_ui/src/app/app.component.spec.ts +42 -0
  55. package/artemis-main/apps/showcase_ui/src/app/app.component.ts +32 -0
  56. package/artemis-main/apps/showcase_ui/src/app/app.config.ts +29 -0
  57. package/artemis-main/apps/showcase_ui/src/app/app.routes.ts +27 -0
  58. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream-startup.spec.ts +108 -0
  59. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html +1436 -0
  60. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss +3973 -0
  61. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts +2235 -0
  62. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html +380 -0
  63. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss +1171 -0
  64. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.ts +270 -0
  65. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html +433 -0
  66. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss +987 -0
  67. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.ts +632 -0
  68. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.html +130 -0
  69. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss +406 -0
  70. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.ts +74 -0
  71. package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss +115 -0
  72. package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts +52 -0
  73. package/artemis-main/apps/showcase_ui/src/app/core/data/smart-tasks.data.ts +334 -0
  74. package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.spec.ts +55 -0
  75. package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.ts +61 -0
  76. package/artemis-main/apps/showcase_ui/src/app/core/i18n/translate.pipe.ts +11 -0
  77. package/artemis-main/apps/showcase_ui/src/app/core/i18n/zh-cn.ts +651 -0
  78. package/artemis-main/apps/showcase_ui/src/app/core/models/markdown.model.ts +43 -0
  79. package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.spec.ts +47 -0
  80. package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.ts +165 -0
  81. package/artemis-main/apps/showcase_ui/src/app/core/models/session.model.ts +85 -0
  82. package/artemis-main/apps/showcase_ui/src/app/core/models/stream.model.ts +246 -0
  83. package/artemis-main/apps/showcase_ui/src/app/core/models/system.model.ts +118 -0
  84. package/artemis-main/apps/showcase_ui/src/app/core/services/task-recommendation.service.ts +111 -0
  85. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.html +2130 -0
  86. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.scss +5204 -0
  87. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.ts +1189 -0
  88. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.html +39 -0
  89. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.scss +151 -0
  90. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.ts +58 -0
  91. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.html +201 -0
  92. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.scss +886 -0
  93. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.ts +324 -0
  94. package/artemis-main/apps/showcase_ui/src/app/services/agent.service.spec.ts +418 -0
  95. package/artemis-main/apps/showcase_ui/src/app/services/agent.service.ts +2012 -0
  96. package/artemis-main/apps/showcase_ui/src/app/services/system.service.spec.ts +173 -0
  97. package/artemis-main/apps/showcase_ui/src/app/services/system.service.ts +620 -0
  98. package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.spec.ts +513 -0
  99. package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.ts +1015 -0
  100. package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.spec.ts +85 -0
  101. package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.ts +662 -0
  102. package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.spec.ts +70 -0
  103. package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.ts +383 -0
  104. package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.spec.ts +107 -0
  105. package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.ts +123 -0
  106. package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.spec.ts +92 -0
  107. package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.ts +116 -0
  108. package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts +640 -0
  109. package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts +1021 -0
  110. package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.spec.ts +375 -0
  111. package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.ts +1419 -0
  112. package/artemis-main/apps/showcase_ui/src/index.html +37 -0
  113. package/artemis-main/apps/showcase_ui/src/main.ts +22 -0
  114. package/artemis-main/apps/showcase_ui/src/styles.scss +60 -0
  115. package/artemis-main/apps/showcase_ui/tsconfig.app.json +23 -0
  116. package/artemis-main/apps/showcase_ui/tsconfig.json +27 -0
  117. package/artemis-main/apps/showcase_ui/tsconfig.spec.json +23 -0
  118. package/artemis-main/artemis/__init__.py +44 -0
  119. package/artemis-main/artemis/__main__.py +18 -0
  120. package/artemis-main/artemis/_legacy_imports.py +94 -0
  121. package/artemis-main/artemis/_version.py +30 -0
  122. package/artemis-main/artemis/agents/__init__.py +17 -0
  123. package/artemis-main/artemis/agents/checker/checker.json +9 -0
  124. package/artemis-main/artemis/agents/checker/checker.py +859 -0
  125. package/artemis-main/artemis/agents/diagnoser/diagnoser.json +23 -0
  126. package/artemis-main/artemis/agents/diagnoser/diagnoser.py +608 -0
  127. package/artemis-main/artemis/agents/explorer/constants.py +52 -0
  128. package/artemis-main/artemis/agents/explorer/explorer.json +57 -0
  129. package/artemis-main/artemis/agents/explorer/explorer.py +473 -0
  130. package/artemis-main/artemis/agents/explorer/geometry.py +73 -0
  131. package/artemis-main/artemis/agents/explorer/native_runner.py +878 -0
  132. package/artemis-main/artemis/agents/explorer/perception_tools.py +632 -0
  133. package/artemis-main/artemis/agents/explorer/run_setup.py +403 -0
  134. package/artemis-main/artemis/agents/explorer/screen_index.py +266 -0
  135. package/artemis-main/artemis/agents/explorer/tiers.py +110 -0
  136. package/artemis-main/artemis/agents/explorer/tool_declarations.py +394 -0
  137. package/artemis-main/artemis/agents/explorer/universal_runner.py +317 -0
  138. package/artemis-main/artemis/agents/flash/context_compressor.py +452 -0
  139. package/artemis-main/artemis/agents/flash/flash_runner.md +51 -0
  140. package/artemis-main/artemis/agents/flash/flash_summarizer.md +53 -0
  141. package/artemis-main/artemis/agents/flash/flash_summarizer_single.md +54 -0
  142. package/artemis-main/artemis/agents/flash/runner.py +1167 -0
  143. package/artemis-main/artemis/agents/flash/summarizer.py +511 -0
  144. package/artemis-main/artemis/agents/history_analyzer/history_analyzer.md +8 -0
  145. package/artemis-main/artemis/agents/history_analyzer/history_analyzer.py +182 -0
  146. package/artemis-main/artemis/agents/image_processor/image_processor.md +74 -0
  147. package/artemis-main/artemis/agents/image_processor/image_processor.py +322 -0
  148. package/artemis-main/artemis/agents/log_analyzer/log_analyzer.md +15 -0
  149. package/artemis-main/artemis/agents/log_analyzer/log_analyzer.py +403 -0
  150. package/artemis-main/artemis/agents/log_analyzer/log_reader_sub_agent.md +8 -0
  151. package/artemis-main/artemis/agents/log_analyzer/output_analyzer.md +11 -0
  152. package/artemis-main/artemis/agents/log_analyzer/output_analyzer.py +163 -0
  153. package/artemis-main/artemis/agents/object_detector/object_detector.json +9 -0
  154. package/artemis-main/artemis/agents/object_detector/object_detector.py +211 -0
  155. package/artemis-main/artemis/agents/operator/operator.json +3 -0
  156. package/artemis-main/artemis/agents/operator/operator.py +1394 -0
  157. package/artemis-main/artemis/agents/operator/prompts.py +1064 -0
  158. package/artemis-main/artemis/agents/outputter/outputter.md +36 -0
  159. package/artemis-main/artemis/agents/outputter/outputter.py +552 -0
  160. package/artemis-main/artemis/agents/planner/planner.json +32 -0
  161. package/artemis-main/artemis/agents/planner/planner.py +436 -0
  162. package/artemis-main/artemis/agents/prompt_assembly.py +79 -0
  163. package/artemis-main/artemis/agents/summarizer/summarizer.py +190 -0
  164. package/artemis-main/artemis/agents/validator/action_execution.py +98 -0
  165. package/artemis-main/artemis/agents/validator/categories.py +33 -0
  166. package/artemis-main/artemis/agents/validator/execution_loop.py +488 -0
  167. package/artemis-main/artemis/agents/validator/incidents.py +120 -0
  168. package/artemis-main/artemis/agents/validator/pixel_safety_net.md +15 -0
  169. package/artemis-main/artemis/agents/validator/precondition_pixel.py +320 -0
  170. package/artemis-main/artemis/agents/validator/precondition_xml.py +672 -0
  171. package/artemis-main/artemis/agents/validator/tool_declarations.py +332 -0
  172. package/artemis-main/artemis/agents/validator/validator.py +188 -0
  173. package/artemis-main/artemis/agents/video_analyzer/__init__.py +15 -0
  174. package/artemis-main/artemis/agents/video_analyzer/audio_native.py +543 -0
  175. package/artemis-main/artemis/agents/video_analyzer/blackboard.py +821 -0
  176. package/artemis-main/artemis/agents/video_analyzer/chunk_agentic.py +455 -0
  177. package/artemis-main/artemis/agents/video_analyzer/chunk_conversation.py +491 -0
  178. package/artemis-main/artemis/agents/video_analyzer/chunk_native.py +437 -0
  179. package/artemis-main/artemis/agents/video_analyzer/conflict_resolution.py +137 -0
  180. package/artemis-main/artemis/agents/video_analyzer/gemini_files.py +162 -0
  181. package/artemis-main/artemis/agents/video_analyzer/native_coordinator.py +541 -0
  182. package/artemis-main/artemis/agents/video_analyzer/reliability.py +128 -0
  183. package/artemis-main/artemis/agents/video_analyzer/universal_engine.py +487 -0
  184. package/artemis-main/artemis/agents/video_analyzer/universal_tools.py +323 -0
  185. package/artemis-main/artemis/agents/video_analyzer/video_analyzer.md +51 -0
  186. package/artemis-main/artemis/agents/video_analyzer/video_analyzer.py +875 -0
  187. package/artemis-main/artemis/agents/video_analyzer/video_sub_agent.md +15 -0
  188. package/artemis-main/artemis/agents/video_analyzer/video_sub_agent_audio.md +12 -0
  189. package/artemis-main/artemis/clients/accessibility_client.py +438 -0
  190. package/artemis-main/artemis/clients/screen_client_factory.py +403 -0
  191. package/artemis-main/artemis/config/__init__.py +309 -0
  192. package/artemis-main/artemis/config/agent.py +1098 -0
  193. package/artemis-main/artemis/config/constants.py +151 -0
  194. package/artemis-main/artemis/config/core/__init__.py +24 -0
  195. package/artemis-main/artemis/config/core/engine.py +118 -0
  196. package/artemis-main/artemis/config/core/interpolator.py +78 -0
  197. package/artemis-main/artemis/config/llm.py +267 -0
  198. package/artemis-main/artemis/config/paths.py +244 -0
  199. package/artemis-main/artemis/config/runtime.py +197 -0
  200. package/artemis-main/artemis/config/settings.py +315 -0
  201. package/artemis-main/artemis/constants.py +47 -0
  202. package/artemis-main/artemis/context.py +289 -0
  203. package/artemis-main/artemis/controllers/__init__.py +41 -0
  204. package/artemis-main/artemis/controllers/controller_factory.py +41 -0
  205. package/artemis-main/artemis/controllers/device_controller.py +29 -0
  206. package/artemis-main/artemis/controllers/types.py +37 -0
  207. package/artemis-main/artemis/controllers/unified_controller.py +649 -0
  208. package/artemis-main/artemis/core/__init__.py +21 -0
  209. package/artemis-main/artemis/core/diagnostics/__init__.py +90 -0
  210. package/artemis-main/artemis/core/diagnostics/adb_keys.py +270 -0
  211. package/artemis-main/artemis/core/diagnostics/adb_server_connection.py +330 -0
  212. package/artemis-main/artemis/core/diagnostics/device_smoke.py +364 -0
  213. package/artemis-main/artemis/core/diagnostics/emulator_manager.py +501 -0
  214. package/artemis-main/artemis/core/diagnostics/engine.py +438 -0
  215. package/artemis-main/artemis/core/diagnostics/hierarchy_parity.py +216 -0
  216. package/artemis-main/artemis/core/diagnostics/probes/adb_probe.py +887 -0
  217. package/artemis-main/artemis/core/diagnostics/probes/base.py +45 -0
  218. package/artemis-main/artemis/core/diagnostics/probes/credentials_probe.py +296 -0
  219. package/artemis-main/artemis/core/diagnostics/probes/host_probe.py +427 -0
  220. package/artemis-main/artemis/core/diagnostics/probes/runtime_probe.py +169 -0
  221. package/artemis-main/artemis/core/diagnostics/probes/toolchain_probe.py +139 -0
  222. package/artemis-main/artemis/core/diagnostics/readiness.py +83 -0
  223. package/artemis-main/artemis/core/diagnostics/schema.py +118 -0
  224. package/artemis-main/artemis/core/tool_declaration.py +43 -0
  225. package/artemis-main/artemis/core/tool_failure.py +56 -0
  226. package/artemis-main/artemis/data_engine/context_vars.py +24 -0
  227. package/artemis-main/artemis/data_engine/engine.py +1615 -0
  228. package/artemis-main/artemis/data_engine/history_reader.py +142 -0
  229. package/artemis-main/artemis/data_engine/models.py +131 -0
  230. package/artemis-main/artemis/data_engine/storage.py +1245 -0
  231. package/artemis-main/artemis/data_engine/trace.py +727 -0
  232. package/artemis-main/artemis/drivers/__init__.py +48 -0
  233. package/artemis-main/artemis/drivers/android/adb_driver.py +507 -0
  234. package/artemis-main/artemis/drivers/android/hierarchy.py +64 -0
  235. package/artemis-main/artemis/drivers/android/input_ime.py +120 -0
  236. package/artemis-main/artemis/drivers/android/recorder.py +57 -0
  237. package/artemis-main/artemis/drivers/base.py +290 -0
  238. package/artemis-main/artemis/drivers/cloud/cloud_driver.py +117 -0
  239. package/artemis-main/artemis/drivers/factory.py +80 -0
  240. package/artemis-main/artemis/drivers/mock/mock_driver.py +198 -0
  241. package/artemis-main/artemis/drivers/types.py +67 -0
  242. package/artemis-main/artemis/graph/checkpoints.py +1243 -0
  243. package/artemis-main/artemis/graph/graph.py +1055 -0
  244. package/artemis-main/artemis/graph/perception.py +234 -0
  245. package/artemis-main/artemis/graph/state.py +151 -0
  246. package/artemis-main/artemis/graph/visibility.py +238 -0
  247. package/artemis-main/artemis/interfaces/cli/__init__.py +19 -0
  248. package/artemis-main/artemis/interfaces/cli/commands/__init__.py +15 -0
  249. package/artemis-main/artemis/interfaces/cli/commands/batch.py +278 -0
  250. package/artemis-main/artemis/interfaces/cli/commands/doctor.py +468 -0
  251. package/artemis-main/artemis/interfaces/cli/commands/helper.py +226 -0
  252. package/artemis-main/artemis/interfaces/cli/commands/init.py +193 -0
  253. package/artemis-main/artemis/interfaces/cli/commands/mcp.py +862 -0
  254. package/artemis-main/artemis/interfaces/cli/commands/run.py +463 -0
  255. package/artemis-main/artemis/interfaces/cli/commands/server.py +151 -0
  256. package/artemis-main/artemis/interfaces/cli/commands/server_lifecycle.py +328 -0
  257. package/artemis-main/artemis/interfaces/cli/commands/trace.py +111 -0
  258. package/artemis-main/artemis/interfaces/cli/commands/ui.py +404 -0
  259. package/artemis-main/artemis/interfaces/cli/main.py +109 -0
  260. package/artemis-main/artemis/interfaces/sdk/__init__.py +31 -0
  261. package/artemis-main/artemis/interfaces/sdk/client.py +19 -0
  262. package/artemis-main/artemis/interfaces/sdk/task.py +72 -0
  263. package/artemis-main/artemis/llm/__init__.py +25 -0
  264. package/artemis-main/artemis/llm/fake_model.py +179 -0
  265. package/artemis-main/artemis/llm/google/__init__.py +57 -0
  266. package/artemis-main/artemis/llm/google/provider.py +185 -0
  267. package/artemis-main/artemis/llm/google/usage.py +148 -0
  268. package/artemis-main/artemis/llm/parser.py +32 -0
  269. package/artemis-main/artemis/llm/reliability.py +267 -0
  270. package/artemis-main/artemis/llm/router.py +389 -0
  271. package/artemis-main/artemis/llm/schemas.py +36 -0
  272. package/artemis-main/artemis/llm/structured.py +196 -0
  273. package/artemis-main/artemis/main.py +58 -0
  274. package/artemis-main/artemis/mcp/__init__.py +20 -0
  275. package/artemis-main/artemis/mcp/action_executor.py +760 -0
  276. package/artemis-main/artemis/mcp/action_manifest.py +320 -0
  277. package/artemis-main/artemis/mcp/action_names.py +172 -0
  278. package/artemis-main/artemis/mcp/action_server.py +202 -0
  279. package/artemis-main/artemis/mcp/action_session.py +245 -0
  280. package/artemis-main/artemis/mcp/action_specs.py +868 -0
  281. package/artemis-main/artemis/mcp/action_types.py +86 -0
  282. package/artemis-main/artemis/mcp/actuators/__init__.py +21 -0
  283. package/artemis-main/artemis/mcp/actuators/adb.py +414 -0
  284. package/artemis-main/artemis/mcp/actuators/base.py +113 -0
  285. package/artemis-main/artemis/mcp/actuators/mock.py +70 -0
  286. package/artemis-main/artemis/mcp/adb_server.py +490 -0
  287. package/artemis-main/artemis/mcp/observation.py +133 -0
  288. package/artemis-main/artemis/mcp/xml_search_server.py +297 -0
  289. package/artemis-main/artemis/memory/__init__.py +90 -0
  290. package/artemis-main/artemis/memory/chunking.py +1843 -0
  291. package/artemis-main/artemis/memory/context_policy.py +226 -0
  292. package/artemis-main/artemis/memory/step_capsule.md +73 -0
  293. package/artemis-main/artemis/memory/step_memory.py +281 -0
  294. package/artemis-main/artemis/memory/transcript.py +798 -0
  295. package/artemis-main/artemis/platform/__init__.py +61 -0
  296. package/artemis-main/artemis/platform/base.py +104 -0
  297. package/artemis-main/artemis/platform/darwin.py +184 -0
  298. package/artemis-main/artemis/platform/linux.py +207 -0
  299. package/artemis-main/artemis/platform/windows.py +193 -0
  300. package/artemis-main/artemis/resources/__init__.py +46 -0
  301. package/artemis-main/artemis/runtime/__init__.py +108 -0
  302. package/artemis-main/artemis/runtime/adb_endpoint.py +173 -0
  303. package/artemis-main/artemis/runtime/awake_lease.py +283 -0
  304. package/artemis-main/artemis/runtime/awake_service.py +343 -0
  305. package/artemis-main/artemis/runtime/cancel_requests.py +254 -0
  306. package/artemis-main/artemis/runtime/daemon_client.py +422 -0
  307. package/artemis-main/artemis/runtime/device_lock.py +1013 -0
  308. package/artemis-main/artemis/runtime/device_pool.py +478 -0
  309. package/artemis-main/artemis/runtime/helper_manager.py +1030 -0
  310. package/artemis-main/artemis/runtime/process_probe.py +98 -0
  311. package/artemis-main/artemis/runtime/server_lifecycle.py +435 -0
  312. package/artemis-main/artemis/runtime/supervisor.py +121 -0
  313. package/artemis-main/artemis/runtime/trace_store.py +375 -0
  314. package/artemis-main/artemis/sdk/__init__.py +26 -0
  315. package/artemis-main/artemis/sdk/agent.py +485 -0
  316. package/artemis-main/artemis/sdk/builders/__init__.py +29 -0
  317. package/artemis-main/artemis/sdk/builders/agent_config_builder.py +474 -0
  318. package/artemis-main/artemis/sdk/constants.py +15 -0
  319. package/artemis-main/artemis/sdk/run_outcome.py +58 -0
  320. package/artemis-main/artemis/sdk/types/__init__.py +61 -0
  321. package/artemis-main/artemis/sdk/types/agent.py +89 -0
  322. package/artemis-main/artemis/sdk/utils.py +28 -0
  323. package/artemis-main/artemis/services/llm.py +1040 -0
  324. package/artemis-main/artemis/services/token_meter.py +267 -0
  325. package/artemis-main/artemis/telemetry/__init__.py +35 -0
  326. package/artemis-main/artemis/telemetry/models.py +64 -0
  327. package/artemis-main/artemis/telemetry/replay.py +34 -0
  328. package/artemis-main/artemis/telemetry/storage/json_storage.py +47 -0
  329. package/artemis-main/artemis/telemetry/tracer.py +85 -0
  330. package/artemis-main/artemis/toolchain/__init__.py +52 -0
  331. package/artemis-main/artemis/toolchain/descriptors.py +92 -0
  332. package/artemis-main/artemis/toolchain/resolver.py +185 -0
  333. package/artemis-main/artemis/tools/base.py +232 -0
  334. package/artemis-main/artemis/tools/command_tool.py +1240 -0
  335. package/artemis-main/artemis/tools/committee_prompts.json +5 -0
  336. package/artemis-main/artemis/tools/committee_tool.py +422 -0
  337. package/artemis-main/artemis/tools/diagnoser_submit_answer_tool.py +87 -0
  338. package/artemis-main/artemis/tools/diagnostic_tool.py +120 -0
  339. package/artemis-main/artemis/tools/explorer_tool.py +581 -0
  340. package/artemis-main/artemis/tools/history/__init__.py +398 -0
  341. package/artemis-main/artemis/tools/history/replay.py +120 -0
  342. package/artemis-main/artemis/tools/history/screenshot.py +139 -0
  343. package/artemis-main/artemis/tools/history/search.py +563 -0
  344. package/artemis-main/artemis/tools/image_processor_tool.py +124 -0
  345. package/artemis-main/artemis/tools/index.py +47 -0
  346. package/artemis-main/artemis/tools/log_tool.py +108 -0
  347. package/artemis-main/artemis/tools/mobile/launch_app.py +132 -0
  348. package/artemis-main/artemis/tools/mobile/log_utils.py +138 -0
  349. package/artemis-main/artemis/tools/mobile/ocr.py +202 -0
  350. package/artemis-main/artemis/tools/mobile/read_hierarchy.py +108 -0
  351. package/artemis-main/artemis/tools/mobile/read_logs.py +116 -0
  352. package/artemis-main/artemis/tools/mobile/search_logs.py +260 -0
  353. package/artemis-main/artemis/tools/object_detection_tool.py +244 -0
  354. package/artemis-main/artemis/tools/scratchpad.py +819 -0
  355. package/artemis-main/artemis/tools/system/adb_shell.py +31 -0
  356. package/artemis-main/artemis/tools/tool_wrapper.py +270 -0
  357. package/artemis-main/artemis/tools/video_tool.py +261 -0
  358. package/artemis-main/artemis/tools/wait_tool.py +97 -0
  359. package/artemis-main/artemis/utils/app_launch_utils.py +207 -0
  360. package/artemis-main/artemis/utils/coordinates.py +606 -0
  361. package/artemis-main/artemis/utils/credentials_validator.py +201 -0
  362. package/artemis-main/artemis/utils/cv_canvas.py +189 -0
  363. package/artemis-main/artemis/utils/cython_compat.py +45 -0
  364. package/artemis-main/artemis/utils/element_hit_test.py +80 -0
  365. package/artemis-main/artemis/utils/image_diff.py +335 -0
  366. package/artemis-main/artemis/utils/image_hash.py +80 -0
  367. package/artemis-main/artemis/utils/notes.py +470 -0
  368. package/artemis-main/artemis/utils/ocr_api.py +105 -0
  369. package/artemis-main/artemis/utils/ocr_xml_fusion.py +318 -0
  370. package/artemis-main/artemis/utils/plan_grammar.py +513 -0
  371. package/artemis-main/artemis/utils/python_executor.py +213 -0
  372. package/artemis-main/artemis/utils/startup_progress.py +60 -0
  373. package/artemis-main/artemis/utils/task_tree.py +1174 -0
  374. package/artemis-main/artemis/utils/text.py +37 -0
  375. package/artemis-main/artemis/utils/ui_filter.py +651 -0
  376. package/artemis-main/artemis/utils/video.py +685 -0
  377. package/artemis-main/artemis/utils/visualization.py +802 -0
  378. package/artemis-main/config/artemis.example.jsonc +12 -0
  379. package/artemis-main/mcp_server/README.md +111 -0
  380. package/artemis-main/mcp_server/__init__.py +21 -0
  381. package/artemis-main/mcp_server/__main__.py +20 -0
  382. package/artemis-main/mcp_server/background/__init__.py +15 -0
  383. package/artemis-main/mcp_server/background/task_runner.py +453 -0
  384. package/artemis-main/mcp_server/base.py +49 -0
  385. package/artemis-main/mcp_server/notifiers/__init__.py +74 -0
  386. package/artemis-main/mcp_server/notifiers/agentapi.py +289 -0
  387. package/artemis-main/mcp_server/notifiers/base.py +45 -0
  388. package/artemis-main/mcp_server/notifiers/composite.py +87 -0
  389. package/artemis-main/mcp_server/notifiers/desktop.py +108 -0
  390. package/artemis-main/mcp_server/notifiers/file.py +69 -0
  391. package/artemis-main/mcp_server/notifiers/script.py +90 -0
  392. package/artemis-main/mcp_server/notifiers/webhook.py +87 -0
  393. package/artemis-main/mcp_server/rules.md +68 -0
  394. package/artemis-main/mcp_server/server.py +106 -0
  395. package/artemis-main/mcp_server/tools/__init__.py +29 -0
  396. package/artemis-main/mcp_server/tools/device_state.py +107 -0
  397. package/artemis-main/mcp_server/tools/diagnose.py +1249 -0
  398. package/artemis-main/mcp_server/tools/inspect_trace.py +394 -0
  399. package/artemis-main/mcp_server/tools/task_manager.py +627 -0
  400. package/artemis-main/mcp_server/tools/task_runner.py +548 -0
  401. package/artemis-main/mcp_server/utils/__init__.py +15 -0
  402. package/artemis-main/mcp_server/utils/device_utils.py +180 -0
  403. package/artemis-main/mcp_server/utils/env_utils.py +65 -0
  404. package/artemis-main/packages/artemis-accessibility-helper/ArtemisAccessibilityHelper.apk +0 -0
  405. package/artemis-main/packages/artemis-accessibility-helper/app/build.gradle.kts +35 -0
  406. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/AndroidManifest.xml +49 -0
  407. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/A11yNode.java +274 -0
  408. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/ArtemisAccessibilityService.java +227 -0
  409. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/CommandServer.java +463 -0
  410. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/DisplayUtils.java +111 -0
  411. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/GestureController.java +232 -0
  412. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/HierarchyDumper.java +832 -0
  413. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenReceiver.java +36 -0
  414. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenStore.java +38 -0
  415. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/XmlUtils.java +102 -0
  416. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/values/strings.xml +4 -0
  417. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/xml/accessibility_service_config.xml +10 -0
  418. package/artemis-main/packages/artemis-accessibility-helper/build.gradle.kts +3 -0
  419. package/artemis-main/packages/artemis-accessibility-helper/build_apk.sh +215 -0
  420. package/artemis-main/packages/artemis-accessibility-helper/debug.keystore +0 -0
  421. package/artemis-main/packages/artemis-accessibility-helper/gradle.properties +2 -0
  422. package/artemis-main/packages/artemis-accessibility-helper/helper_manifest.json +7 -0
  423. package/artemis-main/packages/artemis-accessibility-helper/settings.gradle.kts +17 -0
  424. package/artemis-main/packages/artemis-client/DESIGN.md +89 -0
  425. package/artemis-main/packages/artemis-client/README.md +112 -0
  426. package/artemis-main/packages/artemis-client/examples/basic.py +30 -0
  427. package/artemis-main/packages/artemis-client/pyproject.toml +36 -0
  428. package/artemis-main/packages/artemis-client/src/artemis_client/__init__.py +45 -0
  429. package/artemis-main/packages/artemis-client/src/artemis_client/client.py +401 -0
  430. package/artemis-main/packages/artemis-client/src/artemis_client/errors.py +66 -0
  431. package/artemis-main/packages/artemis-client/src/artemis_client/models.py +212 -0
  432. package/artemis-main/packages/artemis-client/src/artemis_client/py.typed +1 -0
  433. package/artemis-main/packages/artemis-client/src/artemis_client/transport.py +134 -0
  434. package/artemis-main/packages/artemis-client/tests/test_client.py +289 -0
  435. package/artemis-main/packages/artemis-client/tests/test_transport.py +85 -0
  436. package/artemis-main/pyproject.toml +259 -0
  437. package/artemis-main/pyright-core.json +17 -0
  438. package/artemis-main/scripts/check_dependency_sources.py +68 -0
  439. package/artemis-main/scripts/install_deps.ps1 +520 -0
  440. package/artemis-main/scripts/install_deps.sh +533 -0
  441. package/artemis-main/scripts/quality_ratchet.py +97 -0
  442. package/artemis-main/scripts/real_device_video_acceptance.py +483 -0
  443. package/artemis-main/scripts/run_edge_case_part.py +327 -0
  444. package/artemis-main/scripts/run_ui.py +242 -0
  445. package/artemis-main/scripts/start.ps1 +500 -0
  446. package/artemis-main/scripts/test_all_edge_cases.py +269 -0
  447. package/artemis-main/scripts/verify_concurrency_and_devices.py +385 -0
  448. package/artemis-main/setup.py +164 -0
  449. package/artemis-main/start.bat +35 -0
  450. package/artemis-main/start.sh +423 -0
  451. package/artemis-main/third_party/mobile_use/LICENSE +190 -0
  452. package/artemis-main/third_party/mobile_use/METADATA +27 -0
  453. package/artemis-main/third_party/mobile_use/NOTICE +12 -0
  454. package/artemis-main/third_party/mobile_use/__init__.py +19 -0
  455. package/artemis-main/third_party/mobile_use/agents/__init__.py +13 -0
  456. package/artemis-main/third_party/mobile_use/agents/hopper/__init__.py +13 -0
  457. package/artemis-main/third_party/mobile_use/agents/hopper/hopper.md +22 -0
  458. package/artemis-main/third_party/mobile_use/agents/hopper/hopper.py +80 -0
  459. package/artemis-main/third_party/mobile_use/clients/__init__.py +13 -0
  460. package/artemis-main/third_party/mobile_use/clients/ui_automator_client.py +482 -0
  461. package/artemis-main/third_party/mobile_use/config/__init__.py +13 -0
  462. package/artemis-main/third_party/mobile_use/config/llm.py +183 -0
  463. package/artemis-main/third_party/mobile_use/config/output.py +184 -0
  464. package/artemis-main/third_party/mobile_use/context.py +119 -0
  465. package/artemis-main/third_party/mobile_use/controllers/__init__.py +13 -0
  466. package/artemis-main/third_party/mobile_use/controllers/platform_specific_commands_controller.py +151 -0
  467. package/artemis-main/third_party/mobile_use/controllers/types.py +92 -0
  468. package/artemis-main/third_party/mobile_use/controllers/unified_controller.py +183 -0
  469. package/artemis-main/third_party/mobile_use/graph.py +54 -0
  470. package/artemis-main/third_party/mobile_use/llm_service.py +193 -0
  471. package/artemis-main/third_party/mobile_use/main.py +130 -0
  472. package/artemis-main/third_party/mobile_use/sdk/__init__.py +13 -0
  473. package/artemis-main/third_party/mobile_use/sdk/agent.py +1060 -0
  474. package/artemis-main/third_party/mobile_use/sdk/builders/__init__.py +13 -0
  475. package/artemis-main/third_party/mobile_use/sdk/builders/agent_config_builder.py +225 -0
  476. package/artemis-main/third_party/mobile_use/sdk/builders/index.py +33 -0
  477. package/artemis-main/third_party/mobile_use/sdk/builders/task_request_builder.py +243 -0
  478. package/artemis-main/third_party/mobile_use/sdk/types/__init__.py +13 -0
  479. package/artemis-main/third_party/mobile_use/sdk/types/agent.py +98 -0
  480. package/artemis-main/third_party/mobile_use/sdk/types/exceptions.py +109 -0
  481. package/artemis-main/third_party/mobile_use/sdk/types/task.py +234 -0
  482. package/artemis-main/third_party/mobile_use/testing.py +59 -0
  483. package/artemis-main/third_party/mobile_use/tools/__init__.py +13 -0
  484. package/artemis-main/third_party/mobile_use/tools/index.py +44 -0
  485. package/artemis-main/third_party/mobile_use/tools/mobile/__init__.py +13 -0
  486. package/artemis-main/third_party/mobile_use/tools/mobile/launch_app.py +89 -0
  487. package/artemis-main/third_party/mobile_use/tools/tool_wrapper.py +52 -0
  488. package/artemis-main/third_party/mobile_use/utils/__init__.py +13 -0
  489. package/artemis-main/third_party/mobile_use/utils/app_launch_utils.py +217 -0
  490. package/artemis-main/third_party/mobile_use/utils/cli_helpers.py +60 -0
  491. package/artemis-main/third_party/mobile_use/utils/decorators.py +158 -0
  492. package/artemis-main/third_party/mobile_use/utils/file.py +31 -0
  493. package/artemis-main/third_party/mobile_use/utils/logger.py +216 -0
  494. package/artemis-main/third_party/mobile_use/utils/media.py +213 -0
  495. package/artemis-main/third_party/mobile_use/utils/shell_utils.py +39 -0
  496. package/artemis-main/third_party/mobile_use/utils/video.py +398 -0
  497. package/artemis-main/uv.lock +4908 -0
  498. package/bin/android-midscene-automation.js +1 -5
  499. package/bin/artemis-config.mjs +15 -0
  500. package/bin/artemis-embedded.py +56 -0
  501. package/bin/start-artemis.mjs +30 -0
  502. package/bin/start-dev.mjs +112 -0
  503. package/bin/test_artemis_embedded.py +44 -0
  504. package/package.json +4 -2
  505. package/server/artemis.ts +34 -0
  506. package/server/http-api.ts +12 -0
  507. package/src/App.vue +10 -5
  508. package/src/api.ts +5 -0
  509. package/src/appium-recorder/AppiumPage.vue +1 -1
  510. package/src/appium-recorder/components/ComponentInspector.vue +1 -1
  511. package/src/appium-recorder/components/FlowNodeSearch.vue +3 -3
  512. package/src/appium-recorder/components/ImageCheckDialog.vue +3 -3
  513. package/src/appium-recorder/components/RecorderWorkspace.vue +4 -4
  514. package/src/appium-recorder/flow-appearance.ts +4 -4
  515. package/src/components/AppSidebar.vue +8 -7
  516. package/src/components/MidsceneMaintenanceNotice.vue +17 -0
  517. package/src/components/analytics/AnalysisCharts.vue +4 -4
  518. package/src/components/analytics/ReportAnalysis.vue +1 -1
  519. package/src/components/generator/TestCaseFileUpload.vue +1 -1
  520. package/src/components/help/HelpCenter.vue +56 -37
  521. package/src/pages/AiGeneratorPage.vue +1 -1
  522. package/src/pages/ArtemisPage.vue +72 -0
  523. package/src/pages/ConfigPage.vue +18 -19
  524. package/src/style.css +340 -121
  525. package/src/types.ts +1 -1
@@ -0,0 +1,533 @@
1
+ #!/usr/bin/env bash
2
+ # Copyright 2026 Google LLC
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ set -euo pipefail
17
+
18
+ # Visual styling
19
+ BOLD='\033[1m'
20
+ GREEN='\033[0;32m'
21
+ BLUE='\033[0;34m'
22
+ YELLOW='\033[1;33m'
23
+ RED='\033[0;31m'
24
+ CYAN='\033[0;36m'
25
+ DIM='\033[2m'
26
+ NC='\033[0m'
27
+
28
+ AUTO_LAUNCH=false
29
+ for arg in "$@"; do
30
+ case "${arg}" in
31
+ --launch|-l|--open)
32
+ AUTO_LAUNCH=true
33
+ ;;
34
+ esac
35
+ done
36
+
37
+ echo -e "${BOLD}${CYAN}======================================================${NC}"
38
+ echo -e "${BOLD}${CYAN} 🚀 Artemis - Smart Multi-Platform Installer ${NC}"
39
+ echo -e "${BOLD}${CYAN}======================================================${NC}"
40
+ echo ""
41
+
42
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
43
+ ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
44
+ cd "${ROOT_DIR}"
45
+
46
+ # Normalize PATH with standard platform binary paths
47
+ STANDARD_PATHS=(
48
+ "/opt/homebrew/bin"
49
+ "/opt/homebrew/sbin"
50
+ "/usr/local/bin"
51
+ "/usr/local/sbin"
52
+ "${HOME}/.local/bin"
53
+ "${HOME}/.local/share/node/bin"
54
+ "${HOME}/.local/share/platform-tools"
55
+ "${HOME}/.local/share/scrcpy"
56
+ "${HOME}/.cargo/bin"
57
+ "${HOME}/Library/Android/sdk/platform-tools"
58
+ "${HOME}/Android/Sdk/platform-tools"
59
+ )
60
+ for p in "${STANDARD_PATHS[@]}"; do
61
+ if [ -d "${p}" ] && [[ ":${PATH}:" != *":${p}:"* ]]; then
62
+ export PATH="${p}:${PATH}"
63
+ fi
64
+ done
65
+
66
+ OS_TYPE="$(uname -s)"
67
+ ARCH_TYPE="$(uname -m)"
68
+
69
+ # Helper to check command availability
70
+ has_cmd() {
71
+ command -v "$1" >/dev/null 2>&1
72
+ }
73
+
74
+ # Sudo credential and choice state tracking
75
+ SUDO_AUTHENTICATED=false
76
+ SUDO_DECLINED=false
77
+
78
+ # Helper to check, prompt for, or skip administrator (sudo) privileges gracefully
79
+ request_sudo() {
80
+ [ "$(id -u)" -eq 0 ] && return 0
81
+ if ! has_cmd sudo; then
82
+ return 1
83
+ fi
84
+ # If already authenticated or passwordless sudo works
85
+ if [ "${SUDO_AUTHENTICATED}" = true ] || sudo -n true >/dev/null 2>&1; then
86
+ SUDO_AUTHENTICATED=true
87
+ return 0
88
+ fi
89
+ # If previously declined or failed in this session, don't nag again
90
+ if [ "${SUDO_DECLINED}" = true ]; then
91
+ return 1
92
+ fi
93
+ # If interactive terminal, ask user whether to enter sudo password
94
+ if [ -t 0 ]; then
95
+ local action="${1:-install system packages}"
96
+ echo -e " ${CYAN}🔐 Administrator (sudo) privileges can be used to ${action}.${NC}"
97
+ local reply=""
98
+ read -r -p " Enter sudo password now? [Y/n]: " reply
99
+ reply=${reply:-Y}
100
+ if [[ "${reply}" =~ ^[Yy]$ ]]; then
101
+ if sudo -v; then
102
+ SUDO_AUTHENTICATED=true
103
+ echo -e " ${GREEN}✔ Sudo authenticated successfully.${NC}"
104
+ return 0
105
+ else
106
+ echo -e " ${YELLOW}⚠ Sudo authentication failed or cancelled. Using user-space fallback.${NC}"
107
+ SUDO_DECLINED=true
108
+ return 1
109
+ fi
110
+ else
111
+ echo -e " ${YELLOW}⏭️ Skipped sudo. Using user-space fallback.${NC}"
112
+ SUDO_DECLINED=true
113
+ return 1
114
+ fi
115
+ fi
116
+ return 1
117
+ }
118
+
119
+ # Helper to check if Node.js & npm meet Angular CLI 22 requirement (>= 22.22.0, >= 24.15.0, or >= 26.0.0)
120
+ is_node_compatible() {
121
+ if ! has_cmd node || ! has_cmd npm; then
122
+ return 1
123
+ fi
124
+ local node_ver
125
+ node_ver="$(node -v 2>/dev/null | tr -d 'v')"
126
+ [ -z "${node_ver}" ] && return 1
127
+ local major minor
128
+ major="$(echo "${node_ver}" | cut -d. -f1)"
129
+ minor="$(echo "${node_ver}" | cut -d. -f2)"
130
+ if [ "${major}" -ge 26 ] 2>/dev/null; then
131
+ return 0
132
+ elif [ "${major}" -ge 24 ] 2>/dev/null; then
133
+ [ "${minor}" -ge 15 ] 2>/dev/null && return 0
134
+ elif [ "${major}" -ge 22 ] 2>/dev/null; then
135
+ [ "${minor}" -ge 22 ] 2>/dev/null && return 0
136
+ fi
137
+ return 1
138
+ }
139
+
140
+ echo -e "${BOLD}1. Detecting Operating System & Environment...${NC}"
141
+ echo -e " Platform: ${BLUE}${OS_TYPE}${NC} (${ARCH_TYPE})"
142
+ echo -e " Root Dir: ${DIM}${ROOT_DIR}${NC}"
143
+
144
+ # Function to install system toolchains (ADB, FFmpeg, scrcpy)
145
+ install_system_packages() {
146
+ echo -e "\n${BOLD}2. Checking System Toolchains (ADB, FFmpeg, scrcpy)...${NC}"
147
+
148
+ local need_adb=false
149
+ local need_ffmpeg=false
150
+ local need_scrcpy=false
151
+
152
+ if ! has_cmd adb; then
153
+ for candidate in \
154
+ "${ANDROID_HOME:-}/platform-tools" \
155
+ "${ANDROID_SDK_ROOT:-}/platform-tools" \
156
+ "${HOME}/Library/Android/sdk/platform-tools" \
157
+ "${HOME}/Android/Sdk/platform-tools" \
158
+ "${HOME}/.local/share/platform-tools" \
159
+ "/opt/homebrew/bin" \
160
+ "/usr/local/bin"; do
161
+ if [ -n "${candidate}" ] && [ -x "${candidate}/adb" ]; then
162
+ export PATH="${candidate}:${PATH}"
163
+ break
164
+ fi
165
+ done
166
+ fi
167
+
168
+ if ! has_cmd adb; then need_adb=true; fi
169
+ if ! has_cmd ffmpeg; then need_ffmpeg=true; fi
170
+ if ! has_cmd scrcpy; then need_scrcpy=true; fi
171
+
172
+ if [ "${need_adb}" = false ] && [ "${need_ffmpeg}" = false ] && [ "${need_scrcpy}" = false ]; then
173
+ echo -e " ${GREEN}✓ All core system tools are already installed.${NC}"
174
+ if has_cmd adb; then
175
+ (unset ADB_SERVER_SOCKET; adb start-server >/dev/null 2>&1 || true)
176
+ fi
177
+ return 0
178
+ fi
179
+
180
+ echo -e " ${YELLOW}! Missing toolchains detected:${NC}"
181
+ [ "${need_adb}" = true ] && echo -e " • [ADB] Android Debug Bridge"
182
+ [ "${need_ffmpeg}" = true ] && echo -e " • [FFmpeg] Video encoding/trimming framework"
183
+ [ "${need_scrcpy}" = true ] && echo -e " • [scrcpy] Real-time Android screen mirroring"
184
+
185
+ echo -e " Attempting automated installation..."
186
+
187
+ if [ "${OS_TYPE}" = "Darwin" ]; then
188
+ # Ensure Homebrew environment variables to prevent hanging
189
+ export HOMEBREW_NO_AUTO_UPDATE=1
190
+ export HOMEBREW_NO_INSTALL_CLEANUP=1
191
+ export HOMEBREW_NO_ENV_HINTS=1
192
+
193
+ if ! has_cmd brew; then
194
+ if [ -x "/opt/homebrew/bin/brew" ]; then
195
+ eval "$(/opt/homebrew/bin/brew shellenv)"
196
+ elif [ -x "/usr/local/bin/brew" ]; then
197
+ eval "$(/usr/local/bin/brew shellenv)"
198
+ fi
199
+ fi
200
+
201
+ if has_cmd brew; then
202
+ echo -e " ${CYAN}Detected Homebrew ($(brew --version | head -n1)). Installing missing components...${NC}"
203
+ if [ "${need_adb}" = true ]; then
204
+ echo -e " 📦 Installing ${BOLD}android-platform-tools${NC}..."
205
+ brew install --cask android-platform-tools || brew install android-platform-tools || true
206
+ fi
207
+ if [ "${need_ffmpeg}" = true ]; then
208
+ echo -e " 📦 Installing ${BOLD}ffmpeg${NC}..."
209
+ brew install ffmpeg || true
210
+ fi
211
+ if [ "${need_scrcpy}" = true ]; then
212
+ echo -e " 📦 Installing ${BOLD}scrcpy${NC}..."
213
+ brew install scrcpy || true
214
+ fi
215
+ else
216
+ echo -e " ${YELLOW}⚠ Homebrew not found. Please install Homebrew or install tools manually:${NC}"
217
+ echo -e " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
218
+ echo -e " brew install --cask android-platform-tools && brew install ffmpeg scrcpy"
219
+ fi
220
+
221
+ elif [ "${OS_TYPE}" = "Linux" ]; then
222
+ local PKGS=()
223
+ [ "${need_adb}" = true ] && PKGS+=("adb")
224
+ [ "${need_ffmpeg}" = true ] && PKGS+=("ffmpeg")
225
+ [ "${need_scrcpy}" = true ] && PKGS+=("scrcpy")
226
+
227
+ if request_sudo "install missing system components (${PKGS[*]})"; then
228
+ local SUDO_PREFIX=""
229
+ if [ "$(id -u)" -ne 0 ]; then
230
+ SUDO_PREFIX="sudo"
231
+ fi
232
+
233
+ if has_cmd apt-get; then
234
+ echo -e " ${CYAN}Detected Debian/Ubuntu (apt-get). Installing missing packages...${NC}"
235
+ DEBIAN_FRONTEND=noninteractive ${SUDO_PREFIX} apt-get -o DPkg::Lock::Timeout=3 -o Acquire::http::Timeout=3 -o Acquire::https::Timeout=3 install -y "${PKGS[@]}" 2>/dev/null || true
236
+ elif has_cmd dnf; then
237
+ echo -e " ${CYAN}Detected Fedora/RHEL (dnf). Installing packages...${NC}"
238
+ local DNF_PKGS=()
239
+ [ "${need_adb}" = true ] && DNF_PKGS+=("android-tools")
240
+ [ "${need_ffmpeg}" = true ] && DNF_PKGS+=("ffmpeg")
241
+ [ "${need_scrcpy}" = true ] && DNF_PKGS+=("scrcpy")
242
+ ${SUDO_PREFIX} dnf install -y "${DNF_PKGS[@]}" 2>/dev/null || true
243
+ elif has_cmd pacman; then
244
+ echo -e " ${CYAN}Detected Arch Linux (pacman). Installing packages...${NC}"
245
+ local PAC_PKGS=()
246
+ [ "${need_adb}" = true ] && PAC_PKGS+=("android-tools")
247
+ [ "${need_ffmpeg}" = true ] && PAC_PKGS+=("ffmpeg")
248
+ [ "${need_scrcpy}" = true ] && PAC_PKGS+=("scrcpy")
249
+ ${SUDO_PREFIX} pacman -S --noconfirm "${PAC_PKGS[@]}" 2>/dev/null || true
250
+ fi
251
+ fi
252
+
253
+ # Fallback: if scrcpy is still missing, install official precompiled portable scrcpy in user space
254
+ if ! has_cmd scrcpy; then
255
+ local SCRCPY_ARCH=""
256
+ case "${ARCH_TYPE}" in
257
+ x86_64|amd64) SCRCPY_ARCH="x86_64" ;;
258
+ aarch64|arm64) SCRCPY_ARCH="aarch64" ;;
259
+ esac
260
+ if [ -n "${SCRCPY_ARCH}" ]; then
261
+ local SCRCPY_DIR="${HOME}/.local/share/scrcpy"
262
+ if [ ! -x "${SCRCPY_DIR}/scrcpy" ]; then
263
+ echo -e " ${CYAN}📦 Installing portable scrcpy in user space (~/.local)...${NC}"
264
+ mkdir -p "${SCRCPY_DIR}" "${HOME}/.local/bin"
265
+ local SCRCPY_URL="https://github.com/Genymobile/scrcpy/releases/download/v4.1/scrcpy-linux-${SCRCPY_ARCH}-v4.1.tar.gz"
266
+ if curl -fsSL --connect-timeout 5 --max-time 30 "${SCRCPY_URL}" | tar -xz -C "${SCRCPY_DIR}" --strip-components=1 2>/dev/null; then
267
+ ln -sf "${SCRCPY_DIR}/scrcpy" "${HOME}/.local/bin/scrcpy"
268
+ export PATH="${SCRCPY_DIR}:${PATH}"
269
+ echo -e " ${GREEN}✓ scrcpy installed in user space.${NC}"
270
+ fi
271
+ else
272
+ ln -sf "${SCRCPY_DIR}/scrcpy" "${HOME}/.local/bin/scrcpy"
273
+ export PATH="${SCRCPY_DIR}:${PATH}"
274
+ fi
275
+ fi
276
+ fi
277
+
278
+ # User-space fallback for adb if missing and no root privileges
279
+ if ! has_cmd adb; then
280
+ if [[ "${ARCH_TYPE}" = "x86_64" || "${ARCH_TYPE}" = "amd64" ]]; then
281
+ local PT_DIR="${HOME}/.local/share/platform-tools"
282
+ if [ ! -x "${PT_DIR}/adb" ]; then
283
+ echo -e " ${CYAN}📦 Installing Android platform-tools (adb) in user space...${NC}"
284
+ local TEMP_ZIP="/tmp/platform-tools-$$.zip"
285
+ if curl -fsSL --connect-timeout 5 --max-time 30 "https://dl.google.com/android/repository/platform-tools-latest-linux.zip" -o "${TEMP_ZIP}" 2>/dev/null; then
286
+ mkdir -p "${HOME}/.local/share" "${HOME}/.local/bin"
287
+ if has_cmd unzip; then
288
+ unzip -q -o "${TEMP_ZIP}" -d "${HOME}/.local/share" 2>/dev/null || true
289
+ else
290
+ python3 -m zipfile -e "${TEMP_ZIP}" "${HOME}/.local/share" 2>/dev/null || true
291
+ fi
292
+ rm -f "${TEMP_ZIP}"
293
+ fi
294
+ fi
295
+ if [ -x "${PT_DIR}/adb" ]; then
296
+ ln -sf "${PT_DIR}/adb" "${HOME}/.local/bin/adb"
297
+ export PATH="${PT_DIR}:${PATH}"
298
+ echo -e " ${GREEN}✓ adb installed in user space.${NC}"
299
+ fi
300
+ fi
301
+ fi
302
+ fi
303
+
304
+ # Ensure local ADB daemon is warm & listening
305
+ if has_cmd adb; then
306
+ (unset ADB_SERVER_SOCKET; adb start-server >/dev/null 2>&1 || true)
307
+ fi
308
+ }
309
+
310
+ # Function to ensure uv is installed and ready
311
+ ensure_uv() {
312
+ echo -e "\n${BOLD}3. Checking Python Package Manager (uv)...${NC}"
313
+ if ! has_cmd uv; then
314
+ echo -e " ${YELLOW}uv not found. Installing Astral uv (ultra-fast Python package manager)...${NC}"
315
+ curl -LsSf https://astral.sh/uv/install.sh | sh
316
+ export PATH="${HOME}/.local/bin:${HOME}/.cargo/bin:${PATH}"
317
+ fi
318
+
319
+ # Explicitly persist PATH to shell configuration files if not present
320
+ local export_line='export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"'
321
+ for rc in "${HOME}/.bashrc" "${HOME}/.zshrc" "${HOME}/.profile"; do
322
+ if [ -f "${rc}" ]; then
323
+ if ! grep -qs 'HOME/\.local/bin' "${rc}"; then
324
+ echo -e "\n# Added by Artemis installer\n${export_line}" >> "${rc}"
325
+ fi
326
+ fi
327
+ done
328
+
329
+ # If /usr/local/bin is writable or sudo is available, try to create a symlink to make it available immediately
330
+ local uv_bin=""
331
+ if [ -x "${HOME}/.local/bin/uv" ]; then
332
+ uv_bin="${HOME}/.local/bin/uv"
333
+ elif command -v uv >/dev/null 2>&1; then
334
+ uv_bin="$(command -v uv)"
335
+ fi
336
+
337
+ if [ -n "${uv_bin}" ]; then
338
+ if [ -w "/usr/local/bin" ]; then
339
+ ln -sf "${uv_bin}" /usr/local/bin/uv 2>/dev/null || true
340
+ [ -x "${HOME}/.local/bin/uvx" ] && ln -sf "${HOME}/.local/bin/uvx" /usr/local/bin/uvx 2>/dev/null || true
341
+ elif has_cmd sudo; then
342
+ sudo -n ln -sf "${uv_bin}" /usr/local/bin/uv 2>/dev/null || true
343
+ [ -x "${HOME}/.local/bin/uvx" ] && sudo -n ln -sf "${HOME}/.local/bin/uvx" /usr/local/bin/uvx 2>/dev/null || true
344
+ fi
345
+ fi
346
+
347
+ if has_cmd uv; then
348
+ echo -e " ${GREEN}✓ uv is ready ($(uv --version))${NC}"
349
+ else
350
+ echo -e " ${RED}✗ Failed to find uv in PATH. Please add ~/.local/bin to your PATH.${NC}"
351
+ exit 1
352
+ fi
353
+ }
354
+
355
+ # Function to setup Python runtime and sync dependencies
356
+ setup_python_env() {
357
+ echo -e "\n${BOLD}4. Configuring Python Runtime & Syncing Dependencies...${NC}"
358
+ echo -e " ${BLUE}📦 Running uv sync (auto-provisioning Python >=3.12 & dependencies)...${NC}"
359
+ uv sync
360
+ echo -e " ${GREEN}✓ Python runtime and dependencies synced successfully.${NC}"
361
+ }
362
+
363
+ # Function to initialize .env configuration
364
+ setup_env_file() {
365
+ echo -e "\n${BOLD}5. Checking Environment Configuration (.env)...${NC}"
366
+ if [ ! -f ".env" ]; then
367
+ if [ -f ".env.example" ]; then
368
+ cp .env.example .env
369
+ echo -e " ${GREEN}✓ Created .env template from .env.example.${NC}"
370
+ else
371
+ touch .env
372
+ echo -e " ${GREEN}✓ Created empty .env file.${NC}"
373
+ fi
374
+ else
375
+ echo -e " ${GREEN}✓ .env configuration file exists.${NC}"
376
+ fi
377
+ }
378
+
379
+ # Function to check and build Showcase UI
380
+ setup_showcase_ui() {
381
+ echo -e "\n${BOLD}6. Checking Showcase UI Build (Angular)...${NC}"
382
+ local SHOWCASE_INDEX="${ROOT_DIR}/apps/showcase_ui/dist/frontend/browser/index.html"
383
+ local SHOWCASE_INDEX_ALT1="${ROOT_DIR}/apps/showcase_ui/dist/browser/index.html"
384
+ local SHOWCASE_INDEX_ALT2="${ROOT_DIR}/apps/showcase_ui/dist/index.html"
385
+ if [ ! -f "${SHOWCASE_INDEX}" ] && [ ! -f "${SHOWCASE_INDEX_ALT1}" ] && [ ! -f "${SHOWCASE_INDEX_ALT2}" ]; then
386
+ # Try loading nvm if available in user environment
387
+ export NVM_DIR="${HOME}/.nvm"
388
+ if [ -s "${NVM_DIR}/nvm.sh" ]; then
389
+ # shellcheck disable=SC1090,SC1091
390
+ . "${NVM_DIR}/nvm.sh" 2>/dev/null || true
391
+ fi
392
+ if [ -d "${HOME}/.local/share/node/bin" ] && [[ ":${PATH}:" != *":${HOME}/.local/share/node/bin:"* ]]; then
393
+ export PATH="${HOME}/.local/share/node/bin:${PATH}"
394
+ fi
395
+
396
+ # Check if nvm already has a compatible Node version installed
397
+ if ! is_node_compatible && (command -v nvm >/dev/null 2>&1 || type nvm >/dev/null 2>&1); then
398
+ nvm use 22 >/dev/null 2>&1 || true
399
+ fi
400
+
401
+ if ! is_node_compatible; then
402
+ if has_cmd node; then
403
+ local current_node="$(node -v 2>/dev/null)"
404
+ echo -e " ${YELLOW}⚡ Detected Node.js ${current_node}, but Angular CLI requires Node.js >= v22.22.0. Upgrading Node.js...${NC}"
405
+ else
406
+ echo -e " ${YELLOW}⚡ Node.js/npm not found. Auto-installing Node.js 22 LTS for Showcase UI compilation...${NC}"
407
+ fi
408
+
409
+ # 1. Try installing Node 22 via nvm if available
410
+ if command -v nvm >/dev/null 2>&1 || type nvm >/dev/null 2>&1; then
411
+ echo -e " ${CYAN}📦 Installing Node.js 22 LTS via nvm...${NC}"
412
+ nvm install 22 >/dev/null 2>&1 || true
413
+ nvm use 22 >/dev/null 2>&1 || true
414
+ fi
415
+
416
+ # 2. Try brew on macOS
417
+ if ! is_node_compatible && [ "${OS_TYPE}" = "Darwin" ] && has_cmd brew; then
418
+ brew install node >/dev/null 2>&1 || brew upgrade node >/dev/null 2>&1 || true
419
+ fi
420
+
421
+ # 3. Try Linux package managers with sudo / root
422
+ if ! is_node_compatible && [ "${OS_TYPE}" = "Linux" ]; then
423
+ if request_sudo "install or upgrade Node.js to >= 22.22.0"; then
424
+ local SUDO_PREFIX=""
425
+ if [ "$(id -u)" -ne 0 ]; then SUDO_PREFIX="sudo"; fi
426
+ if has_cmd apt-get; then
427
+ echo -e " ${CYAN}📦 Configuring NodeSource Node.js 22 LTS repository...${NC}"
428
+ curl -fsSL --connect-timeout 5 --max-time 30 https://deb.nodesource.com/setup_22.x | ${SUDO_PREFIX} bash - >/dev/null 2>&1 || true
429
+ ${SUDO_PREFIX} apt-get -o DPkg::Lock::Timeout=5 install -y -qq nodejs 2>/dev/null || true
430
+ elif has_cmd dnf; then
431
+ curl -fsSL --connect-timeout 5 --max-time 30 https://rpm.nodesource.com/setup_22.x | ${SUDO_PREFIX} bash - >/dev/null 2>&1 || true
432
+ ${SUDO_PREFIX} dnf install -y nodejs || true
433
+ elif has_cmd pacman; then
434
+ ${SUDO_PREFIX} pacman -S --noconfirm nodejs npm || true
435
+ fi
436
+ fi
437
+ fi
438
+
439
+ # 4. Standalone portable Node.js LTS (zero-admin user space fallback, guaranteed compatible)
440
+ if ! is_node_compatible; then
441
+ local ARCH="$(uname -m)"
442
+ local NODE_ARCH=""
443
+ case "${ARCH}" in
444
+ x86_64|amd64) NODE_ARCH="x64" ;;
445
+ aarch64|arm64) NODE_ARCH="arm64" ;;
446
+ esac
447
+ local OS_SYS="$(uname -s | tr '[:upper:]' '[:lower:]')"
448
+ if [ -n "${NODE_ARCH}" ] && { [ "${OS_SYS}" = "linux" ] || [ "${OS_SYS}" = "darwin" ]; }; then
449
+ local NODE_VER="v22.23.2"
450
+ local NODE_DIR="${HOME}/.local/share/node"
451
+ echo -e " ${CYAN}📦 Installing portable Node.js ${NODE_VER} in user space (~/.local)...${NC}"
452
+ rm -rf "${NODE_DIR}"
453
+ mkdir -p "${NODE_DIR}" "${HOME}/.local/bin"
454
+ if curl -fsSL --connect-timeout 5 --max-time 60 "https://nodejs.org/dist/${NODE_VER}/node-${NODE_VER}-${OS_SYS}-${NODE_ARCH}.tar.gz" | tar -xz -C "${NODE_DIR}" --strip-components=1 2>/dev/null; then
455
+ ln -sf "${NODE_DIR}/bin/node" "${HOME}/.local/bin/node"
456
+ ln -sf "${NODE_DIR}/bin/npm" "${HOME}/.local/bin/npm"
457
+ ln -sf "${NODE_DIR}/bin/npx" "${HOME}/.local/bin/npx"
458
+ export PATH="${NODE_DIR}/bin:${HOME}/.local/bin:${PATH}"
459
+ hash -r 2>/dev/null || true
460
+ echo -e " ${GREEN}✓ Portable Node.js ${NODE_VER} installed in user space.${NC}"
461
+ fi
462
+ fi
463
+ fi
464
+ fi
465
+
466
+ if is_node_compatible; then
467
+ echo -e " ${GREEN}✓ Node.js $(node -v 2>/dev/null) and npm $(npm -v 2>/dev/null) ready.${NC}"
468
+ echo -e " ${YELLOW}🎨 Building Angular Showcase UI...${NC}"
469
+ (
470
+ cd "${ROOT_DIR}/apps/showcase_ui"
471
+ npm install --silent
472
+ CLI_NODE_VERSION="${ROOT_DIR}/apps/showcase_ui/node_modules/@angular/cli/src/utilities/node-version.js"
473
+ if [ -f "${CLI_NODE_VERSION}" ]; then
474
+ if [ "${OS_TYPE}" = "Darwin" ]; then
475
+ sed -i '' 's/22\.22\.3/22.22.0/g' "${CLI_NODE_VERSION}" 2>/dev/null || true
476
+ else
477
+ sed -i 's/22\.22\.3/22.22.0/g' "${CLI_NODE_VERSION}" 2>/dev/null || true
478
+ fi
479
+ fi
480
+ npm run build
481
+ )
482
+ echo -e " ${GREEN}✓ Showcase UI compiled successfully.${NC}"
483
+ else
484
+ echo -e " ${YELLOW}⚠ Could not configure compatible Node.js (>= 22.22.0). Showcase UI will show fallback notice on launch.${NC}"
485
+ fi
486
+ else
487
+ echo -e " ${GREEN}✓ Showcase UI build already exists.${NC}"
488
+ fi
489
+ }
490
+
491
+ # Function to display system readiness report
492
+ verify_readiness() {
493
+ echo -e "\n${BOLD}7. Toolchain Readiness Summary:${NC}"
494
+
495
+ local tools=("adb" "ffmpeg" "scrcpy" "uv" "python3" "npm")
496
+ for t in "${tools[@]}"; do
497
+ if has_cmd "$t"; then
498
+ local loc
499
+ loc="$(command -v "$t")"
500
+ if [ "$t" = "npm" ] && has_cmd node; then
501
+ local n_ver
502
+ n_ver="$(node -v 2>/dev/null || echo '')"
503
+ echo -e " ${GREEN}✔ ${t}${NC} (${n_ver}) -> ${DIM}${loc}${NC}"
504
+ else
505
+ echo -e " ${GREEN}✔ ${t}${NC} -> ${DIM}${loc}${NC}"
506
+ fi
507
+ else
508
+ echo -e " ${YELLOW}○ ${t}${NC} -> ${YELLOW}Not found in PATH (Fallbacks active)${NC}"
509
+ fi
510
+ done
511
+ }
512
+
513
+ # Run setup workflow
514
+ install_system_packages
515
+ ensure_uv
516
+ setup_python_env
517
+ setup_env_file
518
+ setup_showcase_ui
519
+ verify_readiness
520
+
521
+ echo -e "\n${BOLD}${CYAN}======================================================${NC}"
522
+ echo -e "${BOLD}${GREEN} ✨ Artemis Environment Ready! ${NC}"
523
+ echo -e "${BOLD}${CYAN}======================================================${NC}"
524
+
525
+ if [ "${AUTO_LAUNCH}" = true ]; then
526
+ echo -e "\n${GREEN}🚀 Auto-launching Showcase UI...${NC}"
527
+ exec uv run artemis ui --open
528
+ else
529
+ echo -e "To launch the unified UI & live device onboarding:"
530
+ echo -e " 👉 ${BOLD}${CYAN}./start.sh${NC} (Recommended - automatically sets up environment)"
531
+ echo -e " 👉 ${BOLD}source ~/.bashrc && uv run artemis ui${NC} (or open a new terminal tab)"
532
+ echo ""
533
+ fi
@@ -0,0 +1,97 @@
1
+ """Fail CI when exception-handler or type-ignore counts exceed the baseline.
2
+
3
+ Lower the committed baseline when removing existing debt.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import ast
10
+ import json
11
+ from pathlib import Path
12
+ import re
13
+ import sys
14
+
15
+
16
+ ROOT = Path(__file__).resolve().parents[1]
17
+ SOURCE_ROOTS = (
18
+ ROOT / "artemis",
19
+ ROOT / "mcp_server",
20
+ ROOT / "apps" / "admin_console",
21
+ ROOT / "third_party",
22
+ )
23
+ BASELINE_PATH = ROOT / ".quality-baseline.json"
24
+
25
+
26
+ def _exception_names(node: ast.expr | None) -> set[str]:
27
+ if node is None:
28
+ return {"bare-except"}
29
+ if isinstance(node, ast.Name):
30
+ return {node.id}
31
+ if isinstance(node, ast.Attribute):
32
+ return {node.attr}
33
+ if isinstance(node, ast.Tuple):
34
+ names: set[str] = set()
35
+ for child in node.elts:
36
+ names.update(_exception_names(child))
37
+ return names
38
+ return set()
39
+
40
+
41
+ def collect_metrics() -> dict[str, int]:
42
+ metrics = {
43
+ "broad_exception_handlers": 0,
44
+ "silent_broad_exception_handlers": 0,
45
+ "type_ignore_comments": 0,
46
+ }
47
+ for source_root in SOURCE_ROOTS:
48
+ for path in source_root.rglob("*.py"):
49
+ text = path.read_text(encoding="utf-8-sig")
50
+ metrics["type_ignore_comments"] += len(re.findall(r"#\s*type:\s*ignore\b", text))
51
+ try:
52
+ tree = ast.parse(text, filename=str(path))
53
+ except SyntaxError as exc:
54
+ raise RuntimeError(f"Cannot inspect invalid Python file {path}: {exc}") from exc
55
+ for node in ast.walk(tree):
56
+ if not isinstance(node, ast.ExceptHandler):
57
+ continue
58
+ names = _exception_names(node.type)
59
+ if not names.intersection({"Exception", "BaseException", "bare-except"}):
60
+ continue
61
+ metrics["broad_exception_handlers"] += 1
62
+ if node.body and all(isinstance(statement, ast.Pass) for statement in node.body):
63
+ metrics["silent_broad_exception_handlers"] += 1
64
+ return metrics
65
+
66
+
67
+ def main() -> int:
68
+ parser = argparse.ArgumentParser(description=__doc__)
69
+ parser.add_argument("--print-current", action="store_true", help="print metrics as JSON")
70
+ args = parser.parse_args()
71
+
72
+ current = collect_metrics()
73
+ if args.print_current:
74
+ print(json.dumps(current, indent=2, sort_keys=True))
75
+ return 0
76
+
77
+ baseline = json.loads(BASELINE_PATH.read_text(encoding="utf-8"))
78
+ regressions = {
79
+ name: (baseline[name], value)
80
+ for name, value in current.items()
81
+ if value > int(baseline[name])
82
+ }
83
+ if regressions:
84
+ for name, (allowed, actual) in regressions.items():
85
+ print(f"quality regression: {name} increased from {allowed} to {actual}")
86
+ return 1
87
+
88
+ print("quality ratchet passed")
89
+ for name, value in current.items():
90
+ improvement = int(baseline[name]) - value
91
+ suffix = f" ({improvement} below baseline)" if improvement else ""
92
+ print(f" {name}: {value}{suffix}")
93
+ return 0
94
+
95
+
96
+ if __name__ == "__main__":
97
+ sys.exit(main())