vox-code 2.0.0__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 (88) hide show
  1. vox_code-2.0.0.dist-info/METADATA +258 -0
  2. vox_code-2.0.0.dist-info/RECORD +88 -0
  3. vox_code-2.0.0.dist-info/WHEEL +4 -0
  4. vox_code-2.0.0.dist-info/entry_points.txt +3 -0
  5. voxcli/__init__.py +3 -0
  6. voxcli/__main__.py +5 -0
  7. voxcli/agent/__init__.py +12 -0
  8. voxcli/agent/agent.py +449 -0
  9. voxcli/agent/agent_budget.py +133 -0
  10. voxcli/agent/agent_orchestrator.py +414 -0
  11. voxcli/agent/plan_execute_agent.py +514 -0
  12. voxcli/agent/roles.py +80 -0
  13. voxcli/agent/sub_agent.py +351 -0
  14. voxcli/catalog.py +477 -0
  15. voxcli/chat.py +91 -0
  16. voxcli/cli/__init__.py +4 -0
  17. voxcli/cli/main.py +452 -0
  18. voxcli/cli/parser.py +71 -0
  19. voxcli/config.py +518 -0
  20. voxcli/gui/__main__.py +3 -0
  21. voxcli/gui/main.py +22 -0
  22. voxcli/gui/pet/__init__.py +5 -0
  23. voxcli/gui/pet/base.py +62 -0
  24. voxcli/gui/pet/coordinator.py +888 -0
  25. voxcli/gui/pet/data.py +430 -0
  26. voxcli/gui/pet/widgets.py +683 -0
  27. voxcli/gui/pet/windows.py +2298 -0
  28. voxcli/gui/pet/workers.py +54 -0
  29. voxcli/gui/pet_app.py +7 -0
  30. voxcli/hitl/__init__.py +11 -0
  31. voxcli/hitl/handler.py +11 -0
  32. voxcli/hitl/policy.py +32 -0
  33. voxcli/hitl/request.py +13 -0
  34. voxcli/hitl/result.py +11 -0
  35. voxcli/hitl/terminal_handler.py +64 -0
  36. voxcli/hitl/tool_registry.py +64 -0
  37. voxcli/llm/base.py +93 -0
  38. voxcli/llm/factory.py +178 -0
  39. voxcli/llm/ollama_client.py +137 -0
  40. voxcli/llm/openai_compatible.py +249 -0
  41. voxcli/memory/base.py +16 -0
  42. voxcli/memory/budget.py +53 -0
  43. voxcli/memory/compressor.py +198 -0
  44. voxcli/memory/entry.py +36 -0
  45. voxcli/memory/long_term.py +126 -0
  46. voxcli/memory/manager.py +101 -0
  47. voxcli/memory/retriever.py +72 -0
  48. voxcli/memory/short_term.py +84 -0
  49. voxcli/memory/tokenizer.py +21 -0
  50. voxcli/plan/__init__.py +5 -0
  51. voxcli/plan/execution_plan.py +225 -0
  52. voxcli/plan/planner.py +198 -0
  53. voxcli/plan/task.py +123 -0
  54. voxcli/policy/audit_log.py +111 -0
  55. voxcli/policy/command_guard.py +34 -0
  56. voxcli/policy/exception.py +5 -0
  57. voxcli/policy/path_guard.py +32 -0
  58. voxcli/prompting/__init__.py +7 -0
  59. voxcli/prompting/presenter.py +154 -0
  60. voxcli/rag/__init__.py +16 -0
  61. voxcli/rag/analyzer.py +89 -0
  62. voxcli/rag/chunk.py +17 -0
  63. voxcli/rag/chunker.py +137 -0
  64. voxcli/rag/embedding.py +75 -0
  65. voxcli/rag/formatter.py +40 -0
  66. voxcli/rag/index.py +96 -0
  67. voxcli/rag/relation.py +14 -0
  68. voxcli/rag/retriever.py +58 -0
  69. voxcli/rag/store.py +155 -0
  70. voxcli/rag/tokenizer.py +26 -0
  71. voxcli/runtime/__init__.py +6 -0
  72. voxcli/runtime/session_controller.py +386 -0
  73. voxcli/tool/__init__.py +3 -0
  74. voxcli/tool/tool_registry.py +433 -0
  75. voxcli/util/animation.py +219 -0
  76. voxcli/util/ansi.py +82 -0
  77. voxcli/util/markdown.py +98 -0
  78. voxcli/web/__init__.py +17 -0
  79. voxcli/web/base.py +20 -0
  80. voxcli/web/extractor.py +77 -0
  81. voxcli/web/factory.py +38 -0
  82. voxcli/web/fetch_result.py +27 -0
  83. voxcli/web/fetcher.py +42 -0
  84. voxcli/web/network_policy.py +49 -0
  85. voxcli/web/result.py +23 -0
  86. voxcli/web/searxng.py +55 -0
  87. voxcli/web/serpapi.py +53 -0
  88. voxcli/web/zhipu.py +55 -0
voxcli/cli/main.py ADDED
@@ -0,0 +1,452 @@
1
+ """CLI 主程序 - REPL 循环"""
2
+
3
+ import getpass
4
+ import logging
5
+ import os
6
+ import sys
7
+ from typing import Optional, Sequence
8
+
9
+ from ..config import ProviderConfig, pai_config
10
+ from ..llm.factory import create_from_config, default_base_url_for, default_model_for
11
+ from ..agent import Agent, PlanExecuteAgent, AgentOrchestrator
12
+ from ..agent.plan_execute_agent import PlanReviewHandler, PlanReviewDecision, PlanReviewAction
13
+ from ..tool import ToolRegistry
14
+ from ..memory.manager import MemoryManager
15
+ from ..hitl import TerminalHitlHandler, HitlToolRegistry, ApprovalPolicy
16
+ from ..prompting import PresentationMode, ResponsePresenter
17
+ from ..util.ansi import heading, section, subtle, emphasis, success, error
18
+ from ..util.animation import ProgressDots, Typewriter
19
+ from .parser import CliCommandParser, ParsedCommand
20
+
21
+ logger = logging.getLogger(__name__)
22
+ _SUPPORTED_PROVIDERS = ("glm", "deepseek", "qwen", "ollama")
23
+
24
+
25
+ def _init_logging(debug: bool = False):
26
+ level = logging.DEBUG if debug else logging.WARNING
27
+ logging.basicConfig(
28
+ level=level,
29
+ format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
30
+ datefmt="%H:%M:%S",
31
+ )
32
+
33
+
34
+ def run_repl():
35
+ debug = os.environ.get("VOX_CODE_DEBUG", "").lower() in ("1", "true", "yes")
36
+ _init_logging(debug)
37
+
38
+ _animate_startup()
39
+
40
+ llm_client = create_from_config()
41
+ if llm_client is None:
42
+ print("❌ 无法创建 LLM 客户端。请检查环境变量配置。")
43
+ print(" 至少需要配置一个模型提供商:")
44
+ print(" - GLM: GLM_API_KEY + GLM_MODEL")
45
+ print(" - DeepSeek: DEEPSEEK_API_KEY + DEEPSEEK_MODEL")
46
+ print(" - Qwen: QWEN_API_KEY + QWEN_MODEL")
47
+ print(" - Ollama: OLLAMA_MODEL + OLLAMA_BASE_URL")
48
+ print(" 也可以先运行: vox-code init")
49
+ sys.exit(1)
50
+
51
+ tool_registry = ToolRegistry()
52
+ memory_manager = MemoryManager(llm_client)
53
+
54
+ # 三种运行模式
55
+ state = {
56
+ "mode": "single",
57
+ "presentation_mode": _default_presentation_mode().value,
58
+ }
59
+ agent = Agent(llm_client, tool_registry)
60
+ plan_agent = PlanExecuteAgent(llm_client, tool_registry, None, memory_manager, None)
61
+ orchestrator = AgentOrchestrator(llm_client, tool_registry, memory_manager)
62
+ presenter = ResponsePresenter(llm_client)
63
+
64
+ terminal_hitl = TerminalHitlHandler()
65
+ hitl_registry = HitlToolRegistry(tool_registry)
66
+ approval_policy = ApprovalPolicy()
67
+ parser = CliCommandParser()
68
+
69
+ tw = Typewriter()
70
+ tw.write_fast(subtle(f" Model: {llm_client.__class__.__name__}\n"))
71
+ tw.write_fast(subtle(f" Mode: {_render_mode_label(state['mode'])}\n"))
72
+ tw.write_fast(subtle(f" Style: {state['presentation_mode']}\n"))
73
+ print()
74
+
75
+ while True:
76
+ try:
77
+ line = input(">>> ").strip()
78
+ except (EOFError, KeyboardInterrupt):
79
+ print("\n" + subtle("再见!"))
80
+ break
81
+
82
+ if not line:
83
+ continue
84
+
85
+ # Handle commands
86
+ parsed = parser.parse(line)
87
+ if parsed:
88
+ _handle_command(parsed, agent, plan_agent, orchestrator, memory_manager,
89
+ tool_registry, hitl_registry, approval_policy, terminal_hitl,
90
+ llm_client, presenter, lambda: state["mode"],
91
+ lambda m: state.__setitem__("mode", m),
92
+ lambda: state["presentation_mode"],
93
+ lambda m: state.__setitem__("presentation_mode", m))
94
+ continue
95
+
96
+ # Handle normal input
97
+ try:
98
+ mode = state["mode"]
99
+ if mode == "single":
100
+ result = agent.run(line)
101
+ if result:
102
+ _print_presented_result(presenter, state["presentation_mode"], line, result)
103
+ elif mode == "plan":
104
+ result = plan_agent.run(line)
105
+ if result:
106
+ _print_presented_result(presenter, state["presentation_mode"], line, result)
107
+ else:
108
+ result = orchestrator.run(line)
109
+ if result:
110
+ _print_presented_result(presenter, state["presentation_mode"], line, result)
111
+ except Exception as e:
112
+ logger.error("Execution failed", exc_info=True)
113
+ print(f"❌ 执行失败: {e}")
114
+
115
+
116
+ def _handle_command(parsed: ParsedCommand, agent, plan_agent, orchestrator,
117
+ memory_manager, tool_registry, hitl_registry, approval_policy,
118
+ terminal_hitl, llm_client, presenter, get_mode, set_mode,
119
+ get_presentation_mode, set_presentation_mode):
120
+ cmd = parsed.command
121
+
122
+ if cmd == "/exit":
123
+ print(subtle("再见!"))
124
+ sys.exit(0)
125
+
126
+ elif cmd == "/init":
127
+ _cmd_init()
128
+
129
+ elif cmd == "/help":
130
+ CliCommandParser.print_help()
131
+
132
+ elif cmd == "/model":
133
+ _cmd_model(parsed, agent, plan_agent, orchestrator, llm_client, presenter)
134
+
135
+ elif cmd == "/plan":
136
+ print(heading("📋 当前执行计划"))
137
+ print("Plan-and-Execute 模式会在每次任务前自动生成计划。")
138
+ print("使用 /team 切换到多 Agent 团队模式,或保持默认的单 Agent 模式。")
139
+
140
+ elif cmd == "/team":
141
+ current = get_mode()
142
+ modes = ["single", "plan", "team"]
143
+ idx = modes.index(current) if current in modes else 0
144
+ next_mode = modes[(idx + 1) % len(modes)]
145
+ set_mode(next_mode)
146
+ print(f" {success('✓')} {subtle(f'Switched to {_render_mode_label(next_mode)} mode')}")
147
+
148
+ elif cmd == "/style":
149
+ if parsed.args:
150
+ if not PresentationMode.is_valid(parsed.args[0]):
151
+ print(subtle(" Invalid style, options: work, pet"))
152
+ return
153
+ requested = PresentationMode.normalize(parsed.args[0]).value
154
+ set_presentation_mode(requested)
155
+ print(f" {success('✓')} {subtle('Style set to: ' + requested)}")
156
+ else:
157
+ print(subtle(f" Current style: {get_presentation_mode()}"))
158
+
159
+ elif cmd == "/hitl":
160
+ if parsed.args:
161
+ mode = parsed.args[0].lower()
162
+ if mode in ("auto", "always", "never"):
163
+ approval_policy.set_mode(mode)
164
+ print(f" {success('✓')} {subtle('HITL policy set to: ' + mode)}")
165
+ else:
166
+ print(subtle(" Invalid mode, options: auto, always, never"))
167
+ else:
168
+ print(subtle(f" Current HITL policy: {approval_policy.mode}"))
169
+
170
+ elif cmd == "/policy":
171
+ print(heading("🛡️ 安全策略"))
172
+ print(f" 项目根目录: {tool_registry.project_path}")
173
+ print(f" 审批模式: {approval_policy.mode}")
174
+ print(" PathGuard: 路径限制在项目根目录之内")
175
+ print(" CommandGuard: 禁止危险命令")
176
+ print(" 审计日志: ~/.vox-code/audit/")
177
+
178
+ elif cmd == "/audit":
179
+ _cmd_audit(tool_registry)
180
+
181
+ elif cmd == "/index":
182
+ _cmd_index(parsed, tool_registry)
183
+
184
+ elif cmd == "/search":
185
+ _cmd_search(parsed, tool_registry)
186
+
187
+ elif cmd == "/memory":
188
+ print(memory_manager.status_summary())
189
+
190
+ elif cmd == "/clear":
191
+ agent.clear_history()
192
+ print("对话历史已清空")
193
+
194
+ elif cmd == "/context":
195
+ print(agent.get_context_status())
196
+
197
+ elif cmd == "/save":
198
+ _cmd_save(parsed, agent)
199
+
200
+ else:
201
+ print(f"未知命令: {cmd},输入 /help 查看可用命令")
202
+
203
+
204
+ def _cmd_model(parsed, agent, plan_agent, orchestrator, llm_client, presenter):
205
+ from ..llm.factory import create
206
+ target = parsed.args[0] if parsed.args else ""
207
+ if not target:
208
+ print("用法: /model <preset-id|provider[:<model>]>")
209
+ return
210
+
211
+ provider, model_name, preset = pai_config.resolve_model_selection(target)
212
+
213
+ try:
214
+ new_client = create(provider, model_name)
215
+ if new_client is None:
216
+ print(f" {error('✗')} {subtle(f'Failed to create client for provider={provider}')}")
217
+ return
218
+ selected_model = getattr(new_client, "model_name", model_name or "") or (preset.model if preset else "")
219
+ pai_config.persist_model_selection(provider, selected_model)
220
+ agent.set_llm_client(new_client)
221
+ plan_agent._llm = new_client
222
+ orchestrator._llm = new_client
223
+ presenter._llm = new_client
224
+ model_desc = provider + (f" ({selected_model})" if selected_model else "")
225
+ print(f" {success('✓')} {subtle('Model switched to: ' + model_desc)}")
226
+ except Exception as e:
227
+ print(f"切换模型失败: {e}")
228
+
229
+
230
+ def _cmd_audit(tool_registry):
231
+ log = tool_registry.audit_log
232
+ entries = log.recent(10)
233
+ if not entries:
234
+ print("暂无审计记录")
235
+ return
236
+ print(heading("📋 最近审计记录"))
237
+ for entry in entries:
238
+ icon = {"allow": "✅", "deny": "🛡️", "error": "❌"}.get(entry.outcome, "❓")
239
+ print(f" {icon} {entry.tool} ({entry.outcome}) - {entry.reason or '无原因'}")
240
+
241
+
242
+ def _cmd_index(parsed, tool_registry):
243
+ project_path = tool_registry.project_path
244
+ print(f"正在索引项目: {project_path}")
245
+ try:
246
+ from ..rag.index import CodeIndex
247
+ indexer = CodeIndex(project_path)
248
+ count = indexer.index_project()
249
+ print(f"索引完成,共 {count} 个代码块")
250
+ except ImportError:
251
+ print("索引功能不可用(缺少 RAG 模块依赖)")
252
+ except Exception as e:
253
+ print(f"索引失败: {e}")
254
+
255
+
256
+ def _cmd_search(parsed, tool_registry):
257
+ query = " ".join(parsed.args) if parsed.args else ""
258
+ if not query:
259
+ print("用法: /search <query>")
260
+ return
261
+ try:
262
+ result = tool_registry._search_code(query, 5)
263
+ print(result)
264
+ except Exception as e:
265
+ print(f"搜索失败: {e}")
266
+
267
+
268
+ def _cmd_save(parsed, agent):
269
+ filename = " ".join(parsed.args) if parsed.args else "conversation.md"
270
+ try:
271
+ history = agent.conversation_history
272
+ lines = []
273
+ for msg in history:
274
+ role = msg.role.upper()
275
+ content = msg.content or ""
276
+ lines.append(f"## {role}\n{content}\n")
277
+ with open(filename, "w", encoding="utf-8") as f:
278
+ f.write("\n".join(lines))
279
+ print(f"对话已保存到: {filename}")
280
+ except Exception as e:
281
+ print(f"保存失败: {e}")
282
+
283
+
284
+ def _cmd_init():
285
+ try:
286
+ print()
287
+ print(heading("⚙️ Vox Code 初始化"))
288
+ print(subtle(f" 配置文件: {pai_config.config_file()}"))
289
+ print(subtle(" 环境变量仍然优先于 config.json。"))
290
+ print()
291
+
292
+ provider = _prompt_provider()
293
+ current = pai_config.providers.get(provider, ProviderConfig())
294
+ default_model = current.model or default_model_for(provider)
295
+ default_base_url = current.base_url or default_base_url_for(provider)
296
+ model = _prompt_text("模型名", default_model, allow_empty=False)
297
+ base_url = _prompt_text("Base URL", default_base_url, allow_empty=False)
298
+ api_key = ""
299
+ if provider != "ollama":
300
+ api_key = _prompt_secret("API Key", current.api_key, required=True)
301
+
302
+ provider_config = ProviderConfig(
303
+ api_key=api_key,
304
+ base_url=base_url,
305
+ model=model,
306
+ )
307
+ pai_config.set_provider_config(provider, provider_config)
308
+ pai_config.persist_model_selection(provider, model)
309
+
310
+ print()
311
+ print(f" {success('✓')} {subtle('配置已保存到: ' + str(pai_config.config_file()))}")
312
+ print(f" {success('✓')} {subtle('默认模型: ' + provider + ' (' + model + ')')}")
313
+ if provider == "ollama":
314
+ print(subtle(" 下一步: 确认本机 Ollama 已启动,然后运行 vox-code"))
315
+ else:
316
+ print(subtle(" 下一步: 运行 vox-code"))
317
+ except (EOFError, KeyboardInterrupt):
318
+ print()
319
+ print(subtle("已取消初始化。"))
320
+
321
+
322
+ def _prompt_provider() -> str:
323
+ current = pai_config.default_provider_name
324
+ default_provider = current if current in _SUPPORTED_PROVIDERS else "glm"
325
+ print()
326
+ print("选择模型提供商:")
327
+ for idx, provider in enumerate(_SUPPORTED_PROVIDERS, start=1):
328
+ suffix = " (默认)" if provider == default_provider else ""
329
+ print(f" {idx}. {_provider_label(provider)}{suffix}")
330
+
331
+ while True:
332
+ raw = input(f"提供商 [1-{len(_SUPPORTED_PROVIDERS)} / {default_provider}]: ").strip().lower()
333
+ if not raw:
334
+ return default_provider
335
+ if raw.isdigit():
336
+ index = int(raw) - 1
337
+ if 0 <= index < len(_SUPPORTED_PROVIDERS):
338
+ return _SUPPORTED_PROVIDERS[index]
339
+ if raw in _SUPPORTED_PROVIDERS:
340
+ return raw
341
+ print("请输入 1/2/3/4 或 provider 名称(glm、deepseek、qwen、ollama)。")
342
+
343
+
344
+ def _prompt_text(label: str, default: str = "", allow_empty: bool = True) -> str:
345
+ while True:
346
+ suffix = f" [{default}]" if default else ""
347
+ raw = input(f"{label}{suffix}: ").strip()
348
+ if raw:
349
+ return raw
350
+ if default:
351
+ return default
352
+ if allow_empty:
353
+ return ""
354
+ print(f"{label} 不能为空。")
355
+
356
+
357
+ def _prompt_secret(label: str, current: str = "", required: bool = False) -> str:
358
+ masked = _mask_secret(current)
359
+ suffix = f" [{masked}]" if masked else ""
360
+ while True:
361
+ raw = getpass.getpass(f"{label}{suffix}: ").strip()
362
+ if raw:
363
+ return raw
364
+ if current:
365
+ return current
366
+ if not required:
367
+ return ""
368
+ print(f"{label} 不能为空。")
369
+
370
+
371
+ def _mask_secret(value: str) -> str:
372
+ if not value:
373
+ return ""
374
+ if len(value) <= 8:
375
+ return "*" * len(value)
376
+ return value[:4] + "*" * (len(value) - 8) + value[-4:]
377
+
378
+
379
+ def _provider_label(provider: str) -> str:
380
+ return {
381
+ "glm": "GLM",
382
+ "deepseek": "DeepSeek",
383
+ "qwen": "Qwen",
384
+ "ollama": "Ollama",
385
+ }.get(provider, provider.upper())
386
+
387
+
388
+ def _print_cli_usage():
389
+ print("用法:")
390
+ print(" vox-code 启动交互式 REPL")
391
+ print(" vox-code init 初始化模型配置")
392
+ print(" vox-code config-path 显示配置文件路径")
393
+ print(" vox-code --version 显示版本")
394
+
395
+
396
+ def main(argv: Optional[Sequence[str]] = None):
397
+ args = list(sys.argv[1:] if argv is None else argv)
398
+ if not args:
399
+ run_repl()
400
+ return
401
+
402
+ command = args[0].strip().lower()
403
+ if command in {"-h", "--help", "help"}:
404
+ _print_cli_usage()
405
+ return
406
+ if command in {"-v", "--version", "version"}:
407
+ from .. import __version__
408
+ print(__version__)
409
+ return
410
+ if command == "init":
411
+ _cmd_init()
412
+ return
413
+ if command == "config-path":
414
+ print(pai_config.config_file())
415
+ return
416
+
417
+ print(f"未知命令: {args[0]}")
418
+ _print_cli_usage()
419
+
420
+
421
+ def _animate_startup():
422
+ """Claude Code 风格的启动动画"""
423
+ print(heading("╭──────────────────────────────╮"))
424
+ print(heading("│ Vox Code v2.0.0 │"))
425
+ print(heading("│ Web-aware Tool CLI │"))
426
+ print(heading("╰──────────────────────────────╯"))
427
+ dots = ProgressDots("Initializing")
428
+ dots.start()
429
+ import time
430
+ time.sleep(0.4)
431
+ dots.stop(success(" Ready"))
432
+ print(subtle(" /help 查看可用命令, /init 初始化配置, exit 或 /exit 退出"))
433
+ print()
434
+
435
+
436
+ def _render_mode_label(mode: str) -> str:
437
+ return "单 Agent" if mode == "single" else "Plan-and-Execute" if mode == "plan" else "多 Agent 团队"
438
+
439
+
440
+ def _default_presentation_mode() -> PresentationMode:
441
+ return PresentationMode.normalize(os.environ.get("VOX_CODE_PRESENTATION_MODE", "work"))
442
+
443
+
444
+ def _print_presented_result(presenter: ResponsePresenter, presentation_mode: str,
445
+ user_input: str, raw_result: str):
446
+ presented = presenter.present(user_input, raw_result, presentation_mode)
447
+ if presented.display_response:
448
+ print(presented.display_response)
449
+
450
+
451
+ if __name__ == "__main__":
452
+ main()
voxcli/cli/parser.py ADDED
@@ -0,0 +1,71 @@
1
+ """CLI 命令解析器 - 解析斜杠命令"""
2
+
3
+ import shlex
4
+ from dataclasses import dataclass, field
5
+ from typing import List, Optional
6
+
7
+
8
+ @dataclass
9
+ class ParsedCommand:
10
+ command: str
11
+ args: List[str] = field(default_factory=list)
12
+ raw_args: str = ""
13
+
14
+
15
+ class CliCommandParser:
16
+ COMMANDS = {
17
+ "/init": "首次配置模型提供商、API Key 和 URL",
18
+ "/model": "切换 LLM 模型(用法: /model <preset-id|provider[:<model>]>)",
19
+ "/plan": "显示当前执行计划",
20
+ "/team": "切换多 Agent 协作模式",
21
+ "/style": "切换展示模式(work/pet)",
22
+ "/hitl": "设置人工审批模式(auto/always/never)",
23
+ "/policy": "查看或修改安全策略",
24
+ "/audit": "查看审计日志",
25
+ "/index": "索引当前项目代码(供 search_code 检索)",
26
+ "/search": "搜索索引的代码库",
27
+ "/graph": "显示代码关系图",
28
+ "/memory": "查看记忆系统状态",
29
+ "/save": "保存当前对话到文件",
30
+ "/clear": "清空当前对话历史(保留系统提示词)",
31
+ "/context": "查看当前上下文统计",
32
+ "/exit": "退出程序",
33
+ "/help": "显示帮助信息",
34
+ }
35
+
36
+ @staticmethod
37
+ def parse(line: str) -> Optional[ParsedCommand]:
38
+ stripped = line.strip()
39
+ if not stripped or not stripped.startswith("/"):
40
+ return None
41
+
42
+ parts = shlex.split(stripped)
43
+ command = parts[0].lower()
44
+ args = parts[1:] if len(parts) > 1 else []
45
+
46
+ return ParsedCommand(command=command, args=args, raw_args=stripped)
47
+
48
+ @staticmethod
49
+ def is_command(line: str) -> bool:
50
+ return line.strip().startswith("/")
51
+
52
+ @staticmethod
53
+ def print_help():
54
+ print("可用命令:")
55
+ print(" /init 初始化模型配置")
56
+ print(" /model <preset-id|provider[:<model>]> 切换模型")
57
+ print(" /plan 查看当前执行计划")
58
+ print(" /team 切换多 Agent 协作模式")
59
+ print(" /style <work|pet> 切换展示模式")
60
+ print(" /hitl <mode> 设置审批模式 (auto/always/never)")
61
+ print(" /policy 查看安全策略")
62
+ print(" /audit 查看审计日志")
63
+ print(" /index 索引项目代码")
64
+ print(" /search <query> 搜索代码库")
65
+ print(" /graph 显示代码关系图")
66
+ print(" /memory 查看记忆状态")
67
+ print(" /save <file> 保存对话到文件")
68
+ print(" /clear 清空对话历史")
69
+ print(" /context 查看上下文统计")
70
+ print(" /exit 退出程序")
71
+ print(" /help 显示此帮助")