aria-code 4.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. agents/__init__.py +32 -0
  2. agents/base.py +190 -0
  3. agents/deep/__init__.py +37 -0
  4. agents/deep/calibration_loop.py +144 -0
  5. agents/deep/critic.py +125 -0
  6. agents/deep/deepen.py +193 -0
  7. agents/deep/models.py +149 -0
  8. agents/deep/pipeline.py +164 -0
  9. agents/deep/quant_fusion.py +192 -0
  10. agents/deep/themes.py +95 -0
  11. agents/deep/tiers.py +106 -0
  12. agents/financial/__init__.py +10 -0
  13. agents/financial/catalyst.py +279 -0
  14. agents/financial/debate.py +145 -0
  15. agents/financial/earnings.py +303 -0
  16. agents/financial/fundamental.py +159 -0
  17. agents/financial/macro.py +99 -0
  18. agents/financial/news.py +207 -0
  19. agents/financial/risk.py +132 -0
  20. agents/financial/sector.py +279 -0
  21. agents/financial/synthesis.py +274 -0
  22. agents/financial/technical.py +258 -0
  23. agents/portfolio_agent.py +333 -0
  24. agents/realty/__init__.py +62 -0
  25. agents/realty/asset_diagnosis.py +150 -0
  26. agents/realty/business_match.py +165 -0
  27. agents/realty/cashflow_verify.py +208 -0
  28. agents/realty/contract_rules.py +209 -0
  29. agents/realty/energy_anomaly.py +188 -0
  30. agents/realty/exit_settlement.py +207 -0
  31. agents/realty/fulfillment_risk.py +205 -0
  32. agents/realty/ops_optimize.py +159 -0
  33. agents/realty/revenue_share.py +214 -0
  34. agents/registry.py +144 -0
  35. agents/sports/__init__.py +0 -0
  36. agents/sports/football_agent.py +169 -0
  37. agents/team.py +289 -0
  38. aliyun_data_client.py +660 -0
  39. apps/README.md +12 -0
  40. apps/__init__.py +2 -0
  41. apps/channels/README.md +15 -0
  42. apps/cli/README.md +13 -0
  43. apps/cli/__init__.py +2 -0
  44. apps/cli/bootstrap.py +99 -0
  45. apps/cli/codegen_paths.py +29 -0
  46. apps/cli/commands/__init__.py +16 -0
  47. apps/cli/commands/analysis_cmds.py +288 -0
  48. apps/cli/commands/backtest_cmds.py +1887 -0
  49. apps/cli/commands/broker_cmds.py +1154 -0
  50. apps/cli/commands/business_workflow_cmds.py +289 -0
  51. apps/cli/commands/catalog.py +84 -0
  52. apps/cli/commands/data_cmds.py +405 -0
  53. apps/cli/commands/diagnostic_cmds.py +179 -0
  54. apps/cli/commands/diagnostic_ops_cmds.py +696 -0
  55. apps/cli/commands/finance_render.py +12 -0
  56. apps/cli/commands/market.py +399 -0
  57. apps/cli/commands/market_cmds.py +1276 -0
  58. apps/cli/commands/market_context.py +425 -0
  59. apps/cli/commands/market_render.py +7 -0
  60. apps/cli/commands/model_cmds.py +1579 -0
  61. apps/cli/commands/ops_cmds.py +668 -0
  62. apps/cli/commands/portfolio_cmds.py +962 -0
  63. apps/cli/commands/report.py +377 -0
  64. apps/cli/commands/scaffold_templates.py +617 -0
  65. apps/cli/commands/session_cmds.py +179 -0
  66. apps/cli/commands/session_ux_cmds.py +280 -0
  67. apps/cli/commands/team.py +588 -0
  68. apps/cli/commands/team_render.py +8 -0
  69. apps/cli/commands/ui_cmds.py +358 -0
  70. apps/cli/commands/workflow_cmds.py +279 -0
  71. apps/cli/commands/workspace_cmds.py +1414 -0
  72. apps/cli/config_paths.py +70 -0
  73. apps/cli/config_store.py +61 -0
  74. apps/cli/deterministic.py +122 -0
  75. apps/cli/direct.py +48 -0
  76. apps/cli/github_app_auth.py +135 -0
  77. apps/cli/handlers/__init__.py +11 -0
  78. apps/cli/handlers/broker_handlers.py +122 -0
  79. apps/cli/handlers/chart_handlers.py +1309 -0
  80. apps/cli/handlers/market_handlers.py +2509 -0
  81. apps/cli/handlers/realty_handlers.py +114 -0
  82. apps/cli/handlers/strategy_advice.py +82 -0
  83. apps/cli/hooks.py +180 -0
  84. apps/cli/i18n.py +284 -0
  85. apps/cli/intent.py +136 -0
  86. apps/cli/intent_router.py +217 -0
  87. apps/cli/lifecycle_hooks.py +48 -0
  88. apps/cli/main.py +29 -0
  89. apps/cli/market_metadata.py +135 -0
  90. apps/cli/market_universe.py +265 -0
  91. apps/cli/message_processing.py +257 -0
  92. apps/cli/plan_mode.py +139 -0
  93. apps/cli/plotly_html.py +15 -0
  94. apps/cli/prediction_feedback.py +202 -0
  95. apps/cli/preflight.py +497 -0
  96. apps/cli/project_aria.py +60 -0
  97. apps/cli/prompts/__init__.py +0 -0
  98. apps/cli/prompts/coding.py +658 -0
  99. apps/cli/prompts/system_prompts.py +531 -0
  100. apps/cli/prompts/ui.py +434 -0
  101. apps/cli/providers/__init__.py +1 -0
  102. apps/cli/providers/base.py +271 -0
  103. apps/cli/providers/chat_routing.py +80 -0
  104. apps/cli/providers/llm/__init__.py +1 -0
  105. apps/cli/providers/llm/ollama_stream.py +1170 -0
  106. apps/cli/providers/llm/sse_stream.py +216 -0
  107. apps/cli/providers/runtime_bridge.py +185 -0
  108. apps/cli/runtime_consumer.py +489 -0
  109. apps/cli/session_export.py +87 -0
  110. apps/cli/session_jsonl.py +207 -0
  111. apps/cli/session_store.py +112 -0
  112. apps/cli/todo_tracker.py +190 -0
  113. apps/cli/tools/__init__.py +40 -0
  114. apps/cli/tools/context.py +46 -0
  115. apps/cli/tools/file_tools.py +112 -0
  116. apps/cli/tools/market_tools.py +549 -0
  117. apps/cli/tools/notebook_tools.py +111 -0
  118. apps/cli/tools/system_tools.py +669 -0
  119. apps/cli/tools/write_tools.py +715 -0
  120. apps/cli/tradingview_bridge.py +434 -0
  121. apps/cli/update_check.py +152 -0
  122. apps/cli/utils/__init__.py +0 -0
  123. apps/cli/utils/market_detect.py +1578 -0
  124. apps/daemon/README.md +14 -0
  125. apps/vscode/README.md +115 -0
  126. apps/vscode/package.json +70 -0
  127. aria_cli.py +11636 -0
  128. aria_code-4.1.3.dist-info/METADATA +952 -0
  129. aria_code-4.1.3.dist-info/RECORD +284 -0
  130. aria_code-4.1.3.dist-info/WHEEL +5 -0
  131. aria_code-4.1.3.dist-info/entry_points.txt +2 -0
  132. aria_code-4.1.3.dist-info/licenses/LICENSE +121 -0
  133. aria_code-4.1.3.dist-info/top_level.txt +50 -0
  134. aria_daemon.py +1295 -0
  135. aria_feishu_bot.py +1359 -0
  136. aria_relay_client.py +182 -0
  137. aria_relay_server.py +405 -0
  138. aria_telegram_bot.py +202 -0
  139. ariarc.py +328 -0
  140. artifacts.py +491 -0
  141. backtest_report.py +472 -0
  142. brokers/__init__.py +72 -0
  143. brokers/base.py +207 -0
  144. brokers/capabilities.py +264 -0
  145. brokers/cn/__init__.py +10 -0
  146. brokers/cn/easytrader_broker.py +193 -0
  147. brokers/cn/futu_broker.py +194 -0
  148. brokers/cn/longbridge_broker.py +190 -0
  149. brokers/cn/tiger_broker.py +196 -0
  150. brokers/cn/xtquant_broker.py +175 -0
  151. brokers/config.py +364 -0
  152. brokers/intl/__init__.py +5 -0
  153. brokers/intl/alpaca_broker.py +183 -0
  154. brokers/intl/ibkr_broker.py +215 -0
  155. brokers/intl/webull_broker.py +156 -0
  156. brokers/paper_broker.py +259 -0
  157. brokers/planning.py +296 -0
  158. brokers/registry.py +181 -0
  159. brokers/trading.py +237 -0
  160. change_store.py +127 -0
  161. command_safety.py +19 -0
  162. computer_use_tools.py +504 -0
  163. dashboard_generator.py +578 -0
  164. data_analysis_tools.py +808 -0
  165. data_cleaner.py +483 -0
  166. data_service.py +481 -0
  167. datasources/__init__.py +23 -0
  168. datasources/base.py +166 -0
  169. datasources/router.py +221 -0
  170. datasources/sources/__init__.py +15 -0
  171. datasources/sources/akshare_source.py +269 -0
  172. datasources/sources/alpha_vantage_source.py +202 -0
  173. datasources/sources/edgar_source.py +218 -0
  174. datasources/sources/finnhub_source.py +197 -0
  175. datasources/sources/fred_source.py +219 -0
  176. datasources/sources/tushare_source.py +141 -0
  177. datasources/sources/web_scraper_source.py +278 -0
  178. datasources/sources/world_bank_source.py +205 -0
  179. datasources/sources/yfinance_source.py +152 -0
  180. demo_player.py +204 -0
  181. doctor.py +508 -0
  182. file_analysis_tools.py +734 -0
  183. finance_formulas.py +389 -0
  184. football_data_client.py +1670 -0
  185. intent_classifier.py +358 -0
  186. local_finance_tools.py +3221 -0
  187. local_llm_provider.py +552 -0
  188. macro_tools.py +368 -0
  189. market_data_client.py +1899 -0
  190. mcp_client.py +506 -0
  191. memory_manager.py +245 -0
  192. model_capability.py +416 -0
  193. notification_tools.py +248 -0
  194. packages/__init__.py +23 -0
  195. packages/aria_agents/__init__.py +5 -0
  196. packages/aria_agents/manifest.py +69 -0
  197. packages/aria_core/__init__.py +34 -0
  198. packages/aria_core/architecture.py +192 -0
  199. packages/aria_core/export.py +124 -0
  200. packages/aria_core/manifest.py +65 -0
  201. packages/aria_infra/__init__.py +15 -0
  202. packages/aria_infra/arthera.py +52 -0
  203. packages/aria_infra/doctor.py +246 -0
  204. packages/aria_infra/product.py +37 -0
  205. packages/aria_mcp/__init__.py +25 -0
  206. packages/aria_mcp/bridge.py +38 -0
  207. packages/aria_mcp/config.py +97 -0
  208. packages/aria_mcp/tools.py +61 -0
  209. packages/aria_sdk/__init__.py +19 -0
  210. packages/aria_sdk/client.py +396 -0
  211. packages/aria_sdk/providers.py +70 -0
  212. packages/aria_sdk/streaming.py +73 -0
  213. packages/aria_sdk/types.py +86 -0
  214. packages/aria_services/__init__.py +55 -0
  215. packages/aria_services/context.py +258 -0
  216. packages/aria_services/data.py +11 -0
  217. packages/aria_services/provider_health.py +189 -0
  218. packages/aria_services/registry.py +213 -0
  219. packages/aria_services/usage.py +138 -0
  220. packages/aria_skills/__init__.py +5 -0
  221. packages/aria_skills/registry.py +59 -0
  222. packages/aria_tools/__init__.py +5 -0
  223. packages/aria_tools/registry.py +128 -0
  224. packages/quant_engine/__init__.py +6 -0
  225. packages/quant_engine/sports/__init__.py +72 -0
  226. packages/quant_engine/sports/calibrator.py +353 -0
  227. packages/quant_engine/sports/dixon_coles.py +234 -0
  228. packages/quant_engine/sports/elo.py +299 -0
  229. packages/quant_engine/sports/form.py +188 -0
  230. packages/quant_engine/sports/h2h.py +195 -0
  231. packages/quant_engine/sports/ml_model.py +354 -0
  232. packages/quant_engine/sports/predictor.py +311 -0
  233. packages/quant_engine/sports/tracker.py +664 -0
  234. packages/quant_engine/stochastic/__init__.py +27 -0
  235. packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  236. packages/quant_engine/stochastic/ito_calculus.py +477 -0
  237. packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  238. packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  239. packages/quant_engine/stochastic/options_pricing.py +573 -0
  240. packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  241. plan_utils.py +194 -0
  242. plugin_loader.py +328 -0
  243. portfolio_ledger.py +262 -0
  244. privacy/__init__.py +5 -0
  245. privacy/feedback.py +123 -0
  246. project_tools.py +525 -0
  247. providers/__init__.py +30 -0
  248. providers/llm/__init__.py +19 -0
  249. providers/llm/anthropic.py +184 -0
  250. providers/llm/base.py +139 -0
  251. providers/llm/ollama.py +128 -0
  252. providers/llm/openai_compat.py +282 -0
  253. providers/llm/registry.py +358 -0
  254. realty_data_tools.py +659 -0
  255. report_generator.py +1314 -0
  256. runtime/__init__.py +103 -0
  257. runtime/agent_loop.py +1183 -0
  258. runtime/approval.py +51 -0
  259. runtime/events.py +102 -0
  260. runtime/gateway.py +128 -0
  261. runtime/lsp.py +346 -0
  262. runtime/subagent.py +258 -0
  263. runtime/tool_executor.py +104 -0
  264. runtime/tool_policy.py +106 -0
  265. safety/__init__.py +21 -0
  266. safety/permissions.py +275 -0
  267. setup_wizard.py +653 -0
  268. strategy_vault.py +420 -0
  269. ui/__init__.py +100 -0
  270. ui/banner.py +310 -0
  271. ui/completer.py +391 -0
  272. ui/console.py +271 -0
  273. ui/image_render.py +243 -0
  274. ui/input_box.py +376 -0
  275. ui/picker.py +195 -0
  276. ui/render/__init__.py +11 -0
  277. ui/render/finance.py +1480 -0
  278. ui/render/market.py +225 -0
  279. ui/render/output.py +681 -0
  280. ui/render/team.py +346 -0
  281. ui/robot.py +235 -0
  282. workspace/__init__.py +6 -0
  283. workspace/files.py +170 -0
  284. workspace/verify.py +113 -0
@@ -0,0 +1,668 @@
1
+ """OpsCommandsMixin — watch, services, plan, git, and GitHub helper commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class OpsCommandsMixin:
7
+ """Mixin: operational and workflow helper commands."""
8
+
9
+ def cmd_watch(self, args: str):
10
+ parts = args.split() if args else ["list"]
11
+ action = parts[0].lower() if parts else "list"
12
+ watchlist = self.terminal.config.get("watchlist", [])
13
+
14
+ if action == "add" and len(parts) > 1:
15
+ symbol = parts[1].upper()
16
+ if symbol not in watchlist:
17
+ watchlist.append(symbol)
18
+ self.terminal.config["watchlist"] = watchlist
19
+ save_config(self.terminal.config)
20
+ console.print(f"[green]Added {symbol} to watchlist[/green]" if HAS_RICH
21
+ else f"Added {symbol}")
22
+ else:
23
+ console.print(f"[dim]{symbol} already in watchlist[/dim]" if HAS_RICH
24
+ else f"{symbol} already in watchlist")
25
+
26
+ elif action == "remove" and len(parts) > 1:
27
+ symbol = parts[1].upper()
28
+ if symbol in watchlist:
29
+ watchlist.remove(symbol)
30
+ self.terminal.config["watchlist"] = watchlist
31
+ save_config(self.terminal.config)
32
+ console.print(f"[dim]Removed {symbol} from watchlist[/dim]" if HAS_RICH
33
+ else f"Removed {symbol}")
34
+ else:
35
+ console.print(f"[red]{symbol} not in watchlist[/red]" if HAS_RICH
36
+ else f"{symbol} not in watchlist")
37
+
38
+ else:
39
+ if HAS_RICH:
40
+ if watchlist:
41
+ console.print(f" [dim]Watchlist:[/dim] {', '.join(watchlist)}")
42
+ else:
43
+ console.print(" [dim]Watchlist: Empty[/dim]")
44
+ else:
45
+ print(f"Watchlist: {', '.join(watchlist)}")
46
+
47
+ def cmd_services(self, args: str):
48
+ """Show CLI service tiers and core workflows."""
49
+ provider_summary = None
50
+ try:
51
+ from packages.aria_services.provider_health import GLOBAL_PROVIDER_HEALTH
52
+ provider_summary = GLOBAL_PROVIDER_HEALTH.summary()
53
+ except Exception:
54
+ provider_summary = None
55
+
56
+ cfg = getattr(self.terminal, "config", {}) or {}
57
+ auto_compact = bool(cfg.get("auto_compact_context", True))
58
+ try:
59
+ auto_compact_threshold = int(float(cfg.get("auto_compact_threshold", 0.78)) * 100)
60
+ except Exception:
61
+ auto_compact_threshold = 78
62
+
63
+ def _key_status(provider: str) -> str:
64
+ try:
65
+ return "configured" if _get_provider_key(provider) else "not set"
66
+ except Exception:
67
+ return "unknown"
68
+
69
+ runtime_services = [
70
+ ("LLM runtime", cfg.get("model", "unknown"), "local/Ollama with cloud fallback"),
71
+ ("Context manager", f"auto compact {'on' if auto_compact else 'off'} @ {auto_compact_threshold}%", "preflight + post-turn guards"),
72
+ ("Market data", "DataService", "quote/history/fundamentals/TA + provider health"),
73
+ ("Charts/reports", "artifact services", "HTML/PNG charts, dashboards, Markdown/HTML reports"),
74
+ ("News/Web", f"finnhub:{_key_status('finnhub')} · newsapi:{_key_status('newsapi')} · brave:{_key_status('brave')}", "news command + web search fallback"),
75
+ ("Cloud AI/data", f"dashscope:{_key_status('dashscope')} · openai:{_key_status('openai')} · anthropic:{_key_status('anthropic')}", "optional external providers"),
76
+ ("MCP/tools", f"{len(LOCAL_TOOLS)} local tools", "tool loop with repeat-call guard"),
77
+ ]
78
+
79
+ service_groups = [
80
+ (
81
+ "CORE (Standard)",
82
+ [
83
+ "Code agent with local tools (read/write/edit/search/run)",
84
+ "Slash command workflows for quote/analyze/backtest/risk/screen",
85
+ "Session save/load/export and interactive history management",
86
+ "Model switching + thinking mode controls for response depth",
87
+ ],
88
+ ),
89
+ (
90
+ "QUANTUM Automation",
91
+ [
92
+ "Agentic multi-step loop (auto read -> analyze -> edit -> execute)",
93
+ "Auto-recovery guidance for failed commands and code fixes",
94
+ "Strategy generation, backtest reporting, and risk analysis skills",
95
+ "Cross-workspace research sync hooks (session + export pipeline)",
96
+ ],
97
+ ),
98
+ (
99
+ "ENTERPRISE Controls (included in Quantum)",
100
+ [
101
+ "Service health diagnostics (/health) for backend + local model stack",
102
+ "Governed command execution with dangerous-command blocking",
103
+ "Audit-friendly session logs and reproducible command trails",
104
+ "MCP-ready service integration path via external tool endpoints",
105
+ ],
106
+ ),
107
+ ]
108
+
109
+ quick_flow = [
110
+ "/model",
111
+ "/gen-strategy momentum AAPL",
112
+ "/backtest momentum AAPL 2024-01-01 2025-01-01",
113
+ "/risk AAPL",
114
+ "/export md strategy_report.md",
115
+ ]
116
+
117
+ if HAS_RICH:
118
+ console.print()
119
+ console.print("[bold]CLI Services[/bold] [dim](runtime boundaries + workflow)[/dim]")
120
+ console.print()
121
+ console.print(" [bold]Runtime Service Map[/bold]")
122
+ for name, status, detail in runtime_services:
123
+ console.print(f" [dim]{name:<16}[/dim] [bold]{status}[/bold] [dim]{detail}[/dim]")
124
+ if provider_summary is not None:
125
+ color = "green" if provider_summary.status == "ok" else ("red" if provider_summary.status == "err" else "yellow")
126
+ console.print(f" [dim]{'Provider health':<16}[/dim] [{color}]{provider_summary.status}[/{color}] [dim]{provider_summary.detail}[/dim]")
127
+ console.print(f" [dim]{'Suggestion':<16}[/dim] [dim]{provider_summary.suggestion}[/dim]")
128
+ console.print()
129
+ for group_name, items in service_groups:
130
+ console.print(f" [bold #C08050]{group_name}[/bold #C08050]")
131
+ for item in items:
132
+ console.print(f" [dim]> {item}[/dim]")
133
+ console.print()
134
+
135
+ console.print(" [bold]Quick Start Flow[/bold]")
136
+ for cmd in quick_flow:
137
+ console.print(f" [bold]{cmd}[/bold]")
138
+ console.print()
139
+ else:
140
+ print("\nCLI Services (runtime boundaries + workflow)\n")
141
+ print(" Runtime Service Map")
142
+ for name, status, detail in runtime_services:
143
+ print(f" {name:<16} {status} {detail}")
144
+ if provider_summary is not None:
145
+ print(f" {'Provider health':<16} {provider_summary.status} {provider_summary.detail}")
146
+ print(f" {'Suggestion':<16} {provider_summary.suggestion}")
147
+ print()
148
+ for group_name, items in service_groups:
149
+ print(f" {group_name}")
150
+ for item in items:
151
+ print(f" > {item}")
152
+ print()
153
+
154
+ print(" Quick Start Flow")
155
+ for cmd in quick_flow:
156
+ print(f" {cmd}")
157
+ print()
158
+
159
+ def cmd_plan(self, args: str):
160
+ """Create an executable plan and store it for /apply-plan."""
161
+ raw = args.strip()
162
+ if not raw:
163
+ if HAS_RICH:
164
+ console.print("[dim]Usage: /plan <steps> — see examples below[/dim]")
165
+ console.print("[dim] /plan fetch AAPL quote -> generate chart -> write report[/dim]")
166
+ console.print("[dim] /plan 1. Analyze sentiment 2. Build model 3. Backtest[/dim]")
167
+ else:
168
+ print("Usage: /plan <steps>")
169
+ print(" /plan fetch AAPL quote -> generate chart -> write report")
170
+ print(" /plan 1. Analyze sentiment 2. Build model 3. Backtest")
171
+ return
172
+
173
+ from plan_utils import parse_plan
174
+ plan_steps = parse_plan(raw)
175
+ if not plan_steps:
176
+ console.print("[dim]No valid steps found[/dim]" if HAS_RICH else "No valid steps found")
177
+ return
178
+
179
+ self.terminal.pending_plan = [s.description for s in plan_steps]
180
+
181
+ if HAS_RICH:
182
+ console.print()
183
+ console.print(f"[bold]Execution Plan[/bold] [dim]({len(plan_steps)} steps)[/dim]")
184
+ console.print()
185
+ for s in plan_steps:
186
+ dep_str = f" [dim](after {', '.join(str(d) for d in s.deps)})[/dim]" if s.deps else ""
187
+ label = f" [dim][{s.name}][/dim]" if s.name else ""
188
+ console.print(f" [dim]{s.index}.[/dim]{label} [bold]{s.description}[/bold]{dep_str}")
189
+ console.print()
190
+ console.print("[dim]Run /apply-plan to execute these steps.[/dim]")
191
+ console.print()
192
+ else:
193
+ print(f"\nExecution Plan ({len(plan_steps)} steps)")
194
+ for s in plan_steps:
195
+ dep_str = f" (after {', '.join(str(d) for d in s.deps)})" if s.deps else ""
196
+ label = f" [{s.name}]" if s.name else ""
197
+ print(f" {s.index}.{label} {s.description}{dep_str}")
198
+ print("Run /apply-plan to execute these steps.\n")
199
+
200
+ def cmd_plan_report(self, args: str):
201
+ """Show or export last plan execution report."""
202
+ rows = list(getattr(self.terminal, "last_plan_results", []) or [])
203
+ if not rows:
204
+ msg = "No plan report available. Run /apply-plan first."
205
+ console.print(f"[dim]{msg}[/dim]" if HAS_RICH else msg)
206
+ return
207
+
208
+ parts = args.split()
209
+ open_after = "--open" in parts
210
+ parts = [p for p in parts if p != "--open"]
211
+ fmt = parts[0].lower() if parts else "show"
212
+ out_file = parts[1] if len(parts) > 1 else None
213
+
214
+ if fmt == "show":
215
+ if HAS_RICH:
216
+ console.print()
217
+ console.print("[bold]Last Plan Report[/bold]")
218
+ for idx, row in enumerate(rows, 1):
219
+ status_color = "green" if row["status"] == "completed" else ("yellow" if row["status"] == "blocked" else "red")
220
+ console.print(
221
+ f" [dim]{idx}.[/dim] [{status_color}]{row['status']}[/{status_color}] "
222
+ f"[bold]{row['step']}[/bold] [dim]({row['duration']}s, exit={row.get('exit_code')})[/dim]"
223
+ )
224
+ if row.get("error"):
225
+ console.print(f" [red]{row['error']}[/red]")
226
+ console.print()
227
+ else:
228
+ print("\nLast Plan Report")
229
+ for idx, row in enumerate(rows, 1):
230
+ print(f" {idx}. {row['status']} {row['step']} ({row['duration']}s, exit={row.get('exit_code')})")
231
+ if row.get("error"):
232
+ print(f" ERROR: {row['error']}")
233
+ return
234
+
235
+ if fmt not in {"md", "json"}:
236
+ msg = "Usage: /plan-report [md|json] [file] [--open]"
237
+ console.print(f"[dim]{msg}[/dim]" if HAS_RICH else msg)
238
+ return
239
+
240
+ if not out_file:
241
+ out_file = f"plan_report.{fmt}"
242
+
243
+ try:
244
+ if fmt == "json":
245
+ content = json.dumps(rows, ensure_ascii=False, indent=2)
246
+ else:
247
+ md_lines = ["# Plan Execution Report", ""]
248
+ for idx, row in enumerate(rows, 1):
249
+ md_lines.append(
250
+ f"{idx}. **{row['status']}** `{row['step']}` "
251
+ f"({row['duration']}s, exit={row.get('exit_code')})"
252
+ )
253
+ if row.get("error"):
254
+ md_lines.append(f" - Error: {row['error']}")
255
+ md_lines.append("")
256
+ content = "\n".join(md_lines)
257
+
258
+ result = _tool_write_file({"path": out_file, "content": content})
259
+ if result.get("success"):
260
+ saved_path = result["data"]["path"]
261
+ msg = f"Plan report saved to {_display_path(saved_path)}"
262
+ console.print(f"[green]{msg}[/green]" if HAS_RICH else msg)
263
+ if open_after:
264
+ self._open_file(saved_path)
265
+ else:
266
+ err = result.get("error", "Failed to save report")
267
+ console.print(f"[red]{err}[/red]" if HAS_RICH else err)
268
+ except Exception as e:
269
+ console.print(f"[red]{e}[/red]" if HAS_RICH else str(e))
270
+
271
+ def cmd_git(self, args: str):
272
+ """Git helper shortcuts."""
273
+ policy = self.terminal.config.get("command_policy", "safe")
274
+ raw = args.strip()
275
+ if not raw:
276
+ sub = "status"
277
+ sub_args = ""
278
+ else:
279
+ parts = raw.split(maxsplit=1)
280
+ sub = parts[0].lower()
281
+ sub_args = parts[1].strip() if len(parts) > 1 else ""
282
+
283
+ mapping = {
284
+ "status": "git status --short --branch",
285
+ "diff": "git diff --stat",
286
+ "summary": "git status --short --branch && git diff --stat",
287
+ "branch": "git branch -v",
288
+ "stash": "git stash list",
289
+ "remote": "git remote -v",
290
+ }
291
+ if sub == "patch":
292
+ cmd = "git diff" if not sub_args else f"git diff -- {sub_args}"
293
+ elif sub == "log":
294
+ limit = sub_args if sub_args and sub_args.isdigit() else "15"
295
+ cmd = f"git log --oneline --graph --decorate -{limit}"
296
+ elif sub == "commit":
297
+ status_probe = _tool_run_command({"command": "git status --porcelain", "policy": policy})
298
+ if not status_probe.get("success"):
299
+ err = status_probe.get("error", "Failed to inspect git status")
300
+ console.print(f"[red]{err}[/red]" if HAS_RICH else err)
301
+ return
302
+
303
+ status_out = status_probe.get("data", {}).get("stdout", "").strip()
304
+ if not status_out:
305
+ msg = "No changes to commit."
306
+ console.print(f"[dim]{msg}[/dim]" if HAS_RICH else msg)
307
+ return
308
+
309
+ changed_files = []
310
+ for line in status_out.splitlines():
311
+ if len(line) >= 4:
312
+ changed_files.append(line[3:].strip())
313
+ unique_files = [f for f in changed_files if f]
314
+ total_files = len(unique_files)
315
+ file_preview = ", ".join(unique_files[:5]) if unique_files else "workspace"
316
+ body_summary = f"Files changed: {total_files}"
317
+ body_preview = f"Top files: {file_preview}"
318
+
319
+ if not sub_args:
320
+ files = []
321
+ for line in status_out.splitlines()[:3]:
322
+ if len(line) >= 4:
323
+ files.append(line[3:].strip())
324
+ sample = ", ".join(files) if files else "workspace"
325
+ total = len(status_out.splitlines())
326
+ sub_args = f"chore: update {total} file(s) ({sample})"
327
+ if HAS_RICH:
328
+ console.print(f"[dim]Auto commit message:[/dim] {sub_args}")
329
+ else:
330
+ print(f"Auto commit message: {sub_args}")
331
+
332
+ cmd = (
333
+ f"git add -A && git commit "
334
+ f"-m {shlex.quote(sub_args)} "
335
+ f"-m {shlex.quote(body_summary)} "
336
+ f"-m {shlex.quote(body_preview)}"
337
+ )
338
+ elif sub in mapping:
339
+ cmd = mapping[sub]
340
+ else:
341
+ msg = "Usage: /git [status|diff|summary|patch|log [N]|branch|stash|remote|commit <msg>]"
342
+ console.print(f"[dim]{msg}[/dim]" if HAS_RICH else msg)
343
+ return
344
+ result = _tool_run_command({"command": cmd, "policy": policy})
345
+ if not result.get("success"):
346
+ console.print(f"[red]{result.get('error', 'Command failed')}[/red]" if HAS_RICH
347
+ else result.get("error", "Command failed"))
348
+ return
349
+ data = result.get("data", {})
350
+ out = (data.get("stdout", "") + ("\n" + data.get("stderr", "") if data.get("stderr") else "")).strip()
351
+ if out:
352
+ if HAS_RICH:
353
+ console.print(Syntax(out, "text", theme=_SYNTAX_THEME))
354
+ else:
355
+ print(out)
356
+
357
+ def cmd_gh(self, args: str):
358
+ """GitHub CLI helper — prs | issues | pr N | issue N | search | create-pr | diff N | checks N"""
359
+ raw = args.strip()
360
+ if not raw or raw in ("help", "--help"):
361
+ lines = [
362
+ "Usage: /gh <command>",
363
+ " prs List open pull requests",
364
+ " issues List open issues",
365
+ " pr <N> View pull request #N",
366
+ " issue <N> View issue #N",
367
+ " diff <N> Show PR #N diff",
368
+ " checks <N> Show PR #N CI checks",
369
+ " search <q> Search code in this repo",
370
+ " create-pr Create a PR (follow prompts)",
371
+ " commits [N] Show last N commits (default 10)",
372
+ ]
373
+ for ln in lines:
374
+ console.print(f" [dim]{ln}[/dim]" if HAS_RICH else ln)
375
+ return
376
+
377
+ parts = raw.split(maxsplit=1)
378
+ sub = parts[0].lower()
379
+ subarg = parts[1].strip() if len(parts) > 1 else ""
380
+
381
+ def _run(action: str, extra: dict = None):
382
+ p = {"action": action}
383
+ if extra:
384
+ p.update(extra)
385
+ r = _tool_github(p)
386
+ if not r.get("success"):
387
+ msg = r.get("error", "GitHub command failed")
388
+ console.print(f"[red]{msg}[/red]" if HAS_RICH else msg)
389
+ return
390
+ data = r.get("data", {})
391
+ out = data.get("stdout", "") if isinstance(data, dict) else str(data)
392
+ if out.strip():
393
+ if HAS_RICH:
394
+ try:
395
+ import json as _jj
396
+ parsed = _jj.loads(out)
397
+ from rich.pretty import pprint as _pp
398
+ _pp(parsed, expand_all=False)
399
+ except Exception:
400
+ console.print(Syntax(out, "text", theme=_SYNTAX_THEME))
401
+ else:
402
+ print(out)
403
+
404
+ if sub in ("prs", "pr_list"):
405
+ _run("list_prs")
406
+ elif sub in ("issues", "issue_list"):
407
+ _run("list_issues")
408
+ elif sub == "pr" and subarg.isdigit():
409
+ _run("view_pr", {"number": int(subarg)})
410
+ elif sub == "issue" and subarg.isdigit():
411
+ _run("view_issue", {"number": int(subarg)})
412
+ elif sub == "diff" and subarg.isdigit():
413
+ _run("pr_diff", {"number": int(subarg)})
414
+ elif sub == "checks" and subarg.isdigit():
415
+ _run("pr_checks", {"number": int(subarg)})
416
+ elif sub in ("commits", "log"):
417
+ n = int(subarg) if subarg.isdigit() else 10
418
+ _run("list_commits", {"limit": n})
419
+ elif sub == "search":
420
+ if not subarg:
421
+ console.print("[dim]Usage: /gh search <query>[/dim]" if HAS_RICH else "Usage: /gh search <query>")
422
+ return
423
+ _run("search", {"q": subarg, "kind": "code"})
424
+ elif sub in ("create-pr", "createpr", "create_pr"):
425
+ try:
426
+ title = (console.input(" PR title: ") if HAS_RICH else input(" PR title: ")).strip()
427
+ body = (console.input(" PR body (optional): ") if HAS_RICH else input(" PR body (optional): ")).strip()
428
+ base = (console.input(" Base branch [main]: ") if HAS_RICH else input(" Base branch [main]: ")).strip() or "main"
429
+ _run("create_pr", {"title": title, "body": body, "base": base})
430
+ except (EOFError, KeyboardInterrupt):
431
+ console.print("[dim]Cancelled[/dim]" if HAS_RICH else "Cancelled")
432
+ else:
433
+ console.print(f"[dim]Unknown /gh sub-command: {sub}. Try /gh help[/dim]" if HAS_RICH
434
+ else f"Unknown /gh sub-command: {sub}. Try /gh help")
435
+
436
+ def cmd_lsp(self, args: str):
437
+ """Language-server diagnostics for code files.
438
+
439
+ /lsp — show which language servers are installed
440
+ /lsp <file> — run diagnostics (errors/warnings) on a file
441
+ """
442
+ try:
443
+ from runtime.lsp import available_servers, server_for, get_diagnostics
444
+ except ImportError as _e:
445
+ console.print(f"[red]runtime.lsp not available: {_e}[/red]") if HAS_RICH else print(f"Error: {_e}")
446
+ return
447
+
448
+ target = args.strip().strip('"\'')
449
+
450
+ # No arg or "status" → show installed servers
451
+ if not target or target.lower() == "status":
452
+ servers = available_servers()
453
+ if HAS_RICH:
454
+ console.print()
455
+ console.print(" [bold]Language Servers[/bold] [dim]on-demand diagnostics[/dim]")
456
+ console.print()
457
+ _hints = {
458
+ "pylsp": "pip install 'python-lsp-server[all]'",
459
+ "typescript-language-server": "npm i -g typescript-language-server typescript",
460
+ }
461
+ for exe, installed in servers.items():
462
+ if installed:
463
+ console.print(f" [green]●[/green] [bold]{exe}[/bold] [dim]installed[/dim]")
464
+ else:
465
+ console.print(f" [dim]○ {exe} not installed — {_hints.get(exe, '')}[/dim]")
466
+ console.print()
467
+ console.print(" [dim]/lsp <file> — run diagnostics on a file[/dim]")
468
+ console.print()
469
+ else:
470
+ print("\n Language Servers:")
471
+ for exe, installed in servers.items():
472
+ print(f" {'OK' if installed else '--'} {exe}")
473
+ return
474
+
475
+ # File path → run diagnostics
476
+ import pathlib
477
+ p = pathlib.Path(target).expanduser()
478
+ if not p.exists():
479
+ console.print(f"[red]File not found: {p}[/red]" if HAS_RICH else f"File not found: {p}")
480
+ return
481
+ if not server_for(p):
482
+ msg = f"No language server installed for '{p.suffix}' files"
483
+ console.print(f"[yellow]{msg}[/yellow]" if HAS_RICH else msg)
484
+ return
485
+
486
+ if HAS_RICH:
487
+ with console.status(f"[dim]Analyzing {p.name}…[/dim]", spinner="dots"):
488
+ diags = get_diagnostics(p)
489
+ else:
490
+ print(f" Analyzing {p.name}…")
491
+ diags = get_diagnostics(p)
492
+
493
+ if not diags:
494
+ console.print(f"[green]✓ No diagnostics — {p.name} is clean[/green]" if HAS_RICH
495
+ else f"No diagnostics for {p.name}")
496
+ return
497
+
498
+ errors = sum(1 for d in diags if d["severity"] == "error")
499
+ warnings = sum(1 for d in diags if d["severity"] == "warning")
500
+ if HAS_RICH:
501
+ from rich.table import Table
502
+ from rich import box as _rbox
503
+ console.print()
504
+ console.print(f" [bold]{p.name}[/bold] "
505
+ f"[red]{errors} error(s)[/red] · [yellow]{warnings} warning(s)[/yellow]")
506
+ console.print()
507
+ t = Table(box=_rbox.SIMPLE, padding=(0, 1), show_header=True)
508
+ t.add_column("Loc", style="dim", justify="right", min_width=7)
509
+ t.add_column("Severity", min_width=8)
510
+ t.add_column("Message")
511
+ _sev_color = {"error": "red", "warning": "yellow", "info": "cyan", "hint": "dim"}
512
+ for d in diags[:40]:
513
+ color = _sev_color.get(d["severity"], "white")
514
+ src = f" [dim]({d['source']})[/dim]" if d["source"] else ""
515
+ t.add_row(
516
+ f"{d['line']}:{d['col']}",
517
+ f"[{color}]{d['severity']}[/{color}]",
518
+ f"{d['message']}{src}",
519
+ )
520
+ console.print(t)
521
+ console.print()
522
+ else:
523
+ print(f"\n {p.name}: {errors} errors, {warnings} warnings")
524
+ for d in diags[:40]:
525
+ print(f" {d['line']}:{d['col']} {d['severity']:8s} {d['message']}")
526
+
527
+ def cmd_completions(self, args: str):
528
+ """Generate shell completion script for aria-code slash commands.
529
+
530
+ /completions — show instructions
531
+ /completions bash — output bash completion snippet
532
+ /completions zsh — output zsh completion snippet
533
+ /completions install — write and source the completion script
534
+ """
535
+ # NOTE: this method's __globals__ are rebound to aria_cli's namespace by
536
+ # _rebind_mixin_globals(), so module-level names from this file (Path and
537
+ # the _build_*/_detect_user_shell helpers) must be imported locally.
538
+ from pathlib import Path
539
+ from apps.cli.commands.ops_cmds import (
540
+ _build_bash_completion, _build_zsh_completion, _detect_user_shell,
541
+ )
542
+
543
+ shell = args.strip().lower() or "show"
544
+
545
+ # Build sorted slash command list from VISIBLE_SLASH_COMMANDS
546
+ try:
547
+ from apps.cli.commands.catalog import VISIBLE_SLASH_COMMANDS
548
+ cmds = sorted(VISIBLE_SLASH_COMMANDS)
549
+ except ImportError:
550
+ cmds = ["/help", "/model", "/config", "/recall", "/permissions", "/deep", "/quote"]
551
+
552
+ if shell == "bash":
553
+ script = _build_bash_completion(cmds)
554
+ console.print(script if not HAS_RICH else f"[dim]{script}[/dim]")
555
+ return
556
+
557
+ if shell == "zsh":
558
+ script = _build_zsh_completion(cmds)
559
+ console.print(script if not HAS_RICH else f"[dim]{script}[/dim]")
560
+ return
561
+
562
+ if shell == "install":
563
+ import subprocess as _sp
564
+ import shutil as _sh
565
+ detected = _detect_user_shell()
566
+ if detected == "zsh":
567
+ script = _build_zsh_completion(cmds)
568
+ target = Path.home() / ".zsh" / "_aria-code"
569
+ target.parent.mkdir(parents=True, exist_ok=True)
570
+ target.write_text(script)
571
+ rc = Path.home() / ".zshrc"
572
+ fpath_line = f'fpath=("{target.parent}" $fpath)'
573
+ if rc.exists():
574
+ existing = rc.read_text()
575
+ if str(target.parent) not in existing:
576
+ rc.write_text(existing.rstrip("\n") + f"\n{fpath_line}\nautoload -U compinit && compinit\n")
577
+ msg = f"Zsh completion installed → {target}\nRestart your shell or run: source ~/.zshrc"
578
+ else:
579
+ script = _build_bash_completion(cmds)
580
+ target = Path.home() / ".bash_completion.d" / "aria-code"
581
+ target.parent.mkdir(parents=True, exist_ok=True)
582
+ target.write_text(script)
583
+ rc = Path.home() / ".bashrc"
584
+ source_line = f'[ -f "{target}" ] && source "{target}"'
585
+ if rc.exists():
586
+ existing = rc.read_text()
587
+ if str(target) not in existing:
588
+ rc.write_text(existing.rstrip("\n") + f"\n{source_line}\n")
589
+ msg = f"Bash completion installed → {target}\nRestart your shell or run: source ~/.bashrc"
590
+ if HAS_RICH:
591
+ console.print(f"[green]✓[/green] {msg}")
592
+ else:
593
+ print(msg)
594
+ return
595
+
596
+ # Default: instructions
597
+ detected = _detect_user_shell()
598
+ if HAS_RICH:
599
+ console.print()
600
+ console.print(" [bold]Shell Completions[/bold] [dim]aria-code slash commands[/dim]")
601
+ console.print()
602
+ console.print(f" Detected shell: [bold]{detected}[/bold]")
603
+ console.print()
604
+ console.print(" [dim]/completions bash — print bash completion script[/dim]")
605
+ console.print(" [dim]/completions zsh — print zsh completion script[/dim]")
606
+ console.print(" [dim]/completions install — auto-install for your shell[/dim]")
607
+ console.print()
608
+ console.print(f" [dim]{len(cmds)} slash commands registered[/dim]")
609
+ console.print()
610
+ else:
611
+ print(f"\n Shell: {detected} | {len(cmds)} commands registered")
612
+ print(" /completions bash|zsh|install")
613
+
614
+
615
+ def _detect_user_shell() -> str:
616
+ import os as _os
617
+ shell = _os.environ.get("SHELL", "").lower()
618
+ if "zsh" in shell:
619
+ return "zsh"
620
+ if "fish" in shell:
621
+ return "fish"
622
+ return "bash"
623
+
624
+
625
+ def _build_bash_completion(cmds: list) -> str:
626
+ cmd_list = " ".join(cmds)
627
+ return f"""# aria-code bash completion
628
+ # Add to ~/.bashrc: source ~/.bash_completion.d/aria-code
629
+ _aria_code_complete() {{
630
+ local cur prev words
631
+ COMPREPLY=()
632
+ cur="${{COMP_WORDS[COMP_CWORD]}}"
633
+ # Complete slash commands when passed via -p flag
634
+ if [[ "${{cur}}" == /* ]]; then
635
+ COMPREPLY=( $(compgen -W "{cmd_list}" -- "${{cur}}") )
636
+ return 0
637
+ fi
638
+ # Complete top-level flags
639
+ COMPREPLY=( $(compgen -W "--help --version --print -p --no-color" -- "${{cur}}") )
640
+ }}
641
+ complete -F _aria_code_complete aria-code
642
+ complete -F _aria_code_complete aria_cli.py
643
+ """
644
+
645
+
646
+ def _build_zsh_completion(cmds: list) -> str:
647
+ lines = ["#compdef aria-code aria_cli.py", "", "_aria_code() {", " local -a slash_cmds", " slash_cmds=("]
648
+ for cmd in cmds:
649
+ lines.append(f" '{cmd}'")
650
+ lines += [
651
+ " )",
652
+ " _arguments \\",
653
+ " '(-h --help)'{-h,--help}'[show help]' \\",
654
+ " '(-v --version)'{-v,--version}'[show version]' \\",
655
+ " '(-p --print)'{-p,--print}'[non-interactive mode]:prompt:->prompt' \\",
656
+ " '*: :->args'",
657
+ " case $state in",
658
+ " prompt|args)",
659
+ " if [[ $PREFIX == /* ]]; then",
660
+ " _values 'slash command' $slash_cmds",
661
+ " fi",
662
+ " ;;",
663
+ " esac",
664
+ "}",
665
+ "",
666
+ "_aria_code",
667
+ ]
668
+ return "\n".join(lines) + "\n"