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,70 @@
1
+ """Shared config path resolution for Aria CLI components."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class AriaConfigPaths:
12
+ config_dir: Path
13
+ config_file: Path
14
+ history_file: Path
15
+ sessions_dir: Path
16
+ providers_file: Path
17
+ hooks_file: Path
18
+ user_output_root: Path
19
+ user_generated_dir: Path
20
+ user_projects_dir: Path
21
+
22
+
23
+ def resolve_config_dir() -> Path:
24
+ """Resolve the user config directory with stable precedence."""
25
+ if "ARIA_HOME" in os.environ:
26
+ return Path(os.environ["ARIA_HOME"]).expanduser()
27
+ legacy = Path.home() / ".arthera"
28
+ if legacy.exists():
29
+ return legacy
30
+ return Path.home() / ".aria-code"
31
+
32
+
33
+ def resolve_user_output_root() -> Path:
34
+ """Resolve the per-user output root for generated code and user artifacts."""
35
+ configured = os.getenv("ARIA_USER_OUTPUT_ROOT")
36
+ if configured:
37
+ return Path(configured).expanduser()
38
+ return Path.home() / "Documents" / "Aria Code"
39
+
40
+
41
+ def resolve_paths(config_dir: Optional[Path] = None) -> AriaConfigPaths:
42
+ root = Path(config_dir).expanduser() if config_dir else resolve_config_dir()
43
+ user_output_root = resolve_user_output_root()
44
+ return AriaConfigPaths(
45
+ config_dir=root,
46
+ config_file=root / "config.json",
47
+ history_file=root / "history",
48
+ sessions_dir=root / "sessions",
49
+ providers_file=root / "providers.json",
50
+ hooks_file=root / "hooks.json",
51
+ user_output_root=user_output_root,
52
+ user_generated_dir=user_output_root / "generated",
53
+ user_projects_dir=user_output_root / "projects",
54
+ )
55
+
56
+
57
+ def config_snapshot(config_dir: Optional[Path] = None) -> dict[str, str]:
58
+ """Return a JSON-serializable snapshot of resolved config paths."""
59
+ paths = resolve_paths(config_dir)
60
+ return {
61
+ "config_dir": str(paths.config_dir),
62
+ "config_file": str(paths.config_file),
63
+ "history_file": str(paths.history_file),
64
+ "sessions_dir": str(paths.sessions_dir),
65
+ "providers_file": str(paths.providers_file),
66
+ "hooks_file": str(paths.hooks_file),
67
+ "user_output_root": str(paths.user_output_root),
68
+ "user_generated_dir": str(paths.user_generated_dir),
69
+ "user_projects_dir": str(paths.user_projects_dir),
70
+ }
@@ -0,0 +1,61 @@
1
+ """Persistent CLI configuration loading and saving."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections.abc import Callable
7
+
8
+ from apps.cli.config_paths import AriaConfigPaths
9
+
10
+
11
+ STALE_ARIA_MODEL_PREFIXES = ("aria-opus", "aria-prelude", "aria-sonata:3", "aria-sonata:4")
12
+
13
+
14
+ def load_cli_config(
15
+ paths: AriaConfigPaths,
16
+ defaults: dict,
17
+ *,
18
+ sync_policy: Callable[[dict], None] | None = None,
19
+ ) -> dict:
20
+ """Load config.json and merge with defaults."""
21
+ paths.config_dir.mkdir(parents=True, exist_ok=True)
22
+ paths.sessions_dir.mkdir(parents=True, exist_ok=True)
23
+ if paths.config_file.exists():
24
+ try:
25
+ saved = json.loads(paths.config_file.read_text(encoding="utf-8"))
26
+ merged = {**defaults, **saved}
27
+ saved_model = merged.get("model", "")
28
+ if any(saved_model.startswith(prefix) for prefix in STALE_ARIA_MODEL_PREFIXES):
29
+ merged["model"] = defaults["model"]
30
+ if not merged.get("ui_lang"):
31
+ try:
32
+ from apps.cli.i18n import detect_system_lang
33
+
34
+ merged["ui_lang"] = detect_system_lang()
35
+ except Exception:
36
+ merged["ui_lang"] = "en"
37
+ if sync_policy:
38
+ sync_policy(merged)
39
+ return merged
40
+ except Exception:
41
+ pass
42
+
43
+ cfg = dict(defaults)
44
+ try:
45
+ from apps.cli.i18n import auto_select_model, detect_system_lang
46
+
47
+ cfg["ui_lang"] = detect_system_lang()
48
+ ollama_url = cfg.get("ollama_url", "http://localhost:11434")
49
+ cfg["model"] = auto_select_model(ollama_url, fallback=defaults["model"])
50
+ except Exception:
51
+ cfg["ui_lang"] = "en"
52
+ if sync_policy:
53
+ sync_policy(cfg)
54
+ return cfg
55
+
56
+
57
+ def save_cli_config(paths: AriaConfigPaths, cfg: dict) -> None:
58
+ paths.config_dir.mkdir(parents=True, exist_ok=True)
59
+ exclude = {"conversation_history"}
60
+ payload = {key: value for key, value in cfg.items() if key not in exclude}
61
+ paths.config_file.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
@@ -0,0 +1,122 @@
1
+ """Shared deterministic routing for Aria agent entrypoints.
2
+
3
+ This module is intentionally UI-free. The legacy CLI, future daemon/webhook
4
+ entrypoints, and the public SDK can all use the same routing order without
5
+ importing the terminal implementation from ``aria_cli.py``.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+ from typing import Any, Callable
12
+
13
+ from apps.cli.handlers.broker_handlers import handle_broker_query
14
+ from apps.cli.handlers.chart_handlers import handle_stock_chart_analysis
15
+ from apps.cli.handlers.market_handlers import (
16
+ _try_handle_market_overview,
17
+ _try_handle_market_snapshot_analysis,
18
+ )
19
+ from apps.cli.handlers.realty_handlers import handle_realty_query
20
+ from apps.cli.handlers.strategy_advice import handle_strategy_advice
21
+ from apps.cli.utils.market_detect import (
22
+ _CN_CITIES,
23
+ _INTL_CITIES,
24
+ _extract_market_symbol,
25
+ _is_broker_intent,
26
+ _is_realty_query,
27
+ _is_stock_chart_analysis_request,
28
+ )
29
+
30
+
31
+ BrokerRegistryFactory = Callable[[], Any]
32
+
33
+
34
+ def _missing_broker_registry() -> None:
35
+ return None
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class DeterministicRouterConfig:
40
+ """Configuration for deterministic routing outside the model loop."""
41
+
42
+ model_has_tools: bool = True
43
+ has_brokers: bool = False
44
+ get_broker_registry: BrokerRegistryFactory | None = None
45
+
46
+
47
+ def _handle_broker_query(message: str, config: DeterministicRouterConfig) -> dict:
48
+ return handle_broker_query(
49
+ message,
50
+ has_brokers=config.has_brokers,
51
+ is_broker_intent=_is_broker_intent,
52
+ get_broker_registry=config.get_broker_registry or _missing_broker_registry,
53
+ )
54
+
55
+
56
+ def _handle_realty_query(message: str) -> dict:
57
+ return handle_realty_query(
58
+ message,
59
+ is_realty_query=_is_realty_query,
60
+ cn_cities=_CN_CITIES,
61
+ intl_cities=_INTL_CITIES,
62
+ )
63
+
64
+
65
+ def _handle_stock_chart_analysis(message: str) -> dict:
66
+ return handle_stock_chart_analysis(
67
+ message,
68
+ is_chart_request=_is_stock_chart_analysis_request,
69
+ extract_symbol=_extract_market_symbol,
70
+ )
71
+
72
+
73
+ def run_deterministic_chain(
74
+ message: str,
75
+ *,
76
+ model_has_tools: bool,
77
+ history: list | None = None,
78
+ has_brokers: bool = False,
79
+ get_broker_registry: BrokerRegistryFactory | None = None,
80
+ ) -> dict:
81
+ """Run the deterministic routing chain used before LLM fallback.
82
+
83
+ Order matters:
84
+ - broker account reads are only used when the model cannot call tools;
85
+ - realty must run before market parsing, so housing questions do not inherit
86
+ a ticker;
87
+ - chart requests run before snapshots;
88
+ - whole-market overview runs before single-symbol snapshot parsing, so
89
+ "分析A股" is treated as the A-share market instead of ticker ``A``.
90
+ """
91
+
92
+ config = DeterministicRouterConfig(
93
+ model_has_tools=model_has_tools,
94
+ has_brokers=has_brokers,
95
+ get_broker_registry=get_broker_registry,
96
+ )
97
+
98
+ deterministic: dict = {"success": False}
99
+ if not config.model_has_tools:
100
+ deterministic = _handle_broker_query(message, config)
101
+
102
+ for handler in (
103
+ handle_strategy_advice,
104
+ _handle_realty_query,
105
+ _handle_stock_chart_analysis,
106
+ _try_handle_market_overview,
107
+ ):
108
+ if deterministic.get("success"):
109
+ break
110
+ deterministic = handler(message)
111
+
112
+ if not deterministic.get("success"):
113
+ deterministic = _try_handle_market_snapshot_analysis(message, history=history)
114
+
115
+ return deterministic
116
+
117
+
118
+ __all__ = [
119
+ "BrokerRegistryFactory",
120
+ "DeterministicRouterConfig",
121
+ "run_deterministic_chain",
122
+ ]
apps/cli/direct.py ADDED
@@ -0,0 +1,48 @@
1
+ """Direct command dispatcher for non-interactive CLI entrypoints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from typing import Any
7
+
8
+ from apps.cli.commands.catalog import DIRECT_COMMAND_MAP, WATCHABLE_DIRECT_COMMANDS
9
+
10
+
11
+ def is_watchable_direct_command(command: str) -> bool:
12
+ return command.strip().lower() in WATCHABLE_DIRECT_COMMANDS
13
+
14
+
15
+ async def dispatch_direct_command(
16
+ terminal: Any,
17
+ command: str,
18
+ command_args: str = "",
19
+ *,
20
+ json_output: bool = False,
21
+ fmt: str = "table",
22
+ output_file: str | None = None,
23
+ quiet: bool = False,
24
+ ) -> bool:
25
+ """Dispatch a direct CLI command.
26
+
27
+ Returns True when the command was handled by a known direct command handler.
28
+ Unknown direct commands fall back to the normal prompt path and return False.
29
+ """
30
+
31
+ cmd = command.strip().lower()
32
+ spec = DIRECT_COMMAND_MAP.get(cmd)
33
+ if spec is None:
34
+ await terminal.run_prompt(
35
+ f"{command} {command_args}".strip(),
36
+ json_output=json_output,
37
+ fmt=fmt,
38
+ output_file=output_file,
39
+ quiet=quiet,
40
+ )
41
+ return False
42
+
43
+ handler = getattr(terminal.commands, spec.method_name)
44
+ result = handler(command_args)
45
+ if inspect.isawaitable(result):
46
+ await result
47
+ return True
48
+
@@ -0,0 +1,135 @@
1
+ """GitHub App authentication — generates installation tokens for Aria Code[bot].
2
+
3
+ Usage:
4
+ from apps.cli.github_app_auth import get_installation_token, get_aria_git_url
5
+
6
+ token = get_installation_token() # raises if not configured
7
+ url = get_aria_git_url("artherahq/aria-code", token)
8
+ # git push with url as remote
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import base64
14
+ import json
15
+ import os
16
+ import time
17
+ import urllib.request
18
+ import urllib.error
19
+ from pathlib import Path
20
+
21
+ # ── Configuration ──────────────────────────────────────────────────────────────
22
+ ARIA_APP_ID = int(os.getenv("ARIA_GITHUB_APP_ID", "4114189"))
23
+ ARIA_APP_SLUG = os.getenv("ARIA_GITHUB_APP_SLUG", "aria-code")
24
+
25
+ # Search order for private key
26
+ _PEM_SEARCH = [
27
+ os.getenv("ARIA_GITHUB_APP_PEM", ""),
28
+ os.path.expanduser("~/.config/aria-code/github-app.pem"),
29
+ os.path.expanduser("~/.aria-code/github-app.pem"),
30
+ "github-app.pem",
31
+ ]
32
+
33
+ # noreply email GitHub uses for bot commits
34
+ # Format: {app_id}+{app_slug}[bot]@users.noreply.github.com
35
+ ARIA_BOT_EMAIL = f"{ARIA_APP_ID}+{ARIA_APP_SLUG}[bot]@users.noreply.github.com"
36
+ ARIA_BOT_NAME = "Aria Code"
37
+
38
+ # GitHub user account for Aria — shows up in Contributors
39
+ ARIA_GITHUB_LOGIN = os.getenv("ARIA_GITHUB_LOGIN", "ariaaii")
40
+ ARIA_GITHUB_EMAIL = os.getenv("ARIA_GITHUB_EMAIL", "support@arthera.finance")
41
+
42
+
43
+ def _find_pem() -> str:
44
+ for path in _PEM_SEARCH:
45
+ if path and os.path.isfile(path):
46
+ return path
47
+ raise FileNotFoundError(
48
+ "GitHub App private key not found. "
49
+ "Set ARIA_GITHUB_APP_PEM=/path/to/key.pem or place it at "
50
+ "~/.config/aria-code/github-app.pem"
51
+ )
52
+
53
+
54
+ def _b64url(data: bytes) -> str:
55
+ return base64.urlsafe_b64encode(data).rstrip(b"=").decode()
56
+
57
+
58
+ def _make_jwt(pem_path: str, app_id: int) -> str:
59
+ """Create a signed JWT using RSA-SHA256 (pure stdlib + cryptography)."""
60
+ try:
61
+ from cryptography.hazmat.primitives import hashes, serialization
62
+ from cryptography.hazmat.primitives.asymmetric import padding
63
+ from cryptography.hazmat.backends import default_backend
64
+ except ImportError:
65
+ raise ImportError(
66
+ "Install cryptography: pip install cryptography"
67
+ )
68
+
69
+ with open(pem_path, "rb") as f:
70
+ private_key = serialization.load_pem_private_key(f.read(), password=None, backend=default_backend())
71
+
72
+ now = int(time.time())
73
+ header = _b64url(json.dumps({"alg": "RS256", "typ": "JWT"}).encode())
74
+ payload = _b64url(json.dumps({"iat": now - 60, "exp": now + 540, "iss": str(app_id)}).encode())
75
+ signing_input = f"{header}.{payload}".encode()
76
+ signature = _b64url(private_key.sign(signing_input, padding.PKCS1v15(), hashes.SHA256()))
77
+ return f"{header}.{payload}.{signature}"
78
+
79
+
80
+ def _gh_api(path: str, token: str, *, method: str = "GET") -> dict:
81
+ url = f"https://api.github.com{path}"
82
+ req = urllib.request.Request(
83
+ url,
84
+ method=method,
85
+ headers={
86
+ "Authorization": f"Bearer {token}",
87
+ "Accept": "application/vnd.github+json",
88
+ "X-GitHub-Api-Version": "2022-11-28",
89
+ },
90
+ )
91
+ with urllib.request.urlopen(req, timeout=15) as resp:
92
+ return json.loads(resp.read())
93
+
94
+
95
+ def get_installation_token(owner: str = "artherahq") -> str:
96
+ """Return a short-lived installation token (valid ~1 hour)."""
97
+ pem = _find_pem()
98
+ jwt = _make_jwt(pem, ARIA_APP_ID)
99
+
100
+ # Find installation for owner
101
+ installations = _gh_api("/app/installations", jwt)
102
+ installation_id = None
103
+ for inst in installations:
104
+ if inst.get("account", {}).get("login", "").lower() == owner.lower():
105
+ installation_id = inst["id"]
106
+ break
107
+ if installation_id is None:
108
+ accts = [i.get("account", {}).get("login") for i in installations]
109
+ raise RuntimeError(
110
+ f"Aria Code GitHub App not installed for '{owner}'. "
111
+ f"Installed accounts: {accts}. "
112
+ f"Go to https://github.com/apps/aria-code and install it."
113
+ )
114
+
115
+ # Exchange for installation token
116
+ result = _gh_api(f"/app/installations/{installation_id}/access_tokens", jwt, method="POST")
117
+ token = result.get("token")
118
+ if not token:
119
+ raise RuntimeError(f"Failed to get installation token: {result}")
120
+ return token
121
+
122
+
123
+ def get_aria_git_url(repo: str, token: str) -> str:
124
+ """Return an authenticated HTTPS remote URL using the installation token."""
125
+ return f"https://x-access-token:{token}@github.com/{repo}.git"
126
+
127
+
128
+ def aria_bot_env() -> dict[str, str]:
129
+ """Env vars that make git commit as Aria Code[bot]."""
130
+ return {
131
+ "GIT_AUTHOR_NAME": ARIA_BOT_NAME,
132
+ "GIT_AUTHOR_EMAIL": ARIA_BOT_EMAIL,
133
+ "GIT_COMMITTER_NAME": ARIA_BOT_NAME,
134
+ "GIT_COMMITTER_EMAIL": ARIA_BOT_EMAIL,
135
+ }
@@ -0,0 +1,11 @@
1
+ """apps/cli/handlers — deterministic pre-LLM response handlers extracted from aria_cli.py."""
2
+ from .broker_handlers import handle_broker_query
3
+ from .realty_handlers import handle_realty_query
4
+ from .chart_handlers import handle_stock_chart_analysis_direct, handle_stock_chart_analysis
5
+
6
+ __all__ = [
7
+ "handle_broker_query",
8
+ "handle_realty_query",
9
+ "handle_stock_chart_analysis_direct",
10
+ "handle_stock_chart_analysis",
11
+ ]
@@ -0,0 +1,122 @@
1
+ """Deterministic broker data handler extracted from aria_cli.py."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Callable
5
+
6
+
7
+ def handle_broker_query(
8
+ message: str,
9
+ *,
10
+ has_brokers: bool,
11
+ is_broker_intent: Callable[[str], bool],
12
+ get_broker_registry: Callable,
13
+ ) -> dict:
14
+ """Deterministic broker data path for models without reliable tool_calls.
15
+
16
+ When the user asks about their portfolio/account and no tool-call model is
17
+ available, fetch the data directly and return a pre-formatted markdown
18
+ response — same data the LLM tool would have provided.
19
+ """
20
+ if not has_brokers:
21
+ return {"success": False, "error": "brokers_not_available"}
22
+ if not is_broker_intent(message):
23
+ return {"success": False, "error": "not_broker_query"}
24
+
25
+ try:
26
+ _reg = get_broker_registry()
27
+ broker = _reg.active()
28
+ if not broker or not broker.is_connected:
29
+ return {
30
+ "success": True,
31
+ "response": (
32
+ "## 账户未连接\n\n"
33
+ "当前没有已连接的券商账户。\n\n"
34
+ "请运行 `/broker connect <id>` 或 `/broker list` 查看可用账户。"
35
+ ),
36
+ "tools_used": ["broker_query"],
37
+ }
38
+
39
+ _msg_lower = message.lower()
40
+ lines = [f"## {broker.label} 账户信息\n"]
41
+
42
+ _want_positions = any(k in _msg_lower for k in (
43
+ "持仓", "position", "portfolio", "仓位", "我的股票", "持有",
44
+ ))
45
+ _want_orders = any(k in _msg_lower for k in (
46
+ "订单", "order", "委托", "成交", "交易记录",
47
+ ))
48
+ _want_account = any(k in _msg_lower for k in (
49
+ "余额", "balance", "账户", "account", "资金", "cash", "净值",
50
+ ))
51
+ if not (_want_positions or _want_orders or _want_account):
52
+ _want_account = True
53
+ _want_positions = True
54
+
55
+ if _want_account:
56
+ try:
57
+ acct = broker.account_info()
58
+ lines.append("### 资金概况")
59
+ lines.append(f"- **总资产**: {acct.currency} {acct.total_assets:,.2f}")
60
+ lines.append(f"- **可用资金**: {acct.currency} {acct.available_cash:,.2f}")
61
+ if acct.market_value is not None:
62
+ lines.append(f"- **持仓市值**: {acct.currency} {acct.market_value:,.2f}")
63
+ if acct.unrealized_pnl is not None:
64
+ _pnl_sign = "+" if acct.unrealized_pnl >= 0 else ""
65
+ lines.append(f"- **浮动盈亏**: {_pnl_sign}{acct.unrealized_pnl:,.2f}")
66
+ lines.append("")
67
+ except Exception as _ae:
68
+ lines.append(f"*获取账户信息失败: {_ae}*\n")
69
+
70
+ if _want_positions:
71
+ try:
72
+ positions = broker.positions()
73
+ if positions:
74
+ lines.append("### 当前持仓")
75
+ lines.append("| 代码 | 名称 | 数量 | 成本 | 现价 | 盈亏% |")
76
+ lines.append("|------|------|------|------|------|-------|")
77
+ for p in positions[:15]:
78
+ _pct = (
79
+ f"{p.unrealized_pnl_pct:+.2f}%"
80
+ if p.unrealized_pnl_pct is not None else "N/A"
81
+ )
82
+ _cost = f"{p.avg_cost:.2f}" if p.avg_cost is not None else "N/A"
83
+ _price = f"{p.current_price:.2f}" if p.current_price is not None else "N/A"
84
+ lines.append(
85
+ f"| {p.symbol} | {p.name or '-'} | {p.qty:,} "
86
+ f"| {_cost} | {_price} | {_pct} |"
87
+ )
88
+ if len(positions) > 15:
89
+ lines.append(f"\n*共 {len(positions)} 只持仓,仅显示前 15 只*")
90
+ else:
91
+ lines.append("*当前无持仓*")
92
+ lines.append("")
93
+ except Exception as _pe:
94
+ lines.append(f"*获取持仓失败: {_pe}*\n")
95
+
96
+ if _want_orders:
97
+ try:
98
+ orders = broker.orders()
99
+ if orders:
100
+ lines.append("### 最近委托")
101
+ lines.append("| 代码 | 方向 | 数量 | 价格 | 状态 |")
102
+ lines.append("|------|------|------|------|------|")
103
+ for o in orders[:10]:
104
+ _price_str = f"{o.price:.2f}" if o.price else "市价"
105
+ lines.append(
106
+ f"| {o.symbol} | {'买入' if o.side=='buy' else '卖出'} "
107
+ f"| {o.qty:,} | {_price_str} | {o.status} |"
108
+ )
109
+ else:
110
+ lines.append("*暂无委托记录*")
111
+ lines.append("")
112
+ except Exception as _oe:
113
+ lines.append(f"*获取委托记录失败: {_oe}*\n")
114
+
115
+ lines.append("*以上数据来自券商 API,不构成投资建议*")
116
+ return {
117
+ "success": True,
118
+ "response": "\n".join(lines),
119
+ "tools_used": ["broker_query"],
120
+ }
121
+ except Exception as exc:
122
+ return {"success": False, "error": str(exc)}