aria-code 0.52.0__py3-none-any.whl

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 (478) hide show
  1. aria_code/__init__.py +42 -0
  2. aria_code/_version.py +20 -0
  3. aria_code/adk_apps/__init__.py +1 -0
  4. aria_code/adk_apps/aria_code_review/__init__.py +1 -0
  5. aria_code/adk_apps/aria_code_review/agent.py +32 -0
  6. aria_code/adk_apps/aria_research/__init__.py +1 -0
  7. aria_code/adk_apps/aria_research/agent.py +31 -0
  8. aria_code/agents/__init__.py +34 -0
  9. aria_code/agents/base.py +318 -0
  10. aria_code/agents/code_review.py +200 -0
  11. aria_code/agents/deep/__init__.py +37 -0
  12. aria_code/agents/deep/calibration_loop.py +144 -0
  13. aria_code/agents/deep/critic.py +125 -0
  14. aria_code/agents/deep/deepen.py +193 -0
  15. aria_code/agents/deep/models.py +149 -0
  16. aria_code/agents/deep/pipeline.py +164 -0
  17. aria_code/agents/deep/quant_fusion.py +192 -0
  18. aria_code/agents/deep/themes.py +95 -0
  19. aria_code/agents/deep/tiers.py +106 -0
  20. aria_code/agents/engineering/__init__.py +14 -0
  21. aria_code/agents/engineering/coder.py +222 -0
  22. aria_code/agents/engineering/tester.py +145 -0
  23. aria_code/agents/financial/__init__.py +21 -0
  24. aria_code/agents/financial/cashflow_burn.py +119 -0
  25. aria_code/agents/financial/catalyst.py +279 -0
  26. aria_code/agents/financial/corporate_finance.py +175 -0
  27. aria_code/agents/financial/debate.py +224 -0
  28. aria_code/agents/financial/earnings.py +303 -0
  29. aria_code/agents/financial/fundamental.py +191 -0
  30. aria_code/agents/financial/macro.py +99 -0
  31. aria_code/agents/financial/news.py +207 -0
  32. aria_code/agents/financial/risk.py +165 -0
  33. aria_code/agents/financial/sector.py +279 -0
  34. aria_code/agents/financial/strategist.py +121 -0
  35. aria_code/agents/financial/stripe_revenue.py +127 -0
  36. aria_code/agents/financial/synthesis.py +274 -0
  37. aria_code/agents/financial/technical.py +258 -0
  38. aria_code/agents/orchestrator.py +44 -0
  39. aria_code/agents/portfolio_agent.py +363 -0
  40. aria_code/agents/realty/__init__.py +62 -0
  41. aria_code/agents/realty/asset_diagnosis.py +152 -0
  42. aria_code/agents/realty/business_match.py +166 -0
  43. aria_code/agents/realty/cashflow_verify.py +209 -0
  44. aria_code/agents/realty/contract_rules.py +210 -0
  45. aria_code/agents/realty/energy_anomaly.py +189 -0
  46. aria_code/agents/realty/exit_settlement.py +208 -0
  47. aria_code/agents/realty/fulfillment_risk.py +206 -0
  48. aria_code/agents/realty/ops_optimize.py +160 -0
  49. aria_code/agents/realty/revenue_share.py +215 -0
  50. aria_code/agents/registry.py +158 -0
  51. aria_code/agents/signal_scheme.py +111 -0
  52. aria_code/agents/sports/__init__.py +0 -0
  53. aria_code/agents/sports/football_agent.py +197 -0
  54. aria_code/agents/supervisor.py +113 -0
  55. aria_code/agents/team.py +492 -0
  56. aria_code/agents/warehouse/__init__.py +25 -0
  57. aria_code/agents/warehouse/contracts.py +26 -0
  58. aria_code/agents/warehouse/dashboard.py +155 -0
  59. aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
  60. aria_code/agents/warehouse/inbound_exceptions.py +25 -0
  61. aria_code/agents/warehouse/inventory_health.py +24 -0
  62. aria_code/agents/warehouse/local_run.py +71 -0
  63. aria_code/agents/warehouse/local_snapshot.py +132 -0
  64. aria_code/agents/warehouse/logistics_cost.py +84 -0
  65. aria_code/agents/warehouse/logistics_sync.py +21 -0
  66. aria_code/agents/warehouse/workflow.py +31 -0
  67. aria_code/aliyun_data_client.py +4 -0
  68. aria_code/apps/README.md +12 -0
  69. aria_code/apps/__init__.py +2 -0
  70. aria_code/apps/channels/README.md +15 -0
  71. aria_code/apps/channels/__init__.py +17 -0
  72. aria_code/apps/channels/intake.py +92 -0
  73. aria_code/apps/channels/registry.py +95 -0
  74. aria_code/apps/channels/tradingview.py +94 -0
  75. aria_code/apps/cli/README.md +13 -0
  76. aria_code/apps/cli/__init__.py +2 -0
  77. aria_code/apps/cli/_optional.py +120 -0
  78. aria_code/apps/cli/bootstrap.py +145 -0
  79. aria_code/apps/cli/broker_render.py +206 -0
  80. aria_code/apps/cli/codegen_paths.py +29 -0
  81. aria_code/apps/cli/commands/__init__.py +16 -0
  82. aria_code/apps/cli/commands/_business_render.py +78 -0
  83. aria_code/apps/cli/commands/_ui.py +71 -0
  84. aria_code/apps/cli/commands/analysis_cmds.py +369 -0
  85. aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
  86. aria_code/apps/cli/commands/auth_cmds.py +239 -0
  87. aria_code/apps/cli/commands/backtest_cmds.py +2543 -0
  88. aria_code/apps/cli/commands/broker_cmds.py +1234 -0
  89. aria_code/apps/cli/commands/business_workflow_cmds.py +319 -0
  90. aria_code/apps/cli/commands/canvas_cmds.py +77 -0
  91. aria_code/apps/cli/commands/catalog.py +87 -0
  92. aria_code/apps/cli/commands/core_cmds.py +2520 -0
  93. aria_code/apps/cli/commands/data_cmds.py +457 -0
  94. aria_code/apps/cli/commands/diagnostic_cmds.py +230 -0
  95. aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
  96. aria_code/apps/cli/commands/file_cmds.py +312 -0
  97. aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
  98. aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
  99. aria_code/apps/cli/commands/market.py +434 -0
  100. aria_code/apps/cli/commands/market_cmds.py +1381 -0
  101. aria_code/apps/cli/commands/market_context.py +425 -0
  102. aria_code/apps/cli/commands/market_render.py +7 -0
  103. aria_code/apps/cli/commands/model_cmds.py +2153 -0
  104. aria_code/apps/cli/commands/ops_cmds.py +721 -0
  105. aria_code/apps/cli/commands/orchestrator_cmds.py +179 -0
  106. aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
  107. aria_code/apps/cli/commands/portfolio_cmds.py +1278 -0
  108. aria_code/apps/cli/commands/report.py +437 -0
  109. aria_code/apps/cli/commands/scaffold_templates.py +617 -0
  110. aria_code/apps/cli/commands/session_cmds.py +236 -0
  111. aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
  112. aria_code/apps/cli/commands/team.py +669 -0
  113. aria_code/apps/cli/commands/team_render.py +8 -0
  114. aria_code/apps/cli/commands/ui_cmds.py +401 -0
  115. aria_code/apps/cli/commands/warehouse_cmds.py +79 -0
  116. aria_code/apps/cli/commands/workflow_cmds.py +480 -0
  117. aria_code/apps/cli/commands/workspace_cmds.py +1494 -0
  118. aria_code/apps/cli/config_paths.py +73 -0
  119. aria_code/apps/cli/config_store.py +78 -0
  120. aria_code/apps/cli/constants.py +88 -0
  121. aria_code/apps/cli/context.py +34 -0
  122. aria_code/apps/cli/deterministic.py +191 -0
  123. aria_code/apps/cli/direct.py +48 -0
  124. aria_code/apps/cli/finance_service_catalog.py +111 -0
  125. aria_code/apps/cli/football_reports.py +231 -0
  126. aria_code/apps/cli/github_app_auth.py +135 -0
  127. aria_code/apps/cli/google_login.py +196 -0
  128. aria_code/apps/cli/handlers/__init__.py +11 -0
  129. aria_code/apps/cli/handlers/broker_handlers.py +122 -0
  130. aria_code/apps/cli/handlers/chart_handlers.py +1309 -0
  131. aria_code/apps/cli/handlers/market_handlers.py +2513 -0
  132. aria_code/apps/cli/handlers/realty_handlers.py +114 -0
  133. aria_code/apps/cli/handlers/sports_handlers.py +22 -0
  134. aria_code/apps/cli/handlers/strategy_advice.py +82 -0
  135. aria_code/apps/cli/helpers.py +302 -0
  136. aria_code/apps/cli/hooks.py +180 -0
  137. aria_code/apps/cli/i18n.py +297 -0
  138. aria_code/apps/cli/intent.py +136 -0
  139. aria_code/apps/cli/intent_router.py +276 -0
  140. aria_code/apps/cli/intent_signals.py +344 -0
  141. aria_code/apps/cli/lifecycle_hooks.py +48 -0
  142. aria_code/apps/cli/local_tool_schemas.py +536 -0
  143. aria_code/apps/cli/main.py +29 -0
  144. aria_code/apps/cli/market_metadata.py +135 -0
  145. aria_code/apps/cli/market_universe.py +487 -0
  146. aria_code/apps/cli/message_processing.py +310 -0
  147. aria_code/apps/cli/model_catalog.py +620 -0
  148. aria_code/apps/cli/pdf_report.py +977 -0
  149. aria_code/apps/cli/plan_mode.py +139 -0
  150. aria_code/apps/cli/plotly_html.py +15 -0
  151. aria_code/apps/cli/prediction_feedback.py +202 -0
  152. aria_code/apps/cli/preflight.py +502 -0
  153. aria_code/apps/cli/project_aria.py +60 -0
  154. aria_code/apps/cli/prompt_assembly.py +59 -0
  155. aria_code/apps/cli/prompts/__init__.py +0 -0
  156. aria_code/apps/cli/prompts/coding.py +53 -0
  157. aria_code/apps/cli/prompts/select.py +142 -0
  158. aria_code/apps/cli/prompts/system_prompts.py +552 -0
  159. aria_code/apps/cli/prompts/ui.py +436 -0
  160. aria_code/apps/cli/provider_endpoints.py +237 -0
  161. aria_code/apps/cli/provider_keys.py +73 -0
  162. aria_code/apps/cli/providers/__init__.py +1 -0
  163. aria_code/apps/cli/providers/base.py +521 -0
  164. aria_code/apps/cli/providers/chat_routing.py +128 -0
  165. aria_code/apps/cli/providers/collaboration.py +98 -0
  166. aria_code/apps/cli/providers/llm/__init__.py +1 -0
  167. aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
  168. aria_code/apps/cli/providers/llm/ollama_stream.py +1594 -0
  169. aria_code/apps/cli/providers/llm/response_cache.py +60 -0
  170. aria_code/apps/cli/providers/llm/sse_stream.py +257 -0
  171. aria_code/apps/cli/providers/runtime_bridge.py +353 -0
  172. aria_code/apps/cli/providers/vertexai_stream.py +316 -0
  173. aria_code/apps/cli/runtime_consumer.py +657 -0
  174. aria_code/apps/cli/sandbox.py +32 -0
  175. aria_code/apps/cli/session_export.py +120 -0
  176. aria_code/apps/cli/session_jsonl.py +208 -0
  177. aria_code/apps/cli/session_store.py +112 -0
  178. aria_code/apps/cli/skills_catalog.py +273 -0
  179. aria_code/apps/cli/todo_tracker.py +190 -0
  180. aria_code/apps/cli/tool_executor.py +823 -0
  181. aria_code/apps/cli/tool_registry.py +34 -0
  182. aria_code/apps/cli/tool_scope.py +106 -0
  183. aria_code/apps/cli/tools/__init__.py +40 -0
  184. aria_code/apps/cli/tools/context.py +46 -0
  185. aria_code/apps/cli/tools/file_tools.py +180 -0
  186. aria_code/apps/cli/tools/market_tools.py +620 -0
  187. aria_code/apps/cli/tools/notebook_tools.py +111 -0
  188. aria_code/apps/cli/tools/system_tools.py +726 -0
  189. aria_code/apps/cli/tools/write_tools.py +845 -0
  190. aria_code/apps/cli/tradingview_bridge.py +529 -0
  191. aria_code/apps/cli/turn_planning.py +137 -0
  192. aria_code/apps/cli/update_check.py +153 -0
  193. aria_code/apps/cli/utils/__init__.py +0 -0
  194. aria_code/apps/cli/utils/market_detect.py +1597 -0
  195. aria_code/apps/daemon/README.md +14 -0
  196. aria_code/apps/vscode/README.md +115 -0
  197. aria_code/apps/vscode/package.json +70 -0
  198. aria_code/aria_cli.py +6743 -0
  199. aria_code/aria_daemon.py +1398 -0
  200. aria_code/aria_feishu_bot.py +1423 -0
  201. aria_code/aria_mcp_server.py +7 -0
  202. aria_code/aria_relay_client.py +4 -0
  203. aria_code/aria_relay_server.py +490 -0
  204. aria_code/aria_telegram_bot.py +202 -0
  205. aria_code/ariarc.py +385 -0
  206. aria_code/artifacts.py +581 -0
  207. aria_code/backtest_engine.py +4 -0
  208. aria_code/backtest_report.py +4 -0
  209. aria_code/brokers/__init__.py +78 -0
  210. aria_code/brokers/automation.py +193 -0
  211. aria_code/brokers/base.py +231 -0
  212. aria_code/brokers/capabilities.py +264 -0
  213. aria_code/brokers/cn/__init__.py +10 -0
  214. aria_code/brokers/cn/easytrader_broker.py +193 -0
  215. aria_code/brokers/cn/futu_broker.py +194 -0
  216. aria_code/brokers/cn/longbridge_broker.py +190 -0
  217. aria_code/brokers/cn/tiger_broker.py +196 -0
  218. aria_code/brokers/cn/xtquant_broker.py +225 -0
  219. aria_code/brokers/config.py +414 -0
  220. aria_code/brokers/intl/__init__.py +5 -0
  221. aria_code/brokers/intl/alpaca_broker.py +183 -0
  222. aria_code/brokers/intl/ibkr_broker.py +215 -0
  223. aria_code/brokers/intl/trading212_broker.py +183 -0
  224. aria_code/brokers/intl/webull_broker.py +156 -0
  225. aria_code/brokers/paper_broker.py +266 -0
  226. aria_code/brokers/planning.py +296 -0
  227. aria_code/brokers/registry.py +213 -0
  228. aria_code/brokers/trading.py +267 -0
  229. aria_code/canva_client.py +4 -0
  230. aria_code/change_store.py +127 -0
  231. aria_code/clients/__init__.py +0 -0
  232. aria_code/clients/aliyun_data_client.py +660 -0
  233. aria_code/clients/aria_relay_client.py +182 -0
  234. aria_code/clients/canva_client.py +284 -0
  235. aria_code/clients/figma_client.py +100 -0
  236. aria_code/clients/football_data_client.py +1671 -0
  237. aria_code/clients/kling_video_client.py +182 -0
  238. aria_code/clients/market_data_client.py +1976 -0
  239. aria_code/clients/openai_image_client.py +242 -0
  240. aria_code/clients/runway_video_client.py +157 -0
  241. aria_code/clients/warehouse_erp_client.py +105 -0
  242. aria_code/cloud_memory.py +181 -0
  243. aria_code/command_safety.py +19 -0
  244. aria_code/computer_use_tools.py +4 -0
  245. aria_code/dashboard_generator.py +579 -0
  246. aria_code/data_analysis_tools.py +4 -0
  247. aria_code/data_cleaner.py +483 -0
  248. aria_code/data_service.py +513 -0
  249. aria_code/datasources/__init__.py +23 -0
  250. aria_code/datasources/base.py +166 -0
  251. aria_code/datasources/router.py +253 -0
  252. aria_code/datasources/sources/__init__.py +15 -0
  253. aria_code/datasources/sources/akshare_source.py +269 -0
  254. aria_code/datasources/sources/alpha_vantage_source.py +203 -0
  255. aria_code/datasources/sources/edgar_source.py +219 -0
  256. aria_code/datasources/sources/finnhub_source.py +198 -0
  257. aria_code/datasources/sources/fred_source.py +223 -0
  258. aria_code/datasources/sources/tushare_source.py +143 -0
  259. aria_code/datasources/sources/web_scraper_source.py +278 -0
  260. aria_code/datasources/sources/world_bank_source.py +206 -0
  261. aria_code/datasources/sources/yfinance_source.py +152 -0
  262. aria_code/demo_player.py +207 -0
  263. aria_code/doctor.py +813 -0
  264. aria_code/domain/__init__.py +0 -0
  265. aria_code/domain/backtest_engine.py +354 -0
  266. aria_code/domain/backtest_report.py +472 -0
  267. aria_code/domain/finance_formulas.py +389 -0
  268. aria_code/domain/portfolio_ledger.py +307 -0
  269. aria_code/domain/strategy_vault.py +421 -0
  270. aria_code/evals/__init__.py +37 -0
  271. aria_code/evals/harness.py +598 -0
  272. aria_code/evals/runner.py +238 -0
  273. aria_code/external_agent_runner.py +115 -0
  274. aria_code/figma_client.py +4 -0
  275. aria_code/file_analysis_tools.py +4 -0
  276. aria_code/finance_formulas.py +4 -0
  277. aria_code/football_data_client.py +4 -0
  278. aria_code/image_gen_tools.py +4 -0
  279. aria_code/intent_classifier.py +386 -0
  280. aria_code/kling_video_client.py +4 -0
  281. aria_code/licensing.py +151 -0
  282. aria_code/local_finance_tools.py +4 -0
  283. aria_code/local_image_provider.py +4 -0
  284. aria_code/local_llm_provider.py +4 -0
  285. aria_code/macro_tools.py +4 -0
  286. aria_code/markdown_pdf.py +289 -0
  287. aria_code/market_data_client.py +4 -0
  288. aria_code/mcp_client.py +639 -0
  289. aria_code/memory_manager.py +246 -0
  290. aria_code/model_capability.py +486 -0
  291. aria_code/notification_tools.py +4 -0
  292. aria_code/openai_image_client.py +4 -0
  293. aria_code/packages/__init__.py +23 -0
  294. aria_code/packages/adk_bridge/__init__.py +11 -0
  295. aria_code/packages/adk_bridge/code_review_tools.py +49 -0
  296. aria_code/packages/adk_bridge/market_tools.py +125 -0
  297. aria_code/packages/aria_agents/__init__.py +5 -0
  298. aria_code/packages/aria_agents/manifest.py +78 -0
  299. aria_code/packages/aria_core/__init__.py +34 -0
  300. aria_code/packages/aria_core/architecture.py +269 -0
  301. aria_code/packages/aria_core/export.py +147 -0
  302. aria_code/packages/aria_core/manifest.py +65 -0
  303. aria_code/packages/aria_core/paths.py +57 -0
  304. aria_code/packages/aria_core/secure_file.py +68 -0
  305. aria_code/packages/aria_infra/__init__.py +15 -0
  306. aria_code/packages/aria_infra/arthera.py +52 -0
  307. aria_code/packages/aria_infra/doctor.py +246 -0
  308. aria_code/packages/aria_infra/product.py +40 -0
  309. aria_code/packages/aria_mcp/__init__.py +25 -0
  310. aria_code/packages/aria_mcp/bridge.py +201 -0
  311. aria_code/packages/aria_mcp/circuit.py +82 -0
  312. aria_code/packages/aria_mcp/config.py +111 -0
  313. aria_code/packages/aria_mcp/installer.py +95 -0
  314. aria_code/packages/aria_mcp/server.py +1550 -0
  315. aria_code/packages/aria_mcp/tools.py +81 -0
  316. aria_code/packages/aria_sdk/__init__.py +19 -0
  317. aria_code/packages/aria_sdk/client.py +396 -0
  318. aria_code/packages/aria_sdk/providers.py +70 -0
  319. aria_code/packages/aria_sdk/streaming.py +79 -0
  320. aria_code/packages/aria_sdk/types.py +86 -0
  321. aria_code/packages/aria_services/__init__.py +92 -0
  322. aria_code/packages/aria_services/cache.py +60 -0
  323. aria_code/packages/aria_services/context.py +356 -0
  324. aria_code/packages/aria_services/context_checkpoints.py +157 -0
  325. aria_code/packages/aria_services/data.py +11 -0
  326. aria_code/packages/aria_services/provider_health.py +268 -0
  327. aria_code/packages/aria_services/references.py +247 -0
  328. aria_code/packages/aria_services/registry.py +249 -0
  329. aria_code/packages/aria_services/research_protocol.py +57 -0
  330. aria_code/packages/aria_services/research_quality.py +186 -0
  331. aria_code/packages/aria_services/settings.py +157 -0
  332. aria_code/packages/aria_services/usage.py +151 -0
  333. aria_code/packages/aria_skills/__init__.py +46 -0
  334. aria_code/packages/aria_skills/catalog.py +149 -0
  335. aria_code/packages/aria_skills/loader.py +455 -0
  336. aria_code/packages/aria_skills/registry.py +84 -0
  337. aria_code/packages/aria_tools/__init__.py +5 -0
  338. aria_code/packages/aria_tools/financial/__init__.py +8 -0
  339. aria_code/packages/aria_tools/financial/_paths.py +130 -0
  340. aria_code/packages/aria_tools/financial/compliance.py +31 -0
  341. aria_code/packages/aria_tools/financial/factors.py +33 -0
  342. aria_code/packages/aria_tools/financial/risk_tools.py +33 -0
  343. aria_code/packages/aria_tools/financial/strategy.py +31 -0
  344. aria_code/packages/aria_tools/registry.py +128 -0
  345. aria_code/packages/quant_engine/__init__.py +34 -0
  346. aria_code/packages/quant_engine/_namespace.py +62 -0
  347. aria_code/packages/quant_engine/backtest/__init__.py +14 -0
  348. aria_code/packages/quant_engine/backtest/core.py +53 -0
  349. aria_code/packages/quant_engine/backtest/engine.py +96 -0
  350. aria_code/packages/quant_engine/portfolio/__init__.py +13 -0
  351. aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
  352. aria_code/packages/quant_engine/sports/__init__.py +80 -0
  353. aria_code/packages/quant_engine/sports/calibrator.py +354 -0
  354. aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
  355. aria_code/packages/quant_engine/sports/elo.py +300 -0
  356. aria_code/packages/quant_engine/sports/form.py +188 -0
  357. aria_code/packages/quant_engine/sports/h2h.py +195 -0
  358. aria_code/packages/quant_engine/sports/ml_model.py +377 -0
  359. aria_code/packages/quant_engine/sports/predictor.py +312 -0
  360. aria_code/packages/quant_engine/sports/tracker.py +665 -0
  361. aria_code/packages/quant_engine/stochastic/__init__.py +35 -0
  362. aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  363. aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
  364. aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  365. aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  366. aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
  367. aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  368. aria_code/packs/__init__.py +85 -0
  369. aria_code/packs/base.py +168 -0
  370. aria_code/packs/finance/__init__.py +250 -0
  371. aria_code/packs/logistics/__init__.py +169 -0
  372. aria_code/packs/payments/__init__.py +139 -0
  373. aria_code/packs/realty/__init__.py +190 -0
  374. aria_code/packs/registry.py +202 -0
  375. aria_code/packs/rules.py +90 -0
  376. aria_code/plan_utils.py +194 -0
  377. aria_code/plugin_loader.py +328 -0
  378. aria_code/portfolio_ledger.py +4 -0
  379. aria_code/preview_server.py +449 -0
  380. aria_code/privacy/__init__.py +5 -0
  381. aria_code/privacy/feedback.py +123 -0
  382. aria_code/project_review.py +278 -0
  383. aria_code/project_review_api.py +235 -0
  384. aria_code/project_review_server.py +18 -0
  385. aria_code/project_tools.py +4 -0
  386. aria_code/providers/__init__.py +30 -0
  387. aria_code/providers/llm/__init__.py +19 -0
  388. aria_code/providers/llm/anthropic.py +184 -0
  389. aria_code/providers/llm/autoconfig.py +213 -0
  390. aria_code/providers/llm/base.py +139 -0
  391. aria_code/providers/llm/custom.py +158 -0
  392. aria_code/providers/llm/ollama.py +129 -0
  393. aria_code/providers/llm/openai_compat.py +313 -0
  394. aria_code/providers/llm/registry.py +444 -0
  395. aria_code/providers/local_image_provider.py +202 -0
  396. aria_code/providers/local_llm_provider.py +589 -0
  397. aria_code/realty_data_tools.py +4 -0
  398. aria_code/report_exporters.py +150 -0
  399. aria_code/report_generator.py +1764 -0
  400. aria_code/runtime/__init__.py +169 -0
  401. aria_code/runtime/acceptance.py +481 -0
  402. aria_code/runtime/agent_loop.py +1520 -0
  403. aria_code/runtime/approval.py +57 -0
  404. aria_code/runtime/budget.py +232 -0
  405. aria_code/runtime/checkpoints.py +432 -0
  406. aria_code/runtime/crash_report.py +174 -0
  407. aria_code/runtime/events.py +113 -0
  408. aria_code/runtime/gateway.py +150 -0
  409. aria_code/runtime/lsp.py +484 -0
  410. aria_code/runtime/orchestrator.py +55 -0
  411. aria_code/runtime/repo_map.py +700 -0
  412. aria_code/runtime/run_state.py +127 -0
  413. aria_code/runtime/run_store.py +450 -0
  414. aria_code/runtime/self_healing.py +422 -0
  415. aria_code/runtime/subagent.py +547 -0
  416. aria_code/runtime/task_ledger.py +86 -0
  417. aria_code/runtime/task_orchestrator.py +90 -0
  418. aria_code/runtime/tool_executor.py +217 -0
  419. aria_code/runtime/tool_policy.py +107 -0
  420. aria_code/runtime/worktrees.py +215 -0
  421. aria_code/runway_video_client.py +4 -0
  422. aria_code/safety/__init__.py +23 -0
  423. aria_code/safety/permissions.py +306 -0
  424. aria_code/safety/service.py +102 -0
  425. aria_code/setup_wizard.py +673 -0
  426. aria_code/spreadsheet_tools.py +4 -0
  427. aria_code/strategy_vault.py +4 -0
  428. aria_code/tools/__init__.py +0 -0
  429. aria_code/tools/artifact_tools.py +252 -0
  430. aria_code/tools/broker_tools.py +92 -0
  431. aria_code/tools/build_quant_engine.py +97 -0
  432. aria_code/tools/code_audit_tools.py +192 -0
  433. aria_code/tools/computer_use_tools.py +504 -0
  434. aria_code/tools/data_analysis_tools.py +809 -0
  435. aria_code/tools/enterprise_finance_tools.py +156 -0
  436. aria_code/tools/extended_tools.py +240 -0
  437. aria_code/tools/file_analysis_tools.py +885 -0
  438. aria_code/tools/image_gen_tools.py +114 -0
  439. aria_code/tools/local_finance_tools.py +3417 -0
  440. aria_code/tools/logistics_tools.py +135 -0
  441. aria_code/tools/macro_tools.py +369 -0
  442. aria_code/tools/notification_tools.py +249 -0
  443. aria_code/tools/project_tools.py +525 -0
  444. aria_code/tools/realty_data_tools.py +659 -0
  445. aria_code/tools/spreadsheet_tools.py +388 -0
  446. aria_code/tools/stripe_tools.py +115 -0
  447. aria_code/ui/__init__.py +105 -0
  448. aria_code/ui/banner.py +508 -0
  449. aria_code/ui/completer.py +501 -0
  450. aria_code/ui/console.py +281 -0
  451. aria_code/ui/image_render.py +243 -0
  452. aria_code/ui/input_box.py +488 -0
  453. aria_code/ui/picker.py +201 -0
  454. aria_code/ui/render/__init__.py +11 -0
  455. aria_code/ui/render/finance.py +1705 -0
  456. aria_code/ui/render/market.py +225 -0
  457. aria_code/ui/render/output.py +775 -0
  458. aria_code/ui/render/responsive.py +75 -0
  459. aria_code/ui/render/team.py +354 -0
  460. aria_code/ui/robot.py +235 -0
  461. aria_code/ui/startup_dashboard.py +109 -0
  462. aria_code/utils/vscode_uri.py +37 -0
  463. aria_code/video_analysis.py +147 -0
  464. aria_code/video_editor.py +271 -0
  465. aria_code/workspace/__init__.py +6 -0
  466. aria_code/workspace/files.py +193 -0
  467. aria_code/workspace/verify.py +113 -0
  468. aria_code/workspace_changes.py +199 -0
  469. aria_code/workspace_context.py +233 -0
  470. aria_code/workspace_service.py +196 -0
  471. aria_code/workspace_tools.py +150 -0
  472. aria_code-0.52.0.dist-info/METADATA +237 -0
  473. aria_code-0.52.0.dist-info/RECORD +478 -0
  474. aria_code-0.52.0.dist-info/WHEEL +5 -0
  475. aria_code-0.52.0.dist-info/entry_points.txt +3 -0
  476. aria_code-0.52.0.dist-info/licenses/LICENSE +201 -0
  477. aria_code-0.52.0.dist-info/licenses/NOTICE +49 -0
  478. aria_code-0.52.0.dist-info/top_level.txt +1 -0
aria_code/doctor.py ADDED
@@ -0,0 +1,813 @@
1
+ """Reusable health checks for Aria Code installations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.util
6
+ import json
7
+ import os
8
+ import platform
9
+ import shutil
10
+ import subprocess
11
+ import sys
12
+ import time
13
+ import tempfile
14
+ import urllib.request
15
+ from dataclasses import dataclass, field
16
+ from pathlib import Path
17
+ from typing import Any, Dict, Iterable, List, Optional
18
+ from aria_code.packages.aria_core.paths import aria_home
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class DoctorCheck:
23
+ name: str
24
+ status: str
25
+ detail: str = ""
26
+ suggestion: str = ""
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class DoctorReport:
31
+ checks: List[DoctorCheck] = field(default_factory=list)
32
+
33
+ @property
34
+ def passed(self) -> int:
35
+ return sum(1 for check in self.checks if check.status == "ok")
36
+
37
+ @property
38
+ def warnings(self) -> int:
39
+ return sum(1 for check in self.checks if check.status == "warn")
40
+
41
+ @property
42
+ def errors(self) -> int:
43
+ return sum(1 for check in self.checks if check.status == "err")
44
+
45
+ @property
46
+ def status(self) -> str:
47
+ if self.errors:
48
+ return "err"
49
+ if self.warnings:
50
+ return "warn"
51
+ return "ok"
52
+
53
+ def to_dict(self) -> Dict[str, Any]:
54
+ return {
55
+ "status": self.status,
56
+ "passed": self.passed,
57
+ "warnings": self.warnings,
58
+ "errors": self.errors,
59
+ "checks": [check.__dict__ for check in self.checks],
60
+ }
61
+
62
+
63
+ # `npm run repair` used to be here as the npm-install branch of this hint. The
64
+ # npm package no longer has that script, or any script but `test`: it was
65
+ # rewritten from a postinstall that built a venv into a dispatcher that runs a
66
+ # prebuilt binary. An npm install therefore has no venv to rebuild, and this
67
+ # hint only applies to a repo checkout.
68
+ VENV_REBUILD_HINT = (
69
+ "Rebuild the venv with your current Python: bash install.sh --rebuild "
70
+ "(repo checkout), or reinstall with pip install -U aria-code."
71
+ )
72
+
73
+ # What the npm package is today: a zero-dependency launcher that execs a
74
+ # prebuilt binary delivered as an optionalDependency, one package per platform.
75
+ # Nothing to repair in place — the fix for every broken state is a reinstall.
76
+ NPM_REINSTALL_HINT = "Reinstall with: npm install -g @artheras/aria-code@latest"
77
+
78
+
79
+ def analyze_python_drift(
80
+ recorded_version: str,
81
+ running_version: str,
82
+ home_exists: bool,
83
+ ) -> DoctorCheck:
84
+ """Pure drift analysis for a venv's pyvenv.cfg vs the running interpreter.
85
+
86
+ Two real-world failure signals:
87
+ • the venv's base interpreter (``home =``) was removed — e.g. Homebrew
88
+ upgraded python@3.13 → 3.14 and deleted the old keg; the venv breaks
89
+ on the next symlink resolution even though it still "exists";
90
+ • the recorded creation version differs from the interpreter actually
91
+ running — stale metadata after a relink, or the entrypoint resolved a
92
+ different Python than the one the venv was built with.
93
+ """
94
+ if not home_exists:
95
+ return _check(
96
+ "python_venv",
97
+ "err",
98
+ f"venv base interpreter is gone (built with {recorded_version or 'unknown'})",
99
+ VENV_REBUILD_HINT,
100
+ )
101
+ rec = tuple((recorded_version or "").split(".")[:2])
102
+ run = tuple((running_version or "").split(".")[:2])
103
+ if rec and run and rec != run:
104
+ return _check(
105
+ "python_venv",
106
+ "warn",
107
+ f"venv was created with Python {recorded_version} but {running_version} is running",
108
+ VENV_REBUILD_HINT,
109
+ )
110
+ return _check("python_venv", "ok", f"venv matches running Python {running_version}")
111
+
112
+
113
+ def _check_python_drift() -> Optional[DoctorCheck]:
114
+ """Read the active venv's pyvenv.cfg; None when not running inside a venv."""
115
+ if sys.prefix == getattr(sys, "base_prefix", sys.prefix):
116
+ return None
117
+ cfg = Path(sys.prefix) / "pyvenv.cfg"
118
+ if not cfg.exists():
119
+ return None
120
+ recorded, home = "", ""
121
+ try:
122
+ for line in cfg.read_text(encoding="utf-8").splitlines():
123
+ key, _, value = line.partition("=")
124
+ key = key.strip().lower()
125
+ if key == "version":
126
+ recorded = value.strip()
127
+ elif key == "home":
128
+ home = value.strip()
129
+ except Exception:
130
+ return None
131
+ running = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
132
+ return analyze_python_drift(recorded, running, home_exists=bool(home) and Path(home).exists())
133
+
134
+
135
+ def _check(name: str, status: str, detail: str = "", suggestion: str = "") -> DoctorCheck:
136
+ return DoctorCheck(name=name, status=status, detail=detail, suggestion=suggestion)
137
+
138
+
139
+ def _format_age(seconds: float | int | None) -> str:
140
+ if not seconds or seconds <= 0:
141
+ return ""
142
+ seconds = int(seconds)
143
+ if seconds < 60:
144
+ return f"{seconds}s"
145
+ minutes, sec = divmod(seconds, 60)
146
+ if minutes < 60:
147
+ return f"{minutes}m{sec:02d}s"
148
+ hours, rem = divmod(minutes, 60)
149
+ return f"{hours}h{rem:02d}m"
150
+
151
+
152
+ def _has_module(name: str) -> bool:
153
+ return importlib.util.find_spec(name) is not None
154
+
155
+
156
+ def _is_writable(path: Path) -> tuple[bool, str]:
157
+ try:
158
+ path.mkdir(parents=True, exist_ok=True)
159
+ with tempfile.NamedTemporaryFile(prefix=".aria-doctor-", dir=path, delete=True) as handle:
160
+ handle.write(b"ok")
161
+ return True, str(path)
162
+ except Exception as exc:
163
+ return False, str(exc)
164
+
165
+
166
+ def _is_path_ready(path: Path, *, expect_file: bool = False) -> tuple[str, str]:
167
+ """Read-only path readiness check for runtime/config/cache diagnostics."""
168
+ try:
169
+ path = path.expanduser()
170
+ if path.exists():
171
+ if expect_file and not path.is_file():
172
+ return "err", f"{path} exists but is not a file"
173
+ if not expect_file and not path.is_dir():
174
+ return "err", f"{path} exists but is not a directory"
175
+ writable_target = path.parent if expect_file else path
176
+ if os.access(writable_target, os.W_OK):
177
+ return "ok", str(path)
178
+ return "warn", f"{path} exists but may not be writable"
179
+ parent = path.parent
180
+ if parent.exists() and os.access(parent, os.W_OK):
181
+ return "warn", f"{path} not created yet; parent writable"
182
+ return "warn", f"{path} not found"
183
+ except Exception as exc:
184
+ return "err", str(exc)
185
+
186
+
187
+ def _capture_cmd(cmd: list[str], timeout: float = 2.0) -> tuple[int, str]:
188
+ try:
189
+ result = subprocess.run(
190
+ cmd,
191
+ check=False,
192
+ stdout=subprocess.PIPE,
193
+ stderr=subprocess.PIPE,
194
+ text=True,
195
+ timeout=timeout,
196
+ )
197
+ return result.returncode, (result.stdout or result.stderr or "").strip()
198
+ except Exception as exc:
199
+ return 1, str(exc)
200
+
201
+
202
+ def _expand_home(raw: str | None) -> str:
203
+ value = str(raw or "").strip()
204
+ if not value:
205
+ return ""
206
+ if value == "~":
207
+ return str(Path.home())
208
+ if value.startswith("~/") or value.startswith("~\\"):
209
+ return str(Path.home() / value[2:])
210
+ return value
211
+
212
+
213
+ def _npm_config_home() -> tuple[str, str]:
214
+ for key in ("npm_config_aria_code_home", "npm_config_aria_home", "npm_config_ariacode_home"):
215
+ value = _expand_home(os.getenv(key))
216
+ if value:
217
+ return str(Path(value).expanduser().resolve()), f"env:{key}"
218
+ npm = shutil.which("npm")
219
+ if not npm:
220
+ return "", ""
221
+ code, out = _capture_cmd([npm, "config", "get", "aria-code:home"], timeout=1.5)
222
+ value = _expand_home(out)
223
+ if code == 0 and value and value.lower() not in ("undefined", "null"):
224
+ return str(Path(value).expanduser().resolve()), "npm-config:aria-code:home"
225
+ return "", ""
226
+
227
+
228
+ def _platform_data_dir() -> Path:
229
+ system = platform.system().lower()
230
+ if system == "darwin":
231
+ return Path.home() / "Library" / "Application Support" / "Aria Code"
232
+ if system == "windows":
233
+ return Path(os.getenv("LOCALAPPDATA") or (Path.home() / "AppData" / "Local")) / "AriaCode"
234
+ return Path(os.getenv("XDG_DATA_HOME") or (Path.home() / ".local" / "share")) / "aria-code"
235
+
236
+
237
+ def _platform_config_dir() -> Path:
238
+ system = platform.system().lower()
239
+ if system == "darwin":
240
+ return Path.home() / "Library" / "Application Support" / "Aria Code"
241
+ if system == "windows":
242
+ return Path(os.getenv("APPDATA") or (Path.home() / "AppData" / "Roaming")) / "AriaCode"
243
+ return Path(os.getenv("XDG_CONFIG_HOME") or (Path.home() / ".config")) / "aria-code"
244
+
245
+
246
+ def _platform_cache_dir() -> Path:
247
+ system = platform.system().lower()
248
+ if system == "darwin":
249
+ return Path.home() / "Library" / "Caches" / "Aria Code"
250
+ if system == "windows":
251
+ return Path(os.getenv("LOCALAPPDATA") or (Path.home() / "AppData" / "Local")) / "AriaCode" / "Cache"
252
+ return Path(os.getenv("XDG_CACHE_HOME") or (Path.home() / ".cache")) / "aria-code"
253
+
254
+
255
+ def _resolve_runtime_paths() -> dict[str, Any]:
256
+ legacy = Path.home() / ".aria-code"
257
+ source = ""
258
+ install_raw = os.getenv("ARIA_HOME") or os.getenv("ARIA_CODE_HOME") or ""
259
+ if install_raw:
260
+ install_dir = Path(_expand_home(install_raw)).expanduser().resolve()
261
+ source = "env:ARIA_HOME" if os.getenv("ARIA_HOME") else "env:ARIA_CODE_HOME"
262
+ else:
263
+ npm_home, npm_source = _npm_config_home()
264
+ if npm_home:
265
+ install_dir = Path(npm_home)
266
+ source = npm_source
267
+ elif legacy.exists():
268
+ install_dir = legacy
269
+ source = "legacy-existing"
270
+ else:
271
+ install_dir = _platform_data_dir()
272
+ source = "platform-default"
273
+
274
+ config_dir = Path(_expand_home(os.getenv("ARIA_CONFIG_DIR"))).expanduser().resolve() if os.getenv("ARIA_CONFIG_DIR") else _platform_config_dir()
275
+ cache_dir = Path(_expand_home(os.getenv("ARIA_CACHE_DIR"))).expanduser().resolve() if os.getenv("ARIA_CACHE_DIR") else _platform_cache_dir()
276
+ info_file = install_dir / ".npm-install-info.json"
277
+ config_info_file = config_dir / "install.json"
278
+ legacy_info_file = legacy / ".npm-install-info.json"
279
+ info_candidates = []
280
+ for candidate in (info_file, config_info_file, legacy_info_file):
281
+ if candidate not in info_candidates:
282
+ info_candidates.append(candidate)
283
+ return {
284
+ "install_dir": install_dir,
285
+ "install_dir_source": source,
286
+ "legacy_install_dir": legacy,
287
+ "venv_dir": install_dir / ".venv",
288
+ "venv_py": install_dir / ".venv" / ("Scripts/python.exe" if platform.system().lower() == "windows" else "bin/python"),
289
+ "aria_cli": install_dir / "aria_cli.py",
290
+ "config_dir": config_dir,
291
+ "cache_dir": cache_dir,
292
+ "info_file": info_file,
293
+ "config_info_file": config_info_file,
294
+ "legacy_info_file": legacy_info_file,
295
+ "info_candidates": info_candidates,
296
+ }
297
+
298
+
299
+ def _provider_key_present(module_name: str, key_fn: str) -> bool:
300
+ """Call a client module's own key-lookup function (e.g.
301
+ openai_image_client._api_key) rather than re-implementing env-var/
302
+ providers.json lookup here — avoids doctor.py silently drifting out of
303
+ sync with each module's actual lookup logic."""
304
+ try:
305
+ import importlib
306
+ mod = importlib.import_module(module_name)
307
+ result = getattr(mod, key_fn)()
308
+ if isinstance(result, tuple):
309
+ return all(bool(v) for v in result)
310
+ return bool(result)
311
+ except Exception:
312
+ return False
313
+
314
+
315
+ def integration_checks() -> List[DoctorCheck]:
316
+ """Health checks for the report/media generation integrations added
317
+ across the MCP-exposure work: ffmpeg (video editing), faster-whisper +
318
+ opencv (video analysis), self-hosted image generation, and the
319
+ API-key-gated cloud providers (OpenAI images, Kling/Runway video, Canva,
320
+ Figma). None of these are required for core aria-code usage — missing
321
+ ones are "warn", not "err", and each points at how to fix it."""
322
+ checks: List[DoctorCheck] = []
323
+
324
+ if shutil.which("ffmpeg"):
325
+ checks.append(_check("integration:ffmpeg", "ok", "found on PATH — local video editing (aria.video.*) available"))
326
+ else:
327
+ checks.append(_check(
328
+ "integration:ffmpeg", "warn", "not found on PATH",
329
+ "Install ffmpeg (e.g. `brew install ffmpeg`) to use aria.video.trim/concat/overlay_*/convert/change_speed.",
330
+ ))
331
+
332
+ if _has_module("faster_whisper"):
333
+ checks.append(_check("integration:faster_whisper", "ok", "available — aria.video.transcribe ready"))
334
+ else:
335
+ checks.append(_check(
336
+ "integration:faster_whisper", "warn", "optional 'video_analysis' extra not installed",
337
+ "pip install 'aria-code[video_analysis]' to use aria.video.transcribe.",
338
+ ))
339
+
340
+ if _has_module("cv2"):
341
+ checks.append(_check("integration:opencv", "ok", "available — aria.video.detect_scenes ready"))
342
+ else:
343
+ checks.append(_check(
344
+ "integration:opencv", "warn", "optional 'video' extra not installed",
345
+ "pip install 'aria-code[video]' to use aria.video.detect_scenes.",
346
+ ))
347
+
348
+ if _has_module("diffusers") and _has_module("torch"):
349
+ checks.append(_check("integration:local_image_gen", "ok", "available — aria.report.generate_image_local/edit_image_local ready (no API key needed)"))
350
+ else:
351
+ checks.append(_check(
352
+ "integration:local_image_gen", "warn", "optional 'image_gen' extra not installed",
353
+ "pip install 'aria-code[image_gen]' for free local image generation, or use aria.report.generate_image (OpenAI, needs an API key).",
354
+ ))
355
+
356
+ key_checks = [
357
+ ("integration:openai_images", "openai_image_client", "_api_key", "/apikey set openai sk-...", "aria.report.generate_image/edit_image"),
358
+ ("integration:kling", "kling_video_client", "_keys", "/apikey set kling <access_key>:<secret_key>", "aria.video.generate_submit (provider=kling)"),
359
+ ("integration:runway", "runway_video_client", "_api_key", "/apikey set runway <key>", "aria.video.generate_submit (provider=runway)"),
360
+ ("integration:figma", "figma_client", "_token", "/apikey set figma <personal_access_token>", "aria.figma.read_file/comments"),
361
+ ]
362
+ for name, module_name, key_fn, setup_hint, gates in key_checks:
363
+ if _provider_key_present(module_name, key_fn):
364
+ checks.append(_check(name, "ok", f"key configured — {gates} usable"))
365
+ else:
366
+ checks.append(_check(name, "warn", "no API key configured", f"{setup_hint} to use {gates}."))
367
+
368
+ try:
369
+ # Read stored config directly rather than calling _access_token(),
370
+ # which refreshes an expired token over the network — this function
371
+ # is a local-first, no-mutation health check, not a live probe.
372
+ from canva_client import _load_canva_config
373
+
374
+ connected = bool(_load_canva_config().get("access_token"))
375
+ checks.append(_check(
376
+ "integration:canva", "ok" if connected else "warn",
377
+ "connected — aria.report.canva_design/canva_upload_asset usable" if connected else "not connected",
378
+ "" if connected else "Run /canva connect <client_id> <client_secret> (needs a Canva Connect app + brand template).",
379
+ ))
380
+ except Exception as exc:
381
+ checks.append(_check("integration:canva", "warn", f"could not read config: {exc}", "Run /canva connect <client_id> <client_secret>."))
382
+
383
+ return checks
384
+
385
+
386
+ def npm_platform_key(system: str = "", machine: str = "") -> str:
387
+ """This machine's platform key, matching npm/lib/platform.js PLATFORM_KEYS.
388
+
389
+ Returns "" for a platform with no prebuilt binary, which is a real answer:
390
+ those users install from pip.
391
+ """
392
+ system = (system or platform.system()).lower()
393
+ machine = (machine or platform.machine()).lower()
394
+ os_part = {"darwin": "darwin", "linux": "linux", "windows": "win32"}.get(system, "")
395
+ arch_part = {
396
+ "x86_64": "x64", "amd64": "x64",
397
+ "arm64": "arm64", "aarch64": "arm64",
398
+ }.get(machine, "")
399
+ if not os_part or not arch_part:
400
+ return ""
401
+ key = f"{os_part}-{arch_part}"
402
+ # win32-arm64 and linux-arm64-on-win are not built; see PLATFORM_KEYS.
403
+ return key if key in (
404
+ "darwin-arm64", "darwin-x64", "linux-x64", "linux-arm64", "win32-x64"
405
+ ) else ""
406
+
407
+
408
+ def _platform_package_check(npm: Optional[str]) -> Optional[DoctorCheck]:
409
+ """Did this machine's prebuilt-binary package actually get installed?
410
+
411
+ The launcher lists one package per platform as an optionalDependency, and
412
+ npm skips an optionalDependency it cannot resolve *silently* — by design,
413
+ that is what "optional" means. So the failure mode this catches is a
414
+ launcher with no binary behind it, which reports only when the user tries
415
+ to run something.
416
+
417
+ It is not hypothetical: @artheras/aria-code-darwin-arm64 and its four
418
+ siblings have never been published, so every npm install to date has been
419
+ in exactly this state.
420
+ """
421
+ key = npm_platform_key()
422
+ if not key:
423
+ return _check(
424
+ "npm_runtime:binary",
425
+ "skip",
426
+ f"no prebuilt binary for {platform.system()}/{platform.machine()}",
427
+ "Install with pip instead: pip install -U aria-code",
428
+ )
429
+ pkg = f"@artheras/aria-code-{key}"
430
+ if not npm:
431
+ return None
432
+ code, root = _capture_cmd([npm, "root", "-g"], timeout=1.5)
433
+ if code != 0 or not root:
434
+ return None
435
+ installed = Path(root) / "@artheras" / f"aria-code-{key}"
436
+ if installed.is_dir():
437
+ return _check("npm_runtime:binary", "ok", f"{pkg} at {installed}")
438
+ return _check(
439
+ "npm_runtime:binary",
440
+ "err",
441
+ f"{pkg} is not installed — the launcher has no binary to run",
442
+ "npm skips an optional dependency it cannot resolve without saying so. "
443
+ + NPM_REINSTALL_HINT,
444
+ )
445
+
446
+
447
+ def _npm_install_detected(paths: dict) -> bool:
448
+ """Is there an npm-launcher installation for these checks to be about?
449
+
450
+ Any one of these is proof: the launcher's own aria_cli.py, the install
451
+ metadata it writes, or an install dir that came from npm config rather
452
+ than a platform default.
453
+ """
454
+ if paths["aria_cli"].is_file():
455
+ return True
456
+ if any(candidate.is_file() for candidate in paths["info_candidates"]):
457
+ return True
458
+ return str(paths.get("install_dir_source", "")).startswith("npm")
459
+
460
+
461
+ def npm_runtime_checks(*, cwd: Optional[Path] = None) -> List[DoctorCheck]:
462
+ """Return npm launcher/runtime path diagnostics."""
463
+ checks: List[DoctorCheck] = []
464
+ paths = _resolve_runtime_paths()
465
+ cwd = (cwd or Path.cwd()).expanduser().resolve()
466
+ source_cli = cwd / "aria_cli.py"
467
+ source_venv_py = cwd / ".venv" / ("Scripts/python.exe" if platform.system().lower() == "windows" else "bin/python")
468
+ using_source_checkout = source_cli.is_file() and not paths["aria_cli"].is_file()
469
+
470
+ # A pip install has no npm launcher, so every check below is about
471
+ # something that was never supposed to exist. Reporting them made a clean
472
+ # `pip install aria-code` end in "1 errors", and the repair suggestion —
473
+ # `node $(npm root -g)/aria-code/scripts/postinstall.js` — is not just
474
+ # wrong for that user, it is unrunnable on a machine with no node.
475
+ if not using_source_checkout and not _npm_install_detected(paths):
476
+ checks.append(_check(
477
+ "npm_runtime",
478
+ "skip",
479
+ "not an npm install — nothing to check",
480
+ "These checks apply to `npm install -g @artheras/aria-code`.",
481
+ ))
482
+ return checks
483
+
484
+ node = shutil.which("node")
485
+ if node:
486
+ code, version = _capture_cmd([node, "--version"], timeout=1.5)
487
+ checks.append(_check("npm_runtime:node", "ok" if code == 0 else "warn", f"{node} {version}".strip()))
488
+ else:
489
+ checks.append(_check("npm_runtime:node", "warn", "node not found", "Install Node.js if you use the npm launcher."))
490
+
491
+ npm = shutil.which("npm")
492
+ if npm:
493
+ _code, version = _capture_cmd([npm, "--version"], timeout=1.5)
494
+ prefix_code, prefix = _capture_cmd([npm, "config", "get", "prefix"], timeout=1.5)
495
+ root_code, root = _capture_cmd([npm, "root", "-g"], timeout=1.5)
496
+ detail = f"{npm} v{version or '?'}"
497
+ if prefix_code == 0 and prefix:
498
+ detail += f"; prefix={prefix}"
499
+ if root_code == 0 and root:
500
+ detail += f"; root={root}"
501
+ checks.append(_check("npm_runtime:npm", "ok", detail))
502
+ else:
503
+ checks.append(_check("npm_runtime:npm", "warn", "npm not found", "Install Node.js/npm if you use npm install -g aria-code."))
504
+
505
+ binary_check = _platform_package_check(npm)
506
+ if binary_check is not None:
507
+ checks.append(binary_check)
508
+
509
+ install_status, install_detail = _is_path_ready(paths["install_dir"])
510
+ install_suggestion = ""
511
+ if using_source_checkout:
512
+ install_suggestion = f"Set ARIA_HOME={cwd} when launching through npm, or run npm install -g aria-code."
513
+ elif not paths["aria_cli"].exists():
514
+ install_suggestion = "Run npm install -g aria-code or set ARIA_HOME to the cloned repo."
515
+ checks.append(_check(
516
+ "npm_runtime:install_dir",
517
+ "warn" if using_source_checkout else ("ok" if paths["aria_cli"].exists() else install_status),
518
+ (
519
+ f"{install_detail}; source={paths['install_dir_source']}"
520
+ + (f"; source_checkout={cwd}" if using_source_checkout else "")
521
+ ),
522
+ install_suggestion,
523
+ ))
524
+
525
+ actual_cli = paths["aria_cli"] if paths["aria_cli"].is_file() else (source_cli if source_cli.is_file() else paths["aria_cli"])
526
+ checks.append(_check(
527
+ "npm_runtime:aria_cli",
528
+ "ok" if actual_cli.is_file() else "err",
529
+ str(actual_cli),
530
+ # Was "node $(npm root -g)/aria-code/scripts/postinstall.js". That file
531
+ # was deleted with the launcher rewrite, so the suggestion sent anyone
532
+ # who hit it to a path that does not exist.
533
+ NPM_REINSTALL_HINT if not actual_cli.is_file() else "",
534
+ ))
535
+
536
+ actual_venv_py = paths["venv_py"] if paths["venv_py"].is_file() else (source_venv_py if source_venv_py.is_file() else paths["venv_py"])
537
+ checks.append(_check(
538
+ "npm_runtime:venv",
539
+ "ok" if actual_venv_py.is_file() else "warn",
540
+ str(actual_venv_py),
541
+ # No `repair` or `update-engine` script exists any more. A missing venv
542
+ # is also no longer a fault on an npm install, which ships a binary and
543
+ # builds no venv at all — so this is only meaningful for the legacy
544
+ # layout or a source checkout.
545
+ (NPM_REINSTALL_HINT if not actual_venv_py.is_file() else ""),
546
+ ))
547
+
548
+ info_hit = next((p for p in paths["info_candidates"] if p.is_file()), None)
549
+ checks.append(_check(
550
+ "npm_runtime:install_info",
551
+ "ok" if info_hit else "warn",
552
+ str(info_hit) if info_hit else "none found; checked " + ", ".join(str(p) for p in paths["info_candidates"]),
553
+ NPM_REINSTALL_HINT if not info_hit else "",
554
+ ))
555
+
556
+ config_status, config_detail = _is_path_ready(paths["config_dir"])
557
+ cache_status, cache_detail = _is_path_ready(paths["cache_dir"])
558
+ checks.append(_check("npm_runtime:config_dir", config_status, config_detail))
559
+ checks.append(_check("npm_runtime:cache_dir", cache_status, cache_detail))
560
+ return checks
561
+
562
+
563
+ def _check_ollama(url: str, timeout: float = 1.5, *, required: bool = True) -> DoctorCheck:
564
+ """Report on the local Ollama server.
565
+
566
+ ``required`` says whether this install actually depends on it. Ollama is one
567
+ of several ways to get a model, and reporting its absence as a warning meant
568
+ a perfectly healthy cloud-only install could never show green — so the
569
+ signal people were meant to read stopped meaning anything.
570
+ """
571
+ try:
572
+ opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
573
+ with opener.open(f"{url.rstrip('/')}/api/tags", timeout=timeout) as response:
574
+ data = json.loads(response.read().decode("utf-8"))
575
+ models = [str(model.get("name", "")) for model in data.get("models", []) if model.get("name")]
576
+ if models:
577
+ return _check("ollama", "ok", f"{len(models)} models: {', '.join(models[:4])}")
578
+ if required:
579
+ return _check("ollama", "warn", "running but no models installed", "ollama pull qwen2.5-coder:7b")
580
+ return _check("ollama", "skip", "running, no models installed (not this install's provider)")
581
+ except Exception as exc:
582
+ if required:
583
+ return _check("ollama", "warn", f"not reachable at {url}: {exc}",
584
+ "Start Ollama, or switch provider with /model.")
585
+ return _check("ollama", "skip", "not running (offline mode unused — this install uses another provider)")
586
+
587
+
588
+ def provider_health_checks(snapshot: Optional[List[Dict[str, Any]]] = None) -> List[DoctorCheck]:
589
+ """Convert data provider health state into doctor checks."""
590
+ if snapshot is None:
591
+ try:
592
+ from packages.aria_services.provider_health import GLOBAL_PROVIDER_HEALTH
593
+
594
+ snapshot = GLOBAL_PROVIDER_HEALTH.snapshot()
595
+ except Exception:
596
+ snapshot = []
597
+
598
+ if not snapshot:
599
+ return [
600
+ _check(
601
+ "data_provider_health",
602
+ "warn",
603
+ "no provider calls recorded in this session",
604
+ "Run /quote, /ta, /analyze, or /report to populate provider health.",
605
+ )
606
+ ]
607
+
608
+ checks: List[DoctorCheck] = []
609
+ for row in snapshot:
610
+ provider = str(row.get("provider") or "provider")
611
+ status = str(row.get("status") or "unknown")
612
+ failures = int(row.get("failures") or 0)
613
+ cooldown = bool(row.get("cooldown_active"))
614
+ remaining = int(row.get("cooldown_remaining_seconds") or 0)
615
+ detail = status
616
+ if failures:
617
+ detail += f", failures={failures}"
618
+ if cooldown:
619
+ detail += f", cooldown={remaining}s"
620
+ if row.get("last_error"):
621
+ detail += f", last={row.get('last_error')}"
622
+ last_success = _format_age((time.time() - float(row.get("last_success_at"))) if row.get("last_success_at") else None)
623
+ if last_success:
624
+ detail += f", last_ok={last_success}"
625
+ check_status = "ok" if status == "ok" else "warn" if row.get("last_error_category") != "auth" else "err"
626
+ suggestion = "Wait for cooldown or switch provider/API key." if cooldown else ""
627
+ if row.get("last_error_category") == "auth":
628
+ suggestion = "Check provider API key with /apikey list or /apikey set."
629
+ checks.append(_check(f"data_provider:{provider}", check_status, detail, suggestion))
630
+ return checks
631
+
632
+
633
+ def provider_health_summary(snapshot: Optional[List[Dict[str, Any]]] = None) -> DoctorCheck:
634
+ """Summarise provider health into one dashboard-style check."""
635
+ if snapshot is None:
636
+ try:
637
+ from packages.aria_services.provider_health import GLOBAL_PROVIDER_HEALTH
638
+
639
+ snapshot = GLOBAL_PROVIDER_HEALTH.snapshot()
640
+ except Exception:
641
+ snapshot = []
642
+ try:
643
+ from packages.aria_services.provider_health import summarize_provider_health
644
+
645
+ summary = summarize_provider_health(snapshot)
646
+ return _check("provider_health_summary", summary.status, summary.detail, summary.suggestion)
647
+ except Exception:
648
+ if not snapshot:
649
+ return _check(
650
+ "provider_health_summary",
651
+ "warn",
652
+ "no provider calls recorded in this session",
653
+ "Run /quote, /ta, /analyze, or /report to populate provider health.",
654
+ )
655
+ total = len(snapshot)
656
+ return _check(
657
+ "provider_health_summary",
658
+ "warn",
659
+ f"{total} providers",
660
+ "Run /doctor --network or inspect /apikey.",
661
+ )
662
+
663
+
664
+ def _iter_required_modules() -> Iterable[tuple[str, str]]:
665
+ yield "aiohttp", "async HTTP"
666
+ yield "rich", "terminal UI"
667
+ yield "prompt_toolkit", "interactive input"
668
+ yield "requests", "HTTP client"
669
+ yield "pandas", "dataframes"
670
+ yield "numpy", "numeric processing"
671
+ yield "yfinance", "US/HK/global market data"
672
+ yield "akshare", "China market data"
673
+
674
+
675
+ def run_doctor(
676
+ config: Optional[Dict[str, Any]] = None,
677
+ *,
678
+ cwd: Optional[Path] = None,
679
+ check_network: bool = False,
680
+ context_stats: Optional[Dict[str, Any]] = None,
681
+ ) -> DoctorReport:
682
+ """Run local-first diagnostics without mutating user configuration."""
683
+
684
+ config = config or {}
685
+ cwd = (cwd or Path.cwd()).expanduser().resolve()
686
+ checks: List[DoctorCheck] = []
687
+
688
+ pyver = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
689
+ if sys.version_info >= (3, 10):
690
+ checks.append(_check("python", "ok", f"{pyver} on {platform.system()}"))
691
+ else:
692
+ checks.append(_check("python", "err", f"{pyver} is unsupported", "Install Python 3.10 or newer."))
693
+
694
+ drift = _check_python_drift()
695
+ if drift is not None:
696
+ checks.append(drift)
697
+
698
+ if context_stats:
699
+ _fill = float(context_stats.get("fill_ratio") or 0.0)
700
+ _thr = float(context_stats.get("threshold") or 0.78)
701
+ _ctx_status = "ok" if _fill < _thr else ("warn" if _fill < 0.95 else "err")
702
+ checks.append(_check(
703
+ "context",
704
+ _ctx_status,
705
+ f"{context_stats.get('estimated_tokens', 0)}/{context_stats.get('max_tokens', 0)} tokens "
706
+ f"({context_stats.get('fill_pct', 0)}%) across {context_stats.get('message_count', 0)} messages",
707
+ "" if _ctx_status == "ok" else "/compact to shrink the conversation before the window overflows.",
708
+ ))
709
+
710
+ for module, purpose in _iter_required_modules():
711
+ if _has_module(module):
712
+ checks.append(_check(f"package:{module}", "ok", purpose))
713
+ else:
714
+ checks.append(_check(f"package:{module}", "err", f"{purpose} missing", f"pip install {module}"))
715
+
716
+ for module in ("data_service", "artifacts", "report_generator", "backtest_report"):
717
+ checks.append(
718
+ _check(f"module:{module}", "ok" if _has_module(module) else "err", "importable" if _has_module(module) else "missing from install")
719
+ )
720
+
721
+ checks.extend(npm_runtime_checks(cwd=cwd))
722
+ checks.extend(integration_checks())
723
+
724
+ try:
725
+ from artifacts import artifact_root, artifact_summary
726
+
727
+ root = artifact_root()
728
+ writable, detail = _is_writable(root)
729
+ checks.append(_check("artifact_root", "ok" if writable else "err", detail, "Set ARIA_ARTIFACT_ROOT to a writable folder."))
730
+ summary = artifact_summary(root)
731
+ total = int(summary.get("total") or 0)
732
+ total_size = int(summary.get("total_size_bytes") or 0)
733
+ by_kind = summary.get("by_kind") or {}
734
+ detail_bits = [f"{total} artifacts", f"{total_size} bytes"]
735
+ if by_kind:
736
+ detail_bits.extend(f"{kind}={count}" for kind, count in list(by_kind.items())[:4])
737
+ checks.append(
738
+ _check(
739
+ "artifact_inventory",
740
+ "ok" if total else "warn",
741
+ ", ".join(detail_bits),
742
+ "Run /artifacts stats or generate a report to populate local outputs.",
743
+ )
744
+ )
745
+ except Exception as exc:
746
+ checks.append(_check("artifact_root", "err", str(exc)))
747
+
748
+ config_dir = aria_home()
749
+ config_file = config_dir / "config.json"
750
+ if config_file.exists():
751
+ checks.append(_check("config", "ok", str(config_file)))
752
+ else:
753
+ checks.append(_check("config", "warn", f"{config_file} not found", "Run aria-code once or use /config set key=value."))
754
+
755
+ data_sharing = bool(config.get("data_sharing", False))
756
+ feedback_upload = bool(config.get("feedback_upload", False))
757
+ privacy_detail = f"data_sharing={data_sharing}, feedback_upload={feedback_upload}"
758
+ checks.append(_check("privacy", "ok", privacy_detail))
759
+
760
+ try:
761
+ from datasources.router import DataRouter
762
+
763
+ sources = DataRouter().list_sources()
764
+ configured = [src["name"] for src in sources if src.get("configured")]
765
+ missing = [src["name"] for src in sources if src.get("needs_key") and not src.get("configured")]
766
+ status = "ok" if configured else "warn"
767
+ detail = f"configured: {', '.join(configured) or 'none'}"
768
+ suggestion = f"optional keys missing: {', '.join(missing)}" if missing else ""
769
+ checks.append(_check("datasources", status, detail, suggestion))
770
+ except Exception as exc:
771
+ checks.append(_check("datasources", "warn", str(exc)))
772
+
773
+ checks.append(provider_health_summary())
774
+ checks.extend(provider_health_checks())
775
+
776
+ # Ollama only matters when this install routes through it. A user on the
777
+ # gateway or a cloud key never wants it, and flagging that as a warning is
778
+ # how a green report becomes unreachable.
779
+ _uses_ollama = (
780
+ str(config.get("local_provider") or "").lower() == "ollama"
781
+ or not str(config.get("model") or "").strip()
782
+ or ":" in str(config.get("model") or "") # bare Ollama-style tag, e.g. qwen2.5-coder:1.5b
783
+ )
784
+ if check_network:
785
+ checks.append(_check_ollama(
786
+ str(config.get("ollama_url") or "http://localhost:11434"),
787
+ required=_uses_ollama,
788
+ ))
789
+ else:
790
+ # A check that did not run is not a warning about the thing it checks.
791
+ checks.append(_check("ollama", "skip", "network check not requested",
792
+ "Run /doctor --network to probe the local server."))
793
+
794
+ if (cwd / ".ariarc").exists():
795
+ checks.append(_check("project_config", "ok", str(cwd / ".ariarc")))
796
+ else:
797
+ checks.append(_check("project_config", "warn", ".ariarc not found", "Optional: add .ariarc for project-local settings."))
798
+
799
+ return DoctorReport(checks=checks)
800
+
801
+
802
+ def format_doctor_plain(report: DoctorReport) -> str:
803
+ marks = {"ok": "OK", "warn": "WARN", "err": "ERR", "skip": "--"}
804
+ lines = ["Aria Code doctor"]
805
+ for check in report.checks:
806
+ suffix = f" — {check.detail}" if check.detail else ""
807
+ if check.suggestion:
808
+ suffix += f" ({check.suggestion})"
809
+ lines.append(f"{marks.get(check.status, check.status.upper()):<4} {check.name}{suffix}")
810
+ skipped = sum(1 for check in report.checks if check.status == "skip")
811
+ tail = f" · {skipped} skipped" if skipped else ""
812
+ lines.append(f"{report.passed} passed · {report.warnings} warnings · {report.errors} errors{tail}")
813
+ return "\n".join(lines)