erlangshen 0.1.0

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 (93) hide show
  1. package/.claude/agents/equity-agent.md +26 -0
  2. package/.claude/agents/macro-agent.md +25 -0
  3. package/.claude/commands/analyze.md +40 -0
  4. package/.claude/commands/macro.md +29 -0
  5. package/.claude/settings.json +12 -0
  6. package/CODEX_GOAL.md +46 -0
  7. package/README.md +206 -0
  8. package/bin/cli.js +67 -0
  9. package/bin/erlangshen +2 -0
  10. package/bin/xiaoergod +2 -0
  11. package/frontend/index.html +700 -0
  12. package/knowledge/crypto_guide.md +147 -0
  13. package/knowledge/economic_indicators.md +125 -0
  14. package/knowledge/financial_glossary.md +148 -0
  15. package/knowledge/first_principles.md +50 -0
  16. package/knowledge/first_principles_deep.md +115 -0
  17. package/knowledge/global_markets.md +173 -0
  18. package/knowledge/insights.md +141 -0
  19. package/knowledge/market_basics.md +116 -0
  20. package/knowledge/memos/session_20260513_003616.json +6 -0
  21. package/knowledge/memos/session_20260513_003822.json +6 -0
  22. package/knowledge/risk_management.md +151 -0
  23. package/knowledge/team_context.md +42 -0
  24. package/knowledge/trading_strategies.md +114 -0
  25. package/package.json +42 -0
  26. package/requirements.txt +14 -0
  27. package/scripts/postinstall.js +188 -0
  28. package/scripts/preuninstall.js +22 -0
  29. package/src/__init__.py +4 -0
  30. package/src/__pycache__/__init__.cpython-313.pyc +0 -0
  31. package/src/agents/__init__.py +3 -0
  32. package/src/agents/base.py +103 -0
  33. package/src/agents/base_agent.py +86 -0
  34. package/src/agents/equity.py +136 -0
  35. package/src/agents/equity_agent.py +91 -0
  36. package/src/agents/erlang.py +165 -0
  37. package/src/agents/macro.py +137 -0
  38. package/src/agents/macro_agent.py +81 -0
  39. package/src/agents/multi_asset.py +147 -0
  40. package/src/agents/multi_asset_agent.py +87 -0
  41. package/src/api/__init__.py +1 -0
  42. package/src/api/__pycache__/__init__.cpython-313.pyc +0 -0
  43. package/src/api/__pycache__/server.cpython-313.pyc +0 -0
  44. package/src/api/cli.py +435 -0
  45. package/src/api/cli_enhanced.py +537 -0
  46. package/src/api/server.py +266 -0
  47. package/src/brain.py +200 -0
  48. package/src/cli.py +153 -0
  49. package/src/commands/__init__.py +3 -0
  50. package/src/commands/analyze.py +131 -0
  51. package/src/commands/macro.py +100 -0
  52. package/src/commands/memo.py +216 -0
  53. package/src/commands/portfolio.py +154 -0
  54. package/src/commands/report.py +228 -0
  55. package/src/commands/risk.py +183 -0
  56. package/src/commands/search.py +183 -0
  57. package/src/commands/stock.py +124 -0
  58. package/src/config.py +327 -0
  59. package/src/core/__init__.py +1 -0
  60. package/src/core/brain.py +645 -0
  61. package/src/core/cerebellum.py +175 -0
  62. package/src/core/investment_universe.py +423 -0
  63. package/src/core/knowledge.py +207 -0
  64. package/src/core/memory.py +115 -0
  65. package/src/hooks/__init__.py +3 -0
  66. package/src/hooks/session_end.py +57 -0
  67. package/src/hooks/session_start.py +75 -0
  68. package/src/knowledge/__init__.py +1 -0
  69. package/src/mcp/__init__.py +3 -0
  70. package/src/mcp/feishu.py +331 -0
  71. package/src/mcp/fund_tools.py +323 -0
  72. package/src/mcp/macro.py +452 -0
  73. package/src/mcp/market.py +331 -0
  74. package/src/mcp/registry.py +168 -0
  75. package/src/network/__init__.py +15 -0
  76. package/src/network/detector.py +125 -0
  77. package/src/network/proxy.py +199 -0
  78. package/src/network/router.py +103 -0
  79. package/src/prompts/__init__.py +1 -0
  80. package/src/prompts/analysis_framework.md +164 -0
  81. package/src/prompts/persona.md +65 -0
  82. package/src/prompts/report_template.md +144 -0
  83. package/src/skills/__init__.py +3 -0
  84. package/src/skills/framework.py +105 -0
  85. package/src/skills/templates.py +342 -0
  86. package/src/tools/__init__.py +1 -0
  87. package/src/tools/file_tools.py +209 -0
  88. package/src/tools/macro_tools.py +152 -0
  89. package/src/tools/market_tools.py +1172 -0
  90. package/src/tools/registry.py +398 -0
  91. package/src/tools/search_tools.py +777 -0
  92. package/tests/__init__.py +1 -0
  93. package/tests/test_erlangshen.py +140 -0
@@ -0,0 +1,537 @@
1
+ """
2
+ 二郎神CLI增强版
3
+ 参考 Claude Code 的设计理念,添加更多高级功能
4
+ """
5
+ import sys
6
+ import asyncio
7
+ import os
8
+ import time
9
+ import threading
10
+ from pathlib import Path
11
+ from dataclasses import dataclass
12
+ from typing import Optional, Callable, AsyncGenerator
13
+ from collections import deque
14
+
15
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
16
+
17
+ from loguru import logger
18
+ from src.core.brain import Brain
19
+ from src.core.memory import Memory
20
+ from src.core.knowledge import KnowledgeBase
21
+ from src.tools.market_tools import MarketTools
22
+ from src.tools.macro_tools import MacroTools
23
+ from src.tools.search_tools import SearchTools
24
+ from src.tools.file_tools import FileTools
25
+ from src.agents.erlang import 二郎神
26
+
27
+
28
+ # ============================================================
29
+ # ANSI 颜色和格式化
30
+ # ============================================================
31
+ @dataclass
32
+ class C:
33
+ """颜色常量"""
34
+ RESET = "\033[0m"
35
+ BOLD = "\033[1m"
36
+ DIM = "\033[2m"
37
+ ITALIC = "\033[3m"
38
+
39
+ # 前景色
40
+ BLACK = "\033[30m"
41
+ RED = "\033[31m"
42
+ GREEN = "\033[32m"
43
+ YELLOW = "\033[33m"
44
+ BLUE = "\033[34m"
45
+ MAGENTA = "\033[35m"
46
+ CYAN = "\033[36m"
47
+ WHITE = "\033[37m"
48
+
49
+ # 亮色
50
+ BRIGHT_RED = "\033[91m"
51
+ BRIGHT_GREEN = "\033[92m"
52
+ BRIGHT_YELLOW = "\033[93m"
53
+ BRIGHT_BLUE = "\033[94m"
54
+ BRIGHT_MAGENTA = "\033[95m"
55
+ BRIGHT_CYAN = "\033[96m"
56
+ BRIGHT_WHITE = "\033[97m"
57
+
58
+
59
+ def c(color: str, text: str) -> str:
60
+ """彩色输出"""
61
+ return f"{color}{text}{C.RESET}"
62
+
63
+
64
+ def clear_line():
65
+ """清除当前行"""
66
+ sys.stdout.write('\r' + ' ' * 100 + '\r')
67
+ sys.stdout.flush()
68
+
69
+
70
+ # ============================================================
71
+ # Spinner 增强版
72
+ # ============================================================
73
+ class Spinner:
74
+ """带消息的加载动画"""
75
+
76
+ FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
77
+
78
+ def __init__(self, message: str = "处理中"):
79
+ self.message = message
80
+ self.running = False
81
+ self.thread: Optional[threading.Thread] = None
82
+ self._stop_event = threading.Event()
83
+
84
+ def start(self):
85
+ """启动"""
86
+ self.running = True
87
+ self._stop_event.clear()
88
+ self.thread = threading.Thread(target=self._spin, daemon=True)
89
+ self.thread.start()
90
+
91
+ def stop(self, final_msg: Optional[str] = None):
92
+ """停止"""
93
+ self.running = False
94
+ self._stop_event.set()
95
+ if self.thread:
96
+ self.thread.join(timeout=0.3)
97
+ clear_line()
98
+ if final_msg:
99
+ print(c(C.BRIGHT_GREEN, f"✓ {final_msg}"))
100
+
101
+ def _spin(self):
102
+ """动画循环"""
103
+ idx = 0
104
+ while self.running and not self._stop_event.is_set():
105
+ frame = self.FRAMES[idx % len(self.FRAMES)]
106
+ msg = f"{frame} {self.message}..."
107
+ sys.stdout.write('\r' + msg)
108
+ sys.stdout.flush()
109
+ idx += 1
110
+ time.sleep(0.08)
111
+ clear_line()
112
+
113
+
114
+ # ============================================================
115
+ # 打字机效果(流式输出)
116
+ # ============================================================
117
+ async def typewriter_stream(
118
+ text: str,
119
+ delay: float = 0.005,
120
+ prefix: str = "",
121
+ color: str = C.WHITE
122
+ ) -> str:
123
+ """
124
+ 流式输出效果
125
+
126
+ Args:
127
+ text: 要输出的文本
128
+ delay: 每个字符的延迟
129
+ prefix: 每行前缀
130
+ color: 颜色
131
+
132
+ Returns:
133
+ 完整文本
134
+ """
135
+ # 非TTY或文本太长,直接输出
136
+ if not sys.stdout.isatty() or len(text) > 3000:
137
+ print(c(color, text))
138
+ return text
139
+
140
+ result = []
141
+ for i, char in enumerate(text):
142
+ if char == '\n':
143
+ result.append(char)
144
+ print(c(color, ''.join(result)), end='', flush=True)
145
+ result = []
146
+ else:
147
+ result.append(char)
148
+ if len(result) >= 50 or i == len(text) - 1:
149
+ print(c(color, ''.join(result)), end='', flush=True)
150
+ result = []
151
+ await asyncio.sleep(delay)
152
+
153
+ if result:
154
+ print(c(color, ''.join(result)), end='', flush=True)
155
+
156
+ print() # 换行
157
+ return text
158
+
159
+
160
+ # ============================================================
161
+ # Markdown 简单渲染
162
+ # ============================================================
163
+ class MarkdownRenderer:
164
+ """简单的 Markdown 渲染器"""
165
+
166
+ @staticmethod
167
+ def render(text: str) -> str:
168
+ """渲染 Markdown 文本(简化版)"""
169
+ lines = text.split('\n')
170
+ output = []
171
+
172
+ for line in lines:
173
+ # 标题 ##
174
+ if line.startswith('## '):
175
+ title = line[3:]
176
+ output.append(f"\n{C.BRIGHT_CYAN}{C.BOLD}{title}{C.RESET}")
177
+ output.append(f"{C.DIM}{'─' * len(title)}{C.RESET}")
178
+ # 标题 #
179
+ elif line.startswith('# '):
180
+ title = line[2:]
181
+ output.append(f"\n{C.BRIGHT_CYAN}{C.BOLD}{title}{C.RESET}")
182
+ # 列表
183
+ elif line.strip().startswith('- ') or line.strip().startswith('* '):
184
+ content = line.strip()[2:]
185
+ output.append(f" {C.CYAN}●{C.RESET} {content}")
186
+ # 分隔线
187
+ elif line.strip() == '---':
188
+ output.append(f"{C.DIM}{'─' * 50}{C.RESET}")
189
+ # 普通文本
190
+ else:
191
+ output.append(line)
192
+
193
+ return '\n'.join(output)
194
+
195
+
196
+ # ============================================================
197
+ # 命令历史
198
+ # ============================================================
199
+ class CommandHistory:
200
+ """命令历史管理器"""
201
+
202
+ def __init__(self, max_size: int = 100):
203
+ self.history: deque = deque(maxlen=max_size)
204
+ self.current_idx: int = -1
205
+
206
+ def add(self, cmd: str):
207
+ """添加命令"""
208
+ if cmd and (not self.history or self.history[-1] != cmd):
209
+ self.history.append(cmd)
210
+ self.current_idx = len(self.history)
211
+
212
+ def get_prev(self) -> Optional[str]:
213
+ """获取上一条命令"""
214
+ if self.history:
215
+ self.current_idx = max(0, self.current_idx - 1)
216
+ return self.history[self.current_idx]
217
+ return None
218
+
219
+ def get_next(self) -> Optional[str]:
220
+ """获取下一条命令"""
221
+ if self.history:
222
+ self.current_idx = min(len(self.history), self.current_idx + 1)
223
+ if self.current_idx >= len(self.history):
224
+ return ""
225
+ return self.history[self.current_idx]
226
+ return None
227
+
228
+
229
+ # ============================================================
230
+ # UI 组件
231
+ # ============================================================
232
+ class UI:
233
+ """UI 组件库"""
234
+
235
+ # ASCII 艺术横幅
236
+ BANNER = f"""
237
+ {C.CYAN}╔══════════════════════════════════════════════════════════════════════╗
238
+ ║{C.RESET}
239
+ ║{C.RESET} {C.BRIGHT_CYAN}{C.BOLD} ██████╗ ██╗ ██╗ ██████╗ ███████╗ ██████╗ ███╗ ██╗ {C.RESET}
240
+ ║{C.RESET} {C.BRIGHT_CYAN}{C.BOLD} ██╔══██╗██║ ██║██╔═══██╗██╔════╝██╔═══██╗████╗ ██║ {C.RESET}
241
+ ║{C.RESET} {C.BRIGHT_CYAN}{C.BOLD} ██████╔╝██║ ██║██║ ██║█████╗ ██║ ██║██╔██╗ ██║ {C.RESET}
242
+ ║{C.RESET} {C.BRIGHT_CYAN}{C.BOLD} ██╔═══╝ ██║ ██║██║ ██║██╔══╝ ██║ ██║██║╚██╗██║ {C.RESET}
243
+ ║{C.RESET} {C.BRIGHT_CYAN}{C.BOLD} ██║ ╚██████╔╝╚██████╔╝██║ ╚██████╔╝██║ ╚████║ {C.RESET}
244
+ ║{C.RESET} {C.BRIGHT_CYAN}{C.BOLD} ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ {C.RESET}
245
+ ║{C.RESET}
246
+ ║{C.RESET} {C.BRIGHT_CYAN}{C.BOLD} {C.BRIGHT_YELLOW}AI 投 资 智 能 体{C.RESET}{C.BRIGHT_CYAN} {C.RESET}
247
+ ║{C.RESET}
248
+ ║{C.RESET} {C.DIM} 全知全能 · 天眼洞察 · 投资决策 · 风险评估 {C.RESET} {C.CYAN}║
249
+ ╚══════════════════════════════════════════════════════════════════════╝{C.RESET}
250
+ """
251
+
252
+ @staticmethod
253
+ def banner():
254
+ """显示横幅"""
255
+ print(UI.BANNER)
256
+ print(f"{C.DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{C.RESET}")
257
+ print(f" {C.WHITE}输入你的问题,二郎神将为你分析投资机会和风险{C.RESET}")
258
+ print(f"{C.DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{C.RESET}\n")
259
+
260
+ @staticmethod
261
+ def help_panel():
262
+ """帮助面板"""
263
+ print(f"""
264
+ {C.CYAN}╭─────────────────────────────────────────────────────────────────────╮{C.RESET}
265
+ {C.CYAN}│{C.RESET} {C.BOLD}快捷命令{C.RESET} {C.CYAN}│{C.RESET}
266
+ {C.CYAN}├─────────────────────────────────────────────────────────────────────┤{C.RESET}
267
+ {C.CYAN}│{C.RESET} {C.GREEN}/help{C.RESET} 显示帮助信息 {C.CYAN}│{C.RESET}
268
+ {C.CYAN}│{C.RESET} {C.GREEN}/market{C.RESET} 查询股票行情 例: /market 000001 {C.CYAN}│{C.RESET}
269
+ {C.CYAN}│{C.RESET} {C.GREEN}/macro{C.RESET} 查询宏观数据 例: /macro CPI {C.CYAN}│{C.RESET}
270
+ {C.CYAN}│{C.RESET} {C.GREEN}/search{C.RESET} 网络搜索 例: /search A股走势 {C.CYAN}│{C.RESET}
271
+ {C.CYAN}│{C.RESET} {C.GREEN}/report{C.RESET} 生成报告 例: /report 周报 {C.CYAN}│{C.RESET}
272
+ {C.CYAN}│{C.RESET} {C.GREEN}/status{C.RESET} 查看状态 {C.CYAN}│{C.RESET}
273
+ {C.CYAN}│{C.RESET} {C.GREEN}/clear{C.RESET} 清除屏幕 {C.CYAN}│{C.RESET}
274
+ {C.CYAN}│{C.RESET} {C.RED}/quit{C.RESET} 退出程序 {C.CYAN}│{C.RESET}
275
+ {C.CYAN}╰─────────────────────────────────────────────────────────────────────╯{C.RESET}
276
+
277
+ {C.DIM}示例:{C.RESET}
278
+ {C.WHITE}→{C.RESET} 分析美联储加息对A股的影响
279
+ {C.WHITE}→{C.RESET} 茅台现在值得买入吗?
280
+ {C.WHITE}→{C.RESET} 当前应该怎么配置资产?
281
+ {C.WHITE}→{C.RESET} 分析半导体行业前景
282
+ """)
283
+
284
+ @staticmethod
285
+ def status_bar():
286
+ """状态栏"""
287
+ try:
288
+ width = os.get_terminal_size().columns
289
+ except:
290
+ width = 80
291
+ bar = "─" * max(10, width - 20)
292
+ print(f"\n{C.DIM}┌{bar}┐{C.RESET}")
293
+ print(f"{C.DIM}│{C.RESET} {C.DIM}↑↓: 历史{C.RESET} | {C.DIM}Ctrl+L: 清屏{C.RESET} | {C.DIM}Ctrl+C: 中断{C.RESET} | {C.DIM}/quit: 退出{C.RESET} {C.DIM}│{C.RESET}")
294
+ print(f"{C.DIM}└{bar}┘{C.RESET}")
295
+
296
+ @staticmethod
297
+ def thinking():
298
+ """思考中"""
299
+ return f"{C.YELLOW}⏳ 思考中...{C.RESET}"
300
+
301
+ @staticmethod
302
+ def success(msg: str):
303
+ print(c(C.BRIGHT_GREEN, f"✓ {msg}"))
304
+
305
+ @staticmethod
306
+ def error(msg: str):
307
+ print(c(C.BRIGHT_RED, f"✗ {msg}"))
308
+
309
+ @staticmethod
310
+ def warning(msg: str):
311
+ print(c(C.YELLOW, f"⚠ {msg}"))
312
+
313
+ @staticmethod
314
+ def info(msg: str):
315
+ print(c(C.CYAN, f"ℹ {msg}"))
316
+
317
+ @staticmethod
318
+ def section(title: str):
319
+ """分节标题"""
320
+ print(f"\n{C.CYAN}╭{'─' * 58}╮{C.RESET}")
321
+ print(f"{C.CYAN}│{C.RESET}{C.BOLD}{C.BRIGHT_CYAN} {title}{C.RESET}{' ' * (52 - len(title))}{C.CYAN}│{C.RESET}")
322
+ print(f"{C.CYAN}╰{'─' * 58}╯{C.RESET}\n")
323
+
324
+
325
+ # ============================================================
326
+ # 主 CLI 类
327
+ # ============================================================
328
+ class EnhancedCLI:
329
+ """增强版二郎神CLI"""
330
+
331
+ def __init__(self):
332
+ self.erlangshen = self._init_erlangshen()
333
+ self.running = True
334
+ self.history = CommandHistory()
335
+ self.spinner = Spinner()
336
+ self.ui = UI()
337
+ self.md = MarkdownRenderer()
338
+ logger.info("Enhanced CLI initialized")
339
+
340
+ def _init_erlangshen(self) -> 二郎神:
341
+ """初始化二郎神"""
342
+ logger.info("Initializing 二郎神...")
343
+
344
+ try:
345
+ from src.config import load_config
346
+ config = load_config()
347
+ deepseek_key = config.deepseek_api_key or os.getenv("DEEPSEEK_API_KEY")
348
+ if deepseek_key:
349
+ os.environ["DEEPSEEK_API_KEY"] = deepseek_key
350
+ except Exception as e:
351
+ logger.warning(f"Config load failed: {e}")
352
+
353
+ return 二郎神(
354
+ brain=Brain(),
355
+ memory=Memory(),
356
+ knowledge=KnowledgeBase(),
357
+ tools={
358
+ "market_tools": MarketTools(),
359
+ "macro_tools": MacroTools(),
360
+ "search_tools": SearchTools(),
361
+ "file_tools": FileTools(),
362
+ },
363
+ )
364
+
365
+ def _read_input(self, prompt: str) -> str:
366
+ """读取输入(支持历史)"""
367
+ try:
368
+ import readline
369
+ # 配置 readline
370
+ readline.parse_and_bind('tab: complete')
371
+ readline.set_history_length(100)
372
+
373
+ # 尝试使用历史
374
+ for cmd in reversed(list(self.history.history)):
375
+ readline.add_history(cmd)
376
+ except:
377
+ pass
378
+
379
+ return input(prompt)
380
+
381
+ async def _process_command(self, cmd: str) -> Optional[str]:
382
+ """处理命令"""
383
+ cmd = cmd.strip()
384
+ if not cmd:
385
+ return None
386
+
387
+ # 命令路由
388
+ if cmd == "/quit":
389
+ self.running = False
390
+ return c(C.CYAN, "\n👋 再见!二郎神退下了。\n")
391
+
392
+ if cmd == "/help":
393
+ self.ui.help_panel()
394
+ return None
395
+
396
+ if cmd == "/clear":
397
+ os.system('cls' if os.name == 'nt' else 'clear')
398
+ self.ui.banner()
399
+ self.ui.help_panel()
400
+ return None
401
+
402
+ if cmd == "/status":
403
+ stats = self.erlangshen.knowledge.stats()
404
+ return f"\n{self.ui.section('知识库状态')}\n{stats}"
405
+
406
+ if cmd.startswith("/market "):
407
+ symbol = cmd.split(maxsplit=1)[1]
408
+ try:
409
+ result = await self.erlangshen.tools["market_tools"].get_stock_price(symbol)
410
+ return f"\n{self.ui.section(f'📈 {symbol} 行情')}\n{result}"
411
+ except Exception as e:
412
+ return c(C.BRIGHT_RED, f"查询失败: {e}")
413
+
414
+ if cmd.startswith("/macro "):
415
+ indicator = cmd.split(maxsplit=1)[1]
416
+ try:
417
+ result = await self.erlangshen.tools["macro_tools"].get_macro_indicator(indicator)
418
+ return f"\n{self.ui.section(f'📊 {indicator} 数据')}\n{result}"
419
+ except Exception as e:
420
+ return c(C.BRIGHT_RED, f"查询失败: {e}")
421
+
422
+ if cmd.startswith("/search "):
423
+ query = cmd.split(maxsplit=1)[1]
424
+ self.ui.status_bar()
425
+ print(f"\n{self.ui.thinking()}")
426
+ self.spinner.message = "搜索中"
427
+ self.spinner.start()
428
+ try:
429
+ result = await self.erlangshen.tools["search_tools"].web_search(query)
430
+ self.spinner.stop("搜索完成")
431
+ return f"\n{self.ui.section(f'🔍 搜索: {query}')}\n{result}"
432
+ except Exception as e:
433
+ self.spinner.stop()
434
+ return c(C.BRIGHT_RED, f"搜索失败: {e}")
435
+
436
+ if cmd.startswith("/report "):
437
+ title = cmd.split(maxsplit=1)[1]
438
+ return c(C.YELLOW, f"报告功能开发中: /report {title}")
439
+
440
+ # 默认:分析查询
441
+ return await self._analyze(cmd)
442
+
443
+ async def _analyze(self, query: str) -> str:
444
+ """执行分析"""
445
+ self.ui.status_bar()
446
+ print(f"\n{self.ui.thinking()}")
447
+ self.spinner.message = "二郎神正在分析"
448
+ self.spinner.start()
449
+
450
+ try:
451
+ result = await self.erlangshen.process(query)
452
+ self.spinner.stop("分析完成")
453
+
454
+ # 格式化输出
455
+ output = self._format_result(result)
456
+ return output
457
+ except Exception as e:
458
+ self.spinner.stop()
459
+ logger.error(f"Analysis error: {e}")
460
+ return c(C.BRIGHT_RED, f"\n❌ 分析出错: {e}")
461
+
462
+ def _format_result(self, result: dict) -> str:
463
+ """格式化分析结果"""
464
+ lines = []
465
+
466
+ lines.append(f"\n{self.ui.section('二郎神分析结果')}")
467
+
468
+ # 核心结论
469
+ if "conclusion" in result:
470
+ conclusion = result["conclusion"]
471
+ lines.append(f"{C.BOLD}📊 核心结论{C.RESET}")
472
+ lines.append(f"{C.DIM}{'─' * 50}{C.RESET}")
473
+ lines.append(self.md.render(conclusion[:800]))
474
+ lines.append("")
475
+
476
+ # 洞察
477
+ if "erlangshen_insight" in result:
478
+ lines.append(f"{C.BOLD}💡 二郎神洞察{C.RESET}")
479
+ lines.append(f"{C.DIM}{'─' * 50}{C.RESET}")
480
+ insight = result["erlangshen_insight"]
481
+ lines.append(c(C.CYAN, insight[:400]))
482
+ lines.append("")
483
+
484
+ # 置信度
485
+ if "analysis" in result:
486
+ analysis = result["analysis"]
487
+ if "confidence" in analysis:
488
+ conf = analysis["confidence"]
489
+ conf_color = C.BRIGHT_GREEN if conf > 0.7 else C.BRIGHT_YELLOW if conf > 0.4 else C.BRIGHT_RED
490
+ lines.append(f"{C.BOLD}🎯 置信度{C.RESET} {c(conf_color, f'{conf:.0%}')}")
491
+
492
+ lines.append(f"\n{C.DIM}{'─' * 60}{C.RESET}")
493
+ return "\n".join(lines)
494
+
495
+ async def run(self):
496
+ """运行CLI"""
497
+ os.system('cls' if os.name == 'nt' else 'clear')
498
+ self.ui.banner()
499
+ self.ui.help_panel()
500
+
501
+ while self.running:
502
+ try:
503
+ self.ui.status_bar()
504
+ user_input = self._read_input(f"{C.BRIGHT_CYAN}❓ 你:{C.RESET} ")
505
+
506
+ if not user_input.strip():
507
+ continue
508
+
509
+ # 添加到历史
510
+ self.history.add(user_input)
511
+
512
+ # 处理命令
513
+ result = await self._process_command(user_input)
514
+ if result:
515
+ print(result)
516
+
517
+ except KeyboardInterrupt:
518
+ print(c(C.YELLOW, "\n\n⚠ 使用 /quit 退出"))
519
+ continue
520
+ except EOFError:
521
+ break
522
+ except Exception as e:
523
+ logger.error(f"CLI error: {e}")
524
+ print(c(C.BRIGHT_RED, f"\n❌ 错误: {e}"))
525
+
526
+ print(c(C.CYAN, "\n👋 二郎神已退出。再见!\n"))
527
+
528
+
529
+ async def main():
530
+ """入口"""
531
+ print(f"{C.DIM}启动二郎神增强版CLI...{C.RESET}")
532
+ cli = EnhancedCLI()
533
+ await cli.run()
534
+
535
+
536
+ if __name__ == "__main__":
537
+ asyncio.run(main())