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.
- agents/__init__.py +32 -0
- agents/base.py +190 -0
- agents/deep/__init__.py +37 -0
- agents/deep/calibration_loop.py +144 -0
- agents/deep/critic.py +125 -0
- agents/deep/deepen.py +193 -0
- agents/deep/models.py +149 -0
- agents/deep/pipeline.py +164 -0
- agents/deep/quant_fusion.py +192 -0
- agents/deep/themes.py +95 -0
- agents/deep/tiers.py +106 -0
- agents/financial/__init__.py +10 -0
- agents/financial/catalyst.py +279 -0
- agents/financial/debate.py +145 -0
- agents/financial/earnings.py +303 -0
- agents/financial/fundamental.py +159 -0
- agents/financial/macro.py +99 -0
- agents/financial/news.py +207 -0
- agents/financial/risk.py +132 -0
- agents/financial/sector.py +279 -0
- agents/financial/synthesis.py +274 -0
- agents/financial/technical.py +258 -0
- agents/portfolio_agent.py +333 -0
- agents/realty/__init__.py +62 -0
- agents/realty/asset_diagnosis.py +150 -0
- agents/realty/business_match.py +165 -0
- agents/realty/cashflow_verify.py +208 -0
- agents/realty/contract_rules.py +209 -0
- agents/realty/energy_anomaly.py +188 -0
- agents/realty/exit_settlement.py +207 -0
- agents/realty/fulfillment_risk.py +205 -0
- agents/realty/ops_optimize.py +159 -0
- agents/realty/revenue_share.py +214 -0
- agents/registry.py +144 -0
- agents/sports/__init__.py +0 -0
- agents/sports/football_agent.py +169 -0
- agents/team.py +289 -0
- aliyun_data_client.py +660 -0
- apps/README.md +12 -0
- apps/__init__.py +2 -0
- apps/channels/README.md +15 -0
- apps/cli/README.md +13 -0
- apps/cli/__init__.py +2 -0
- apps/cli/bootstrap.py +99 -0
- apps/cli/codegen_paths.py +29 -0
- apps/cli/commands/__init__.py +16 -0
- apps/cli/commands/analysis_cmds.py +288 -0
- apps/cli/commands/backtest_cmds.py +1887 -0
- apps/cli/commands/broker_cmds.py +1154 -0
- apps/cli/commands/business_workflow_cmds.py +289 -0
- apps/cli/commands/catalog.py +84 -0
- apps/cli/commands/data_cmds.py +405 -0
- apps/cli/commands/diagnostic_cmds.py +179 -0
- apps/cli/commands/diagnostic_ops_cmds.py +696 -0
- apps/cli/commands/finance_render.py +12 -0
- apps/cli/commands/market.py +399 -0
- apps/cli/commands/market_cmds.py +1276 -0
- apps/cli/commands/market_context.py +425 -0
- apps/cli/commands/market_render.py +7 -0
- apps/cli/commands/model_cmds.py +1579 -0
- apps/cli/commands/ops_cmds.py +668 -0
- apps/cli/commands/portfolio_cmds.py +962 -0
- apps/cli/commands/report.py +377 -0
- apps/cli/commands/scaffold_templates.py +617 -0
- apps/cli/commands/session_cmds.py +179 -0
- apps/cli/commands/session_ux_cmds.py +280 -0
- apps/cli/commands/team.py +588 -0
- apps/cli/commands/team_render.py +8 -0
- apps/cli/commands/ui_cmds.py +358 -0
- apps/cli/commands/workflow_cmds.py +279 -0
- apps/cli/commands/workspace_cmds.py +1414 -0
- apps/cli/config_paths.py +70 -0
- apps/cli/config_store.py +61 -0
- apps/cli/deterministic.py +122 -0
- apps/cli/direct.py +48 -0
- apps/cli/github_app_auth.py +135 -0
- apps/cli/handlers/__init__.py +11 -0
- apps/cli/handlers/broker_handlers.py +122 -0
- apps/cli/handlers/chart_handlers.py +1309 -0
- apps/cli/handlers/market_handlers.py +2509 -0
- apps/cli/handlers/realty_handlers.py +114 -0
- apps/cli/handlers/strategy_advice.py +82 -0
- apps/cli/hooks.py +180 -0
- apps/cli/i18n.py +284 -0
- apps/cli/intent.py +136 -0
- apps/cli/intent_router.py +217 -0
- apps/cli/lifecycle_hooks.py +48 -0
- apps/cli/main.py +29 -0
- apps/cli/market_metadata.py +135 -0
- apps/cli/market_universe.py +265 -0
- apps/cli/message_processing.py +257 -0
- apps/cli/plan_mode.py +139 -0
- apps/cli/plotly_html.py +15 -0
- apps/cli/prediction_feedback.py +202 -0
- apps/cli/preflight.py +497 -0
- apps/cli/project_aria.py +60 -0
- apps/cli/prompts/__init__.py +0 -0
- apps/cli/prompts/coding.py +658 -0
- apps/cli/prompts/system_prompts.py +531 -0
- apps/cli/prompts/ui.py +434 -0
- apps/cli/providers/__init__.py +1 -0
- apps/cli/providers/base.py +271 -0
- apps/cli/providers/chat_routing.py +80 -0
- apps/cli/providers/llm/__init__.py +1 -0
- apps/cli/providers/llm/ollama_stream.py +1170 -0
- apps/cli/providers/llm/sse_stream.py +216 -0
- apps/cli/providers/runtime_bridge.py +185 -0
- apps/cli/runtime_consumer.py +489 -0
- apps/cli/session_export.py +87 -0
- apps/cli/session_jsonl.py +207 -0
- apps/cli/session_store.py +112 -0
- apps/cli/todo_tracker.py +190 -0
- apps/cli/tools/__init__.py +40 -0
- apps/cli/tools/context.py +46 -0
- apps/cli/tools/file_tools.py +112 -0
- apps/cli/tools/market_tools.py +549 -0
- apps/cli/tools/notebook_tools.py +111 -0
- apps/cli/tools/system_tools.py +669 -0
- apps/cli/tools/write_tools.py +715 -0
- apps/cli/tradingview_bridge.py +434 -0
- apps/cli/update_check.py +152 -0
- apps/cli/utils/__init__.py +0 -0
- apps/cli/utils/market_detect.py +1578 -0
- apps/daemon/README.md +14 -0
- apps/vscode/README.md +115 -0
- apps/vscode/package.json +70 -0
- aria_cli.py +11636 -0
- aria_code-4.1.3.dist-info/METADATA +952 -0
- aria_code-4.1.3.dist-info/RECORD +284 -0
- aria_code-4.1.3.dist-info/WHEEL +5 -0
- aria_code-4.1.3.dist-info/entry_points.txt +2 -0
- aria_code-4.1.3.dist-info/licenses/LICENSE +121 -0
- aria_code-4.1.3.dist-info/top_level.txt +50 -0
- aria_daemon.py +1295 -0
- aria_feishu_bot.py +1359 -0
- aria_relay_client.py +182 -0
- aria_relay_server.py +405 -0
- aria_telegram_bot.py +202 -0
- ariarc.py +328 -0
- artifacts.py +491 -0
- backtest_report.py +472 -0
- brokers/__init__.py +72 -0
- brokers/base.py +207 -0
- brokers/capabilities.py +264 -0
- brokers/cn/__init__.py +10 -0
- brokers/cn/easytrader_broker.py +193 -0
- brokers/cn/futu_broker.py +194 -0
- brokers/cn/longbridge_broker.py +190 -0
- brokers/cn/tiger_broker.py +196 -0
- brokers/cn/xtquant_broker.py +175 -0
- brokers/config.py +364 -0
- brokers/intl/__init__.py +5 -0
- brokers/intl/alpaca_broker.py +183 -0
- brokers/intl/ibkr_broker.py +215 -0
- brokers/intl/webull_broker.py +156 -0
- brokers/paper_broker.py +259 -0
- brokers/planning.py +296 -0
- brokers/registry.py +181 -0
- brokers/trading.py +237 -0
- change_store.py +127 -0
- command_safety.py +19 -0
- computer_use_tools.py +504 -0
- dashboard_generator.py +578 -0
- data_analysis_tools.py +808 -0
- data_cleaner.py +483 -0
- data_service.py +481 -0
- datasources/__init__.py +23 -0
- datasources/base.py +166 -0
- datasources/router.py +221 -0
- datasources/sources/__init__.py +15 -0
- datasources/sources/akshare_source.py +269 -0
- datasources/sources/alpha_vantage_source.py +202 -0
- datasources/sources/edgar_source.py +218 -0
- datasources/sources/finnhub_source.py +197 -0
- datasources/sources/fred_source.py +219 -0
- datasources/sources/tushare_source.py +141 -0
- datasources/sources/web_scraper_source.py +278 -0
- datasources/sources/world_bank_source.py +205 -0
- datasources/sources/yfinance_source.py +152 -0
- demo_player.py +204 -0
- doctor.py +508 -0
- file_analysis_tools.py +734 -0
- finance_formulas.py +389 -0
- football_data_client.py +1670 -0
- intent_classifier.py +358 -0
- local_finance_tools.py +3221 -0
- local_llm_provider.py +552 -0
- macro_tools.py +368 -0
- market_data_client.py +1899 -0
- mcp_client.py +506 -0
- memory_manager.py +245 -0
- model_capability.py +416 -0
- notification_tools.py +248 -0
- packages/__init__.py +23 -0
- packages/aria_agents/__init__.py +5 -0
- packages/aria_agents/manifest.py +69 -0
- packages/aria_core/__init__.py +34 -0
- packages/aria_core/architecture.py +192 -0
- packages/aria_core/export.py +124 -0
- packages/aria_core/manifest.py +65 -0
- packages/aria_infra/__init__.py +15 -0
- packages/aria_infra/arthera.py +52 -0
- packages/aria_infra/doctor.py +246 -0
- packages/aria_infra/product.py +37 -0
- packages/aria_mcp/__init__.py +25 -0
- packages/aria_mcp/bridge.py +38 -0
- packages/aria_mcp/config.py +97 -0
- packages/aria_mcp/tools.py +61 -0
- packages/aria_sdk/__init__.py +19 -0
- packages/aria_sdk/client.py +396 -0
- packages/aria_sdk/providers.py +70 -0
- packages/aria_sdk/streaming.py +73 -0
- packages/aria_sdk/types.py +86 -0
- packages/aria_services/__init__.py +55 -0
- packages/aria_services/context.py +258 -0
- packages/aria_services/data.py +11 -0
- packages/aria_services/provider_health.py +189 -0
- packages/aria_services/registry.py +213 -0
- packages/aria_services/usage.py +138 -0
- packages/aria_skills/__init__.py +5 -0
- packages/aria_skills/registry.py +59 -0
- packages/aria_tools/__init__.py +5 -0
- packages/aria_tools/registry.py +128 -0
- packages/quant_engine/__init__.py +6 -0
- packages/quant_engine/sports/__init__.py +72 -0
- packages/quant_engine/sports/calibrator.py +353 -0
- packages/quant_engine/sports/dixon_coles.py +234 -0
- packages/quant_engine/sports/elo.py +299 -0
- packages/quant_engine/sports/form.py +188 -0
- packages/quant_engine/sports/h2h.py +195 -0
- packages/quant_engine/sports/ml_model.py +354 -0
- packages/quant_engine/sports/predictor.py +311 -0
- packages/quant_engine/sports/tracker.py +664 -0
- packages/quant_engine/stochastic/__init__.py +27 -0
- packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
- packages/quant_engine/stochastic/ito_calculus.py +477 -0
- packages/quant_engine/stochastic/kelly_criterion.py +181 -0
- packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
- packages/quant_engine/stochastic/options_pricing.py +573 -0
- packages/quant_engine/stochastic/stochastic_processes.py +90 -0
- plan_utils.py +194 -0
- plugin_loader.py +328 -0
- portfolio_ledger.py +262 -0
- privacy/__init__.py +5 -0
- privacy/feedback.py +123 -0
- project_tools.py +525 -0
- providers/__init__.py +30 -0
- providers/llm/__init__.py +19 -0
- providers/llm/anthropic.py +184 -0
- providers/llm/base.py +139 -0
- providers/llm/ollama.py +128 -0
- providers/llm/openai_compat.py +282 -0
- providers/llm/registry.py +358 -0
- realty_data_tools.py +659 -0
- report_generator.py +1314 -0
- runtime/__init__.py +103 -0
- runtime/agent_loop.py +1183 -0
- runtime/approval.py +51 -0
- runtime/events.py +102 -0
- runtime/gateway.py +128 -0
- runtime/lsp.py +346 -0
- runtime/subagent.py +258 -0
- runtime/tool_executor.py +104 -0
- runtime/tool_policy.py +106 -0
- safety/__init__.py +21 -0
- safety/permissions.py +275 -0
- setup_wizard.py +653 -0
- strategy_vault.py +420 -0
- ui/__init__.py +100 -0
- ui/banner.py +310 -0
- ui/completer.py +391 -0
- ui/console.py +271 -0
- ui/image_render.py +243 -0
- ui/input_box.py +376 -0
- ui/picker.py +195 -0
- ui/render/__init__.py +11 -0
- ui/render/finance.py +1480 -0
- ui/render/market.py +225 -0
- ui/render/output.py +681 -0
- ui/render/team.py +346 -0
- ui/robot.py +235 -0
- workspace/__init__.py +6 -0
- workspace/files.py +170 -0
- 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"
|