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
@@ -0,0 +1,114 @@
1
+ """Deterministic real-estate query handler extracted from aria_cli.py."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Callable
5
+
6
+
7
+ def handle_realty_query(
8
+ message: str,
9
+ *,
10
+ is_realty_query: Callable[[str], bool],
11
+ cn_cities: tuple | list,
12
+ intl_cities: tuple | list,
13
+ ) -> dict:
14
+ """Deterministic handler for natural-language real-estate / housing questions.
15
+
16
+ Detects city names + real-estate keywords and calls the realty data tools
17
+ directly, returning a formatted response without needing the LLM to parse it.
18
+ """
19
+ if not is_realty_query(message):
20
+ return {"success": False, "error": "not_realty_query"}
21
+
22
+ try:
23
+ from realty_data_tools import get_house_price_index, get_re_investment
24
+ except ImportError:
25
+ return {"success": False, "error": "realty_data_tools_not_available"}
26
+
27
+ cities_found: list[str] = []
28
+ for city in cn_cities:
29
+ if city in message:
30
+ cities_found.append(city)
31
+ if len(cities_found) >= 2:
32
+ break
33
+
34
+ _low_msg = message.lower()
35
+ _intl_found = [c for c in intl_cities if c in _low_msg]
36
+
37
+ if _intl_found and not cities_found:
38
+ _intl_name = _intl_found[0]
39
+ return {
40
+ "success": True,
41
+ "response": (
42
+ f"## 🌍 {_intl_name.title()} 房地产市场\n\n"
43
+ f"国际城市房价数据目前依赖 LLM 知识库分析(无实时数据接入)。\n\n"
44
+ f"**当前支持实时数据的城市:** 中国大陆 70 个主要城市(北上广深等)\n\n"
45
+ f"如需国际市场数据,建议查阅:\n"
46
+ f"- 美国:`/realty us` — 联邦住房数据(Case-Shiller 指数、新屋开工)\n"
47
+ f"- 其他国际城市:可直接提问,Aria 将基于训练知识回答(数据截止至知识库更新时间)\n\n"
48
+ f"---\n\n"
49
+ f"请问您具体想了解 **{_intl_name.title()}** 哪方面的房地产信息?"
50
+ ),
51
+ "tools_used": ["realty_query"],
52
+ }
53
+
54
+ city1 = cities_found[0] if cities_found else "全国"
55
+ city2 = cities_found[1] if len(cities_found) > 1 else ("上海" if city1 != "上海" else "北京")
56
+
57
+ lines: list[str] = []
58
+ lines.append(f"## 🏠 {city1} 房地产市场")
59
+ if len(cities_found) > 1:
60
+ lines[-1] += f" vs {city2}"
61
+ lines.append("")
62
+
63
+ try:
64
+ r = get_house_price_index(city1, city2)
65
+ if r.get("success"):
66
+ for lbl, cd in (
67
+ (city1, r.get("latest_city1", {})),
68
+ (city2, r.get("latest_city2", {})),
69
+ ):
70
+ if not cd:
71
+ continue
72
+ lines.append(f"### {lbl}")
73
+ if cd.get("new_yoy") is not None:
74
+ lines.append(f"- **新房同比**:{float(cd['new_yoy']):+.2f}%")
75
+ if cd.get("new_mom") is not None:
76
+ lines.append(f"- **新房环比**:{float(cd['new_mom']):+.2f}%")
77
+ if cd.get("second_yoy") is not None:
78
+ lines.append(f"- **二手房同比**:{float(cd['second_yoy']):+.2f}%")
79
+ if cd.get("second_mom") is not None:
80
+ lines.append(f"- **二手房环比**:{float(cd['second_mom']):+.2f}%")
81
+ if cd.get("date"):
82
+ lines.append(f"- **数据期**:{cd['date']}")
83
+ lines.append("")
84
+ else:
85
+ lines.append(f"房价指数数据暂时不可用({r.get('error', '数据源未响应')})")
86
+ lines.append("")
87
+ except Exception as _e:
88
+ lines.append(f"获取房价数据失败: {_e}")
89
+ lines.append("")
90
+
91
+ try:
92
+ ri = get_re_investment()
93
+ if ri.get("success") and ri.get("latest"):
94
+ lt = ri["latest"]
95
+ lines.append("### 全国房地产开发投资")
96
+ lines.append(f"- **最新值**:{lt.get('最新值', 'N/A')}")
97
+ lines.append(f"- **日期**:{lt.get('日期', 'N/A')}")
98
+ lines.append(f"- **涨跌幅**:{lt.get('涨跌幅', 'N/A')}")
99
+ lines.append(f"- **近1年涨跌幅**:{lt.get('近1年涨跌幅', 'N/A')}")
100
+ lines.append("")
101
+ except Exception:
102
+ pass
103
+
104
+ lines.append("**更多操作**")
105
+ lines.append(f"- `/realty market {city1}` — 完整城市房价走势图")
106
+ lines.append(f"- `/realty compare {city1} {city2}` — 城市横向对比")
107
+ lines.append("- `/realty rent` — 租金收益率计算")
108
+ lines.append("- `/realty reit` — REIT 市场数据")
109
+
110
+ return {
111
+ "success": True,
112
+ "response": "\n".join(lines),
113
+ "tools_used": ["realty_query"],
114
+ }
@@ -0,0 +1,22 @@
1
+ """Sports context prefetch for the chat path.
2
+
3
+ The market equivalent already lives in handlers/market_handlers.py; this one
4
+ was still inline in aria_cli and borrowed from its globals by stream_ollama,
5
+ so the fallback path (SDK, daemon) raised NameError on any sports query.
6
+
7
+ Kept deliberately quiet: a prefetch is an optimisation, and a missing or
8
+ failing football client must degrade to "no extra context" rather than break
9
+ the turn.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+
15
+ def try_prefetch_sports_data(message: str) -> str:
16
+ """Live sports context for a query, or "" when unavailable."""
17
+ try:
18
+ from football_data_client import get_sports_context_for_query
19
+
20
+ return get_sports_context_for_query(message) or ""
21
+ except Exception:
22
+ return ""
@@ -0,0 +1,82 @@
1
+ """Deterministic strategy-advice responses.
2
+
3
+ This keeps advisory questions from being treated as code-generation tasks.
4
+ When the user asks "how should I write a strategy" or "from which angles",
5
+ the CLI should answer with a framework and explicit next commands, not create
6
+ files or run backtests without a generation/execution intent.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+
12
+ _STRATEGY_TERMS = (
13
+ "量化策略", "交易策略", "美股策略", "回测策略", "策略框架",
14
+ "quant strategy", "trading strategy", "backtest strategy",
15
+ )
16
+
17
+ _ADVICE_TERMS = (
18
+ "几个角度", "哪些角度", "从几个方面", "怎么写", "如何写",
19
+ "思路", "框架", "结构", "要考虑", "你觉得", "建议",
20
+ "how", "what should", "framework", "approach", "angles",
21
+ )
22
+
23
+ _EXECUTION_TERMS = (
24
+ "开始写", "帮我写", "生成", "实现", "创建", "保存", "运行",
25
+ "回测一下", "直接做", "落地", "完善代码",
26
+ "write", "generate", "implement", "create", "save", "run",
27
+ )
28
+
29
+
30
+ def is_strategy_advice_request(message: str) -> bool:
31
+ text = (message or "").strip()
32
+ if not text or text.startswith("/"):
33
+ return False
34
+ low = text.lower()
35
+ has_strategy = any(term in text or term in low for term in _STRATEGY_TERMS)
36
+ has_advice = any(term in text or term in low for term in _ADVICE_TERMS)
37
+ has_execution = any(term in text or term in low for term in _EXECUTION_TERMS)
38
+ return bool(has_strategy and has_advice and not has_execution)
39
+
40
+
41
+ def handle_strategy_advice(message: str) -> dict:
42
+ if not is_strategy_advice_request(message):
43
+ return {"success": False, "error": "not_strategy_advice"}
44
+
45
+ response = """## 美股量化策略写作框架
46
+
47
+ 建议从 8 个层级写,先定义假设,再验证和风控,最后才进入交易执行。
48
+ 当前是咨询型问题,不需要先写文件,也不会自动运行回测。
49
+
50
+ | 层级 | 要解决的问题 | 产物 |
51
+ |---|---|---|
52
+ | 1. 市场假设 | 为什么这个信号可能赚钱 | 策略假设、适用市场、失效条件 |
53
+ | 2. 标的池 | 交易哪些股票/ETF,如何剔除不可交易标的 | universe 规则、流动性/价格过滤 |
54
+ | 3. 数据层 | OHLCV、财报、因子、宏观、新闻是否可得且可信 | 数据源、清洗、复权、缺失处理 |
55
+ | 4. 信号层 | 入场/出场依据是什么 | 因子、阈值、信号延迟、防未来函数 |
56
+ | 5. 组合层 | 单票仓位、行业暴露、相关性怎么控制 | 权重、再平衡、最大持仓、现金规则 |
57
+ | 6. 交易成本 | 手续费、滑点、冲击成本是否吞噬收益 | 成本模型、成交假设 |
58
+ | 7. 回测评估 | 策略是否稳健,而不是只在样本内好看 | CAGR、Sharpe、最大回撤、胜率、换手、分阶段表现 |
59
+ | 8. 风控部署 | 实盘如何限制损失并监控失效 | 止损、熔断、告警、paper/live 分层 |
60
+
61
+ **推荐落地顺序**
62
+
63
+ 1. 先写一页策略说明:假设、标的池、调仓频率、风险边界。
64
+ 2. 再写最小回测脚本:只包含数据、信号、仓位、绩效。
65
+ 3. 加 walk-forward、不同市场阶段、参数敏感性测试。
66
+ 4. 通过后接 paper trading,不直接上实盘。
67
+ 5. 实盘前加入订单预览、资金上限、熔断、审计日志。
68
+
69
+ **下一步命令**
70
+
71
+ - `/scaffold my_us_strategy --template strategy`:生成策略项目骨架。
72
+ - `/backtest momentum SPY --period 1y`:先跑一个内置动量回测。
73
+ - `/auto-strategy momentum SPY --target sharpe=1.2`:进入自动优化流程。
74
+
75
+ *这只是策略设计框架,不构成投资建议。需要我生成代码时,请明确说“生成/实现/写文件”。*"""
76
+
77
+ return {
78
+ "success": True,
79
+ "response": response,
80
+ "tools_used": ["strategy_advice"],
81
+ "analysis_complete": True,
82
+ }
@@ -0,0 +1,302 @@
1
+ """Stateless helpers lifted out of aria_cli.py.
2
+
3
+ These twelve functions never touched aria_cli's module state — no console, no
4
+ HAS_RICH, no tool registry — yet twelve command mixins reached back into a
5
+ 7000-line module to call them, because that is simply where they happened to
6
+ be defined. Nothing about them needs to live there.
7
+
8
+ aria_cli re-exports them under their original names, so its own callers and the
9
+ globals-rebind for the modules that still rely on it are unaffected.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import pathlib
16
+ from typing import Optional
17
+
18
+ __all__ = [
19
+ "detect_ollama_models",
20
+ "detect_ollama_models_rich",
21
+ "_load_project_context",
22
+ "_display_value",
23
+ "_chart_display_label",
24
+ "_chart_period_from_ta_days",
25
+ "_copy_text_to_clipboard",
26
+ "_reveal_path_in_finder",
27
+ "_open_path_or_url",
28
+ "_extract_code_block",
29
+ "_clean_tool_error_message",
30
+ "_fuzzy_match",
31
+ "_is_ashare_symbol",
32
+ "format_sparkline",
33
+ ]
34
+
35
+
36
+ def _load_project_context() -> str:
37
+ """Load ARIA.md / CLAUDE.md by walking up from cwd (Claude Code style).
38
+
39
+ Search order per directory: ARIA.md → .aria.md → CLAUDE.md
40
+ Walks up at most 5 levels, stops at home dir.
41
+ Multiple files are concatenated (child file takes precedence at top).
42
+ Total cap: 12KB.
43
+ """
44
+ _MAX_BYTES = 12288
45
+ _MAX_LEVELS = 5
46
+ _NAMES = ("ARIA.md", ".aria.md", "CLAUDE.md")
47
+
48
+ home = pathlib.Path.home()
49
+ cwd = pathlib.Path.cwd().resolve()
50
+
51
+ found: list[tuple[pathlib.Path, str]] = [] # (file_path, content)
52
+ current = cwd
53
+ for _ in range(_MAX_LEVELS):
54
+ for name in _NAMES:
55
+ p = current / name
56
+ if p.is_file():
57
+ try:
58
+ content = p.read_text(encoding="utf-8")
59
+ found.append((p, content))
60
+ except Exception:
61
+ pass
62
+ break # only one file per directory level
63
+ if current == home or current.parent == current:
64
+ break
65
+ current = current.parent
66
+
67
+ # Global user file — lowest priority background layer, project files override it.
68
+ # Lives at ~/.arthera/ARIA.md; user can edit with /memory edit global
69
+ _global_aria = home / ".arthera" / "ARIA.md"
70
+ if _global_aria.is_file() and not any(f == _global_aria for f, _ in found):
71
+ try:
72
+ _gc = _global_aria.read_text(encoding="utf-8")
73
+ if _gc.strip():
74
+ found.insert(0, (_global_aria, _gc)) # prepend = lowest priority
75
+ except Exception:
76
+ pass
77
+
78
+ if not found:
79
+ return ""
80
+
81
+ # Child directories first (most specific context wins), then parents
82
+ blocks: list[str] = []
83
+ total = 0
84
+ for fpath, content in found:
85
+ rel = fpath.relative_to(home) if fpath.is_relative_to(home) else fpath
86
+ snippet = content[:(_MAX_BYTES - total)]
87
+ blocks.append(f"### {rel}\n{snippet}")
88
+ total += len(snippet)
89
+ if total >= _MAX_BYTES:
90
+ break
91
+
92
+ return "\n\n## Project Context\n" + "\n\n".join(blocks)
93
+
94
+
95
+ def _display_value(value, digits: int = 2, suffix: str = "") -> str:
96
+ try:
97
+ if value in (None, "", "N/A", "-", "nan"):
98
+ return "—"
99
+ if isinstance(value, (int, float)):
100
+ return f"{float(value):,.{digits}f}{suffix}"
101
+ return str(value)
102
+ except Exception:
103
+ return "—"
104
+
105
+
106
+ def _chart_display_label(raw: str, resolved: str, result: dict | None = None) -> str:
107
+ result = result or {}
108
+ name = str(result.get("name") or result.get("display_name") or "").strip()
109
+ resolved = str(resolved or "").strip().upper()
110
+ raw_label = str(raw or "").strip().upper()
111
+ if name and name.upper() != resolved:
112
+ return f"{name} ({resolved})"
113
+ if raw_label and raw_label != resolved and not raw_label.startswith(resolved):
114
+ return f"{raw_label} ({resolved})"
115
+ return resolved or raw_label or "chart"
116
+
117
+
118
+ def _chart_period_from_ta_days(days: int) -> str:
119
+ try:
120
+ d = int(days)
121
+ except Exception:
122
+ return "1y"
123
+ if d <= 45:
124
+ return "1mo"
125
+ if d <= 110:
126
+ return "3mo"
127
+ if d <= 220:
128
+ return "6mo"
129
+ if d <= 430:
130
+ return "1y"
131
+ if d <= 800:
132
+ return "2y"
133
+ return "3y"
134
+
135
+
136
+ def _copy_text_to_clipboard(text: str) -> tuple[bool, str]:
137
+ try:
138
+ import subprocess as _sp
139
+ _sp.run(["pbcopy"], input=text.encode("utf-8"), check=True, timeout=3)
140
+ return True, ""
141
+ except Exception as exc:
142
+ return False, str(exc)
143
+
144
+
145
+ def _reveal_path_in_finder(path: str) -> tuple[bool, str]:
146
+ try:
147
+ import subprocess as _sp
148
+ _sp.Popen(["open", "-R", path])
149
+ return True, ""
150
+ except Exception as exc:
151
+ return False, str(exc)
152
+
153
+
154
+ def _open_path_or_url(target: str) -> tuple[bool, str]:
155
+ try:
156
+ import subprocess as _sp
157
+ _sp.Popen(["open", target])
158
+ return True, ""
159
+ except Exception as exc:
160
+ return False, str(exc)
161
+
162
+
163
+ def _extract_code_block(text: str) -> Optional[str]:
164
+ """Extract the first code block from markdown-formatted text."""
165
+ import re
166
+ # Match ```python ... ``` or ``` ... ```
167
+ pattern = r'```(?:python|py)?\s*\n(.*?)```'
168
+ match = re.search(pattern, text, re.DOTALL)
169
+ if match:
170
+ return match.group(1).strip()
171
+ # Fallback: try to find any code block
172
+ pattern2 = r'```\w*\s*\n(.*?)```'
173
+ match2 = re.search(pattern2, text, re.DOTALL)
174
+ if match2:
175
+ return match2.group(1).strip()
176
+ return None
177
+
178
+
179
+ def _clean_tool_error_message(error: object) -> str:
180
+ from ui.render.output import clean_tool_error_message as _ctm
181
+ return _ctm(error)
182
+
183
+
184
+ def _fuzzy_match(query: str, candidates: list, max_results: int = 3) -> list:
185
+ """Find closest matches using simple edit distance."""
186
+ def _edit_dist(a, b):
187
+ if len(a) > len(b):
188
+ a, b = b, a
189
+ dists = range(len(a) + 1)
190
+ for j, cb in enumerate(b):
191
+ new_dists = [j + 1]
192
+ for i, ca in enumerate(a):
193
+ cost = 0 if ca == cb else 1
194
+ new_dists.append(min(new_dists[-1] + 1, dists[i + 1] + 1, dists[i] + cost))
195
+ dists = new_dists
196
+ return dists[-1]
197
+
198
+ scored = [(c, _edit_dist(query.lower(), c.lower())) for c in candidates]
199
+ scored.sort(key=lambda x: x[1])
200
+ # Only suggest if edit distance is reasonable (< half the length)
201
+ threshold = max(3, len(query) // 2)
202
+ return [c for c, d in scored[:max_results] if d <= threshold]
203
+
204
+
205
+ def _is_ashare_symbol(symbol: str) -> bool:
206
+ """Quick check whether a symbol looks like a Chinese A-share code."""
207
+ s = symbol.strip().lower()
208
+ return (
209
+ s.startswith("sh") or s.startswith("sz")
210
+ or (len(s) == 6 and s.isdigit())
211
+ or s.endswith(".ss") or s.endswith(".sz")
212
+ )
213
+
214
+
215
+ def format_sparkline(prices: list, width: int = 30) -> str:
216
+ """Generate Unicode sparkline from price data."""
217
+ if not prices or len(prices) < 2:
218
+ return ""
219
+ blocks = "▁▂▃▄▅▆▇█"
220
+ mn, mx = min(prices), max(prices)
221
+ rng = mx - mn or 1
222
+ result = ""
223
+ step = max(1, len(prices) // width)
224
+ for i in range(0, len(prices), step):
225
+ idx = int((prices[i] - mn) / rng * (len(blocks) - 1))
226
+ result += blocks[idx]
227
+ return result[:width]
228
+
229
+
230
+ def detect_ollama_models(ollama_url: str = "http://localhost:11434") -> list:
231
+ """Query Ollama /api/tags and return list of available model names.
232
+
233
+ Always bypasses HTTP_PROXY so localhost is reached directly even when a
234
+ system proxy (VPN / clash / surge) is active.
235
+ """
236
+ import urllib.request
237
+ # Force direct connection — bypass any HTTP_PROXY / HTTPS_PROXY env vars
238
+ _opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
239
+ try:
240
+ with _opener.open(f"{ollama_url}/api/tags", timeout=5) as r:
241
+ data = json.loads(r.read())
242
+ return [m["name"] for m in data.get("models", [])]
243
+ except Exception:
244
+ # Also try 127.0.0.1 if hostname is "localhost" (IPv6 resolution fallback)
245
+ if "localhost" in ollama_url:
246
+ try:
247
+ fallback = ollama_url.replace("localhost", "127.0.0.1")
248
+ with _opener.open(f"{fallback}/api/tags", timeout=5) as r:
249
+ data = json.loads(r.read())
250
+ return [m["name"] for m in data.get("models", [])]
251
+ except Exception:
252
+ pass
253
+ return []
254
+
255
+
256
+ def detect_ollama_models_rich(ollama_url: str = "http://localhost:11434") -> tuple:
257
+ """Return (models_list, error_str) where each entry in models_list is a dict:
258
+ {"name": str, "size_label": str, "family": str, "quant": str,
259
+ "execution": "local" | "remote", "remote_host": str,
260
+ "context_window": int, "capabilities": list[str]}
261
+ error_str is None on success, or a short human-readable reason on failure.
262
+ """
263
+ import urllib.request
264
+ _opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
265
+
266
+ def _try(url: str):
267
+ with _opener.open(f"{url}/api/tags", timeout=5) as r:
268
+ return json.loads(r.read())
269
+
270
+ data = None
271
+ last_err = None
272
+ for u in [ollama_url] + ([ollama_url.replace("localhost", "127.0.0.1")]
273
+ if "localhost" in ollama_url else []):
274
+ try:
275
+ data = _try(u)
276
+ break
277
+ except OSError as e:
278
+ last_err = str(e)
279
+ except Exception as e:
280
+ last_err = str(e)
281
+
282
+ if data is None:
283
+ return [], last_err or "connection failed"
284
+
285
+ results = []
286
+ for m in data.get("models", []):
287
+ det = m.get("details", {})
288
+ size = det.get("parameter_size", "")
289
+ fam = det.get("family", "")
290
+ qnt = det.get("quantization_level", "")
291
+ results.append({
292
+ "name": m["name"],
293
+ "size_label": size, # e.g. "1.5B", "7B", "671.0B"
294
+ "family": fam, # e.g. "qwen2", "deepseek2"
295
+ "quant": qnt, # e.g. "Q4_K_M", "MXFP4"
296
+ "execution": "remote" if m.get("remote_host") else "local",
297
+ "remote_model": m.get("remote_model", ""),
298
+ "remote_host": m.get("remote_host", ""),
299
+ "context_window": int(det.get("context_length") or 0),
300
+ "capabilities": list(m.get("capabilities") or []),
301
+ })
302
+ return results, None