aria-code 0.167.0__py3-none-win_amd64.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 (601) hide show
  1. aria_bots/__init__.py +7 -0
  2. aria_bots/channels/README.md +97 -0
  3. aria_bots/channels/__init__.py +1 -0
  4. aria_bots/channels/approvals.py +263 -0
  5. aria_bots/channels/conversation.py +280 -0
  6. aria_bots/channels/digest.py +186 -0
  7. aria_bots/channels/registry.py +95 -0
  8. aria_bots/engine.py +144 -0
  9. aria_bots/feishu_bot.py +1385 -0
  10. aria_bots/logistics/__init__.py +1 -0
  11. aria_bots/logistics/carriers.py +253 -0
  12. aria_bots/logistics/freight.py +154 -0
  13. aria_bots/logistics/inventory.py +242 -0
  14. aria_bots/logistics/mcp_server.py +156 -0
  15. aria_bots/logistics/records.py +119 -0
  16. aria_bots/logistics/tenancy.py +152 -0
  17. aria_bots/market.py +91 -0
  18. aria_bots/relay_client.py +326 -0
  19. aria_bots/telegram_bot.py +299 -0
  20. aria_code/__init__.py +111 -0
  21. aria_code/_bin/aria-native.exe +0 -0
  22. aria_code/_bin/aria-runtime.exe +0 -0
  23. aria_code/_version.py +20 -0
  24. aria_code/account_memory.py +296 -0
  25. aria_code/account_session.py +166 -0
  26. aria_code/adk_apps/__init__.py +1 -0
  27. aria_code/adk_apps/aria_code_review/__init__.py +1 -0
  28. aria_code/adk_apps/aria_code_review/agent.py +32 -0
  29. aria_code/adk_apps/aria_research/__init__.py +1 -0
  30. aria_code/adk_apps/aria_research/agent.py +31 -0
  31. aria_code/agents/__init__.py +49 -0
  32. aria_code/agents/base.py +329 -0
  33. aria_code/agents/code_review.py +200 -0
  34. aria_code/agents/deep/__init__.py +37 -0
  35. aria_code/agents/deep/calibration_loop.py +144 -0
  36. aria_code/agents/deep/critic.py +125 -0
  37. aria_code/agents/deep/deepen.py +193 -0
  38. aria_code/agents/deep/models.py +149 -0
  39. aria_code/agents/deep/pipeline.py +164 -0
  40. aria_code/agents/deep/quant_fusion.py +192 -0
  41. aria_code/agents/deep/themes.py +95 -0
  42. aria_code/agents/deep/tiers.py +106 -0
  43. aria_code/agents/engineering/__init__.py +14 -0
  44. aria_code/agents/engineering/coder.py +222 -0
  45. aria_code/agents/engineering/tester.py +145 -0
  46. aria_code/agents/financial/__init__.py +21 -0
  47. aria_code/agents/financial/cashflow_burn.py +119 -0
  48. aria_code/agents/financial/catalyst.py +279 -0
  49. aria_code/agents/financial/corporate_finance.py +175 -0
  50. aria_code/agents/financial/debate.py +224 -0
  51. aria_code/agents/financial/earnings.py +315 -0
  52. aria_code/agents/financial/fundamental.py +178 -0
  53. aria_code/agents/financial/macro.py +111 -0
  54. aria_code/agents/financial/news.py +231 -0
  55. aria_code/agents/financial/risk.py +146 -0
  56. aria_code/agents/financial/sector.py +279 -0
  57. aria_code/agents/financial/strategist.py +121 -0
  58. aria_code/agents/financial/stripe_revenue.py +127 -0
  59. aria_code/agents/financial/synthesis.py +274 -0
  60. aria_code/agents/financial/technical.py +277 -0
  61. aria_code/agents/orchestrator.py +44 -0
  62. aria_code/agents/portfolio_agent.py +392 -0
  63. aria_code/agents/realty/__init__.py +62 -0
  64. aria_code/agents/realty/asset_diagnosis.py +152 -0
  65. aria_code/agents/realty/business_match.py +166 -0
  66. aria_code/agents/realty/cashflow_verify.py +209 -0
  67. aria_code/agents/realty/contract_rules.py +210 -0
  68. aria_code/agents/realty/energy_anomaly.py +189 -0
  69. aria_code/agents/realty/exit_settlement.py +208 -0
  70. aria_code/agents/realty/fulfillment_risk.py +206 -0
  71. aria_code/agents/realty/ops_optimize.py +160 -0
  72. aria_code/agents/realty/revenue_share.py +215 -0
  73. aria_code/agents/registry.py +168 -0
  74. aria_code/agents/signal_scheme.py +111 -0
  75. aria_code/agents/sports/__init__.py +0 -0
  76. aria_code/agents/sports/football_agent.py +197 -0
  77. aria_code/agents/supervisor.py +113 -0
  78. aria_code/agents/team.py +500 -0
  79. aria_code/agents/video/__init__.py +16 -0
  80. aria_code/agents/video/director.py +376 -0
  81. aria_code/agents/video/qa_agent.py +143 -0
  82. aria_code/agents/video/script_doctor.py +112 -0
  83. aria_code/agents/warehouse/__init__.py +25 -0
  84. aria_code/agents/warehouse/contracts.py +26 -0
  85. aria_code/agents/warehouse/dashboard.py +155 -0
  86. aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
  87. aria_code/agents/warehouse/inbound_exceptions.py +25 -0
  88. aria_code/agents/warehouse/inventory_health.py +24 -0
  89. aria_code/agents/warehouse/local_run.py +71 -0
  90. aria_code/agents/warehouse/local_snapshot.py +132 -0
  91. aria_code/agents/warehouse/logistics_cost.py +84 -0
  92. aria_code/agents/warehouse/logistics_sync.py +21 -0
  93. aria_code/agents/warehouse/navigation.schema.json +110 -0
  94. aria_code/agents/warehouse/task_agent.py +172 -0
  95. aria_code/agents/warehouse/workflow.py +31 -0
  96. aria_code/aliyun_data_client.py +4 -0
  97. aria_code/apps/README.md +12 -0
  98. aria_code/apps/__init__.py +2 -0
  99. aria_code/apps/channels/README.md +9 -0
  100. aria_code/apps/channels/__init__.py +17 -0
  101. aria_code/apps/channels/approvals.py +11 -0
  102. aria_code/apps/channels/conversation.py +11 -0
  103. aria_code/apps/channels/digest.py +11 -0
  104. aria_code/apps/channels/intake.py +92 -0
  105. aria_code/apps/channels/registry.py +11 -0
  106. aria_code/apps/channels/tradingview.py +94 -0
  107. aria_code/apps/cli/README.md +13 -0
  108. aria_code/apps/cli/__init__.py +2 -0
  109. aria_code/apps/cli/_optional.py +120 -0
  110. aria_code/apps/cli/app_server.py +656 -0
  111. aria_code/apps/cli/auto_update.py +125 -0
  112. aria_code/apps/cli/bootstrap.py +206 -0
  113. aria_code/apps/cli/broker_render.py +206 -0
  114. aria_code/apps/cli/chat_turn.py +1327 -0
  115. aria_code/apps/cli/cloud_models.py +231 -0
  116. aria_code/apps/cli/codegen_paths.py +29 -0
  117. aria_code/apps/cli/commands/__init__.py +34 -0
  118. aria_code/apps/cli/commands/_business_render.py +78 -0
  119. aria_code/apps/cli/commands/_ui.py +71 -0
  120. aria_code/apps/cli/commands/analysis_cmds.py +378 -0
  121. aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
  122. aria_code/apps/cli/commands/auth_cmds.py +215 -0
  123. aria_code/apps/cli/commands/backtest_cmds.py +2518 -0
  124. aria_code/apps/cli/commands/broker_cmds.py +1236 -0
  125. aria_code/apps/cli/commands/business_workflow_cmds.py +379 -0
  126. aria_code/apps/cli/commands/canvas_cmds.py +77 -0
  127. aria_code/apps/cli/commands/catalog.py +189 -0
  128. aria_code/apps/cli/commands/core_cmds.py +2748 -0
  129. aria_code/apps/cli/commands/data_cmds.py +521 -0
  130. aria_code/apps/cli/commands/diagnostic_cmds.py +285 -0
  131. aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
  132. aria_code/apps/cli/commands/engineering_cmds.py +399 -0
  133. aria_code/apps/cli/commands/file_cmds.py +312 -0
  134. aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
  135. aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
  136. aria_code/apps/cli/commands/market.py +544 -0
  137. aria_code/apps/cli/commands/market_cmds.py +1419 -0
  138. aria_code/apps/cli/commands/market_context.py +432 -0
  139. aria_code/apps/cli/commands/market_render.py +7 -0
  140. aria_code/apps/cli/commands/model_cmds.py +2374 -0
  141. aria_code/apps/cli/commands/ops_cmds.py +721 -0
  142. aria_code/apps/cli/commands/orchestrator_cmds.py +181 -0
  143. aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
  144. aria_code/apps/cli/commands/portfolio_cmds.py +1316 -0
  145. aria_code/apps/cli/commands/report.py +476 -0
  146. aria_code/apps/cli/commands/scaffold_templates.py +617 -0
  147. aria_code/apps/cli/commands/session_cmds.py +236 -0
  148. aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
  149. aria_code/apps/cli/commands/team.py +723 -0
  150. aria_code/apps/cli/commands/team_render.py +8 -0
  151. aria_code/apps/cli/commands/ui_cmds.py +401 -0
  152. aria_code/apps/cli/commands/warehouse_cmds.py +297 -0
  153. aria_code/apps/cli/commands/workflow_cmds.py +629 -0
  154. aria_code/apps/cli/commands/workspace_cmds.py +1543 -0
  155. aria_code/apps/cli/config_paths.py +73 -0
  156. aria_code/apps/cli/config_store.py +158 -0
  157. aria_code/apps/cli/constants.py +88 -0
  158. aria_code/apps/cli/context.py +34 -0
  159. aria_code/apps/cli/conversation_policy.py +53 -0
  160. aria_code/apps/cli/deterministic.py +200 -0
  161. aria_code/apps/cli/direct.py +48 -0
  162. aria_code/apps/cli/earnings_view.py +172 -0
  163. aria_code/apps/cli/endpoint_migration.py +64 -0
  164. aria_code/apps/cli/exec_events.py +255 -0
  165. aria_code/apps/cli/file_references.py +155 -0
  166. aria_code/apps/cli/finance_service_catalog.py +111 -0
  167. aria_code/apps/cli/football_reports.py +231 -0
  168. aria_code/apps/cli/github_app_auth.py +135 -0
  169. aria_code/apps/cli/google_login.py +239 -0
  170. aria_code/apps/cli/handlers/__init__.py +11 -0
  171. aria_code/apps/cli/handlers/broker_handlers.py +122 -0
  172. aria_code/apps/cli/handlers/chart_handlers.py +1314 -0
  173. aria_code/apps/cli/handlers/market_handlers.py +2570 -0
  174. aria_code/apps/cli/handlers/realty_handlers.py +114 -0
  175. aria_code/apps/cli/handlers/sports_handlers.py +22 -0
  176. aria_code/apps/cli/handlers/strategy_advice.py +82 -0
  177. aria_code/apps/cli/headless.py +352 -0
  178. aria_code/apps/cli/helpers.py +326 -0
  179. aria_code/apps/cli/hooks.py +180 -0
  180. aria_code/apps/cli/i18n.py +310 -0
  181. aria_code/apps/cli/intent.py +136 -0
  182. aria_code/apps/cli/intent_router.py +305 -0
  183. aria_code/apps/cli/intent_shadow.py +72 -0
  184. aria_code/apps/cli/intent_signals.py +347 -0
  185. aria_code/apps/cli/lifecycle_hooks.py +48 -0
  186. aria_code/apps/cli/local_tool_schemas.py +550 -0
  187. aria_code/apps/cli/main.py +39 -0
  188. aria_code/apps/cli/market_hours.py +100 -0
  189. aria_code/apps/cli/market_metadata.py +135 -0
  190. aria_code/apps/cli/market_universe.py +487 -0
  191. aria_code/apps/cli/message_processing.py +310 -0
  192. aria_code/apps/cli/mixin_binding.py +32 -0
  193. aria_code/apps/cli/model_catalog.py +652 -0
  194. aria_code/apps/cli/model_probe.py +169 -0
  195. aria_code/apps/cli/model_retirement.py +65 -0
  196. aria_code/apps/cli/native_branding.py +12 -0
  197. aria_code/apps/cli/native_bridge.py +118 -0
  198. aria_code/apps/cli/native_entry.py +58 -0
  199. aria_code/apps/cli/pdf_report.py +977 -0
  200. aria_code/apps/cli/plan_mode.py +139 -0
  201. aria_code/apps/cli/plotly_html.py +15 -0
  202. aria_code/apps/cli/prediction_feedback.py +202 -0
  203. aria_code/apps/cli/preflight.py +502 -0
  204. aria_code/apps/cli/project_aria.py +60 -0
  205. aria_code/apps/cli/prompt_assembly.py +78 -0
  206. aria_code/apps/cli/prompts/__init__.py +0 -0
  207. aria_code/apps/cli/prompts/coding.py +71 -0
  208. aria_code/apps/cli/prompts/select.py +193 -0
  209. aria_code/apps/cli/prompts/system_prompts.py +562 -0
  210. aria_code/apps/cli/prompts/ui.py +436 -0
  211. aria_code/apps/cli/provider_endpoints.py +237 -0
  212. aria_code/apps/cli/provider_keys.py +73 -0
  213. aria_code/apps/cli/providers/__init__.py +1 -0
  214. aria_code/apps/cli/providers/base.py +775 -0
  215. aria_code/apps/cli/providers/chat_routing.py +139 -0
  216. aria_code/apps/cli/providers/collaboration.py +98 -0
  217. aria_code/apps/cli/providers/llm/__init__.py +1 -0
  218. aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
  219. aria_code/apps/cli/providers/llm/ollama_stream.py +1574 -0
  220. aria_code/apps/cli/providers/llm/response_cache.py +60 -0
  221. aria_code/apps/cli/providers/llm/sse_stream.py +298 -0
  222. aria_code/apps/cli/providers/runtime_bridge.py +586 -0
  223. aria_code/apps/cli/providers/vertexai_stream.py +592 -0
  224. aria_code/apps/cli/release_download.py +159 -0
  225. aria_code/apps/cli/review_runner.py +111 -0
  226. aria_code/apps/cli/runtime_consumer.py +769 -0
  227. aria_code/apps/cli/sandbox.py +32 -0
  228. aria_code/apps/cli/session_export.py +120 -0
  229. aria_code/apps/cli/session_jsonl.py +208 -0
  230. aria_code/apps/cli/session_refresh.py +123 -0
  231. aria_code/apps/cli/session_store.py +125 -0
  232. aria_code/apps/cli/skills_catalog.py +273 -0
  233. aria_code/apps/cli/task_isolation.py +211 -0
  234. aria_code/apps/cli/terminal_chrome.py +412 -0
  235. aria_code/apps/cli/todo_tracker.py +190 -0
  236. aria_code/apps/cli/tool_executor.py +932 -0
  237. aria_code/apps/cli/tool_registry.py +34 -0
  238. aria_code/apps/cli/tool_scope.py +113 -0
  239. aria_code/apps/cli/tools/__init__.py +40 -0
  240. aria_code/apps/cli/tools/context.py +46 -0
  241. aria_code/apps/cli/tools/file_tools.py +181 -0
  242. aria_code/apps/cli/tools/market_tools.py +626 -0
  243. aria_code/apps/cli/tools/notebook_tools.py +111 -0
  244. aria_code/apps/cli/tools/system_tools.py +656 -0
  245. aria_code/apps/cli/tools/write_tools.py +961 -0
  246. aria_code/apps/cli/tradingview_bridge.py +529 -0
  247. aria_code/apps/cli/transactions.py +269 -0
  248. aria_code/apps/cli/turn_planning.py +137 -0
  249. aria_code/apps/cli/update_check.py +257 -0
  250. aria_code/apps/cli/updater.py +373 -0
  251. aria_code/apps/cli/utils/__init__.py +0 -0
  252. aria_code/apps/cli/utils/market_detect.py +1663 -0
  253. aria_code/apps/cli/web_login.py +136 -0
  254. aria_code/apps/cli/workflow_runner.py +127 -0
  255. aria_code/apps/cli/workspace_route.py +121 -0
  256. aria_code/apps/daemon/README.md +14 -0
  257. aria_code/apps/vscode/README.md +115 -0
  258. aria_code/apps/vscode/package.json +70 -0
  259. aria_code/aria_cli.py +5244 -0
  260. aria_code/aria_daemon.py +1372 -0
  261. aria_code/aria_feishu_bot.py +16 -0
  262. aria_code/aria_mcp_server.py +61 -0
  263. aria_code/aria_relay_client.py +4 -0
  264. aria_code/aria_telegram_bot.py +11 -0
  265. aria_code/ariarc.py +385 -0
  266. aria_code/artifacts.py +581 -0
  267. aria_code/backtest_engine.py +4 -0
  268. aria_code/backtest_report.py +4 -0
  269. aria_code/brokers/__init__.py +78 -0
  270. aria_code/brokers/automation.py +193 -0
  271. aria_code/brokers/base.py +231 -0
  272. aria_code/brokers/capabilities.py +264 -0
  273. aria_code/brokers/cn/__init__.py +10 -0
  274. aria_code/brokers/cn/easytrader_broker.py +193 -0
  275. aria_code/brokers/cn/futu_broker.py +194 -0
  276. aria_code/brokers/cn/longbridge_broker.py +190 -0
  277. aria_code/brokers/cn/tiger_broker.py +196 -0
  278. aria_code/brokers/cn/xtquant_broker.py +225 -0
  279. aria_code/brokers/config.py +414 -0
  280. aria_code/brokers/intl/__init__.py +5 -0
  281. aria_code/brokers/intl/alpaca_broker.py +183 -0
  282. aria_code/brokers/intl/ibkr_broker.py +215 -0
  283. aria_code/brokers/intl/trading212_broker.py +183 -0
  284. aria_code/brokers/intl/webull_broker.py +156 -0
  285. aria_code/brokers/paper_broker.py +266 -0
  286. aria_code/brokers/planning.py +296 -0
  287. aria_code/brokers/registry.py +213 -0
  288. aria_code/brokers/trading.py +267 -0
  289. aria_code/canva_client.py +4 -0
  290. aria_code/change_store.py +127 -0
  291. aria_code/clients/__init__.py +0 -0
  292. aria_code/clients/aliyun_data_client.py +660 -0
  293. aria_code/clients/aria_relay_client.py +16 -0
  294. aria_code/clients/canva_client.py +284 -0
  295. aria_code/clients/figma_client.py +100 -0
  296. aria_code/clients/football_data_client.py +1671 -0
  297. aria_code/clients/kling_video_client.py +182 -0
  298. aria_code/clients/market_data_client.py +1985 -0
  299. aria_code/clients/openai_image_client.py +242 -0
  300. aria_code/clients/runway_video_client.py +157 -0
  301. aria_code/clients/warehouse_erp_client.py +105 -0
  302. aria_code/cloud_memory.py +190 -0
  303. aria_code/command_safety.py +19 -0
  304. aria_code/computer_use_server.py +276 -0
  305. aria_code/computer_use_tools.py +4 -0
  306. aria_code/dashboard_generator.py +579 -0
  307. aria_code/data_analysis_tools.py +4 -0
  308. aria_code/data_cleaner.py +483 -0
  309. aria_code/data_service.py +513 -0
  310. aria_code/datasources/__init__.py +23 -0
  311. aria_code/datasources/base.py +166 -0
  312. aria_code/datasources/router.py +253 -0
  313. aria_code/datasources/sec_statements.py +217 -0
  314. aria_code/datasources/sources/__init__.py +15 -0
  315. aria_code/datasources/sources/akshare_source.py +269 -0
  316. aria_code/datasources/sources/alpha_vantage_source.py +203 -0
  317. aria_code/datasources/sources/edgar_source.py +219 -0
  318. aria_code/datasources/sources/finnhub_source.py +198 -0
  319. aria_code/datasources/sources/fred_source.py +223 -0
  320. aria_code/datasources/sources/tushare_source.py +143 -0
  321. aria_code/datasources/sources/web_scraper_source.py +278 -0
  322. aria_code/datasources/sources/world_bank_source.py +206 -0
  323. aria_code/datasources/sources/yfinance_source.py +152 -0
  324. aria_code/doctor.py +860 -0
  325. aria_code/domain/__init__.py +0 -0
  326. aria_code/domain/_finance_core/__init__.py +5 -0
  327. aria_code/domain/_finance_core/backtest_engine.py +325 -0
  328. aria_code/domain/_finance_core/performance_stats.py +101 -0
  329. aria_code/domain/backtest_engine.py +75 -0
  330. aria_code/domain/backtest_report.py +488 -0
  331. aria_code/domain/finance_formulas.py +389 -0
  332. aria_code/domain/performance_stats.py +6 -0
  333. aria_code/domain/portfolio_ledger.py +307 -0
  334. aria_code/domain/strategy_vault.py +421 -0
  335. aria_code/evals/__init__.py +37 -0
  336. aria_code/evals/behavior.py +113 -0
  337. aria_code/evals/gap.py +93 -0
  338. aria_code/evals/harness.py +949 -0
  339. aria_code/evals/review_probe.py +132 -0
  340. aria_code/evals/runner.py +425 -0
  341. aria_code/evals/scorecard.py +322 -0
  342. aria_code/evals/summary.py +125 -0
  343. aria_code/external_agent_runner.py +115 -0
  344. aria_code/figma_client.py +4 -0
  345. aria_code/file_analysis_tools.py +4 -0
  346. aria_code/finance_formulas.py +4 -0
  347. aria_code/football_data_client.py +4 -0
  348. aria_code/image_gen_tools.py +4 -0
  349. aria_code/intent_classifier.py +459 -0
  350. aria_code/kling_video_client.py +4 -0
  351. aria_code/licensing.py +151 -0
  352. aria_code/local_finance_tools.py +4 -0
  353. aria_code/local_image_provider.py +4 -0
  354. aria_code/local_llm_provider.py +4 -0
  355. aria_code/macro_tools.py +4 -0
  356. aria_code/markdown_pdf.py +289 -0
  357. aria_code/market_data_client.py +4 -0
  358. aria_code/mcp_client.py +744 -0
  359. aria_code/memory_manager.py +273 -0
  360. aria_code/model_capability.py +512 -0
  361. aria_code/notification_tools.py +4 -0
  362. aria_code/openai_image_client.py +4 -0
  363. aria_code/packages/__init__.py +26 -0
  364. aria_code/packages/adk_bridge/__init__.py +11 -0
  365. aria_code/packages/adk_bridge/code_review_tools.py +49 -0
  366. aria_code/packages/adk_bridge/market_tools.py +125 -0
  367. aria_code/packages/aria_agents/__init__.py +5 -0
  368. aria_code/packages/aria_agents/manifest.py +78 -0
  369. aria_code/packages/aria_core/__init__.py +34 -0
  370. aria_code/packages/aria_core/architecture.py +269 -0
  371. aria_code/packages/aria_core/export.py +147 -0
  372. aria_code/packages/aria_core/manifest.py +65 -0
  373. aria_code/packages/aria_core/paths.py +57 -0
  374. aria_code/packages/aria_core/secure_file.py +68 -0
  375. aria_code/packages/aria_infra/__init__.py +15 -0
  376. aria_code/packages/aria_infra/arthera.py +58 -0
  377. aria_code/packages/aria_infra/doctor.py +246 -0
  378. aria_code/packages/aria_infra/product.py +40 -0
  379. aria_code/packages/aria_mcp/__init__.py +25 -0
  380. aria_code/packages/aria_mcp/bridge.py +232 -0
  381. aria_code/packages/aria_mcp/circuit.py +82 -0
  382. aria_code/packages/aria_mcp/config.py +162 -0
  383. aria_code/packages/aria_mcp/installer.py +95 -0
  384. aria_code/packages/aria_mcp/server.py +1705 -0
  385. aria_code/packages/aria_mcp/tools.py +81 -0
  386. aria_code/packages/aria_sdk/__init__.py +19 -0
  387. aria_code/packages/aria_sdk/client.py +393 -0
  388. aria_code/packages/aria_sdk/providers.py +70 -0
  389. aria_code/packages/aria_sdk/streaming.py +75 -0
  390. aria_code/packages/aria_sdk/types.py +86 -0
  391. aria_code/packages/aria_services/__init__.py +92 -0
  392. aria_code/packages/aria_services/cache.py +60 -0
  393. aria_code/packages/aria_services/context.py +356 -0
  394. aria_code/packages/aria_services/context_checkpoints.py +157 -0
  395. aria_code/packages/aria_services/data.py +11 -0
  396. aria_code/packages/aria_services/provider_health.py +304 -0
  397. aria_code/packages/aria_services/references.py +247 -0
  398. aria_code/packages/aria_services/registry.py +249 -0
  399. aria_code/packages/aria_services/research_protocol.py +137 -0
  400. aria_code/packages/aria_services/research_quality.py +186 -0
  401. aria_code/packages/aria_services/settings.py +160 -0
  402. aria_code/packages/aria_services/usage.py +151 -0
  403. aria_code/packages/aria_skills/__init__.py +46 -0
  404. aria_code/packages/aria_skills/catalog.py +149 -0
  405. aria_code/packages/aria_skills/loader.py +455 -0
  406. aria_code/packages/aria_skills/registry.py +84 -0
  407. aria_code/packages/aria_tools/__init__.py +5 -0
  408. aria_code/packages/aria_tools/financial/__init__.py +5 -0
  409. aria_code/packages/aria_tools/financial/_paths.py +92 -0
  410. aria_code/packages/aria_tools/registry.py +129 -0
  411. aria_code/packages/quant_engine/__init__.py +33 -0
  412. aria_code/packages/quant_engine/_namespace.py +24 -0
  413. aria_code/packages/quant_engine/backtest/__init__.py +10 -0
  414. aria_code/packages/quant_engine/backtest/core.py +53 -0
  415. aria_code/packages/quant_engine/backtest/engine.py +102 -0
  416. aria_code/packages/quant_engine/portfolio/__init__.py +9 -0
  417. aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
  418. aria_code/packages/quant_engine/sports/__init__.py +76 -0
  419. aria_code/packages/quant_engine/sports/calibrator.py +354 -0
  420. aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
  421. aria_code/packages/quant_engine/sports/elo.py +300 -0
  422. aria_code/packages/quant_engine/sports/form.py +188 -0
  423. aria_code/packages/quant_engine/sports/h2h.py +195 -0
  424. aria_code/packages/quant_engine/sports/predictor.py +312 -0
  425. aria_code/packages/quant_engine/sports/tracker.py +665 -0
  426. aria_code/packages/quant_engine/stochastic/__init__.py +31 -0
  427. aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  428. aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
  429. aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  430. aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  431. aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
  432. aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  433. aria_code/packs/__init__.py +85 -0
  434. aria_code/packs/base.py +168 -0
  435. aria_code/packs/finance/__init__.py +250 -0
  436. aria_code/packs/healthcare/__init__.py +40 -0
  437. aria_code/packs/logistics/__init__.py +171 -0
  438. aria_code/packs/payments/__init__.py +139 -0
  439. aria_code/packs/realty/__init__.py +190 -0
  440. aria_code/packs/registry.py +202 -0
  441. aria_code/packs/rules.py +90 -0
  442. aria_code/plan_utils.py +194 -0
  443. aria_code/plugin_loader.py +328 -0
  444. aria_code/portfolio_ledger.py +4 -0
  445. aria_code/preview_server.py +449 -0
  446. aria_code/privacy/__init__.py +5 -0
  447. aria_code/privacy/feedback.py +123 -0
  448. aria_code/project_review.py +278 -0
  449. aria_code/project_review_api.py +235 -0
  450. aria_code/project_review_server.py +18 -0
  451. aria_code/project_tools.py +4 -0
  452. aria_code/providers/__init__.py +30 -0
  453. aria_code/providers/google_video_provider.py +256 -0
  454. aria_code/providers/llm/__init__.py +19 -0
  455. aria_code/providers/llm/anthropic.py +184 -0
  456. aria_code/providers/llm/arthera_gateway.py +90 -0
  457. aria_code/providers/llm/autoconfig.py +213 -0
  458. aria_code/providers/llm/base.py +139 -0
  459. aria_code/providers/llm/custom.py +158 -0
  460. aria_code/providers/llm/ollama.py +137 -0
  461. aria_code/providers/llm/openai_compat.py +350 -0
  462. aria_code/providers/llm/registry.py +457 -0
  463. aria_code/providers/local_image_provider.py +202 -0
  464. aria_code/providers/local_llm_provider.py +549 -0
  465. aria_code/providers/ollama_models.py +79 -0
  466. aria_code/providers/openai_params.py +32 -0
  467. aria_code/providers/tool_messages.py +100 -0
  468. aria_code/realty_data_tools.py +4 -0
  469. aria_code/report_exporters.py +150 -0
  470. aria_code/report_generator.py +1764 -0
  471. aria_code/review_service.py +497 -0
  472. aria_code/runtime/__init__.py +176 -0
  473. aria_code/runtime/acceptance.py +632 -0
  474. aria_code/runtime/agent_loop.py +1851 -0
  475. aria_code/runtime/agent_profiles.py +75 -0
  476. aria_code/runtime/approval.py +71 -0
  477. aria_code/runtime/baseline.py +219 -0
  478. aria_code/runtime/budget.py +232 -0
  479. aria_code/runtime/checkpoints.py +481 -0
  480. aria_code/runtime/consult.py +65 -0
  481. aria_code/runtime/contract.py +282 -0
  482. aria_code/runtime/delivery.py +432 -0
  483. aria_code/runtime/events.py +113 -0
  484. aria_code/runtime/gateway.py +167 -0
  485. aria_code/runtime/lsp.py +484 -0
  486. aria_code/runtime/native_index.py +153 -0
  487. aria_code/runtime/orchestrator.py +55 -0
  488. aria_code/runtime/processes.py +343 -0
  489. aria_code/runtime/project_graph.py +614 -0
  490. aria_code/runtime/repo_map.py +712 -0
  491. aria_code/runtime/research_bundle.py +105 -0
  492. aria_code/runtime/review.py +269 -0
  493. aria_code/runtime/run_state.py +127 -0
  494. aria_code/runtime/run_store.py +522 -0
  495. aria_code/runtime/scoped_runs.py +113 -0
  496. aria_code/runtime/self_healing.py +422 -0
  497. aria_code/runtime/semantic_diff.py +171 -0
  498. aria_code/runtime/subagent.py +721 -0
  499. aria_code/runtime/symbol_edit.py +117 -0
  500. aria_code/runtime/task_brief.py +46 -0
  501. aria_code/runtime/task_context.py +145 -0
  502. aria_code/runtime/task_ledger.py +108 -0
  503. aria_code/runtime/task_orchestrator.py +90 -0
  504. aria_code/runtime/task_outcome.py +33 -0
  505. aria_code/runtime/task_verification.py +266 -0
  506. aria_code/runtime/task_worktree.py +448 -0
  507. aria_code/runtime/tool_executor.py +329 -0
  508. aria_code/runtime/tool_policy.py +107 -0
  509. aria_code/runtime/transactions.py +216 -0
  510. aria_code/runtime/windows_job.py +115 -0
  511. aria_code/runtime/workflows.py +222 -0
  512. aria_code/runtime/worktrees.py +215 -0
  513. aria_code/runway_video_client.py +4 -0
  514. aria_code/safety/__init__.py +27 -0
  515. aria_code/safety/client_isolation.py +178 -0
  516. aria_code/safety/permissions.py +339 -0
  517. aria_code/safety/risk.py +699 -0
  518. aria_code/safety/sandbox.py +148 -0
  519. aria_code/safety/service.py +102 -0
  520. aria_code/service_endpoints.py +14 -0
  521. aria_code/setup_wizard.py +684 -0
  522. aria_code/spreadsheet_tools.py +4 -0
  523. aria_code/strategy_vault.py +4 -0
  524. aria_code/tools/__init__.py +0 -0
  525. aria_code/tools/artifact_tools.py +252 -0
  526. aria_code/tools/broker_tools.py +92 -0
  527. aria_code/tools/build_quant_engine.py +97 -0
  528. aria_code/tools/code_audit_tools.py +192 -0
  529. aria_code/tools/computer_use_tools.py +509 -0
  530. aria_code/tools/data_analysis_tools.py +831 -0
  531. aria_code/tools/enterprise_finance_tools.py +202 -0
  532. aria_code/tools/extended_tools.py +240 -0
  533. aria_code/tools/file_analysis_tools.py +880 -0
  534. aria_code/tools/image_gen_tools.py +114 -0
  535. aria_code/tools/local_finance_tools.py +3488 -0
  536. aria_code/tools/logistics_carriers.py +11 -0
  537. aria_code/tools/logistics_inventory.py +11 -0
  538. aria_code/tools/logistics_io.py +11 -0
  539. aria_code/tools/logistics_tenancy.py +11 -0
  540. aria_code/tools/logistics_tools.py +11 -0
  541. aria_code/tools/macro_tools.py +412 -0
  542. aria_code/tools/notification_tools.py +249 -0
  543. aria_code/tools/project_tools.py +525 -0
  544. aria_code/tools/realty_data_tools.py +662 -0
  545. aria_code/tools/spreadsheet_tools.py +399 -0
  546. aria_code/tools/stripe_tools.py +115 -0
  547. aria_code/tools/video_tools.py +500 -0
  548. aria_code/training/__init__.py +1 -0
  549. aria_code/training/compare.py +117 -0
  550. aria_code/training/prelude_commands.py +111 -0
  551. aria_code/training/prelude_eval.py +390 -0
  552. aria_code/training/prelude_real.py +183 -0
  553. aria_code/training/prelude_train.py +248 -0
  554. aria_code/training/sft_dataset.py +230 -0
  555. aria_code/training/trajectory_dataset.py +263 -0
  556. aria_code/ui/__init__.py +105 -0
  557. aria_code/ui/banner.py +404 -0
  558. aria_code/ui/completer.py +571 -0
  559. aria_code/ui/console.py +285 -0
  560. aria_code/ui/input_box.py +505 -0
  561. aria_code/ui/mentions.py +206 -0
  562. aria_code/ui/picker.py +248 -0
  563. aria_code/ui/render/__init__.py +11 -0
  564. aria_code/ui/render/actions.py +297 -0
  565. aria_code/ui/render/finance.py +1732 -0
  566. aria_code/ui/render/market.py +253 -0
  567. aria_code/ui/render/output.py +954 -0
  568. aria_code/ui/render/responsive.py +75 -0
  569. aria_code/ui/render/team.py +367 -0
  570. aria_code/ui/robot.py +239 -0
  571. aria_code/ui/startup_dashboard.py +110 -0
  572. aria_code/video_analysis.py +147 -0
  573. aria_code/video_editor.py +518 -0
  574. aria_code/video_workflow/__init__.py +8 -0
  575. aria_code/video_workflow/__main__.py +61 -0
  576. aria_code/video_workflow/engines/__init__.py +18 -0
  577. aria_code/video_workflow/engines/base.py +69 -0
  578. aria_code/video_workflow/engines/custom_model.py +204 -0
  579. aria_code/video_workflow/engines/google_veo.py +94 -0
  580. aria_code/video_workflow/engines/local_engine.py +71 -0
  581. aria_code/video_workflow/engines/registry.py +98 -0
  582. aria_code/video_workflow/google_models.py +278 -0
  583. aria_code/video_workflow/music/__init__.py +5 -0
  584. aria_code/video_workflow/music/foley_service.py +132 -0
  585. aria_code/video_workflow/music/service.py +212 -0
  586. aria_code/video_workflow/pipeline.py +620 -0
  587. aria_code/video_workflow/plan.py +202 -0
  588. aria_code/video_workflow/session.py +297 -0
  589. aria_code/workspace/__init__.py +6 -0
  590. aria_code/workspace/files.py +219 -0
  591. aria_code/workspace/verify.py +167 -0
  592. aria_code/workspace_changes.py +199 -0
  593. aria_code/workspace_service.py +196 -0
  594. aria_code/workspace_tools.py +150 -0
  595. aria_code-0.167.0.dist-info/METADATA +330 -0
  596. aria_code-0.167.0.dist-info/RECORD +601 -0
  597. aria_code-0.167.0.dist-info/WHEEL +5 -0
  598. aria_code-0.167.0.dist-info/entry_points.txt +5 -0
  599. aria_code-0.167.0.dist-info/licenses/LICENSE +201 -0
  600. aria_code-0.167.0.dist-info/licenses/NOTICE +49 -0
  601. aria_code-0.167.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,1543 @@
1
+ """
2
+ WorkspaceCommandsMixin — Workspace commands: packages, file, project, init, setup, memory.
3
+
4
+ Extracted from aria_cli.py. Methods' __globals__ are rebound to aria_cli's namespace
5
+ by _rebind_mixin_globals() called at module load time.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from ._ui import print_error, rich_box
11
+ from aria_code.packages.aria_core.paths import aria_home
12
+
13
+
14
+ import json
15
+ import asyncio
16
+ import datetime
17
+ import time
18
+ import shlex
19
+ import sys
20
+ import os
21
+ from typing import Dict, Any, Optional
22
+
23
+ def detect_ollama_models_rich(*args, **kwargs):
24
+ from aria_code.apps.cli.helpers import detect_ollama_models_rich as fn
25
+ return fn(*args, **kwargs)
26
+ def _load_project_context(*args, **kwargs):
27
+ from aria_code.apps.cli.helpers import _load_project_context as fn
28
+ return fn(*args, **kwargs)
29
+ def _get__HAS_MCP():
30
+ from aria_code.apps.cli._optional import HAS_MCP as val
31
+ return val
32
+ def _get_LOCAL_TOOL_SCHEMAS():
33
+ # Owned by apps/cli/tool_registry.py; aria_cli fills it in place.
34
+ from ..tool_registry import LOCAL_TOOL_SCHEMAS as val
35
+ return val
36
+ import pathlib
37
+ def _get_provider_key(*args, **kwargs):
38
+ from aria_code.apps.cli.provider_keys import get_provider_key as fn
39
+ return fn(*args, **kwargs)
40
+ def _get_rich_box():
41
+ # Resolved locally now — see ._ui; no aria_cli round-trip.
42
+ return rich_box
43
+ def _arrow_select(*args, **kwargs):
44
+ # Re-exported by aria_cli; taken from its own module here.
45
+ from aria_code.ui.picker import arrow_select as fn
46
+ return fn(*args, **kwargs)
47
+ def _get___version__():
48
+ from aria_code.aria_cli import __version__ as val
49
+ return val
50
+ def _get_LOCAL_TOOLS():
51
+ # Owned by apps/cli/tool_registry.py; aria_cli fills it in place.
52
+ from ..tool_registry import LOCAL_TOOLS as val
53
+ return val
54
+ def get_model_capability(*args, **kwargs):
55
+ from aria_code.apps.cli._optional import get_model_capability as fn
56
+ return fn(*args, **kwargs)
57
+ def _get_MCP_CONFIG_PATH():
58
+ from aria_code.apps.cli._optional import MCP_CONFIG_PATH as val
59
+ return val
60
+ def _get_SESSIONS_DIR():
61
+ from aria_code.aria_cli import SESSIONS_DIR as val
62
+ return val
63
+ def _get__HAS_MODEL_CAP():
64
+ from aria_code.apps.cli._optional import HAS_MODEL_CAP as val
65
+ return val
66
+ def _strip_markdown_fences(*args, **kwargs):
67
+ from aria_code.aria_cli import _strip_markdown_fences as fn
68
+ return fn(*args, **kwargs)
69
+ def _load_data_keys(*args, **kwargs):
70
+ from aria_code.apps.cli.provider_keys import load_data_keys as fn
71
+ return fn(*args, **kwargs)
72
+
73
+ import json
74
+ import asyncio
75
+ import datetime
76
+ import time
77
+ import shlex
78
+ import sys
79
+ import os
80
+ from typing import Dict, Any, Optional
81
+
82
+
83
+ class WorkspaceCommandsMixin:
84
+ """Mixin: Workspace commands: packages, file, project, init, setup, memory."""
85
+
86
+ async def cmd_packages(self, args: str):
87
+ """Show Aria Code package facades and Arthera package bridge status."""
88
+ try:
89
+ from aria_code.packages.aria_agents import list_agent_manifests
90
+ from aria_code.packages.aria_core import (
91
+ build_package_manifest,
92
+ list_architecture_layers,
93
+ required_architecture_layer_names,
94
+ write_package_manifest,
95
+ )
96
+ from aria_code.packages.aria_infra import (
97
+ aria_code_identity,
98
+ build_package_doctor_report,
99
+ discover_arthera_packages,
100
+ )
101
+ from aria_code.packages.aria_mcp import (
102
+ arthera_quant_engine_server_config,
103
+ default_exposures,
104
+ load_mcp_config,
105
+ merge_server_config,
106
+ mcp_server_status,
107
+ mcp_tools_to_specs,
108
+ write_mcp_config,
109
+ )
110
+ from aria_code.packages.aria_services import (
111
+ list_service_specs,
112
+ list_service_usage_specs,
113
+ required_service_names,
114
+ )
115
+ from aria_code.packages.aria_services.provider_health import GLOBAL_PROVIDER_HEALTH
116
+ from aria_code.packages.aria_skills import builtin_skill_specs
117
+ from aria_code.packages.aria_tools import build_registry_from_legacy
118
+ except Exception as exc:
119
+ print_error(self.context, f"packages facade unavailable: {exc}")
120
+ return
121
+
122
+ sub = args.strip().lower()
123
+ identity = aria_code_identity(_get___version__())
124
+ arthera = discover_arthera_packages()
125
+ tool_registry = build_registry_from_legacy(_get_LOCAL_TOOLS(), _get_LOCAL_TOOL_SCHEMAS())
126
+ services = list_service_specs()
127
+ agent_count = len(list_agent_manifests())
128
+ skill_count = len(builtin_skill_specs())
129
+ mcp_exposure_count = len(default_exposures())
130
+ try:
131
+ server_cfg = arthera_quant_engine_server_config()
132
+ except ValueError:
133
+ server_cfg = None
134
+ mcp_config_path = _get_MCP_CONFIG_PATH() if _get__HAS_MCP() else aria_home() / "mcp_servers.json"
135
+
136
+ if sub.startswith("export-manifest") or sub.startswith("manifest"):
137
+ raw_parts = args.strip().split(maxsplit=1)
138
+ out_path = None
139
+ if len(raw_parts) > 1:
140
+ out_path = pathlib.Path(raw_parts[1]).expanduser()
141
+ else:
142
+ from aria_code.artifacts import artifact_dir
143
+ out_path = artifact_dir("manifests", "packages") / "aria_package_manifest.json"
144
+
145
+ reg = getattr(self.terminal, "_mcp_registry", None)
146
+ mcp_tools = []
147
+ if reg:
148
+ try:
149
+ mcp_tools = [
150
+ tool for tool in reg.all_tools()
151
+ if tool.get("server") == "arthera_quant_engine"
152
+ ]
153
+ except Exception:
154
+ mcp_tools = []
155
+ arthera_specs = mcp_tools_to_specs(mcp_tools, "arthera_quant_engine")
156
+ manifest = build_package_manifest(
157
+ identity=identity,
158
+ services=services,
159
+ tools=tool_registry.list(),
160
+ agents=list_agent_manifests(),
161
+ skills=builtin_skill_specs(),
162
+ mcp_exposures=default_exposures(),
163
+ arthera_packages=arthera,
164
+ arthera_mcp_tools=arthera_specs,
165
+ )
166
+ write_package_manifest(out_path, manifest)
167
+ if self.context.has_rich:
168
+ self.context.console.print()
169
+ self.context.console.print(f" [green]Package manifest exported[/green] [dim]{out_path}[/dim]")
170
+ self.context.console.print(
171
+ f" [dim]tools={len(manifest['capabilities']['tools'])} "
172
+ f"services={len(manifest['capabilities']['services'])} "
173
+ f"agents={len(manifest['capabilities']['agents'])} "
174
+ f"skills={len(manifest['capabilities']['skills'])} "
175
+ f"arthera_mcp_tools={len(manifest['capabilities']['arthera_mcp_tools'])}[/dim]\n"
176
+ )
177
+ else:
178
+ print(f"Package manifest exported: {out_path}")
179
+ return
180
+
181
+ if sub in ("doctor", "doctor arthera", "arthera doctor"):
182
+ reg = getattr(self.terminal, "_mcp_registry", None)
183
+ runtime_status = []
184
+ mcp_tools = []
185
+ if reg:
186
+ try:
187
+ runtime_status = reg.status()
188
+ mcp_tools = [
189
+ tool for tool in reg.all_tools()
190
+ if tool.get("server") == "arthera_quant_engine"
191
+ ]
192
+ except Exception:
193
+ runtime_status = []
194
+ mcp_tools = []
195
+ status = mcp_server_status(mcp_config_path, "arthera_quant_engine", runtime_status)
196
+ specs = mcp_tools_to_specs(mcp_tools, "arthera_quant_engine")
197
+ from aria_code.artifacts import artifact_dir
198
+ manifest_path = artifact_dir("manifests", "packages", create=False) / "aria_package_manifest.json"
199
+ report = build_package_doctor_report(
200
+ arthera=arthera,
201
+ mcp_status=status,
202
+ tool_count=len(specs),
203
+ manifest_can_export=True,
204
+ manifest_path=manifest_path,
205
+ services=services,
206
+ required_services=required_service_names(),
207
+ architecture_layers=list_architecture_layers(),
208
+ required_architecture_layers=required_architecture_layer_names(),
209
+ provider_health=GLOBAL_PROVIDER_HEALTH.snapshot(),
210
+ )
211
+ if self.context.has_rich:
212
+ from rich.table import Table as _Table
213
+ color = "green" if report.status == "ok" else "yellow" if report.status == "warn" else "red"
214
+ self.context.console.print()
215
+ self.context.console.print(
216
+ f" [bold]{identity.product}[/bold] "
217
+ f"[dim]· {identity.company} packages doctor ·[/dim] [{color}]{report.status}[/{color}]\n"
218
+ )
219
+ tbl = _Table(
220
+ box=_get_rich_box().ROUNDED,
221
+ border_style="dim",
222
+ show_header=True,
223
+ header_style="bold dim",
224
+ )
225
+ tbl.add_column("Check", width=22)
226
+ tbl.add_column("Status", width=8)
227
+ tbl.add_column("Detail")
228
+ tbl.add_column("Next")
229
+ for check in report.checks:
230
+ st_color = "green" if check.status == "ok" else "yellow" if check.status == "warn" else "red"
231
+ tbl.add_row(
232
+ check.name,
233
+ f"[{st_color}]{check.status}[/{st_color}]",
234
+ check.detail,
235
+ check.remediation,
236
+ )
237
+ self.context.console.print(tbl)
238
+ self.context.console.print()
239
+ else:
240
+ print(f"{identity.product} packages doctor: {report.status}")
241
+ for check in report.checks:
242
+ print(f"{check.status:5s} {check.name}: {check.detail} {check.remediation}")
243
+ return
244
+
245
+ if sub in ("status", "status arthera", "arthera status"):
246
+ runtime_status = []
247
+ reg = getattr(self.terminal, "_mcp_registry", None)
248
+ if reg:
249
+ try:
250
+ runtime_status = reg.status()
251
+ except Exception:
252
+ runtime_status = []
253
+ status = mcp_server_status(mcp_config_path, "arthera_quant_engine", runtime_status)
254
+
255
+ if self.context.has_rich:
256
+ self.context.console.print()
257
+ self.context.console.print(
258
+ f" [bold]{identity.product}[/bold] "
259
+ f"[dim]· {identity.company} package bridge status[/dim]\n"
260
+ )
261
+ rows = [
262
+ ("config", status["config_path"]),
263
+ ("configured", "yes" if status["configured"] else "no"),
264
+ ("server", status["server_path"] or "—"),
265
+ ("server file", "found" if status["server_file_exists"] else "missing"),
266
+ ("runtime", "running" if status["running"] else "not running"),
267
+ ("tools", str(status["tool_count"])),
268
+ ]
269
+ for key, value in rows:
270
+ style = "green" if value in ("yes", "found", "running") else "yellow" if value in ("no", "missing", "not running") else "dim"
271
+ self.context.console.print(f" [dim]{key:12s}[/dim] [{style}]{value}[/{style}]")
272
+ if status["tools"]:
273
+ self.context.console.print(" [dim]tool names:[/dim] " + ", ".join(status["tools"][:12]))
274
+ if not status["configured"]:
275
+ self.context.console.print(" [dim]Run /packages connect arthera to write the MCP bridge.[/dim]")
276
+ elif not status["running"]:
277
+ self.context.console.print(" [dim]Run /mcp reload or /packages connect arthera --reload.[/dim]")
278
+ self.context.console.print()
279
+ else:
280
+ print(f"{identity.product} · {identity.company} package bridge status")
281
+ for key, value in status.items():
282
+ if key != "tools":
283
+ print(f"{key}: {value}")
284
+ return
285
+
286
+ if sub in ("tools arthera", "arthera tools", "tools"):
287
+ reg = getattr(self.terminal, "_mcp_registry", None)
288
+ mcp_tools = []
289
+ if reg:
290
+ try:
291
+ mcp_tools = [
292
+ tool for tool in reg.all_tools()
293
+ if tool.get("server") == "arthera_quant_engine"
294
+ ]
295
+ except Exception:
296
+ mcp_tools = []
297
+ specs = mcp_tools_to_specs(mcp_tools, "arthera_quant_engine")
298
+
299
+ if self.context.has_rich:
300
+ from rich.table import Table as _Table
301
+ self.context.console.print()
302
+ self.context.console.print(
303
+ f" [bold]{identity.product}[/bold] "
304
+ f"[dim]· {identity.company} MCP tool manifests[/dim]\n"
305
+ )
306
+ if not specs:
307
+ self.context.console.print(" [yellow]No Arthera MCP tools discovered.[/yellow]")
308
+ self.context.console.print(" [dim]Run /packages connect arthera --reload, then retry /packages tools arthera.[/dim]\n")
309
+ return
310
+ tbl = _Table(
311
+ title="[bold]Arthera QuantEngine Tools[/bold]",
312
+ box=_get_rich_box().ROUNDED,
313
+ border_style="dim",
314
+ show_header=True,
315
+ header_style="bold dim",
316
+ )
317
+ tbl.add_column("Tool", width=34)
318
+ tbl.add_column("Permissions", width=24)
319
+ tbl.add_column("Capabilities", width=30)
320
+ tbl.add_column("Schema")
321
+ for spec in specs:
322
+ perms = ", ".join(p.value for p in spec.permissions)
323
+ caps = ", ".join(spec.capabilities)
324
+ schema_state = "yes" if spec.schema else "no"
325
+ tbl.add_row(spec.name, perms, caps, schema_state)
326
+ self.context.console.print(tbl)
327
+ self.context.console.print()
328
+ else:
329
+ if not specs:
330
+ print("No Arthera MCP tools discovered. Run /packages connect arthera --reload.")
331
+ return
332
+ for spec in specs:
333
+ print(f"{spec.name}: {', '.join(spec.capabilities)}")
334
+ return
335
+
336
+ if sub in ("services", "service", "usage", "use", "map"):
337
+ usage_specs = list_service_usage_specs()
338
+ if self.context.has_rich:
339
+ from rich.table import Table as _Table
340
+ self.context.console.print()
341
+ self.context.console.print(
342
+ f" [bold]{identity.product}[/bold] "
343
+ f"[dim]· service usage map[/dim]\n"
344
+ )
345
+ tbl = _Table(
346
+ title="[bold]Project Services[/bold]",
347
+ box=_get_rich_box().ROUNDED,
348
+ border_style="dim",
349
+ show_header=True,
350
+ header_style="bold dim",
351
+ )
352
+ tbl.add_column("Service", width=22)
353
+ tbl.add_column("Purpose", width=34)
354
+ tbl.add_column("CLI", width=32)
355
+ tbl.add_column("Arthera / MCP", width=34)
356
+ tbl.add_column("Next")
357
+ for spec in usage_specs:
358
+ mcp = ", ".join(spec.mcp_tools[:4]) if spec.mcp_tools else "—"
359
+ packages = ", ".join(spec.package_sources[:2])
360
+ tbl.add_row(
361
+ spec.name,
362
+ spec.purpose,
363
+ ", ".join(spec.cli_entrypoints),
364
+ f"{packages}\n[dim]{mcp}[/dim]",
365
+ spec.next_step,
366
+ )
367
+ self.context.console.print(tbl)
368
+ self.context.console.print("[dim]连接 Arthera MCP: /packages connect arthera --reload;券商接入: /broker guide[/dim]\n")
369
+ else:
370
+ for spec in usage_specs:
371
+ print(f"{spec.name}: {spec.purpose} -> {', '.join(spec.cli_entrypoints)}")
372
+ return
373
+
374
+ if sub.startswith("connect arthera") or sub in ("connect", "connect-quant", "connect quant"):
375
+ if server_cfg is None:
376
+ print_error(self.context, "Set an absolute ARTHERA_ROOT for the legacy bridge, or use /finance setup for the standalone financial-service.")
377
+ return
378
+ server_path = pathlib.Path(str(server_cfg["args"][0]))
379
+ if not server_path.is_file():
380
+ print_error(self.context, "The configured backend has no QuantEngine MCP server. No configuration was written.")
381
+ return
382
+ existing = load_mcp_config(mcp_config_path)
383
+ updated = merge_server_config(existing, server_cfg)
384
+ write_mcp_config(mcp_config_path, updated)
385
+ server_path = pathlib.Path(str(server_cfg["args"][0]))
386
+ ready = server_path.exists()
387
+ if self.context.has_rich:
388
+ self.context.console.print()
389
+ self.context.console.print(
390
+ f" [bold]{identity.product}[/bold] "
391
+ f"[dim]connected to {identity.company} package bridge[/dim]"
392
+ )
393
+ status = "[green]ready[/green]" if ready else "[yellow]configured, server file not found[/yellow]"
394
+ self.context.console.print(f" {status} [dim]{server_cfg['name']}[/dim]")
395
+ self.context.console.print(f" [dim]config:[/dim] {mcp_config_path}")
396
+ self.context.console.print(f" [dim]server:[/dim] {server_path}")
397
+ else:
398
+ print(f"Connected {identity.product} -> {server_cfg['name']}")
399
+ print(f"config: {mcp_config_path}")
400
+ print(f"server: {server_path} ({'ready' if ready else 'missing'})")
401
+
402
+ if "--reload" in sub or " reload" in sub:
403
+ await self.cmd_mcp("reload")
404
+ else:
405
+ if self.context.has_rich:
406
+ self.context.console.print(" [dim]Run /mcp reload to start the server.[/dim]\n")
407
+ else:
408
+ print("Run /mcp reload to start the server.")
409
+ return
410
+
411
+ if self.context.has_rich:
412
+ from rich.table import Table as _Table
413
+
414
+ self.context.console.print()
415
+ self.context.console.print(
416
+ f" [bold]{identity.product}[/bold] "
417
+ f"[dim]v{identity.version} · {identity.company} product[/dim]"
418
+ )
419
+ self.context.console.print(f" [dim]{identity.description}[/dim]\n")
420
+
421
+ tbl = _Table(
422
+ title="[bold]Package Facades[/bold]",
423
+ box=_get_rich_box().ROUNDED,
424
+ border_style="dim",
425
+ show_header=True,
426
+ header_style="bold dim",
427
+ )
428
+ tbl.add_column("Package")
429
+ tbl.add_column("Status")
430
+ tbl.add_column("Surface")
431
+ tbl.add_row("aria_core", "ready", "CapabilityManifest, permissions")
432
+ tbl.add_row("aria_services", "ready", f"{len(services)} service boundaries")
433
+ tbl.add_row("aria_tools", "ready", f"{len(tool_registry.list())} tools")
434
+ tbl.add_row("aria_agents", "ready", f"{agent_count} agents")
435
+ tbl.add_row("aria_skills", "ready", f"{skill_count} skills")
436
+ tbl.add_row("aria_mcp", "ready", f"{mcp_exposure_count} planned exposures")
437
+ tbl.add_row("aria_infra", "ready", "Arthera package discovery")
438
+ self.context.console.print(tbl)
439
+
440
+ self.context.console.print()
441
+ self.context.console.print("[bold]Arthera Packages[/bold]")
442
+ from aria_code.ui.render.output import display_path as _display_path
443
+ if arthera.available:
444
+ self.context.console.print(f" [green]found[/green] [dim]{_display_path(arthera.root, fallback='package root')}[/dim]")
445
+ for name, path in sorted(arthera.packages.items()):
446
+ self.context.console.print(f" [dim]·[/dim] [bold]{name:14s}[/bold] [dim]{_display_path(path, fallback='package')}[/dim]")
447
+ if arthera.mcp_servers:
448
+ self.context.console.print(" [dim]MCP server candidates:[/dim]")
449
+ for path in arthera.mcp_servers[:5]:
450
+ self.context.console.print(f" [dim]{_display_path(path, fallback='server')}[/dim]")
451
+ else:
452
+ self.context.console.print(f" [yellow]not found[/yellow] [dim]{_display_path(arthera.root, fallback='package root')}[/dim]")
453
+
454
+ self.context.console.print()
455
+ self.context.console.print("[bold]Recommended MCP bridge[/bold]")
456
+ if server_cfg is not None:
457
+ self.context.console.print(f" [dim]name:[/dim] {server_cfg['name']}")
458
+ self.context.console.print(f" [dim]command:[/dim] {server_cfg['command']} {' '.join(server_cfg['args'])}")
459
+ self.context.console.print(f" [dim]env PYTHONPATH:[/dim] {server_cfg['env']['PYTHONPATH']}")
460
+ self.context.console.print(f" [dim]config:[/dim] {mcp_config_path}")
461
+ self.context.console.print(" [dim]Run /packages connect arthera to write this MCP bridge.[/dim]\n")
462
+ else:
463
+ self.context.console.print(" [dim]No private source bridge configured. Use /finance setup for the standalone financial-service.[/dim]\n")
464
+ else:
465
+ print(f"{identity.product} v{identity.version} · {identity.company} product")
466
+ print(f"services={len(services)} tools={len(tool_registry.list())} agents={agent_count} skills={skill_count} mcp={mcp_exposure_count}")
467
+ print(f"Arthera packages: {'found' if arthera.available else 'not found'} {arthera.root}")
468
+ print(f"Recommended MCP: {server_cfg}")
469
+
470
+ async def cmd_file(self, args: str):
471
+ """
472
+ /file load <路径> — 加载文件到会话
473
+ /file analyze [1|2|3|4] — 分层分析(1=摘要 2=深度 3=领域 4=建议)
474
+ /file ask <问题> — 就已加载文件提问
475
+ /file list — 列出会话中的所有文件
476
+ /file switch <文件名> — 切换活跃文件
477
+ /file clear [文件名] — 清除文件
478
+ /file check — 检查可用解析器
479
+ """
480
+ import asyncio as _asyncio
481
+ loop = _asyncio.get_event_loop()
482
+ parts = args.strip().split(None, 1) if args.strip() else []
483
+ sub = parts[0].lower() if parts else "help"
484
+ rest = parts[1].strip() if len(parts) > 1 else ""
485
+
486
+ # ── 确保 file_session 已初始化 ────────────────────────────────────────
487
+ if self.terminal._file_session is None:
488
+ try:
489
+ from aria_code.file_analysis_tools import FileSession
490
+ self.terminal._file_session = FileSession()
491
+ except ImportError as e:
492
+ if self.context.has_rich:
493
+ self.context.console.print(f"[red]file_analysis_tools 未加载: {e}[/red]")
494
+ return
495
+
496
+ fs = self.terminal._file_session
497
+
498
+ # ────────────────── /file load ────────────────────────────────────────
499
+ if sub == "load":
500
+ if not rest:
501
+ if self.context.has_rich:
502
+ self.context.console.print("[dim]用法: /file load <文件路径>[/dim]")
503
+ self.context.console.print("[dim]支持: PDF DOCX XLSX CSV JSON TXT MD 图片 代码文件[/dim]")
504
+ return
505
+
506
+ if self.context.has_rich:
507
+ self.context.console.print(f"[dim]正在解析 {rest}...[/dim]")
508
+
509
+ # Include images only for vision-capable models
510
+ _curr_model = self.terminal.config.get("model", "")
511
+ include_img = False
512
+ if _get__HAS_MODEL_CAP():
513
+ try:
514
+ _mc = get_model_capability(_curr_model)
515
+ include_img = bool(_mc.vision)
516
+ except Exception:
517
+ pass
518
+
519
+ from aria_code.file_analysis_tools import parse_file, check_parsers
520
+ fc = await loop.run_in_executor(
521
+ None, lambda: parse_file(rest, include_images=include_img))
522
+
523
+ if not fc.success:
524
+ if self.context.has_rich:
525
+ self.context.console.print(f"[red]解析失败: {fc.error}[/red]")
526
+ # Show which parsers are available
527
+ parsers = check_parsers()
528
+ missing = [k for k, v in parsers.items() if not v]
529
+ if missing:
530
+ self.context.console.print(f"[yellow]⚠ 未安装解析器: {', '.join(missing)}[/yellow]")
531
+ self.context.console.print(f"[dim]安装命令: pip install {' '.join(missing)}[/dim]")
532
+ return
533
+
534
+ fs.load(rest, include_images=include_img)
535
+ self.terminal._file_ctx_injected = False # Reset so next msg injects file
536
+
537
+ if self.context.has_rich:
538
+ from rich.panel import Panel as _P
539
+ from rich import box as _box
540
+ info_lines = [
541
+ f"[green]✓[/green] [bold]{fc.filename}[/bold]",
542
+ f"[dim]类型: {fc.file_type.upper()} 大小: {fc.size_kb:.1f} KB "
543
+ f"提取: {fc.char_count:,} 字符[/dim]",
544
+ ]
545
+ for k, v in fc.metadata.items():
546
+ if k in ("pages","rows","columns","lines","language",
547
+ "sheets","title","author","symbols"):
548
+ val = v[:5] if isinstance(v, list) else v
549
+ info_lines.append(f"[dim]{k}: {val}[/dim]")
550
+ if fc.truncated:
551
+ info_lines.append("[yellow]⚠ 内容已截断(文件较大)[/yellow]")
552
+ if fc.tables:
553
+ info_lines.append(f"[dim]包含 {len(fc.tables)} 个表格[/dim]")
554
+ info_lines.append("\n[dim]发送任何消息即可开始分析,或使用 /file analyze 1-4[/dim]")
555
+ self.context.console.print(_P("\n".join(info_lines),
556
+ title="[bold]📄 文件已加载[/bold]",
557
+ border_style="green", box=_box.ROUNDED))
558
+
559
+ # ────────────────── /file analyze ─────────────────────────────────────
560
+ elif sub == "analyze":
561
+ fc = fs.get_active()
562
+ if not fc:
563
+ if self.context.has_rich: self.context.console.print("[dim]请先使用 /file load <路径> 加载文件[/dim]")
564
+ return
565
+
566
+ # Determine layer(s) to run
567
+ layer_arg = rest.strip()
568
+ if layer_arg == "all":
569
+ layers_to_run = [1, 2, 3, 4]
570
+ else:
571
+ try:
572
+ layers_to_run = [int(layer_arg)] if layer_arg else [1, 2]
573
+ except ValueError:
574
+ layers_to_run = [1, 2]
575
+
576
+ layer_names = {1: "📌 快速摘要", 2: "🔍 深度分析", 3: "💡 领域洞察", 4: "✅ 行动建议"}
577
+
578
+ from aria_code.file_analysis_tools import build_analysis_prompt
579
+
580
+ for layer in layers_to_run:
581
+ if self.context.has_rich:
582
+ self.context.console.print(f"\n[bold]{layer_names.get(layer, f'层{layer}')}[/bold]")
583
+ self.context.console.print(f"[dim]{'─'*50}[/dim]")
584
+
585
+ prompt = build_analysis_prompt(fc, layer=layer)
586
+ # Send to LLM via the normal message pipeline
587
+ await self.terminal.send_message(
588
+ prompt,
589
+ system_override=(
590
+ "你是专业文档分析助手,具备金融、法律、技术、不动产等多领域知识。"
591
+ "分析要精确、结构化,优先使用数字和具体事实。"
592
+ ),
593
+ )
594
+
595
+ if len(layers_to_run) > 1 and layer < layers_to_run[-1]:
596
+ if self.context.has_rich:
597
+ self.context.console.print(f"\n[dim]{'═'*60}[/dim]")
598
+ self.context.console.print("[dim]进入下一层分析...[/dim]\n")
599
+
600
+ # ────────────────── /file ask ──────────────────────────────────────────
601
+ elif sub == "ask":
602
+ fc = fs.get_active()
603
+ if not fc:
604
+ if self.context.has_rich: self.context.console.print("[dim]请先使用 /file load <路径> 加载文件[/dim]")
605
+ return
606
+ question = rest.strip()
607
+ if not question:
608
+ if self.context.has_rich:
609
+ from rich.prompt import Prompt as _Prompt
610
+ question = _Prompt.ask(" 请输入问题")
611
+ else:
612
+ question = input("请输入问题: ")
613
+ if not question:
614
+ return
615
+
616
+ from aria_code.file_analysis_tools import build_analysis_prompt
617
+ prompt = build_analysis_prompt(fc, layer=0, question=question)
618
+ await self.terminal.send_message(
619
+ prompt,
620
+ system_override=(
621
+ "你是专业文档分析助手。请基于用户提供的文件内容准确回答问题,"
622
+ "若文件中无法找到答案,请如实说明。"
623
+ ),
624
+ )
625
+
626
+ # ────────────────── /file list ─────────────────────────────────────────
627
+ elif sub == "list":
628
+ files = fs.list_files()
629
+ if not files:
630
+ if self.context.has_rich: self.context.console.print("[dim]会话中暂无已加载文件。使用 /file load <路径>[/dim]")
631
+ return
632
+ if self.context.has_rich:
633
+ from rich.table import Table as _T
634
+ from rich import box as _box
635
+ tb = _T(title="[bold]📂 已加载文件[/bold]", box=_box.ROUNDED)
636
+ tb.add_column("状态", width=4); tb.add_column("文件名")
637
+ tb.add_column("类型", style="dim"); tb.add_column("大小KB", justify="right", style="dim")
638
+ tb.add_column("字符数", justify="right", style="dim"); tb.add_column("截断", style="dim")
639
+ for f in files:
640
+ status = "[green]●[/green]" if f["active"] else "[dim]○[/dim]"
641
+ tb.add_row(status, f["filename"], f["type"].upper(),
642
+ str(f["size_kb"]), f"{f['chars']:,}",
643
+ "[yellow]是[/yellow]" if f["truncated"] else "否")
644
+ self.context.console.print(tb)
645
+ self.context.console.print("[dim]/file ask <问题> 向活跃文件提问[/dim]")
646
+
647
+ # ────────────────── /file switch ──────────────────────────────────────
648
+ elif sub == "switch":
649
+ if not rest:
650
+ if self.context.has_rich: self.context.console.print("[dim]用法: /file switch <文件名>[/dim]")
651
+ return
652
+ if fs.set_active(rest):
653
+ fc = fs.get_active()
654
+ self.terminal._file_ctx_injected = False
655
+ if self.context.has_rich:
656
+ self.context.console.print(f"[green]✓ 已切换到: {fc.filename}[/green]")
657
+ else:
658
+ if self.context.has_rich: self.context.console.print(f"[red]未找到文件: {rest}[/red]")
659
+
660
+ # ────────────────── /file clear ──────────────────────────────────────
661
+ elif sub == "clear":
662
+ fs.clear(rest if rest else None)
663
+ self.terminal._file_ctx_injected = False
664
+ msg = f"已清除文件: {rest}" if rest else "已清除所有已加载文件"
665
+ if self.context.has_rich: self.context.console.print(f"[dim]{msg}[/dim]")
666
+
667
+ # ────────────────── /file check ───────────────────────────────────────
668
+ elif sub == "check":
669
+ from aria_code.file_analysis_tools import check_parsers
670
+ parsers = check_parsers()
671
+ if self.context.has_rich:
672
+ from rich.table import Table as _T
673
+ from rich import box as _box
674
+ tb = _T(title="[bold]📦 文件解析器状态[/bold]", box=_box.ROUNDED)
675
+ tb.add_column("库"); tb.add_column("状态"); tb.add_column("安装命令", style="dim")
676
+ _CMDS = {
677
+ "pdfplumber": "pip install pdfplumber",
678
+ "pypdf": "pip install pypdf",
679
+ "python-docx":"pip install python-docx",
680
+ "pandas": "pip install pandas",
681
+ "openpyxl": "pip install openpyxl",
682
+ "beautifulsoup4": "pip install beautifulsoup4",
683
+ "Pillow": "pip install Pillow",
684
+ }
685
+ for lib, ok in parsers.items():
686
+ status = "[green]✓ 已安装[/green]" if ok else "[red]✗ 未安装[/red]"
687
+ tb.add_row(lib, status, "" if ok else _CMDS.get(lib,""))
688
+ self.context.console.print(tb)
689
+ formats_ok = []
690
+ if parsers.get("pdfplumber") or parsers.get("pypdf"):
691
+ formats_ok.append("PDF")
692
+ if parsers.get("python-docx"):
693
+ formats_ok.append("Word/DOCX")
694
+ if parsers.get("pandas") and parsers.get("openpyxl"):
695
+ formats_ok.append("Excel/CSV")
696
+ formats_ok.extend(["JSON", "TXT/MD", "代码文件"])
697
+ self.context.console.print(f"[dim]可解析格式: {', '.join(formats_ok)}[/dim]")
698
+
699
+ # ────────────────── /file help ─────────────────────────────────────────
700
+ else:
701
+ if self.context.has_rich:
702
+ self.context.console.print("[bold]📄 /file 文件分析命令[/bold]")
703
+ rows = [
704
+ ("/file load <路径>", "加载文件 (PDF/DOCX/XLSX/CSV/JSON/TXT/代码/图片)"),
705
+ ("/file analyze 1", "快速摘要 (300字)"),
706
+ ("/file analyze 2", "深度内容分析 (结构/要点/异常)"),
707
+ ("/file analyze 3", "领域专项分析 (财务/法律/技术/不动产)"),
708
+ ("/file analyze 4", "行动建议与风险清单"),
709
+ ("/file analyze all", "依次运行 4 层分析"),
710
+ ("/file ask <问题>", "就文件内容多轮提问"),
711
+ ("/file list", "查看已加载文件"),
712
+ ("/file switch <文件名>", "切换分析目标文件"),
713
+ ("/file clear", "清除所有已加载文件"),
714
+ ("/file check", "检查已安装的解析器"),
715
+ ]
716
+ from rich.table import Table as _T
717
+ from rich import box as _box
718
+ tb = _T(box=_box.MINIMAL)
719
+ tb.add_column("命令", style="cyan"); tb.add_column("说明", style="dim")
720
+ for cmd, desc in rows:
721
+ tb.add_row(cmd, desc)
722
+ self.context.console.print(tb)
723
+
724
+ async def cmd_project(self, args: str):
725
+ """项目分析 (Claude Code / Codex 风格): /project load|tree|grep|ask|task|status|info <参数>"""
726
+ # 本方法体内三处用到 _T / _box,但导入只写在别的方法里。普通方法的
727
+ # __globals__ 被 _rebind_mixin_globals 换成了 aria_cli 的,本文件模块级
728
+ # 的名字一律看不见——所以必须在方法内自己导入,跟本文件其它方法一致。
729
+ from rich.table import Table as _T
730
+ from rich import box as _box
731
+ try:
732
+ from aria_code.project_tools import ProjectSession, scan_project, format_grep_results
733
+ _HAS_PT = True
734
+ except ImportError:
735
+ _HAS_PT = False
736
+
737
+ if not _HAS_PT:
738
+ self.context.console.print("[red]❌ project_tools.py 未找到,请确保文件存在。[/red]")
739
+ return
740
+
741
+ parts = args.strip().split(maxsplit=1)
742
+ sub = parts[0].lower() if parts else "info"
743
+ rest = parts[1].strip() if len(parts) > 1 else ""
744
+ ps = self.terminal._project_session # type: ignore[attr-defined]
745
+
746
+ # ── load ──────────────────────────────────────────────────────────────
747
+ if sub == "load":
748
+ if not rest:
749
+ self.context.console.print("[yellow]用法: /project load <目录路径>[/yellow]")
750
+ return
751
+ from pathlib import Path as _Path
752
+ target = _Path(rest).expanduser().resolve()
753
+ if not target.exists():
754
+ self.context.console.print(f"[red]路径不存在: {target}[/red]")
755
+ return
756
+
757
+ self.context.console.print(f"[dim]正在扫描项目: {target} …[/dim]")
758
+ try:
759
+ new_ps = scan_project(str(target), max_files=2000)
760
+ except Exception as e:
761
+ self.context.console.print(f"[red]扫描失败: {e}[/red]")
762
+ return
763
+
764
+ self.terminal._project_session = new_ps # type: ignore[attr-defined]
765
+ self.terminal._project_ctx_injected = False # type: ignore[attr-defined]
766
+
767
+ # Auto-archive project into global memory
768
+ if getattr(self.terminal, "memory_mgr", None):
769
+ try:
770
+ _ps_s = new_ps.summary()
771
+ self.terminal.memory_mgr.upsert_project(_ps_s["name"], {
772
+ "root": _ps_s["root"],
773
+ "type": _ps_s["type"],
774
+ "languages": _ps_s.get("languages", []),
775
+ })
776
+ except Exception:
777
+ pass
778
+
779
+ s = new_ps.summary()
780
+ tb = _T(box=_box.ROUNDED, show_header=False, padding=(0, 1))
781
+ tb.add_column("k", style="dim", width=14)
782
+ tb.add_column("v", style="cyan")
783
+ tb.add_row("项目名", s["name"])
784
+ tb.add_row("路径", s["root"])
785
+ tb.add_row("类型", s["type"])
786
+ tb.add_row("语言", ", ".join(s["languages"][:4]))
787
+ tb.add_row("文件数", str(s["total_files"]))
788
+ tb.add_row("代码行", f"{s['total_lines']:,}")
789
+ tb.add_row("总大小", f"{s['total_size_kb']} KB")
790
+ if s["git"].get("branch"):
791
+ tb.add_row("Git 分支", s["git"]["branch"])
792
+ if s["git"].get("changed_count"):
793
+ tb.add_row("变更文件", str(s["git"]["changed_count"]))
794
+ self.context.console.print("\n[bold]项目已加载 ✓[/bold]")
795
+ self.context.console.print(tb)
796
+ self.context.console.print(f"\n[dim]关键文件: {', '.join(s['key_files'][:6])}[/dim]")
797
+ self.context.console.print("[dim]现在可以直接对话,Aria 将根据项目上下文回答。[/dim]\n")
798
+ return
799
+
800
+ # ── 未加载时提示 ──────────────────────────────────────────────────────
801
+ if ps is None:
802
+ self.context.console.print("[yellow]请先加载项目: /project load <目录路径>[/yellow]")
803
+ return
804
+
805
+ # ── tree ──────────────────────────────────────────────────────────────
806
+ if sub == "tree":
807
+ depth_arg = rest.strip()
808
+ max_lines = 120
809
+ if depth_arg.isdigit():
810
+ max_lines = int(depth_arg) * 30 # rough approximation
811
+ tree_str = ps.get_tree(max_lines=max_lines)
812
+ self.context.console.print(f"\n[bold]{ps.name}/[/bold]")
813
+ self.context.console.print(f"[dim]{tree_str}[/dim]")
814
+ self.context.console.print(f"\n[dim]共 {ps.stats.get('total_files', 0)} 个文件[/dim]\n")
815
+
816
+ # ── grep / search ─────────────────────────────────────────────────────
817
+ elif sub in ("grep", "search"):
818
+ if not rest:
819
+ self.context.console.print("[yellow]用法: /project grep <正则表达式> [glob模式][/yellow]")
820
+ return
821
+ parts2 = rest.split(maxsplit=1)
822
+ pattern = parts2[0]
823
+ glob = parts2[1] if len(parts2) > 1 else "**/*"
824
+ self.context.console.print(f"[dim]搜索 \"{pattern}\" …[/dim]")
825
+ results = ps.grep(pattern, glob=glob, max_results=60)
826
+ self.context.console.print(format_grep_results(results, pattern))
827
+
828
+ # ── status ────────────────────────────────────────────────────────────
829
+ elif sub == "status":
830
+ gi = ps.git_info
831
+ if not gi:
832
+ self.context.console.print("[dim]当前项目不是 Git 仓库[/dim]")
833
+ return
834
+ self.context.console.print(f"\n[bold]Git 状态[/bold] — {ps.name}")
835
+ self.context.console.print(f" 分支: [cyan]{gi.get('branch','?')}[/cyan] "
836
+ f"变更: [yellow]{gi.get('changed_count', 0)}[/yellow] 个文件")
837
+ if gi.get("changed_files"):
838
+ for f in gi["changed_files"][:15]:
839
+ self.context.console.print(f" [dim]{f}[/dim]")
840
+ if gi.get("recent_commits"):
841
+ self.context.console.print("\n[bold]最近提交:[/bold]")
842
+ for c in gi["recent_commits"][:5]:
843
+ self.context.console.print(f" [dim]{c}[/dim]")
844
+ self.context.console.print()
845
+
846
+ # ── info ──────────────────────────────────────────────────────────────
847
+ elif sub in ("info", "summary", ""):
848
+ s = ps.summary()
849
+ tb = _T(box=_box.ROUNDED, show_header=False, padding=(0, 1))
850
+ tb.add_column("k", style="dim", width=14)
851
+ tb.add_column("v", style="cyan")
852
+ tb.add_row("项目名", s["name"])
853
+ tb.add_row("路径", s["root"])
854
+ tb.add_row("类型", s["type"])
855
+ tb.add_row("主要语言", ", ".join(s["languages"][:4]))
856
+ tb.add_row("文件数", str(s["total_files"]))
857
+ tb.add_row("代码行", f"{s['total_lines']:,}")
858
+ tb.add_row("大小", f"{s['total_size_kb']} KB")
859
+ if s["git"].get("branch"):
860
+ tb.add_row("Git 分支", s["git"]["branch"])
861
+ self.context.console.print(tb)
862
+ self.context.console.print(f"\n[dim]关键文件: {', '.join(s['key_files'][:8])}[/dim]\n")
863
+
864
+ # ── read ──────────────────────────────────────────────────────────────
865
+ elif sub == "read":
866
+ if not rest:
867
+ self.context.console.print("[yellow]用法: /project read <文件路径>[/yellow]")
868
+ return
869
+ ok, content = ps.read_file(rest)
870
+ if not ok:
871
+ self.context.console.print(f"[red]{content}[/red]")
872
+ return
873
+ lang = rest.rsplit(".", 1)[-1] if "." in rest else "text"
874
+ self.context.console.print(f"\n[bold]{rest}[/bold]")
875
+ if self.context.has_rich:
876
+ from rich.syntax import Syntax
877
+ self.context.console.print(Syntax(content, lang, theme="monokai", line_numbers=True,
878
+ word_wrap=False))
879
+ else:
880
+ print(content)
881
+
882
+ # ── clear ─────────────────────────────────────────────────────────────
883
+ elif sub == "clear":
884
+ self.terminal._project_session = None # type: ignore[attr-defined]
885
+ self.terminal._project_ctx_injected = False # type: ignore[attr-defined]
886
+ self.context.console.print("[dim]项目上下文已清除[/dim]")
887
+
888
+ # ── ask / task → forward to AI with project context ───────────────────
889
+ elif sub in ("ask", "task"):
890
+ if not rest:
891
+ self.context.console.print(f"[yellow]用法: /project {sub} <问题或任务描述>[/yellow]")
892
+ return
893
+ # Delegate to the AI; project context is injected automatically via send_message
894
+ prefix = "请基于当前项目完成以下任务:\n" if sub == "task" else ""
895
+ await self.terminal.send_message(prefix + rest) # type: ignore[attr-defined]
896
+
897
+ # ── help ─────────────────────────────────────────────────────────────
898
+ else:
899
+ rows = [
900
+ ("/project load <path>", "加载项目目录,构建文件索引"),
901
+ ("/project tree [depth]", "显示文件树结构"),
902
+ ("/project grep <pattern>", "跨文件正则搜索"),
903
+ ("/project read <file>", "查看项目中的文件内容"),
904
+ ("/project status", "Git 状态 + 最近提交"),
905
+ ("/project info", "项目摘要(类型/语言/规模)"),
906
+ ("/project ask <question>", "向 AI 提问(使用项目上下文)"),
907
+ ("/project task <description>", "让 AI 执行任务(工具调用模式)"),
908
+ ("/project clear", "卸载当前项目上下文"),
909
+ ]
910
+ tb = _T(box=_box.MINIMAL)
911
+ tb.add_column("命令", style="cyan")
912
+ tb.add_column("说明", style="dim")
913
+ for cmd, desc in rows:
914
+ tb.add_row(cmd, desc)
915
+ self.context.console.print("\n[bold]/project — 项目分析命令[/bold]\n")
916
+ self.context.console.print(tb)
917
+ self.context.console.print()
918
+
919
+ async def cmd_init(self, args: str):
920
+ """Bootstrap an ARIA.md memory file, or scaffold a new project.
921
+
922
+ Without arguments: scans the current directory and generates ARIA.md.
923
+ With a template name: creates a fully-runnable project scaffold.
924
+
925
+ Usage:
926
+ /init — generate ARIA.md for current project
927
+ /init --force — regenerate even if ARIA.md already exists
928
+ /init list — list available scaffold templates
929
+ /init quant [dir] — quantitative strategy project
930
+ /init analysis [dir] — data analysis project
931
+ /init fastapi [dir] — FastAPI financial data service
932
+ /init dashboard [dir] — Plotly Dash interactive dashboard
933
+ """
934
+ global _PROJECT_CONTEXT
935
+ cwd = pathlib.Path.cwd()
936
+
937
+ # ── /init <template> — project scaffold ──────────────────────────────
938
+ _tmpl_key = args.strip().lower().split()[0] if args.strip() else ""
939
+ if _tmpl_key == "list":
940
+ rows = [(k, v["desc"]) for k, v in self._SCAFFOLD_TEMPLATES.items()]
941
+ if self.context.has_rich:
942
+ from rich.table import Table as _Table
943
+ t = _Table(box=None, show_header=True, header_style="bold cyan", padding=(0,2))
944
+ t.add_column("模板", style="green")
945
+ t.add_column("说明")
946
+ for k, d in rows:
947
+ t.add_row(k, d)
948
+ self.context.console.print("\n [bold]可用脚手架模板[/bold]")
949
+ self.context.console.print(t)
950
+ self.context.console.print("\n 用法: [cyan]/init <模板名> [目录名][/cyan]\n")
951
+ else:
952
+ print("可用模板:"); [print(f" {k}: {d}") for k, d in rows]
953
+ return
954
+
955
+ if _tmpl_key in self._SCAFFOLD_TEMPLATES:
956
+ tmpl = self._SCAFFOLD_TEMPLATES[_tmpl_key]
957
+ # Optional second arg: target directory name
958
+ _args_parts = args.strip().split()
959
+ _target_name = _args_parts[1] if len(_args_parts) > 1 else f"{_tmpl_key}_project"
960
+ _target_path = pathlib.Path(_target_name).expanduser()
961
+ if _target_path.is_absolute():
962
+ target_dir = _target_path
963
+ else:
964
+ from aria_code.artifacts import user_projects_dir as _user_projects_dir
965
+ target_dir = _user_projects_dir() / _target_name
966
+ if target_dir.exists():
967
+ self.context.console.print(f"[yellow]目录已存在: {target_dir}[/yellow]") if self.context.has_rich else print(f"目录已存在: {target_dir}")
968
+ else:
969
+ target_dir.mkdir(parents=True)
970
+ created = self._create_scaffold(target_dir, tmpl)
971
+ if self.context.has_rich:
972
+ from rich.panel import Panel as _SPanel
973
+ from rich import box as _sbox
974
+ lines = "\n".join(f" [dim]{pathlib.Path(p)}[/dim]" for p in created)
975
+ self.context.console.print(_SPanel(
976
+ f"[green]✅ 项目脚手架已创建[/green] [bold]{_target_name}[/bold]\n\n{lines}\n\n"
977
+ f"[dim]cd \"{target_dir}\" && pip install -r requirements.txt[/dim]",
978
+ title=f"[bold cyan]/init {_tmpl_key}[/bold cyan]",
979
+ border_style="cyan",
980
+ box=_sbox.ROUNDED,
981
+ padding=(1, 2),
982
+ ))
983
+ else:
984
+ print(f"✅ 创建: {target_dir}"); [print(f" {p}") for p in created]
985
+ # Optionally generate ARIA.md inside the new project
986
+ try:
987
+ from aria_code.apps.cli.project_aria import build_project_aria_md
988
+ _aria_tgt = target_dir / "ARIA.md"
989
+ if not _aria_tgt.exists():
990
+ _aria_tgt.write_text(
991
+ build_project_aria_md(
992
+ project_name=_target_name,
993
+ stack=tmpl["desc"],
994
+ entry="main.py",
995
+ purpose=f"由 /init {_tmpl_key} 生成的脚手架项目",
996
+ notes=[
997
+ "脚手架目录已经就绪,后续功能与服务应继续按模块拆分。",
998
+ ],
999
+ ),
1000
+ encoding="utf-8",
1001
+ )
1002
+ except Exception:
1003
+ pass
1004
+ return
1005
+ # ── /init [--force] — generate ARIA.md for current project ──────────
1006
+
1007
+ aria_md = cwd / "ARIA.md"
1008
+ force = "--force" in args
1009
+
1010
+ if aria_md.exists() and not force:
1011
+ msg = "ARIA.md already exists. Use /init --force to regenerate."
1012
+ self.context.console.print(f"[yellow]{msg}[/yellow]") if self.context.has_rich else print(msg)
1013
+ return
1014
+
1015
+ # Scan for common project signal files
1016
+ _SCAN_FILES = [
1017
+ "README.md", "README.rst", "README.txt",
1018
+ "package.json", "pyproject.toml", "setup.py", "setup.cfg",
1019
+ "requirements.txt", "Pipfile", "poetry.lock",
1020
+ "Cargo.toml", "go.mod", "pom.xml", "build.gradle",
1021
+ "Makefile", "Dockerfile", ".env.example",
1022
+ "CLAUDE.md", ".ariarc",
1023
+ ]
1024
+ snippets, found_files = [], []
1025
+ for fname in _SCAN_FILES:
1026
+ fp = cwd / fname
1027
+ if fp.exists():
1028
+ found_files.append(fname)
1029
+ try:
1030
+ snippets.append(f"### {fname}\n{fp.read_text(errors='replace')[:1200]}")
1031
+ except Exception:
1032
+ pass
1033
+
1034
+ code_exts = {".py", ".ts", ".js", ".go", ".rs", ".java", ".cpp", ".c"}
1035
+ code_files = sorted(
1036
+ f.name for f in cwd.iterdir()
1037
+ if f.is_file() and f.suffix in code_exts
1038
+ )[:10]
1039
+
1040
+ scan_summary = "\n\n".join(snippets[:5])
1041
+
1042
+ prompt = (
1043
+ f"分析以下项目信息,生成一个 ARIA.md 记忆文件。\n\n"
1044
+ f"目录: {cwd}\n"
1045
+ f"发现的配置文件: {', '.join(found_files) or '无'}\n"
1046
+ f"代码文件: {', '.join(code_files) or '无'}\n\n"
1047
+ f"文件内容:\n{scan_summary}\n\n"
1048
+ f"请生成符合以下格式的 ARIA.md(只输出文件内容本身,不加任何解释):\n\n"
1049
+ f"# Memory\n\n"
1050
+ f"- **Project**: <项目名称>\n"
1051
+ f"- **Stack**: <语言/框架>\n"
1052
+ f"- **Entry**: <主入口文件>\n"
1053
+ f"- **Conventions**: <代码规范或约定>\n"
1054
+ f"- **Notes**: <其他重要信息>\n"
1055
+ )
1056
+
1057
+ self.context.console.print("[dim]分析项目结构中...[/dim]") if self.context.has_rich else print("Analyzing project...")
1058
+ await self.terminal.send_message(prompt)
1059
+
1060
+ # Extract the last assistant response and write to ARIA.md
1061
+ if self.terminal.conversation:
1062
+ last_ai = next(
1063
+ (m["content"] for m in reversed(self.terminal.conversation)
1064
+ if m.get("role") == "assistant"),
1065
+ None,
1066
+ )
1067
+ if last_ai:
1068
+ content = _strip_markdown_fences(last_ai).strip()
1069
+ # Strip injected market-data blocks (lines starting with ## 📊 or *⚠️*)
1070
+ # that the market-data prefetch may have appended to the AI response.
1071
+ import re as _re_init
1072
+ content = _re_init.sub(
1073
+ r'\n*## 📊.*?(?=\n#|\Z)', '', content, flags=_re_init.DOTALL
1074
+ ).strip()
1075
+ content = _re_init.sub(r'\n*\*⚠️.*?\*\n*', '\n', content).strip()
1076
+ if not content.startswith("# Memory"):
1077
+ content = "# Memory\n\n" + content
1078
+ aria_md.write_text(content + "\n", encoding="utf-8")
1079
+ _PROJECT_CONTEXT = _load_project_context()
1080
+ msg = f"ARIA.md created at {aria_md}"
1081
+ self.context.console.print(f"\n[green]{msg}[/green]") if self.context.has_rich else print(f"\n{msg}")
1082
+
1083
+ async def cmd_setup(self, args: str):
1084
+ """Guided first-run setup wizard (Open Interpreter style).
1085
+
1086
+ Usage: /setup
1087
+ """
1088
+ import getpass as _gp
1089
+
1090
+ _is_interactive = sys.stdin.isatty()
1091
+
1092
+ if self.context.has_rich:
1093
+ self.context.console.print()
1094
+ self.context.console.print("[bold cyan]━━ Aria Setup Wizard ━━━━━━━━━━━━━━━━━━━━━━━━━━━[/bold cyan]")
1095
+ self.context.console.print()
1096
+
1097
+ # ── Step 1: Detect LOCAL backends only (not cloud LLM providers) ───────
1098
+ _LOCAL_BACKENDS_ONLY = {"ollama", "lmstudio", "vllm", "llamacpp", "jan"}
1099
+ try:
1100
+ from aria_code.local_llm_provider import probe_all_backends, BACKEND_DEFAULTS
1101
+ _all_backends = probe_all_backends()
1102
+ # Filter to only true local backends — cloud providers appear in Step 3
1103
+ backends = {k: v for k, v in _all_backends.items() if k in _LOCAL_BACKENDS_ONLY}
1104
+ except ImportError:
1105
+ backends = {}
1106
+
1107
+ self.context.console.print(" [bold]Step 1/4 · 本地 Backend[/bold]") if self.context.has_rich else print("Step 1: Local Backends")
1108
+ ollama_online = backends.get("ollama", False)
1109
+ for name, ok in backends.items():
1110
+ icon = "✅" if ok else "○"
1111
+ color = "green" if ok else "dim"
1112
+ url = BACKEND_DEFAULTS.get(name, {}).get("default_url", "") if "BACKEND_DEFAULTS" in dir() else ""
1113
+ if self.context.has_rich:
1114
+ self.context.console.print(f" {icon} [{color}]{name:12s}[/{color}] [dim]{url}[/dim]")
1115
+ else:
1116
+ print(f" {'✓' if ok else '✗'} {name:12s} {url}")
1117
+ self.context.console.print() if self.context.has_rich else print()
1118
+
1119
+ # ── Step 2: Pick default Ollama model (if Ollama online) ────────────
1120
+ if ollama_online and _is_interactive:
1121
+ self.context.console.print(" [bold]Step 2/4 · 选择默认本地模型[/bold]") if self.context.has_rich else print("Step 2: Default model")
1122
+ rich_models, _ = detect_ollama_models_rich(
1123
+ self.terminal.config.get("ollama_url", "http://localhost:11434")
1124
+ )
1125
+ if rich_models:
1126
+ model_names = [m["name"] for m in rich_models]
1127
+ current_id = self.terminal.config.get("model", "")
1128
+ sel_idx = next((i for i, n in enumerate(model_names) if n == current_id), 0)
1129
+ options = [(f" {n}", "") for n in model_names]
1130
+ picked = _arrow_select(options, sel_idx, "选择默认模型")
1131
+ if picked is not None:
1132
+ chosen = model_names[picked]
1133
+ self.terminal.config["model"] = chosen
1134
+ self.context.save_config(self.terminal.config)
1135
+ msg = f"✓ 默认模型设为 {chosen}"
1136
+ self.context.console.print(f" [green]{msg}[/green]") if self.context.has_rich else print(f" {msg}")
1137
+ self.context.console.print() if self.context.has_rich else print()
1138
+ else:
1139
+ self.context.console.print(" [dim]Step 2/4 · (Ollama 未运行,跳过模型选择)[/dim]") if self.context.has_rich else print(" Skipping model select (Ollama offline)")
1140
+ self.context.console.print() if self.context.has_rich else print()
1141
+
1142
+ # ── Step 3: Cloud API keys ───────────────────────────────────────────
1143
+ self.context.console.print(" [bold]Step 3/4 · Cloud API Key 配置[/bold]") if self.context.has_rich else print("Step 3: Cloud API Keys")
1144
+ _SETUP_PROVIDERS = [
1145
+ ("deepseek", "DeepSeek", "推荐:deepseek-chat,性价比最高"),
1146
+ ("openai", "OpenAI", "GPT-4o,o1等"),
1147
+ ("groq", "Groq", "免费 llama/mixtral 推理,极速"),
1148
+ ("anthropic", "Anthropic", "Claude 3.5/3.7"),
1149
+ ]
1150
+ for prov, label, desc in _SETUP_PROVIDERS:
1151
+ existing_key = _get_provider_key(prov)
1152
+ if existing_key:
1153
+ masked = existing_key[:6] + "****" + existing_key[-4:]
1154
+ self.context.console.print(f" 🔑 {label:12s} [dim]已配置 ({masked})[/dim]") if self.context.has_rich else print(f" {label}: 已配置")
1155
+ continue
1156
+ if _is_interactive:
1157
+ self.context.console.print(f" [cyan]{label}[/cyan] [dim]({desc})[/dim]") if self.context.has_rich else print(f" {label}: {desc}")
1158
+ try:
1159
+ key = _gp.getpass(f" Enter {label} API key (留空跳过): ").strip()
1160
+ except Exception:
1161
+ key = ""
1162
+ if key:
1163
+ self.cmd_apikey(f"set {prov} {key}")
1164
+ else:
1165
+ self.context.console.print(f" ○ {label:12s} [dim]未配置 → /apikey set {prov} <key>[/dim]") if self.context.has_rich else print(f" {label}: not configured")
1166
+ self.context.console.print() if self.context.has_rich else print()
1167
+
1168
+ # ── Step 3.5: Data Service API keys ──────────────────────────────────
1169
+ self.context.console.print(" [bold]Step 3.5/4 · 市场数据服务 Key(后端离线时使用)[/bold]") if self.context.has_rich else print("Step 3.5: Data Service Keys")
1170
+ _SETUP_DATA = [
1171
+ ("finnhub", "Finnhub", "股票实时行情+新闻", "https://finnhub.io/register"),
1172
+ ("newsapi", "NewsAPI", "财经新闻聚合", "https://newsapi.org/register"),
1173
+ ("brave", "Brave Search", "网页搜索", "https://api.search.brave.com/app/keys"),
1174
+ ("alphavantage", "Alpha Vantage", "股票历史数据", "https://www.alphavantage.co/support/#api-key"),
1175
+ ]
1176
+ _existing_data = _load_data_keys()
1177
+ for svc, label, desc, signup_url in _SETUP_DATA:
1178
+ existing_key = _existing_data.get(svc, "")
1179
+ if existing_key:
1180
+ masked = existing_key[:6] + "****" + existing_key[-4:]
1181
+ self.context.console.print(f" 🔑 {label:16s} [dim]已配置 ({masked})[/dim]") if self.context.has_rich else print(f" {label}: configured")
1182
+ continue
1183
+ if _is_interactive:
1184
+ self.context.console.print(f" [cyan]{label}[/cyan] [dim]({desc})[/dim]") if self.context.has_rich else print(f" {label}: {desc}")
1185
+ self.context.console.print(f" [dim]注册:{signup_url}[/dim]") if self.context.has_rich else print(f" Register: {signup_url}")
1186
+ try:
1187
+ key = _gp.getpass(f" Enter {label} API key (留空跳过): ").strip()
1188
+ except Exception:
1189
+ key = ""
1190
+ if key:
1191
+ self.cmd_apikey(f"set {svc} {key}")
1192
+ else:
1193
+ if self.context.has_rich:
1194
+ self.context.console.print(f" ○ {label:16s} [dim]未配置 → /apikey set {svc} <key>[/dim]")
1195
+ self.context.console.print(f" [dim]注册:{signup_url}[/dim]")
1196
+ else:
1197
+ print(f" {label}: not configured → /apikey set {svc} <key>")
1198
+ self.context.console.print() if self.context.has_rich else print()
1199
+
1200
+ # ── Step 3.8: MCP servers ────────────────────────────────────────────
1201
+ sub = args.strip().lower()
1202
+ if sub in ("mcp", "all"):
1203
+ self.context.console.print(" [bold]Step 3.8/4 · MCP 服务器[/bold]") if self.context.has_rich else print("Step 3.8: MCP Servers")
1204
+ _mcp_cfg_path = aria_home() / "mcp_servers.json"
1205
+ if _mcp_cfg_path.exists():
1206
+ try:
1207
+ import json as _j2
1208
+ _mcp_data = _j2.loads(_mcp_cfg_path.read_text())
1209
+ _servers = _mcp_data.get("servers", [])
1210
+ enabled_srv = [s for s in _servers if s.get("enabled", False)]
1211
+ disabled_srv = [s for s in _servers if not s.get("enabled", True)]
1212
+ for s in enabled_srv:
1213
+ self.context.console.print(f" ✅ {s['name']:16s} [dim]{s.get('description','')[:50]}[/dim]") if self.context.has_rich else print(f" ✓ {s['name']}")
1214
+ for s in disabled_srv:
1215
+ note = s.get("_setup", "")
1216
+ self.context.console.print(f" ○ {s['name']:16s} [dim]{s.get('description','')[:50]}[/dim]") if self.context.has_rich else print(f" ✗ {s['name']}")
1217
+ if note:
1218
+ self.context.console.print(f" [dim]安装: {note}[/dim]") if self.context.has_rich else print(f" Setup: {note}")
1219
+ if disabled_srv:
1220
+ self.context.console.print() if self.context.has_rich else print()
1221
+ self.context.console.print(" [dim]安装后编辑 ~/.arthera/mcp_servers.json 将对应项 enabled 改为 true[/dim]") if self.context.has_rich else print(" Edit mcp_servers.json: set enabled=true after installing")
1222
+ except Exception:
1223
+ pass
1224
+ self.context.console.print() if self.context.has_rich else print()
1225
+
1226
+ # ── Step 4: Messaging channels (Feishu / Telegram) ──────────────────
1227
+ if sub in ("feishu", "telegram", "notify", "all", ""):
1228
+ self.context.console.print(" [bold]Step 4/5 · 消息通知连接[/bold]") if self.context.has_rich else print("Step 4: Messaging")
1229
+ _env_path = pathlib.Path.home() / ".aria" / ".env"
1230
+ _env_vars: dict = {}
1231
+ if _env_path.exists():
1232
+ for _line in _env_path.read_text().splitlines():
1233
+ if "=" in _line and not _line.startswith("#"):
1234
+ k, _, v = _line.partition("=")
1235
+ _env_vars[k.strip()] = v.strip()
1236
+
1237
+ # Feishu status
1238
+ _fs_mode = _env_vars.get("ARIA_RELAY_MODE", "")
1239
+ _fs_id = _env_vars.get("ARIA_RELAY_CLIENT_ID", "")
1240
+ _fs_app = _env_vars.get("FEISHU_APP_ID", "")
1241
+ if _fs_mode == "relay" and _fs_id:
1242
+ _fs_status = f"[green]✓ 中继模式[/green] ID: {_fs_id[:12]}…"
1243
+ elif _fs_mode == "own_app" and _fs_app:
1244
+ _fs_status = f"[green]✓ 自建应用[/green] {_fs_app}"
1245
+ else:
1246
+ _fs_status = "[dim]未配置[/dim] → /setup feishu"
1247
+
1248
+ # Telegram status
1249
+ _tg_token = _env_vars.get("TELEGRAM_BOT_TOKEN", "")
1250
+ _tg_ids = _env_vars.get("TELEGRAM_ALLOWED_IDS", "")
1251
+ if _tg_token and _tg_token != "your_bot_token_here":
1252
+ _tg_status = f"[green]✓ 已配置[/green] Chat IDs: {_tg_ids or '(未设置)'}"
1253
+ else:
1254
+ _tg_status = "[dim]未配置[/dim] → /setup telegram"
1255
+
1256
+ if self.context.has_rich:
1257
+ self.context.console.print(f" 飞书 {_fs_status}")
1258
+ self.context.console.print(f" Telegram {_tg_status}")
1259
+ else:
1260
+ print(f" Feishu: {_fs_mode or 'not configured'}")
1261
+ print(f" Telegram: {'configured' if _tg_token else 'not configured'}")
1262
+
1263
+ # Sub-command: launch wizard for just this channel
1264
+ if sub in ("feishu", "telegram"):
1265
+ self.context.console.print() if self.context.has_rich else print()
1266
+ try:
1267
+ # setup_wizard 是随包发布的顶层模块(见 pyproject py-modules),
1268
+ # 直接 import 即可。原先用 __file__ 往上数四层拼路径:这个方法
1269
+ # 的 __globals__ 被 _rebind_mixin_globals 换成了 aria_cli 的,
1270
+ # __file__ 因此指向仓库根的 aria_cli.py 而非本文件,四层 parent
1271
+ # 算出 /Users/setup_wizard.py,永远不存在——异常被下面的
1272
+ # except 吞掉,这条分支一直静默退化成"请手动运行"提示。
1273
+ from aria_code import setup_wizard as _wiz
1274
+ _e = _wiz._load_env()
1275
+ if sub == "feishu":
1276
+ _wiz.setup_feishu(_e)
1277
+ else:
1278
+ _wiz.setup_telegram(_e)
1279
+ _wiz._save_env(_e)
1280
+ except Exception as _we:
1281
+ _fallback_flag = "--feishu" if sub == "feishu" else "--telegram"
1282
+ if self.context.has_rich:
1283
+ self.context.console.print(f" [yellow]请运行: python3 -m aria_code.setup_wizard {_fallback_flag}[/yellow]")
1284
+ else:
1285
+ print(f" Run: python3 -m aria_code.setup_wizard {_fallback_flag}")
1286
+ return
1287
+
1288
+ self.context.console.print() if self.context.has_rich else print()
1289
+
1290
+ # ── Step 5: Summary ─────────────────────────────────────────────────
1291
+ self.context.console.print(" [bold]Step 5/5 · 配置完成[/bold]") if self.context.has_rich else print("Step 5: Done")
1292
+ model = self.terminal.config.get("model", "?")
1293
+ provider = self.terminal.config.get("local_provider", "ollama")
1294
+ self.context.console.print(f" 模型: [cyan]{model}[/cyan] Provider: [cyan]{provider}[/cyan]") if self.context.has_rich else print(f" Model: {model} Provider: {provider}")
1295
+ self.context.console.print() if self.context.has_rich else print()
1296
+ self.context.console.print(
1297
+ " [dim]提示: /model — 切换模型 /providers — 查看所有 provider\n"
1298
+ " /setup feishu — 配置飞书 /setup telegram — 配置 Telegram[/dim]"
1299
+ ) if self.context.has_rich else print(" Tip: /model /providers /setup feishu /setup telegram")
1300
+ self.context.console.print("[bold cyan]━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[/bold cyan]") if self.context.has_rich else print("─" * 50)
1301
+ self.context.console.print() if self.context.has_rich else print()
1302
+
1303
+ def cmd_memory(self, args: str):
1304
+ """Manage persistent memory: project ARIA.md and global user profile.
1305
+
1306
+ Usage:
1307
+ /memory show — display current project ARIA.md
1308
+ /memory add <fact> — append fact to project ARIA.md
1309
+ /memory clear — wipe project ARIA.md memory section
1310
+ /memory search <query> — search across ARIA.md + sessions
1311
+ /memory profile — show global 当前账户的 ARIA.md (injected every session)
1312
+ /memory profile add <text> — append to global profile
1313
+ /memory profile clear — reset global profile
1314
+ /memory global — legacy global Memory entries
1315
+ """
1316
+ global _PROJECT_CONTEXT
1317
+ aria_md = pathlib.Path.cwd() / "ARIA.md"
1318
+ parts = args.strip().split(maxsplit=1)
1319
+ sub = parts[0].lower() if parts else "show"
1320
+ rest = parts[1].strip() if len(parts) > 1 else ""
1321
+
1322
+ if sub in {"sync", "status", "account", "forget"}:
1323
+ from aria_code.account_memory import AccountMemory
1324
+ memory = AccountMemory(self.terminal.config, save=self.context.save_config)
1325
+ if sub == "sync":
1326
+ result = memory.sync(force=True)
1327
+ message = "账户记忆已同步。" if result.get("ok") else "账户记忆未同步:请检查登录状态或网络,待同步内容仍保留。"
1328
+ if result.get("ok") and result.get("pending"):
1329
+ message = f"本次同步已完成,仍有 {result['pending']} 条待同步。"
1330
+ if result.get("enabled") is False:
1331
+ message = "账户记忆已关闭,未上传或使用记忆。"
1332
+ elif sub == "forget":
1333
+ message = "已标记删除,下次同步会更新云端。" if memory.forget(rest) else "请先运行 /login。"
1334
+ elif sub == "account":
1335
+ if rest.startswith("add "):
1336
+ from aria_code.memory_manager import _SENSITIVE_PATTERN
1337
+ fact = rest[4:].strip()
1338
+ saved = bool(fact) and not _SENSITIVE_PATTERN.search(fact) and memory.add(fact)
1339
+ message = "已保存到当前账户的待同步记忆。" if saved else "未保存:请检查登录、记忆开关或内容。"
1340
+ else:
1341
+ facts = memory.facts()
1342
+ message = "\n".join(f"{f['id']} {f['content']}" for f in facts) or "当前账户暂无记忆。"
1343
+ else:
1344
+ status = memory.status()
1345
+ sync_label = {"sign-in-required":"需要重新登录", "service-unavailable":"服务暂不可用,请稍后重试", "pending-retry":"部分记忆待重试"}.get(status['error'], '部分记忆待同步' if status['pending'] else ('已同步' if status['last_synced'] else '尚未同步'))
1346
+ message = (f"账户:{status['email'] or ('已登录账户' if status['account_id'] else '未登录')}\n"
1347
+ f"记忆:{'开启' if status['enabled'] else '关闭'} · 待同步:{status['pending']}\n"
1348
+ f"同步状态:{sync_label}")
1349
+ # Memory is user-authored content, never interpret Rich markup in it.
1350
+ print(message)
1351
+ return
1352
+
1353
+ if sub == "show":
1354
+ if not aria_md.exists():
1355
+ msg = f"No ARIA.md in {pathlib.Path.cwd()}"
1356
+ self.context.console.print(f"[dim]{msg}[/dim]") if self.context.has_rich else print(msg)
1357
+ return
1358
+ content = aria_md.read_text(encoding="utf-8")
1359
+ if self.context.has_rich:
1360
+ try:
1361
+ from rich.markdown import Markdown as _RMd
1362
+ self.context.console.print(_RMd(content))
1363
+ except Exception:
1364
+ self.context.console.print(content)
1365
+ else:
1366
+ print(content)
1367
+
1368
+ elif sub == "add":
1369
+ if not rest:
1370
+ self.context.console.print("[dim]Usage: /memory add <fact>[/dim]") if self.context.has_rich else print("Usage: /memory add <fact>")
1371
+ return
1372
+ self.cmd_note(rest)
1373
+
1374
+ elif sub == "clear":
1375
+ if aria_md.exists():
1376
+ aria_md.write_text("# Memory\n\n", encoding="utf-8")
1377
+ _PROJECT_CONTEXT = _load_project_context()
1378
+ self.context.console.print("[dim]Memory cleared.[/dim]") if self.context.has_rich else print("Memory cleared.")
1379
+ else:
1380
+ self.context.console.print("[dim]Nothing to clear.[/dim]") if self.context.has_rich else print("Nothing to clear.")
1381
+
1382
+ elif sub == "search":
1383
+ # Semantic search in ARIA.md and strategy vault using simple grep
1384
+ # (ChromaDB RAG upgrade planned for Phase 2)
1385
+ if not rest:
1386
+ self.context.console.print("[dim]Usage: /memory search <query>[/dim]") if self.context.has_rich else print("Usage: /memory search <query>")
1387
+ return
1388
+ query_low = rest.lower()
1389
+ results = []
1390
+ # 1. Search ARIA.md
1391
+ if aria_md.exists():
1392
+ for line in aria_md.read_text(encoding="utf-8").splitlines():
1393
+ if query_low in line.lower() and line.strip():
1394
+ results.append(("ARIA.md", line.strip()))
1395
+ # 2. Search session history titles
1396
+ for sess_file in sorted(_get_SESSIONS_DIR().glob("*.json"), key=lambda p: -p.stat().st_mtime)[:20]:
1397
+ try:
1398
+ sess = json.loads(sess_file.read_text(encoding="utf-8"))
1399
+ title = sess.get("metadata", {}).get("title", "")
1400
+ if query_low in title.lower():
1401
+ results.append(("Session", title[:80]))
1402
+ except Exception:
1403
+ pass
1404
+ # 3. Search strategy vault
1405
+ try:
1406
+ from aria_code.strategy_vault import get_vault as _gv
1407
+ vault = _gv()
1408
+ for s in (vault.list() or []):
1409
+ name = str(s.get("name", ""))
1410
+ msg = str(s.get("message", ""))
1411
+ if query_low in name.lower() or query_low in msg.lower():
1412
+ results.append(("Strategy", f"{name}: {msg[:60]}"))
1413
+ except Exception:
1414
+ pass
1415
+
1416
+ if results:
1417
+ if self.context.has_rich:
1418
+ self.context.console.print()
1419
+ self.context.console.print(f" [bold]记忆搜索: '{rest}'[/bold] [dim]{len(results)} 条结果[/dim]")
1420
+ self.context.console.print()
1421
+ for src, text in results[:15]:
1422
+ self.context.console.print(f" [dim]{src:<12s}[/dim] {text}")
1423
+ self.context.console.print()
1424
+ else:
1425
+ print(f" Search '{rest}': {len(results)} results")
1426
+ for src, text in results[:15]:
1427
+ print(f" [{src}] {text}")
1428
+ else:
1429
+ msg = f"未找到与 '{rest}' 相关的记忆"
1430
+ self.context.console.print(f"[dim]{msg}[/dim]") if self.context.has_rich else print(msg)
1431
+
1432
+ elif sub == "profile":
1433
+ # Per-user ARIA.md at 当前账户的 ARIA.md — injected into every session
1434
+ from aria_code.account_session import profile_path
1435
+ _profile_path = profile_path(self.terminal.config)
1436
+ gparts = rest.strip().split(maxsplit=1)
1437
+ gsub = gparts[0].lower() if gparts else "show"
1438
+ grest = gparts[1].strip() if len(gparts) > 1 else ""
1439
+
1440
+ if gsub == "show":
1441
+ if not _profile_path.exists():
1442
+ if self.context.has_rich:
1443
+ self.context.console.print("[dim]当前账户的 ARIA.md 还不存在。用 /memory profile add <内容> 创建。[/dim]")
1444
+ else:
1445
+ print("当前账户的 ARIA.md not found. Use /memory profile add <text> to create.")
1446
+ return
1447
+ content = _profile_path.read_text(encoding="utf-8")
1448
+ if self.context.has_rich:
1449
+ try:
1450
+ from rich.markdown import Markdown as _RMd3
1451
+ self.context.console.print()
1452
+ self.context.console.print(" [dim]当前账户的 ARIA.md[/dim]")
1453
+ self.context.console.print(_RMd3(content))
1454
+ except Exception:
1455
+ self.context.console.print(content)
1456
+ else:
1457
+ print(content)
1458
+
1459
+ elif gsub == "add":
1460
+ if not grest:
1461
+ self.context.console.print("[dim]Usage: /memory profile add <内容>[/dim]") if self.context.has_rich else print("Usage: /memory profile add <text>")
1462
+ return
1463
+ _profile_path.parent.mkdir(parents=True, exist_ok=True)
1464
+ now_str = datetime.now().strftime("%Y-%m-%d")
1465
+ if _profile_path.exists():
1466
+ existing = _profile_path.read_text(encoding="utf-8")
1467
+ if "## 偏好与背景" not in existing and "## Preferences" not in existing:
1468
+ existing += "\n\n## 偏好与背景\n"
1469
+ existing += f"\n- [{now_str}] {grest}"
1470
+ _profile_path.write_text(existing, encoding="utf-8")
1471
+ else:
1472
+ _profile_path.write_text(
1473
+ f"# 用户背景\n\n## 偏好与背景\n\n- [{now_str}] {grest}\n",
1474
+ encoding="utf-8",
1475
+ )
1476
+ from aria_code.account_memory import AccountMemory
1477
+ from aria_code.memory_manager import _SENSITIVE_PATTERN
1478
+ if not _SENSITIVE_PATTERN.search(grest):
1479
+ AccountMemory(self.terminal.config).add(grest)
1480
+ # Refresh project context so change takes effect immediately
1481
+ _PROJECT_CONTEXT = _load_project_context()
1482
+ if self.context.has_rich:
1483
+ self.context.console.print(" [dim]✓ 已写入 当前账户的 ARIA.md — 下次对话自动注入[/dim]")
1484
+ else:
1485
+ print("Saved to 当前账户的 ARIA.md")
1486
+
1487
+ elif gsub == "clear":
1488
+ if _profile_path.exists():
1489
+ _profile_path.write_text("# 用户背景\n\n", encoding="utf-8")
1490
+ _PROJECT_CONTEXT = _load_project_context()
1491
+ self.context.console.print("[dim]当前账户的 ARIA.md 已清空。[/dim]") if self.context.has_rich else print("Profile cleared.")
1492
+ else:
1493
+ self.context.console.print("[dim]文件不存在,无需清空。[/dim]") if self.context.has_rich else print("Nothing to clear.")
1494
+
1495
+ else:
1496
+ if self.context.has_rich:
1497
+ self.context.console.print("[dim]Usage: /memory profile [show|add <内容>|clear][/dim]")
1498
+ else:
1499
+ print("Usage: /memory profile [show|add <text>|clear]")
1500
+
1501
+ elif sub == "global":
1502
+ # Global user memory (cross-project, cross-session)
1503
+ if not self.memory_mgr:
1504
+ self.context.console.print("[dim]Memory manager not available.[/dim]") if self.context.has_rich else print("Memory manager not available.")
1505
+ return
1506
+ gparts = rest.strip().split(maxsplit=1)
1507
+ gsub = gparts[0].lower() if gparts else "show"
1508
+ grest = gparts[1].strip() if len(gparts) > 1 else ""
1509
+
1510
+ if gsub == "show":
1511
+ entries = self.memory_mgr.list_all()
1512
+ if not entries:
1513
+ self.context.console.print("[dim]全局 Memory 为空。用 /memory global add <内容> 添加。[/dim]") if self.context.has_rich else print("Global memory is empty.")
1514
+ return
1515
+ if self.context.has_rich:
1516
+ from rich.markdown import Markdown as _RMd2
1517
+ for e in entries:
1518
+ self.context.console.print(f"\n[bold cyan]{e['title']}[/bold cyan] [dim]{e['file']}[/dim]")
1519
+ self.context.console.print(_RMd2(e["content"]) if e["content"] else "[dim](empty)[/dim]")
1520
+ else:
1521
+ for e in entries:
1522
+ print(f"\n## {e['title']}\n{e['content']}")
1523
+
1524
+ elif gsub == "add":
1525
+ if not grest:
1526
+ self.context.console.print("[dim]Usage: /memory global add <内容>[/dim]") if self.context.has_rich else print("Usage: /memory global add <content>")
1527
+ return
1528
+ saved = self.memory_mgr.append("user_profile", grest, title="User Profile")
1529
+ print("已保存记忆;账户记忆将在下次对话或 /memory sync 时同步。" if saved is not False else "未保存:请检查记忆开关和内容。")
1530
+
1531
+ elif gsub == "clear":
1532
+ n = self.memory_mgr.clear_all()
1533
+ print(f"已清除当前记忆({n} 项);云端删除将在下次同步时完成。")
1534
+
1535
+ else:
1536
+ self.context.console.print("[dim]Usage: /memory global [show|add <内容>|clear][/dim]") if self.context.has_rich else print("Usage: /memory global [show|add|clear]")
1537
+
1538
+ else:
1539
+ if self.context.has_rich:
1540
+ self.context.console.print("[dim]Usage: /memory [show|add <fact>|clear|search <query>|profile|global][/dim]")
1541
+ self.context.console.print("[dim] /memory profile add <内容> — 写入全局用户背景(每次会话自动注入)[/dim]")
1542
+ else:
1543
+ print("Usage: /memory [show|add <fact>|clear|search <query>|profile|global]")