devlake-mcp 0.4.1__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.
devlake_mcp/cli.py ADDED
@@ -0,0 +1,794 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ DevLake MCP CLI 工具
5
+
6
+ 提供命令行工具,用于初始化项目的 Claude Code 和 Cursor hooks 配置。
7
+
8
+ 命令:
9
+ devlake-mcp init - 初始化 .claude/settings.json 配置(Claude Code)
10
+ devlake-mcp init-cursor - 初始化 ~/.cursor/hooks.json 配置(Cursor IDE)
11
+ devlake-mcp --help - 显示帮助信息
12
+ """
13
+
14
+ import sys
15
+ import json
16
+ import subprocess
17
+ import shutil
18
+ from pathlib import Path
19
+
20
+
21
+ def print_help():
22
+ """打印帮助信息"""
23
+ help_text = """
24
+ DevLake MCP - AI 编程数据采集工具
25
+
26
+ 用法:
27
+ devlake-mcp <command> [options]
28
+
29
+ 命令:
30
+ init 初始化 Claude Code hooks 配置(默认全局: ~/.claude/settings.json)
31
+ init-cursor 初始化 Cursor hooks 配置(默认全局: ~/.cursor/hooks.json)
32
+ retry 手动触发重试失败的上传记录
33
+ queue-status 查看失败队列状态和统计信息
34
+ queue-clean 清理过期的失败记录
35
+ info 显示详细的版本和功能支持信息
36
+ --help, -h 显示此帮助信息
37
+ --version, -v 显示版本号
38
+
39
+ 选项:
40
+ --force, -f 强制完全覆盖已存在的配置文件(不合并,将丢失现有配置)
41
+ --project 使用项目配置(./.claude/ 或 ./.cursor/)而非全局配置
42
+
43
+ 示例:
44
+ # 全局配置(推荐,所有项目共享)
45
+ devlake-mcp init # Claude Code 全局配置 (~/.claude/)
46
+ # 如已有配置,将智能合并 hooks 部分
47
+ devlake-mcp init-cursor # Cursor 全局配置 (~/.cursor/)
48
+
49
+ # 项目配置(仅当前项目)
50
+ cd your-project
51
+ devlake-mcp init --project # Claude Code 项目配置 (./.claude/)
52
+ devlake-mcp init-cursor --project # Cursor 项目配置 (./.cursor/)
53
+
54
+ # 强制完全覆盖(不推荐,除非需要重置配置)
55
+ devlake-mcp init --force # 完全覆盖,丢失现有配置
56
+ devlake-mcp init-cursor --force --project
57
+
58
+ # 重试管理
59
+ devlake-mcp retry # 手动重试失败的上传
60
+ devlake-mcp queue-status # 查看失败队列状态
61
+ devlake-mcp queue-clean # 清理过期记录
62
+
63
+ # 版本信息
64
+ devlake-mcp --version # 显示版本号
65
+ devlake-mcp info # 显示详细版本和功能支持信息
66
+
67
+ 日志位置:
68
+ 全局配置: ~/.claude/logs/ 或 ~/.cursor/logs/
69
+ 项目配置: .claude/logs/ 或 .cursor/logs/
70
+
71
+ 更多信息请访问: https://github.com/engineering-efficiency/devlake-mcp
72
+ """
73
+ print(help_text)
74
+
75
+
76
+ def print_version():
77
+ """打印简洁的版本号(标准格式)"""
78
+ from devlake_mcp import __version__
79
+ print(f"devlake-mcp {__version__}")
80
+
81
+
82
+ def print_info():
83
+ """打印详细的版本和功能支持信息"""
84
+ from devlake_mcp import __version__
85
+ from devlake_mcp.compat import get_version_info
86
+
87
+ info = get_version_info()
88
+
89
+ print("=" * 60)
90
+ print("DevLake MCP - 版本信息")
91
+ print("=" * 60)
92
+ print(f"DevLake MCP: v{__version__}")
93
+ print(f"Python: {info['python_version']}")
94
+
95
+ # 显示功能状态
96
+ print("\n功能支持:")
97
+ print(f" - Hooks 模式: {'✓' if info['features']['hooks'] else '✗'}")
98
+
99
+ if info['mcp_available']:
100
+ print(f" - MCP Server: ✓ (FastMCP {info['fastmcp_version']})")
101
+ elif info['mcp_supported']:
102
+ print(f" - MCP Server: ✗ (未安装 fastmcp)")
103
+ else:
104
+ print(f" - MCP Server: ✗ (需要 Python 3.10+)")
105
+
106
+ # 显示建议
107
+ if info['recommended_action'] != "✓ 所有功能可用":
108
+ print(f"\n建议: {info['recommended_action']}")
109
+
110
+ print("=" * 60)
111
+
112
+
113
+ def get_devlake_hooks_config() -> dict:
114
+ """
115
+ 获取 DevLake hooks 配置(仅包含 hooks 部分)
116
+
117
+ Returns:
118
+ dict: DevLake hooks 配置字典
119
+ """
120
+ return {
121
+ "Stop": [
122
+ {
123
+ "hooks": [
124
+ {
125
+ "type": "command",
126
+ "command": "python3 -m devlake_mcp.hooks.stop",
127
+ "timeout": 5
128
+ }
129
+ ]
130
+ }
131
+ ],
132
+ "SubagentStop": [
133
+ {
134
+ "hooks": [
135
+ {
136
+ "type": "command",
137
+ "command": "python3 -m devlake_mcp.hooks.stop",
138
+ "timeout": 5
139
+ }
140
+ ]
141
+ }
142
+ ],
143
+ "UserPromptSubmit": [
144
+ {
145
+ "hooks": [
146
+ {
147
+ "type": "command",
148
+ "command": "python3 -m devlake_mcp.hooks.user_prompt_submit",
149
+ "timeout": 5
150
+ }
151
+ ]
152
+ }
153
+ ],
154
+ "PreToolUse": [
155
+ {
156
+ "matcher": "Write|Edit|NotebookEdit",
157
+ "hooks": [
158
+ {
159
+ "type": "command",
160
+ "command": "python3 -m devlake_mcp.hooks.pre_tool_use",
161
+ "timeout": 5
162
+ }
163
+ ]
164
+ }
165
+ ],
166
+ "PostToolUse": [
167
+ {
168
+ "matcher": "Write|Edit|NotebookEdit",
169
+ "hooks": [
170
+ {
171
+ "type": "command",
172
+ "command": "python3 -m devlake_mcp.hooks.post_tool_use",
173
+ "timeout": 5
174
+ }
175
+ ]
176
+ }
177
+ ],
178
+ "SessionStart": [
179
+ {
180
+ "hooks": [
181
+ {
182
+ "type": "command",
183
+ "command": "python3 -m devlake_mcp.hooks.session_start",
184
+ "timeout": 5
185
+ }
186
+ ]
187
+ }
188
+ ],
189
+ "SessionEnd": [
190
+ {
191
+ "hooks": [
192
+ {
193
+ "type": "command",
194
+ "command": "python3 -m devlake_mcp.hooks.record_session",
195
+ "timeout": 5
196
+ }
197
+ ]
198
+ }
199
+ ]
200
+ }
201
+
202
+
203
+ def get_settings_template() -> dict:
204
+ """
205
+ 获取完整的 settings.json 模板(用于全新创建)
206
+
207
+ Returns:
208
+ dict: settings.json 配置字典
209
+ """
210
+ return {
211
+ "hooks": get_devlake_hooks_config()
212
+ }
213
+
214
+
215
+ def is_devlake_hook(hook: dict) -> bool:
216
+ """
217
+ 检查一个 hook 是否是 DevLake 的 hook
218
+
219
+ Args:
220
+ hook: hook 配置字典
221
+
222
+ Returns:
223
+ bool: 是否是 DevLake hook
224
+ """
225
+ if hook.get("type") == "command":
226
+ command = hook.get("command", "")
227
+ # 检查是否包含 devlake_mcp.hooks
228
+ return "devlake_mcp.hooks" in command
229
+ return False
230
+
231
+
232
+ def merge_hooks_config(existing_config: dict, devlake_hooks: dict) -> tuple[dict, list[str]]:
233
+ """
234
+ 将 DevLake hooks 配置合并到现有配置中
235
+
236
+ Args:
237
+ existing_config: 现有的 settings.json 配置
238
+ devlake_hooks: DevLake hooks 配置
239
+
240
+ Returns:
241
+ tuple: (合并后的配置, 新增/更新的 hook 事件列表)
242
+ """
243
+ # 创建配置的深拷贝,避免修改原始数据
244
+ import copy
245
+ merged_config = copy.deepcopy(existing_config)
246
+
247
+ # 确保有 hooks 字段
248
+ if "hooks" not in merged_config:
249
+ merged_config["hooks"] = {}
250
+
251
+ added_or_updated = []
252
+
253
+ # 遍历 DevLake 的每个 hook 事件
254
+ for event_name, event_configs in devlake_hooks.items():
255
+ if event_name not in merged_config["hooks"]:
256
+ # 事件不存在,直接添加
257
+ merged_config["hooks"][event_name] = event_configs
258
+ added_or_updated.append(f"{event_name} (新增)")
259
+ else:
260
+ # 事件已存在,需要检查是否已有 DevLake 的 hook
261
+ existing_event_configs = merged_config["hooks"][event_name]
262
+
263
+ # 检查现有配置中是否已有 DevLake hook
264
+ has_devlake_hook = False
265
+ for config_block in existing_event_configs:
266
+ if "hooks" in config_block:
267
+ for hook in config_block["hooks"]:
268
+ if is_devlake_hook(hook):
269
+ has_devlake_hook = True
270
+ break
271
+ if has_devlake_hook:
272
+ break
273
+
274
+ if not has_devlake_hook:
275
+ # 没有 DevLake hook,追加到列表
276
+ # 注意:这里追加整个 event_configs,保持与模板一致
277
+ merged_config["hooks"][event_name].extend(event_configs)
278
+ added_or_updated.append(f"{event_name} (追加)")
279
+ else:
280
+ # 已有 DevLake hook,跳过
281
+ pass
282
+
283
+ return merged_config, added_or_updated
284
+
285
+
286
+ def create_settings_file(force: bool = False, global_config: bool = True) -> bool:
287
+ """
288
+ 创建或更新 .claude/settings.json 配置文件(智能合并模式)
289
+
290
+ Args:
291
+ force: 是否强制覆盖已存在的文件(完全替换,不合并)
292
+ global_config: 是否使用全局配置(True: ~/.claude/settings.json, False: ./.claude/settings.json)
293
+
294
+ Returns:
295
+ bool: 是否成功创建或更新
296
+ """
297
+ if global_config:
298
+ claude_dir = Path.home() / ".claude"
299
+ else:
300
+ claude_dir = Path.cwd() / ".claude"
301
+
302
+ settings_file = claude_dir / "settings.json"
303
+
304
+ # 创建 .claude 目录
305
+ claude_dir.mkdir(parents=True, exist_ok=True)
306
+
307
+ # 获取 DevLake hooks 配置
308
+ devlake_hooks = get_devlake_hooks_config()
309
+
310
+ # 检查文件是否已存在
311
+ if settings_file.exists():
312
+ if force:
313
+ # force 模式:完全覆盖
314
+ print(f"⚠️ 配置文件已存在: {settings_file}")
315
+ response = input("确认完全覆盖(将丢失现有配置)? [y/N]: ")
316
+ if response.lower() not in ['y', 'yes']:
317
+ print("❌ 已取消")
318
+ return False
319
+ print()
320
+
321
+ # 完全覆盖模式
322
+ settings = get_settings_template()
323
+ with open(settings_file, 'w', encoding='utf-8') as f:
324
+ json.dump(settings, f, indent=2, ensure_ascii=False)
325
+
326
+ print(f"✅ 已覆盖配置文件: {settings_file}")
327
+ return True
328
+ else:
329
+ # 智能合并模式
330
+ print(f"📋 检测到现有配置: {settings_file}")
331
+ print(" 将采用智能合并模式(保留您的现有配置)")
332
+ print()
333
+
334
+ try:
335
+ # 读取现有配置
336
+ with open(settings_file, 'r', encoding='utf-8') as f:
337
+ existing_config = json.load(f)
338
+
339
+ # 合并 hooks 配置
340
+ merged_config, added_hooks = merge_hooks_config(existing_config, devlake_hooks)
341
+
342
+ if not added_hooks:
343
+ print("✅ DevLake hooks 已全部配置,无需更新")
344
+ return True
345
+
346
+ # 显示将要添加的 hooks
347
+ print("📝 将要添加/更新的 hooks:")
348
+ for hook_name in added_hooks:
349
+ print(f" • {hook_name}")
350
+ print()
351
+
352
+ response = input("确认更新配置? [Y/n]: ")
353
+ if response.lower() in ['n', 'no']:
354
+ print("❌ 已取消")
355
+ return False
356
+ print()
357
+
358
+ # 写入合并后的配置
359
+ with open(settings_file, 'w', encoding='utf-8') as f:
360
+ json.dump(merged_config, f, indent=2, ensure_ascii=False)
361
+
362
+ print(f"✅ 已更新配置文件: {settings_file}")
363
+ print(f" 新增/更新了 {len(added_hooks)} 个 hook 事件")
364
+ return True
365
+
366
+ except json.JSONDecodeError as e:
367
+ print(f"❌ 错误:无法解析现有配置文件: {e}")
368
+ print(" 建议使用 --force 选项重新创建配置")
369
+ return False
370
+ except Exception as e:
371
+ print(f"❌ 错误:{e}")
372
+ return False
373
+ else:
374
+ # 文件不存在,创建新文件
375
+ settings = get_settings_template()
376
+
377
+ with open(settings_file, 'w', encoding='utf-8') as f:
378
+ json.dump(settings, f, indent=2, ensure_ascii=False)
379
+
380
+ print(f"✅ 创建配置文件: {settings_file}")
381
+ return True
382
+
383
+
384
+ def init_command(force: bool = False, global_config: bool = True):
385
+ """
386
+ 初始化 Claude Code hooks 配置
387
+
388
+ Args:
389
+ force: 是否强制覆盖已存在的文件
390
+ global_config: 是否使用全局配置(默认为 True)
391
+ """
392
+ print("\n🚀 开始初始化 DevLake MCP hooks 配置(Claude Code)...\n")
393
+
394
+ config_scope = "全局配置" if global_config else "项目配置"
395
+ print(f"📌 配置范围:{config_scope}")
396
+ print()
397
+
398
+ # 1. 如果是项目配置,检查是否在 Git 仓库中(可选)
399
+ if not global_config and not Path(".git").exists():
400
+ print("⚠️ 警告:当前目录不是 Git 仓库,建议在项目根目录执行此命令。")
401
+ response = input("是否继续? [y/N]: ")
402
+ if response.lower() not in ['y', 'yes']:
403
+ print("❌ 已取消")
404
+ sys.exit(0)
405
+ print()
406
+
407
+ # 2. 创建 settings.json 文件
408
+ success = create_settings_file(force, global_config)
409
+
410
+ if not success:
411
+ sys.exit(0)
412
+
413
+ # 3. 显示完成信息
414
+ print(f"\n✨ 初始化完成!")
415
+
416
+ # 4. 显示下一步提示
417
+ print("\n📝 下一步:")
418
+ if global_config:
419
+ print(" 1. 重启 Claude Code")
420
+ print(" 2. 配置日志级别(可选):")
421
+ print(" export DEVLAKE_MCP_LOG_LEVEL=INFO # DEBUG, INFO, WARNING, ERROR")
422
+ print()
423
+ print(" 3. 日志位置:~/.claude/logs/")
424
+ else:
425
+ print(" 1. 配置 Git 用户信息(如果未配置):")
426
+ print(" git config user.email 'your-email@example.com'")
427
+ print(" git config user.name 'Your Name'")
428
+ print()
429
+ print(" 2. 配置 Git 远程仓库(如果未配置):")
430
+ print(" git remote add origin <repository-url>")
431
+ print()
432
+ print(" 3. 日志位置:.claude/logs/")
433
+ print()
434
+ print(" 开始使用 Claude Code,hooks 会自动工作!")
435
+ print()
436
+
437
+
438
+ def get_cursor_hooks_template() -> dict:
439
+ """
440
+ 获取 Cursor hooks.json 模板
441
+
442
+ Returns:
443
+ dict: hooks.json 配置字典
444
+ """
445
+ return {
446
+ "version": 1,
447
+ "hooks": {
448
+ "beforeSubmitPrompt": [
449
+ {
450
+ "command": "python3 -m devlake_mcp.hooks.cursor.before_submit_prompt"
451
+ }
452
+ ],
453
+ "afterAgentResponse": [
454
+ {
455
+ "command": "python3 -m devlake_mcp.hooks.cursor.after_agent_response"
456
+ }
457
+ ],
458
+ "beforeReadFile": [
459
+ {
460
+ "command": "python3 -m devlake_mcp.hooks.cursor.before_read_file"
461
+ }
462
+ ],
463
+ "beforeShellExecution": [
464
+ {
465
+ "command": "python3 -m devlake_mcp.hooks.cursor.before_shell_execution"
466
+ }
467
+ ],
468
+ "afterShellExecution": [
469
+ {
470
+ "command": "python3 -m devlake_mcp.hooks.cursor.after_shell_execution"
471
+ }
472
+ ],
473
+ "afterFileEdit": [
474
+ {
475
+ "command": "python3 -m devlake_mcp.hooks.cursor.after_file_edit"
476
+ }
477
+ ],
478
+ "stop": [
479
+ {
480
+ "command": "python3 -m devlake_mcp.hooks.cursor.stop_hook"
481
+ }
482
+ ]
483
+ }
484
+ }
485
+
486
+
487
+ def check_python3():
488
+ """检查 Python 3 是否可用"""
489
+ if not shutil.which('python3'):
490
+ print("❌ 错误:未找到 python3,请先安装 Python 3")
491
+ sys.exit(1)
492
+ print("✅ Python 3 已安装")
493
+
494
+
495
+ def check_devlake_mcp_installed():
496
+ """检查 devlake-mcp 模块是否已安装"""
497
+ try:
498
+ import devlake_mcp
499
+ print("✅ devlake-mcp 模块已安装")
500
+ return True
501
+ except ImportError:
502
+ print("❌ 错误:devlake-mcp 模块未安装")
503
+ print()
504
+ print("请先安装 devlake-mcp:")
505
+ print(" pipx install devlake-mcp")
506
+ print(" 或")
507
+ print(" pip install -e .")
508
+ sys.exit(1)
509
+
510
+
511
+ def check_git_config():
512
+ """检查 Git 配置"""
513
+ try:
514
+ result = subprocess.run(['git', 'config', 'user.name'], capture_output=True, text=True)
515
+ git_user = result.stdout.strip()
516
+
517
+ result = subprocess.run(['git', 'config', 'user.email'], capture_output=True, text=True)
518
+ git_email = result.stdout.strip()
519
+
520
+ if not git_user or not git_email:
521
+ print()
522
+ print("⚠️ 警告:Git 用户信息未配置")
523
+ print("请配置 Git 用户信息:")
524
+ print(" git config --global user.name \"Your Name\"")
525
+ print(" git config --global user.email \"your.email@example.com\"")
526
+ return False
527
+
528
+ print(f"✅ Git 配置已设置 ({git_user} <{git_email}>)")
529
+ return True
530
+ except FileNotFoundError:
531
+ print("⚠️ 警告:未找到 git 命令")
532
+ return False
533
+
534
+
535
+ def create_cursor_hooks_file(force: bool = False, global_config: bool = True) -> bool:
536
+ """
537
+ 创建 Cursor hooks.json 配置文件
538
+
539
+ Args:
540
+ force: 是否强制覆盖已存在的文件
541
+ global_config: 是否使用全局配置(True: ~/.cursor/hooks.json, False: ./.cursor/hooks.json)
542
+
543
+ Returns:
544
+ bool: 是否成功创建
545
+ """
546
+ if global_config:
547
+ cursor_dir = Path.home() / ".cursor"
548
+ else:
549
+ cursor_dir = Path.cwd() / ".cursor"
550
+
551
+ hooks_file = cursor_dir / "hooks.json"
552
+
553
+ # 检查文件是否已存在
554
+ if hooks_file.exists() and not force:
555
+ print(f"⚠️ 配置文件已存在: {hooks_file}")
556
+
557
+ # 备份现有文件
558
+ backup_file = cursor_dir / "hooks.json.backup"
559
+ shutil.copy2(hooks_file, backup_file)
560
+ print(f"✅ 已备份现有配置: {backup_file}")
561
+
562
+ response = input("是否覆盖? [y/N]: ")
563
+ if response.lower() not in ['y', 'yes']:
564
+ print("❌ 已取消")
565
+ return False
566
+ print()
567
+
568
+ # 创建 .cursor 目录
569
+ cursor_dir.mkdir(parents=True, exist_ok=True)
570
+
571
+ # 获取模板并写入文件
572
+ hooks = get_cursor_hooks_template()
573
+
574
+ with open(hooks_file, 'w', encoding='utf-8') as f:
575
+ json.dump(hooks, f, indent=2, ensure_ascii=False)
576
+
577
+ print(f"✅ 创建配置文件: {hooks_file}")
578
+ return True
579
+
580
+
581
+ def init_cursor_command(force: bool = False, global_config: bool = True):
582
+ """
583
+ 初始化 Cursor hooks 配置
584
+
585
+ Args:
586
+ force: 是否强制覆盖已存在的文件
587
+ global_config: 是否使用全局配置(默认为 True)
588
+ """
589
+ print("\n🚀 开始初始化 Cursor hooks 配置...\n")
590
+
591
+ config_scope = "全局配置" if global_config else "项目配置"
592
+ print(f"📌 配置范围:{config_scope}")
593
+ print("=" * 60)
594
+
595
+ # 1. 检查 Python 3
596
+ check_python3()
597
+
598
+ # 2. 检查 devlake-mcp 模块
599
+ check_devlake_mcp_installed()
600
+
601
+ # 3. 检查 Git 配置(警告但不阻止)
602
+ check_git_config()
603
+
604
+ print("=" * 60)
605
+ print()
606
+
607
+ # 4. 创建 hooks.json 文件
608
+ success = create_cursor_hooks_file(force, global_config)
609
+
610
+ if not success:
611
+ sys.exit(0)
612
+
613
+ # 5. 显示完成信息
614
+ print("\n✨ Cursor hooks 初始化完成!")
615
+
616
+ # 6. 显示下一步提示
617
+ print("\n📝 下一步:")
618
+ print(" 1. 重启 Cursor IDE")
619
+ print(" 2. 在 Cursor 设置中查看 Hooks 选项卡,确认 hooks 已激活")
620
+ print(" 3. 配置日志级别(可选):")
621
+ print(" export DEVLAKE_MCP_LOG_LEVEL=INFO # DEBUG, INFO, WARNING, ERROR")
622
+ print()
623
+ if global_config:
624
+ print(" 4. 日志位置:~/.cursor/logs/")
625
+ else:
626
+ print(" 4. 日志位置:.cursor/logs/")
627
+ print()
628
+ print(" 5. 开始使用 Cursor Agent,hooks 会自动采集数据!")
629
+ print()
630
+ print("📚 详细文档:")
631
+ print(" - 使用指南:CURSOR_HOOKS.md")
632
+ if global_config:
633
+ print(" - 故障排查:查看 ~/.cursor/logs/cursor_*.log")
634
+ else:
635
+ print(" - 故障排查:查看 .cursor/logs/cursor_*.log")
636
+ print()
637
+
638
+
639
+ def retry_command():
640
+ """手动触发重试失败的上传记录"""
641
+ from devlake_mcp.retry_queue import retry_failed_uploads, get_retry_config
642
+
643
+ print("\n🔄 开始重试失败的上传记录...\n")
644
+
645
+ config = get_retry_config()
646
+ if not config['enabled']:
647
+ print("⚠️ 重试功能已禁用(DEVLAKE_RETRY_ENABLED=false)")
648
+ print(" 如需启用,请设置环境变量:")
649
+ print(" export DEVLAKE_RETRY_ENABLED=true")
650
+ return
651
+
652
+ print(f"配置:")
653
+ print(f" - 最大重试次数:{config['max_attempts']}")
654
+ print(f" - 记录保留天数:{config['cleanup_days']}")
655
+ print()
656
+
657
+ # 执行重试(不限制数量)
658
+ stats = retry_failed_uploads(max_parallel=999)
659
+
660
+ # 显示结果
661
+ print("\n📊 重试统计:")
662
+ print(f" - 检查记录数:{stats['checked']}")
663
+ print(f" - 尝试重试数:{stats['retried']}")
664
+ print(f" - 重试成功数:{stats['succeeded']} ✅")
665
+ print(f" - 重试失败数:{stats['failed']} ❌")
666
+ print(f" - 跳过记录数:{stats['skipped']} ⏭️")
667
+ print()
668
+
669
+ if stats['succeeded'] > 0:
670
+ print(f"✨ 成功重试 {stats['succeeded']} 条记录!")
671
+ elif stats['retried'] == 0:
672
+ print("💡 没有需要重试的记录")
673
+ else:
674
+ print("⚠️ 部分记录重试失败,将在下次自动重试")
675
+
676
+
677
+ def queue_status_command():
678
+ """查看失败队列状态和统计信息"""
679
+ from devlake_mcp.retry_queue import get_queue_statistics, get_retry_config
680
+
681
+ print("\n📊 失败队列状态\n")
682
+
683
+ config = get_retry_config()
684
+ stats = get_queue_statistics()
685
+
686
+ # 显示配置
687
+ print("⚙️ 重试配置:")
688
+ print(f" - 启用状态:{'✅ 已启用' if config['enabled'] else '❌ 已禁用'}")
689
+ print(f" - 最大重试次数:{config['max_attempts']}")
690
+ print(f" - 记录保留天数:{config['cleanup_days']}")
691
+ print(f" - Hook 触发检查:{'✅ 已启用' if config['check_on_hook'] else '❌ 已禁用'}")
692
+ print()
693
+
694
+ # 显示总体统计
695
+ summary = stats['summary']
696
+ print("📈 总体统计:")
697
+ print(f" - 总记录数:{summary['total']}")
698
+ print(f" - 待重试数:{summary['pending']}")
699
+ print(f" - 已达最大重试次数:{summary['max_retried']}")
700
+ print()
701
+
702
+ # 显示各队列详情
703
+ if summary['total'] > 0:
704
+ print("📋 队列详情:")
705
+ for queue_type in ['session', 'prompt', 'file_change']:
706
+ queue_stats = stats[queue_type]
707
+ if queue_stats['total'] > 0:
708
+ queue_name = {
709
+ 'session': 'Session 会话',
710
+ 'prompt': 'Prompt 提示',
711
+ 'file_change': '文件变更'
712
+ }[queue_type]
713
+ print(f" - {queue_name}:总数 {queue_stats['total']}, "
714
+ f"待重试 {queue_stats['pending']}, "
715
+ f"已达上限 {queue_stats['max_retried']}")
716
+ print()
717
+
718
+ if summary['total'] == 0:
719
+ print("✨ 队列为空,没有失败记录!")
720
+ elif summary['pending'] > 0:
721
+ print(f"💡 提示:有 {summary['pending']} 条记录待重试")
722
+ print(" 可运行 'devlake-mcp retry' 手动触发重试")
723
+
724
+
725
+ def queue_clean_command():
726
+ """清理过期的失败记录"""
727
+ from devlake_mcp.retry_queue import cleanup_expired_failures, get_retry_config
728
+
729
+ print("\n🧹 清理过期的失败记录...\n")
730
+
731
+ config = get_retry_config()
732
+ max_age_hours = config['cleanup_days'] * 24
733
+
734
+ print(f"清理条件:")
735
+ print(f" - 超过 {config['cleanup_days']} 天的记录")
736
+ print(f" - 已达最大重试次数 ({config['max_attempts']}) 的记录")
737
+ print()
738
+
739
+ # 执行清理
740
+ cleaned_count = cleanup_expired_failures(max_age_hours=max_age_hours)
741
+
742
+ # 显示结果
743
+ if cleaned_count > 0:
744
+ print(f"✅ 已清理 {cleaned_count} 条过期记录")
745
+ else:
746
+ print("💡 没有需要清理的记录")
747
+
748
+
749
+ def main():
750
+ """CLI 主入口
751
+
752
+ 无参数运行时启动 MCP 服务器,有参数时执行 CLI 命令。
753
+ """
754
+ # 无参数时启动 MCP 服务器(用于 Claude Desktop 集成)
755
+ if len(sys.argv) < 2:
756
+ from devlake_mcp.server import main as server_main
757
+ server_main()
758
+ return
759
+
760
+ command = sys.argv[1]
761
+
762
+ # 处理命令
763
+ if command in ['--help', '-h', 'help']:
764
+ print_help()
765
+ elif command in ['--version', '-v', 'version']:
766
+ print_version()
767
+ elif command == 'info':
768
+ print_info()
769
+ elif command == 'init':
770
+ # 检查参数
771
+ force = '--force' in sys.argv or '-f' in sys.argv
772
+ # 默认全局配置,除非明确指定 --project
773
+ global_config = '--project' not in sys.argv
774
+ init_command(force=force, global_config=global_config)
775
+ elif command == 'init-cursor':
776
+ # 检查参数
777
+ force = '--force' in sys.argv or '-f' in sys.argv
778
+ # 默认全局配置,除非明确指定 --project
779
+ global_config = '--project' not in sys.argv
780
+ init_cursor_command(force=force, global_config=global_config)
781
+ elif command == 'retry':
782
+ retry_command()
783
+ elif command == 'queue-status':
784
+ queue_status_command()
785
+ elif command == 'queue-clean':
786
+ queue_clean_command()
787
+ else:
788
+ print(f"❌ 错误:未知命令: {command}")
789
+ print_help()
790
+ sys.exit(1)
791
+
792
+
793
+ if __name__ == '__main__':
794
+ main()