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
doctor.py
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
"""Reusable health checks for Aria Code installations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib.util
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import platform
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
import time
|
|
13
|
+
import tempfile
|
|
14
|
+
import urllib.request
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, Dict, Iterable, List, Optional
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class DoctorCheck:
|
|
22
|
+
name: str
|
|
23
|
+
status: str
|
|
24
|
+
detail: str = ""
|
|
25
|
+
suggestion: str = ""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class DoctorReport:
|
|
30
|
+
checks: List[DoctorCheck] = field(default_factory=list)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def passed(self) -> int:
|
|
34
|
+
return sum(1 for check in self.checks if check.status == "ok")
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def warnings(self) -> int:
|
|
38
|
+
return sum(1 for check in self.checks if check.status == "warn")
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def errors(self) -> int:
|
|
42
|
+
return sum(1 for check in self.checks if check.status == "err")
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def status(self) -> str:
|
|
46
|
+
if self.errors:
|
|
47
|
+
return "err"
|
|
48
|
+
if self.warnings:
|
|
49
|
+
return "warn"
|
|
50
|
+
return "ok"
|
|
51
|
+
|
|
52
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
53
|
+
return {
|
|
54
|
+
"status": self.status,
|
|
55
|
+
"passed": self.passed,
|
|
56
|
+
"warnings": self.warnings,
|
|
57
|
+
"errors": self.errors,
|
|
58
|
+
"checks": [check.__dict__ for check in self.checks],
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _check(name: str, status: str, detail: str = "", suggestion: str = "") -> DoctorCheck:
|
|
63
|
+
return DoctorCheck(name=name, status=status, detail=detail, suggestion=suggestion)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _format_age(seconds: float | int | None) -> str:
|
|
67
|
+
if not seconds or seconds <= 0:
|
|
68
|
+
return ""
|
|
69
|
+
seconds = int(seconds)
|
|
70
|
+
if seconds < 60:
|
|
71
|
+
return f"{seconds}s"
|
|
72
|
+
minutes, sec = divmod(seconds, 60)
|
|
73
|
+
if minutes < 60:
|
|
74
|
+
return f"{minutes}m{sec:02d}s"
|
|
75
|
+
hours, rem = divmod(minutes, 60)
|
|
76
|
+
return f"{hours}h{rem:02d}m"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _has_module(name: str) -> bool:
|
|
80
|
+
return importlib.util.find_spec(name) is not None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _is_writable(path: Path) -> tuple[bool, str]:
|
|
84
|
+
try:
|
|
85
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
with tempfile.NamedTemporaryFile(prefix=".aria-doctor-", dir=path, delete=True) as handle:
|
|
87
|
+
handle.write(b"ok")
|
|
88
|
+
return True, str(path)
|
|
89
|
+
except Exception as exc:
|
|
90
|
+
return False, str(exc)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _is_path_ready(path: Path, *, expect_file: bool = False) -> tuple[str, str]:
|
|
94
|
+
"""Read-only path readiness check for runtime/config/cache diagnostics."""
|
|
95
|
+
try:
|
|
96
|
+
path = path.expanduser()
|
|
97
|
+
if path.exists():
|
|
98
|
+
if expect_file and not path.is_file():
|
|
99
|
+
return "err", f"{path} exists but is not a file"
|
|
100
|
+
if not expect_file and not path.is_dir():
|
|
101
|
+
return "err", f"{path} exists but is not a directory"
|
|
102
|
+
writable_target = path.parent if expect_file else path
|
|
103
|
+
if os.access(writable_target, os.W_OK):
|
|
104
|
+
return "ok", str(path)
|
|
105
|
+
return "warn", f"{path} exists but may not be writable"
|
|
106
|
+
parent = path.parent
|
|
107
|
+
if parent.exists() and os.access(parent, os.W_OK):
|
|
108
|
+
return "warn", f"{path} not created yet; parent writable"
|
|
109
|
+
return "warn", f"{path} not found"
|
|
110
|
+
except Exception as exc:
|
|
111
|
+
return "err", str(exc)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _capture_cmd(cmd: list[str], timeout: float = 2.0) -> tuple[int, str]:
|
|
115
|
+
try:
|
|
116
|
+
result = subprocess.run(
|
|
117
|
+
cmd,
|
|
118
|
+
check=False,
|
|
119
|
+
stdout=subprocess.PIPE,
|
|
120
|
+
stderr=subprocess.PIPE,
|
|
121
|
+
text=True,
|
|
122
|
+
timeout=timeout,
|
|
123
|
+
)
|
|
124
|
+
return result.returncode, (result.stdout or result.stderr or "").strip()
|
|
125
|
+
except Exception as exc:
|
|
126
|
+
return 1, str(exc)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _expand_home(raw: str | None) -> str:
|
|
130
|
+
value = str(raw or "").strip()
|
|
131
|
+
if not value:
|
|
132
|
+
return ""
|
|
133
|
+
if value == "~":
|
|
134
|
+
return str(Path.home())
|
|
135
|
+
if value.startswith("~/") or value.startswith("~\\"):
|
|
136
|
+
return str(Path.home() / value[2:])
|
|
137
|
+
return value
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _npm_config_home() -> tuple[str, str]:
|
|
141
|
+
for key in ("npm_config_aria_code_home", "npm_config_aria_home", "npm_config_ariacode_home"):
|
|
142
|
+
value = _expand_home(os.getenv(key))
|
|
143
|
+
if value:
|
|
144
|
+
return str(Path(value).expanduser().resolve()), f"env:{key}"
|
|
145
|
+
npm = shutil.which("npm")
|
|
146
|
+
if not npm:
|
|
147
|
+
return "", ""
|
|
148
|
+
code, out = _capture_cmd([npm, "config", "get", "aria-code:home"], timeout=1.5)
|
|
149
|
+
value = _expand_home(out)
|
|
150
|
+
if code == 0 and value and value.lower() not in ("undefined", "null"):
|
|
151
|
+
return str(Path(value).expanduser().resolve()), "npm-config:aria-code:home"
|
|
152
|
+
return "", ""
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _platform_data_dir() -> Path:
|
|
156
|
+
system = platform.system().lower()
|
|
157
|
+
if system == "darwin":
|
|
158
|
+
return Path.home() / "Library" / "Application Support" / "Aria Code"
|
|
159
|
+
if system == "windows":
|
|
160
|
+
return Path(os.getenv("LOCALAPPDATA") or (Path.home() / "AppData" / "Local")) / "AriaCode"
|
|
161
|
+
return Path(os.getenv("XDG_DATA_HOME") or (Path.home() / ".local" / "share")) / "aria-code"
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _platform_config_dir() -> Path:
|
|
165
|
+
system = platform.system().lower()
|
|
166
|
+
if system == "darwin":
|
|
167
|
+
return Path.home() / "Library" / "Application Support" / "Aria Code"
|
|
168
|
+
if system == "windows":
|
|
169
|
+
return Path(os.getenv("APPDATA") or (Path.home() / "AppData" / "Roaming")) / "AriaCode"
|
|
170
|
+
return Path(os.getenv("XDG_CONFIG_HOME") or (Path.home() / ".config")) / "aria-code"
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _platform_cache_dir() -> Path:
|
|
174
|
+
system = platform.system().lower()
|
|
175
|
+
if system == "darwin":
|
|
176
|
+
return Path.home() / "Library" / "Caches" / "Aria Code"
|
|
177
|
+
if system == "windows":
|
|
178
|
+
return Path(os.getenv("LOCALAPPDATA") or (Path.home() / "AppData" / "Local")) / "AriaCode" / "Cache"
|
|
179
|
+
return Path(os.getenv("XDG_CACHE_HOME") or (Path.home() / ".cache")) / "aria-code"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _resolve_runtime_paths() -> dict[str, Any]:
|
|
183
|
+
legacy = Path.home() / ".aria-code"
|
|
184
|
+
source = ""
|
|
185
|
+
install_raw = os.getenv("ARIA_HOME") or os.getenv("ARIA_CODE_HOME") or ""
|
|
186
|
+
if install_raw:
|
|
187
|
+
install_dir = Path(_expand_home(install_raw)).expanduser().resolve()
|
|
188
|
+
source = "env:ARIA_HOME" if os.getenv("ARIA_HOME") else "env:ARIA_CODE_HOME"
|
|
189
|
+
else:
|
|
190
|
+
npm_home, npm_source = _npm_config_home()
|
|
191
|
+
if npm_home:
|
|
192
|
+
install_dir = Path(npm_home)
|
|
193
|
+
source = npm_source
|
|
194
|
+
elif legacy.exists():
|
|
195
|
+
install_dir = legacy
|
|
196
|
+
source = "legacy-existing"
|
|
197
|
+
else:
|
|
198
|
+
install_dir = _platform_data_dir()
|
|
199
|
+
source = "platform-default"
|
|
200
|
+
|
|
201
|
+
config_dir = Path(_expand_home(os.getenv("ARIA_CONFIG_DIR"))).expanduser().resolve() if os.getenv("ARIA_CONFIG_DIR") else _platform_config_dir()
|
|
202
|
+
cache_dir = Path(_expand_home(os.getenv("ARIA_CACHE_DIR"))).expanduser().resolve() if os.getenv("ARIA_CACHE_DIR") else _platform_cache_dir()
|
|
203
|
+
info_file = install_dir / ".npm-install-info.json"
|
|
204
|
+
config_info_file = config_dir / "install.json"
|
|
205
|
+
legacy_info_file = legacy / ".npm-install-info.json"
|
|
206
|
+
info_candidates = []
|
|
207
|
+
for candidate in (info_file, config_info_file, legacy_info_file):
|
|
208
|
+
if candidate not in info_candidates:
|
|
209
|
+
info_candidates.append(candidate)
|
|
210
|
+
return {
|
|
211
|
+
"install_dir": install_dir,
|
|
212
|
+
"install_dir_source": source,
|
|
213
|
+
"legacy_install_dir": legacy,
|
|
214
|
+
"venv_dir": install_dir / ".venv",
|
|
215
|
+
"venv_py": install_dir / ".venv" / ("Scripts/python.exe" if platform.system().lower() == "windows" else "bin/python"),
|
|
216
|
+
"aria_cli": install_dir / "aria_cli.py",
|
|
217
|
+
"config_dir": config_dir,
|
|
218
|
+
"cache_dir": cache_dir,
|
|
219
|
+
"info_file": info_file,
|
|
220
|
+
"config_info_file": config_info_file,
|
|
221
|
+
"legacy_info_file": legacy_info_file,
|
|
222
|
+
"info_candidates": info_candidates,
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def npm_runtime_checks(*, cwd: Optional[Path] = None) -> List[DoctorCheck]:
|
|
227
|
+
"""Return npm launcher/runtime path diagnostics."""
|
|
228
|
+
checks: List[DoctorCheck] = []
|
|
229
|
+
paths = _resolve_runtime_paths()
|
|
230
|
+
cwd = (cwd or Path.cwd()).expanduser().resolve()
|
|
231
|
+
source_cli = cwd / "aria_cli.py"
|
|
232
|
+
source_venv_py = cwd / ".venv" / ("Scripts/python.exe" if platform.system().lower() == "windows" else "bin/python")
|
|
233
|
+
using_source_checkout = source_cli.is_file() and not paths["aria_cli"].is_file()
|
|
234
|
+
|
|
235
|
+
node = shutil.which("node")
|
|
236
|
+
if node:
|
|
237
|
+
code, version = _capture_cmd([node, "--version"], timeout=1.5)
|
|
238
|
+
checks.append(_check("npm_runtime:node", "ok" if code == 0 else "warn", f"{node} {version}".strip()))
|
|
239
|
+
else:
|
|
240
|
+
checks.append(_check("npm_runtime:node", "warn", "node not found", "Install Node.js if you use the npm launcher."))
|
|
241
|
+
|
|
242
|
+
npm = shutil.which("npm")
|
|
243
|
+
if npm:
|
|
244
|
+
_code, version = _capture_cmd([npm, "--version"], timeout=1.5)
|
|
245
|
+
prefix_code, prefix = _capture_cmd([npm, "config", "get", "prefix"], timeout=1.5)
|
|
246
|
+
root_code, root = _capture_cmd([npm, "root", "-g"], timeout=1.5)
|
|
247
|
+
detail = f"{npm} v{version or '?'}"
|
|
248
|
+
if prefix_code == 0 and prefix:
|
|
249
|
+
detail += f"; prefix={prefix}"
|
|
250
|
+
if root_code == 0 and root:
|
|
251
|
+
detail += f"; root={root}"
|
|
252
|
+
checks.append(_check("npm_runtime:npm", "ok", detail))
|
|
253
|
+
else:
|
|
254
|
+
checks.append(_check("npm_runtime:npm", "warn", "npm not found", "Install Node.js/npm if you use npm install -g aria-code."))
|
|
255
|
+
|
|
256
|
+
install_status, install_detail = _is_path_ready(paths["install_dir"])
|
|
257
|
+
install_suggestion = ""
|
|
258
|
+
if using_source_checkout:
|
|
259
|
+
install_suggestion = f"Set ARIA_HOME={cwd} when launching through npm, or run npm install -g aria-code."
|
|
260
|
+
elif not paths["aria_cli"].exists():
|
|
261
|
+
install_suggestion = "Run npm install -g aria-code or set ARIA_HOME to the cloned repo."
|
|
262
|
+
checks.append(_check(
|
|
263
|
+
"npm_runtime:install_dir",
|
|
264
|
+
"warn" if using_source_checkout else ("ok" if paths["aria_cli"].exists() else install_status),
|
|
265
|
+
(
|
|
266
|
+
f"{install_detail}; source={paths['install_dir_source']}"
|
|
267
|
+
+ (f"; source_checkout={cwd}" if using_source_checkout else "")
|
|
268
|
+
),
|
|
269
|
+
install_suggestion,
|
|
270
|
+
))
|
|
271
|
+
|
|
272
|
+
actual_cli = paths["aria_cli"] if paths["aria_cli"].is_file() else (source_cli if source_cli.is_file() else paths["aria_cli"])
|
|
273
|
+
checks.append(_check(
|
|
274
|
+
"npm_runtime:aria_cli",
|
|
275
|
+
"ok" if actual_cli.is_file() else "err",
|
|
276
|
+
str(actual_cli),
|
|
277
|
+
"Repair with: node $(npm root -g)/aria-code/scripts/postinstall.js" if not actual_cli.is_file() else "",
|
|
278
|
+
))
|
|
279
|
+
|
|
280
|
+
actual_venv_py = paths["venv_py"] if paths["venv_py"].is_file() else (source_venv_py if source_venv_py.is_file() else paths["venv_py"])
|
|
281
|
+
checks.append(_check(
|
|
282
|
+
"npm_runtime:venv",
|
|
283
|
+
"ok" if actual_venv_py.is_file() else "warn",
|
|
284
|
+
str(actual_venv_py),
|
|
285
|
+
"Run npm repair/update-engine or reinstall dependencies." if not actual_venv_py.is_file() else "",
|
|
286
|
+
))
|
|
287
|
+
|
|
288
|
+
info_hit = next((p for p in paths["info_candidates"] if p.is_file()), None)
|
|
289
|
+
checks.append(_check(
|
|
290
|
+
"npm_runtime:install_info",
|
|
291
|
+
"ok" if info_hit else "warn",
|
|
292
|
+
str(info_hit) if info_hit else "none found; checked " + ", ".join(str(p) for p in paths["info_candidates"]),
|
|
293
|
+
"Run npm repair/update-engine to rewrite install metadata." if not info_hit else "",
|
|
294
|
+
))
|
|
295
|
+
|
|
296
|
+
config_status, config_detail = _is_path_ready(paths["config_dir"])
|
|
297
|
+
cache_status, cache_detail = _is_path_ready(paths["cache_dir"])
|
|
298
|
+
checks.append(_check("npm_runtime:config_dir", config_status, config_detail))
|
|
299
|
+
checks.append(_check("npm_runtime:cache_dir", cache_status, cache_detail))
|
|
300
|
+
return checks
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _check_ollama(url: str, timeout: float = 1.5) -> DoctorCheck:
|
|
304
|
+
try:
|
|
305
|
+
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
|
306
|
+
with opener.open(f"{url.rstrip('/')}/api/tags", timeout=timeout) as response:
|
|
307
|
+
data = json.loads(response.read().decode("utf-8"))
|
|
308
|
+
models = [str(model.get("name", "")) for model in data.get("models", []) if model.get("name")]
|
|
309
|
+
if models:
|
|
310
|
+
return _check("ollama", "ok", f"{len(models)} models: {', '.join(models[:4])}")
|
|
311
|
+
return _check("ollama", "warn", "running but no models installed", "ollama pull qwen2.5-coder:7b")
|
|
312
|
+
except Exception as exc:
|
|
313
|
+
return _check("ollama", "warn", f"not reachable at {url}: {exc}", "Start Ollama or configure a cloud provider.")
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def provider_health_checks(snapshot: Optional[List[Dict[str, Any]]] = None) -> List[DoctorCheck]:
|
|
317
|
+
"""Convert data provider health state into doctor checks."""
|
|
318
|
+
if snapshot is None:
|
|
319
|
+
try:
|
|
320
|
+
from packages.aria_services.provider_health import GLOBAL_PROVIDER_HEALTH
|
|
321
|
+
|
|
322
|
+
snapshot = GLOBAL_PROVIDER_HEALTH.snapshot()
|
|
323
|
+
except Exception:
|
|
324
|
+
snapshot = []
|
|
325
|
+
|
|
326
|
+
if not snapshot:
|
|
327
|
+
return [
|
|
328
|
+
_check(
|
|
329
|
+
"data_provider_health",
|
|
330
|
+
"warn",
|
|
331
|
+
"no provider calls recorded in this session",
|
|
332
|
+
"Run /quote, /ta, /analyze, or /report to populate provider health.",
|
|
333
|
+
)
|
|
334
|
+
]
|
|
335
|
+
|
|
336
|
+
checks: List[DoctorCheck] = []
|
|
337
|
+
for row in snapshot:
|
|
338
|
+
provider = str(row.get("provider") or "provider")
|
|
339
|
+
status = str(row.get("status") or "unknown")
|
|
340
|
+
failures = int(row.get("failures") or 0)
|
|
341
|
+
cooldown = bool(row.get("cooldown_active"))
|
|
342
|
+
remaining = int(row.get("cooldown_remaining_seconds") or 0)
|
|
343
|
+
detail = status
|
|
344
|
+
if failures:
|
|
345
|
+
detail += f", failures={failures}"
|
|
346
|
+
if cooldown:
|
|
347
|
+
detail += f", cooldown={remaining}s"
|
|
348
|
+
if row.get("last_error"):
|
|
349
|
+
detail += f", last={row.get('last_error')}"
|
|
350
|
+
last_success = _format_age((time.time() - float(row.get("last_success_at"))) if row.get("last_success_at") else None)
|
|
351
|
+
if last_success:
|
|
352
|
+
detail += f", last_ok={last_success}"
|
|
353
|
+
check_status = "ok" if status == "ok" else "warn" if row.get("last_error_category") != "auth" else "err"
|
|
354
|
+
suggestion = "Wait for cooldown or switch provider/API key." if cooldown else ""
|
|
355
|
+
if row.get("last_error_category") == "auth":
|
|
356
|
+
suggestion = "Check provider API key with /apikey list or /apikey set."
|
|
357
|
+
checks.append(_check(f"data_provider:{provider}", check_status, detail, suggestion))
|
|
358
|
+
return checks
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def provider_health_summary(snapshot: Optional[List[Dict[str, Any]]] = None) -> DoctorCheck:
|
|
362
|
+
"""Summarise provider health into one dashboard-style check."""
|
|
363
|
+
if snapshot is None:
|
|
364
|
+
try:
|
|
365
|
+
from packages.aria_services.provider_health import GLOBAL_PROVIDER_HEALTH
|
|
366
|
+
|
|
367
|
+
snapshot = GLOBAL_PROVIDER_HEALTH.snapshot()
|
|
368
|
+
except Exception:
|
|
369
|
+
snapshot = []
|
|
370
|
+
try:
|
|
371
|
+
from packages.aria_services.provider_health import summarize_provider_health
|
|
372
|
+
|
|
373
|
+
summary = summarize_provider_health(snapshot)
|
|
374
|
+
return _check("provider_health_summary", summary.status, summary.detail, summary.suggestion)
|
|
375
|
+
except Exception:
|
|
376
|
+
if not snapshot:
|
|
377
|
+
return _check(
|
|
378
|
+
"provider_health_summary",
|
|
379
|
+
"warn",
|
|
380
|
+
"no provider calls recorded in this session",
|
|
381
|
+
"Run /quote, /ta, /analyze, or /report to populate provider health.",
|
|
382
|
+
)
|
|
383
|
+
total = len(snapshot)
|
|
384
|
+
return _check(
|
|
385
|
+
"provider_health_summary",
|
|
386
|
+
"warn",
|
|
387
|
+
f"{total} providers",
|
|
388
|
+
"Run /doctor --network or inspect /apikey.",
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _iter_required_modules() -> Iterable[tuple[str, str]]:
|
|
393
|
+
yield "aiohttp", "async HTTP"
|
|
394
|
+
yield "rich", "terminal UI"
|
|
395
|
+
yield "prompt_toolkit", "interactive input"
|
|
396
|
+
yield "requests", "HTTP client"
|
|
397
|
+
yield "pandas", "dataframes"
|
|
398
|
+
yield "numpy", "numeric processing"
|
|
399
|
+
yield "yfinance", "US/HK/global market data"
|
|
400
|
+
yield "akshare", "China market data"
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def run_doctor(
|
|
404
|
+
config: Optional[Dict[str, Any]] = None,
|
|
405
|
+
*,
|
|
406
|
+
cwd: Optional[Path] = None,
|
|
407
|
+
check_network: bool = False,
|
|
408
|
+
) -> DoctorReport:
|
|
409
|
+
"""Run local-first diagnostics without mutating user configuration."""
|
|
410
|
+
|
|
411
|
+
config = config or {}
|
|
412
|
+
cwd = (cwd or Path.cwd()).expanduser().resolve()
|
|
413
|
+
checks: List[DoctorCheck] = []
|
|
414
|
+
|
|
415
|
+
pyver = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
416
|
+
if sys.version_info >= (3, 10):
|
|
417
|
+
checks.append(_check("python", "ok", f"{pyver} on {platform.system()}"))
|
|
418
|
+
else:
|
|
419
|
+
checks.append(_check("python", "err", f"{pyver} is unsupported", "Install Python 3.10 or newer."))
|
|
420
|
+
|
|
421
|
+
for module, purpose in _iter_required_modules():
|
|
422
|
+
if _has_module(module):
|
|
423
|
+
checks.append(_check(f"package:{module}", "ok", purpose))
|
|
424
|
+
else:
|
|
425
|
+
checks.append(_check(f"package:{module}", "err", f"{purpose} missing", f"pip install {module}"))
|
|
426
|
+
|
|
427
|
+
for module in ("data_service", "artifacts", "report_generator", "backtest_report"):
|
|
428
|
+
checks.append(
|
|
429
|
+
_check(f"module:{module}", "ok" if _has_module(module) else "err", "importable" if _has_module(module) else "missing from install")
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
checks.extend(npm_runtime_checks(cwd=cwd))
|
|
433
|
+
|
|
434
|
+
try:
|
|
435
|
+
from artifacts import artifact_root, artifact_summary
|
|
436
|
+
|
|
437
|
+
root = artifact_root()
|
|
438
|
+
writable, detail = _is_writable(root)
|
|
439
|
+
checks.append(_check("artifact_root", "ok" if writable else "err", detail, "Set ARIA_ARTIFACT_ROOT to a writable folder."))
|
|
440
|
+
summary = artifact_summary(root)
|
|
441
|
+
total = int(summary.get("total") or 0)
|
|
442
|
+
total_size = int(summary.get("total_size_bytes") or 0)
|
|
443
|
+
by_kind = summary.get("by_kind") or {}
|
|
444
|
+
detail_bits = [f"{total} artifacts", f"{total_size} bytes"]
|
|
445
|
+
if by_kind:
|
|
446
|
+
detail_bits.extend(f"{kind}={count}" for kind, count in list(by_kind.items())[:4])
|
|
447
|
+
checks.append(
|
|
448
|
+
_check(
|
|
449
|
+
"artifact_inventory",
|
|
450
|
+
"ok" if total else "warn",
|
|
451
|
+
", ".join(detail_bits),
|
|
452
|
+
"Run /artifacts stats or generate a report to populate local outputs.",
|
|
453
|
+
)
|
|
454
|
+
)
|
|
455
|
+
except Exception as exc:
|
|
456
|
+
checks.append(_check("artifact_root", "err", str(exc)))
|
|
457
|
+
|
|
458
|
+
config_dir = Path.home() / ".arthera"
|
|
459
|
+
config_file = config_dir / "config.json"
|
|
460
|
+
if config_file.exists():
|
|
461
|
+
checks.append(_check("config", "ok", str(config_file)))
|
|
462
|
+
else:
|
|
463
|
+
checks.append(_check("config", "warn", f"{config_file} not found", "Run aria-code once or use /config set key=value."))
|
|
464
|
+
|
|
465
|
+
data_sharing = bool(config.get("data_sharing", False))
|
|
466
|
+
feedback_upload = bool(config.get("feedback_upload", False))
|
|
467
|
+
privacy_detail = f"data_sharing={data_sharing}, feedback_upload={feedback_upload}"
|
|
468
|
+
checks.append(_check("privacy", "ok", privacy_detail))
|
|
469
|
+
|
|
470
|
+
try:
|
|
471
|
+
from datasources.router import DataRouter
|
|
472
|
+
|
|
473
|
+
sources = DataRouter().list_sources()
|
|
474
|
+
configured = [src["name"] for src in sources if src.get("configured")]
|
|
475
|
+
missing = [src["name"] for src in sources if src.get("needs_key") and not src.get("configured")]
|
|
476
|
+
status = "ok" if configured else "warn"
|
|
477
|
+
detail = f"configured: {', '.join(configured) or 'none'}"
|
|
478
|
+
suggestion = f"optional keys missing: {', '.join(missing)}" if missing else ""
|
|
479
|
+
checks.append(_check("datasources", status, detail, suggestion))
|
|
480
|
+
except Exception as exc:
|
|
481
|
+
checks.append(_check("datasources", "warn", str(exc)))
|
|
482
|
+
|
|
483
|
+
checks.append(provider_health_summary())
|
|
484
|
+
checks.extend(provider_health_checks())
|
|
485
|
+
|
|
486
|
+
if check_network:
|
|
487
|
+
checks.append(_check_ollama(str(config.get("ollama_url") or "http://localhost:11434")))
|
|
488
|
+
else:
|
|
489
|
+
checks.append(_check("ollama", "warn", "network check skipped", "Run /doctor --network to verify local Ollama."))
|
|
490
|
+
|
|
491
|
+
if (cwd / ".ariarc").exists():
|
|
492
|
+
checks.append(_check("project_config", "ok", str(cwd / ".ariarc")))
|
|
493
|
+
else:
|
|
494
|
+
checks.append(_check("project_config", "warn", ".ariarc not found", "Optional: add .ariarc for project-local settings."))
|
|
495
|
+
|
|
496
|
+
return DoctorReport(checks=checks)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def format_doctor_plain(report: DoctorReport) -> str:
|
|
500
|
+
marks = {"ok": "OK", "warn": "WARN", "err": "ERR"}
|
|
501
|
+
lines = ["Aria Code doctor"]
|
|
502
|
+
for check in report.checks:
|
|
503
|
+
suffix = f" — {check.detail}" if check.detail else ""
|
|
504
|
+
if check.suggestion:
|
|
505
|
+
suffix += f" ({check.suggestion})"
|
|
506
|
+
lines.append(f"{marks.get(check.status, check.status.upper()):<4} {check.name}{suffix}")
|
|
507
|
+
lines.append(f"{report.passed} passed · {report.warnings} warnings · {report.errors} errors")
|
|
508
|
+
return "\n".join(lines)
|