devmate 2.3.0__tar.gz → 2.4.0__tar.gz
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.
- {devmate-2.3.0 → devmate-2.4.0}/PKG-INFO +1 -1
- {devmate-2.3.0 → devmate-2.4.0}/pyproject.toml +1 -1
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/__init__.py +1 -1
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/app/cli.py +534 -3
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/app/web/__init__.py +355 -24
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/rag/__init__.py +68 -0
- devmate-2.4.0/src/devmate/workflow/__init__.py +865 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate.egg-info/PKG-INFO +1 -1
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate_agent/__init__.py +298 -7
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate_monitor/__init__.py +164 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate_toolkit/__init__.py +82 -0
- devmate-2.3.0/src/devmate/workflow/__init__.py +0 -315
- {devmate-2.3.0 → devmate-2.4.0}/README.md +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/setup.cfg +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/__main__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/ai_config/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/ai_scenarios/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/app/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/app/tui.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/app/web/cli_docs.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/bus/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/bus/bus.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/bus/command.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/core/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/core/config.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/core/logging.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/core/security.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/events/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/events/bus.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/events/domain_event.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/mcp/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/module/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/module/registry.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/plugin/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/plugin_market/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/plugin_market/ratings.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/plugin_market/remote.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/security/audit.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate/security/vault.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate.egg-info/SOURCES.txt +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate.egg-info/dependency_links.txt +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate.egg-info/entry_points.txt +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate.egg-info/requires.txt +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate.egg-info/top_level.txt +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate_apidev/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate_apihub/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate_dbadmin/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate_gitflow/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate_notekeeper/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate_publisher/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate_regexlab/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate_reporter/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate_scaffold/__init__.py +0 -0
- {devmate-2.3.0 → devmate-2.4.0}/src/devmate_sshman/__init__.py +0 -0
|
@@ -276,6 +276,53 @@ def tk_dup(path, min_size):
|
|
|
276
276
|
console.print(f" [dim]{f}[/dim]")
|
|
277
277
|
|
|
278
278
|
|
|
279
|
+
@tk_group.command("dedup", help="清理重复文件(默认预览)")
|
|
280
|
+
@click.argument("path", default=".")
|
|
281
|
+
@click.option("--apply", is_flag=True, help="实际删除(默认只预览)")
|
|
282
|
+
@click.option("--min-size", default=1, type=int, help="最小文件大小(字节)")
|
|
283
|
+
@click.option("--keep", type=click.Choice(["first", "last"]), default="first",
|
|
284
|
+
help="每组保留哪个:first=最早(默认) last=最新")
|
|
285
|
+
def tk_dedup(path, apply, min_size, keep):
|
|
286
|
+
"""清理重复文件——安全模式
|
|
287
|
+
|
|
288
|
+
默认只预览(列出将删除的文件,不实际删除)。
|
|
289
|
+
确认无误后加 --apply 才真正删除。每组保留最早(first)或最新(last)的副本。
|
|
290
|
+
"""
|
|
291
|
+
from rich.table import Table
|
|
292
|
+
|
|
293
|
+
from devmate_toolkit import clean_duplicates
|
|
294
|
+
|
|
295
|
+
result = clean_duplicates(path, dry_run=not apply,
|
|
296
|
+
min_size=min_size, keep=keep)
|
|
297
|
+
if not result.success:
|
|
298
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
299
|
+
return
|
|
300
|
+
|
|
301
|
+
data = result.data
|
|
302
|
+
mode_style = "yellow" if data["mode"] == "dry_run" else "green"
|
|
303
|
+
console.print(f"\n[{mode_style}]{data['message']}[/{mode_style}]\n")
|
|
304
|
+
|
|
305
|
+
if data["to_delete"]:
|
|
306
|
+
table = Table(title=f"待删除文件({data['to_delete_count']} 个,共 {data['freed']})")
|
|
307
|
+
table.add_column("#", style="dim", width=4)
|
|
308
|
+
table.add_column("文件路径", style="cyan")
|
|
309
|
+
for i, f in enumerate(data["to_delete"], 1):
|
|
310
|
+
table.add_row(str(i), f)
|
|
311
|
+
console.print(table)
|
|
312
|
+
|
|
313
|
+
if data["kept"]:
|
|
314
|
+
console.print("[bold]保留的副本:[/bold]")
|
|
315
|
+
for k in data["kept"][:10]:
|
|
316
|
+
console.print(f" [green]✓[/green] [dim]{k}[/dim]")
|
|
317
|
+
|
|
318
|
+
if data["mode"] == "dry_run":
|
|
319
|
+
console.print("\n[dim]提示: 确认无误后加 [yellow]--apply[/yellow] 实际删除[/dim]")
|
|
320
|
+
elif data.get("errors"):
|
|
321
|
+
console.print(f"\n[red]⚠️ {len(data['errors'])} 个文件删除失败:[/red]")
|
|
322
|
+
for e in data["errors"][:5]:
|
|
323
|
+
console.print(f" [red]{e}[/red]")
|
|
324
|
+
|
|
325
|
+
|
|
279
326
|
@tk_group.command("json", help="JSON 格式化/校验/压缩")
|
|
280
327
|
@click.option("--path", type=click.Path(exists=False), help="JSON 文件路径")
|
|
281
328
|
@click.option("--text", "-t", help="JSON 字符串")
|
|
@@ -1068,8 +1115,16 @@ def agent_group():
|
|
|
1068
1115
|
@agent_group.command("chat", help="与 Agent 对话,完成任务")
|
|
1069
1116
|
@click.argument("message")
|
|
1070
1117
|
@click.option("--system", "-s", help="自定义系统提示词")
|
|
1071
|
-
|
|
1072
|
-
|
|
1118
|
+
@click.option("--dry-run", is_flag=True, help="预览模式:只展示执行计划,不执行")
|
|
1119
|
+
@click.option("--confirm", is_flag=True, help="执行前确认(先预览计划,确认后执行)")
|
|
1120
|
+
@click.option("--mcp", is_flag=True, help="连接已配置的外部 MCP 工具")
|
|
1121
|
+
def agent_chat(message, system, dry_run, confirm, mcp):
|
|
1122
|
+
"""向 Agent 发送任务,Agent 自动调用 LLM + 11 个工具完成
|
|
1123
|
+
|
|
1124
|
+
--dry-run 仅生成执行计划不执行(信任机制)
|
|
1125
|
+
--confirm 先展示计划,确认后执行(信任机制)
|
|
1126
|
+
--mcp 动态加载已连接的外部 MCP Server 工具(双向兼容)
|
|
1127
|
+
"""
|
|
1073
1128
|
import uuid
|
|
1074
1129
|
|
|
1075
1130
|
from rich.table import Table
|
|
@@ -1081,8 +1136,41 @@ def agent_chat(message, system):
|
|
|
1081
1136
|
if debug:
|
|
1082
1137
|
console.print("[dim]🔍 调试模式 (DEVMATE_AGENT_DEBUG=1)[/dim]")
|
|
1083
1138
|
|
|
1139
|
+
runtime = AgentRuntime(system_prompt=system or "", connect_mcp=mcp)
|
|
1140
|
+
if mcp and runtime.external_servers:
|
|
1141
|
+
console.print(f"[dim]🔌 已连接 {len(runtime.external_servers)} 个外部 MCP Server,"
|
|
1142
|
+
f"Agent 工具总数: {len(runtime.tools.list_names())}[/dim]")
|
|
1143
|
+
elif mcp:
|
|
1144
|
+
console.print("[dim]🔌 未配置外部 MCP Server(devmate mcp connect <url> 添加)[/dim]")
|
|
1145
|
+
|
|
1146
|
+
# 信任机制:先预览计划
|
|
1147
|
+
if dry_run or confirm:
|
|
1148
|
+
console.print("[dim]🤖 生成执行计划中…(预览模式,不执行)[/dim]\n")
|
|
1149
|
+
result = runtime.run(message, dry_run=True)
|
|
1150
|
+
data = result.data
|
|
1151
|
+
save_trace(session_id, message, result)
|
|
1152
|
+
|
|
1153
|
+
if data["output"]:
|
|
1154
|
+
console.print(f"[cyan]{data['output']}[/cyan]\n")
|
|
1155
|
+
|
|
1156
|
+
if data.get("planned_tools"):
|
|
1157
|
+
console.print("[yellow]📋 将执行以下工具操作:[/yellow]")
|
|
1158
|
+
for i, t in enumerate(data["planned_tools"], 1):
|
|
1159
|
+
console.print(f" {i}. [cyan]{t}[/cyan]")
|
|
1160
|
+
|
|
1161
|
+
if dry_run:
|
|
1162
|
+
console.print("\n[dim]提示: 确认无误后去掉 --dry-run 实际执行[/dim]")
|
|
1163
|
+
console.print(f"\n[dim]Trace ID: {session_id}[/dim]")
|
|
1164
|
+
return
|
|
1165
|
+
|
|
1166
|
+
# confirm 模式:询问确认
|
|
1167
|
+
if not click.confirm("\n是否执行以上操作?", default=True):
|
|
1168
|
+
console.print("[yellow]已取消执行[/yellow]")
|
|
1169
|
+
console.print(f"\n[dim]Trace ID: {session_id}[/dim]")
|
|
1170
|
+
return
|
|
1171
|
+
console.print("\n[dim]🤖 开始执行…[/dim]\n")
|
|
1172
|
+
|
|
1084
1173
|
console.print("[dim]🤖 Agent v2.0 思考中…(LLM + 工具调用)[/dim]\n")
|
|
1085
|
-
runtime = AgentRuntime(system_prompt=system or "")
|
|
1086
1174
|
result = runtime.run(message)
|
|
1087
1175
|
data = result.data
|
|
1088
1176
|
save_trace(session_id, message, result)
|
|
@@ -1108,9 +1196,85 @@ def agent_chat(message, system):
|
|
|
1108
1196
|
table.add_row(str(s["step"]), s.get("type", ""), tool_name, status, result_text)
|
|
1109
1197
|
console.print(table)
|
|
1110
1198
|
|
|
1199
|
+
# 变更回滚提示
|
|
1200
|
+
snapshots = runtime.list_snapshots()
|
|
1201
|
+
if snapshots.success and snapshots.data["total"] > 0:
|
|
1202
|
+
console.print(f"\n[dim]💾 本次执行有 {snapshots.data['total']} 处文件变更,"
|
|
1203
|
+
f"可用 [yellow]devmate agent rollback[/yellow] 回滚[/dim]")
|
|
1204
|
+
|
|
1111
1205
|
console.print(f"\n[dim]Trace ID: {session_id}[/dim]")
|
|
1112
1206
|
|
|
1113
1207
|
|
|
1208
|
+
@agent_group.command("rollback", help="回滚 Agent 文件变更")
|
|
1209
|
+
@click.option("--index", type=int, help="回滚第几次变更(默认最后一次)")
|
|
1210
|
+
def agent_rollback(index):
|
|
1211
|
+
"""回滚 Agent 执行产生的文件变更(信任机制)"""
|
|
1212
|
+
from devmate_agent import AgentRuntime
|
|
1213
|
+
|
|
1214
|
+
runtime = AgentRuntime()
|
|
1215
|
+
result = runtime.rollback(index)
|
|
1216
|
+
if result.success:
|
|
1217
|
+
console.print(f"[green]{result.data['message']}[/green]")
|
|
1218
|
+
else:
|
|
1219
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
1220
|
+
|
|
1221
|
+
|
|
1222
|
+
@agent_group.command("snapshots", help="查看 Agent 变更快照")
|
|
1223
|
+
def agent_snapshots():
|
|
1224
|
+
"""列出 Agent 执行的变更快照(可回滚)"""
|
|
1225
|
+
from rich.table import Table
|
|
1226
|
+
|
|
1227
|
+
from devmate_agent import AgentRuntime
|
|
1228
|
+
|
|
1229
|
+
runtime = AgentRuntime()
|
|
1230
|
+
result = runtime.list_snapshots()
|
|
1231
|
+
if not result.success:
|
|
1232
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
1233
|
+
return
|
|
1234
|
+
data = result.data
|
|
1235
|
+
if data["total"] == 0:
|
|
1236
|
+
console.print("[yellow]暂无变更快照[/yellow]")
|
|
1237
|
+
return
|
|
1238
|
+
table = Table(title=f"变更快照({data['total']} 处,从新到旧)")
|
|
1239
|
+
table.add_column("时间", style="dim")
|
|
1240
|
+
table.add_column("文件", style="cyan")
|
|
1241
|
+
table.add_column("大小", style="yellow")
|
|
1242
|
+
for s in data["snapshots"]:
|
|
1243
|
+
table.add_row(s["time"], s["path"], f"{s['size']}B")
|
|
1244
|
+
console.print(table)
|
|
1245
|
+
console.print("[dim]回滚: devmate agent rollback [--index N][/dim]")
|
|
1246
|
+
|
|
1247
|
+
|
|
1248
|
+
@agent_group.command("audit", help="查看 Agent 操作审计")
|
|
1249
|
+
@click.option("--limit", default=20, help="显示条数")
|
|
1250
|
+
def agent_audit(limit):
|
|
1251
|
+
"""查看 Agent 每次工具调用的审计记录(信任机制)"""
|
|
1252
|
+
from rich.table import Table
|
|
1253
|
+
|
|
1254
|
+
from devmate_agent import AgentRuntime
|
|
1255
|
+
|
|
1256
|
+
runtime = AgentRuntime()
|
|
1257
|
+
result = runtime.audit_log(limit=limit)
|
|
1258
|
+
if not result.success:
|
|
1259
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
1260
|
+
return
|
|
1261
|
+
data = result.data
|
|
1262
|
+
if data["total"] == 0:
|
|
1263
|
+
console.print("[yellow]暂无审计记录[/yellow]")
|
|
1264
|
+
return
|
|
1265
|
+
table = Table(title=f"Agent 操作审计({data['total']} 条)")
|
|
1266
|
+
table.add_column("时间", style="dim")
|
|
1267
|
+
table.add_column("模式", style="cyan")
|
|
1268
|
+
table.add_column("工具", style="yellow")
|
|
1269
|
+
table.add_column("参数", style="white")
|
|
1270
|
+
table.add_column("耗时", style="dim", justify="right")
|
|
1271
|
+
for e in data["entries"]:
|
|
1272
|
+
args = json.dumps(e.get("args", {}), ensure_ascii=False)[:40] # noqa: F821
|
|
1273
|
+
table.add_row(e["time"], e["action"], e["tool"], args,
|
|
1274
|
+
f"{e['duration_ms']}ms")
|
|
1275
|
+
console.print(table)
|
|
1276
|
+
|
|
1277
|
+
|
|
1114
1278
|
@agent_group.command("tools", help="列出 Agent 可用工具")
|
|
1115
1279
|
def agent_tools():
|
|
1116
1280
|
"""列出 Agent 可调用的所有工具"""
|
|
@@ -2065,6 +2229,124 @@ def top_port(port):
|
|
|
2065
2229
|
console.print(table)
|
|
2066
2230
|
|
|
2067
2231
|
|
|
2232
|
+
@top_group.command("net", help="网络流量实时监控")
|
|
2233
|
+
@click.option("--interval", default=1.0, type=float, help="采样间隔秒数")
|
|
2234
|
+
def top_net(interval):
|
|
2235
|
+
"""实时网络流量(收/发速率 + 总流量)"""
|
|
2236
|
+
from rich.table import Table
|
|
2237
|
+
|
|
2238
|
+
from devmate_monitor import network_usage
|
|
2239
|
+
|
|
2240
|
+
console.print(f"[dim]采样中({interval}s)…[/dim]")
|
|
2241
|
+
result = network_usage(interval=interval)
|
|
2242
|
+
if not result.success:
|
|
2243
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
2244
|
+
return
|
|
2245
|
+
|
|
2246
|
+
data = result.data
|
|
2247
|
+
table = Table(title="🌐 网络流量")
|
|
2248
|
+
table.add_column("方向", style="cyan", width=10)
|
|
2249
|
+
table.add_column("速率", style="yellow")
|
|
2250
|
+
table.add_column("总流量", style="green")
|
|
2251
|
+
table.add_row("📥 接收", data["recv_speed"], data["total_recv"])
|
|
2252
|
+
table.add_row("📤 发送", data["sent_speed"], data["total_sent"])
|
|
2253
|
+
console.print(table)
|
|
2254
|
+
|
|
2255
|
+
|
|
2256
|
+
@top_group.command("disk", help="所有磁盘分区用量")
|
|
2257
|
+
def top_disk():
|
|
2258
|
+
"""列出所有磁盘分区/挂载点用量(按使用率排序)"""
|
|
2259
|
+
from rich.table import Table
|
|
2260
|
+
|
|
2261
|
+
from devmate_monitor import disk_usage_all
|
|
2262
|
+
|
|
2263
|
+
result = disk_usage_all()
|
|
2264
|
+
if not result.success:
|
|
2265
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
2266
|
+
return
|
|
2267
|
+
|
|
2268
|
+
partitions = result.data["partitions"]
|
|
2269
|
+
if not partitions:
|
|
2270
|
+
console.print("[yellow]未检测到磁盘分区[/yellow]")
|
|
2271
|
+
return
|
|
2272
|
+
|
|
2273
|
+
table = Table(title=f"💾 磁盘分区({result.data['total']} 个,按使用率排序)")
|
|
2274
|
+
table.add_column("挂载点", style="cyan")
|
|
2275
|
+
table.add_column("设备", style="dim")
|
|
2276
|
+
table.add_column("已用", style="yellow")
|
|
2277
|
+
table.add_column("可用", style="green")
|
|
2278
|
+
table.add_column("总容量", style="white")
|
|
2279
|
+
table.add_column("使用率", style="bold", justify="right")
|
|
2280
|
+
for p in partitions:
|
|
2281
|
+
percent = p["percent"]
|
|
2282
|
+
color = "red" if percent >= 90 else ("yellow" if percent >= 75 else "green")
|
|
2283
|
+
table.add_row(p["mountpoint"], p["device"], p["used"],
|
|
2284
|
+
p["free"], p["total"], f"[{color}]{percent:.1f}%[/{color}]")
|
|
2285
|
+
console.print(table)
|
|
2286
|
+
|
|
2287
|
+
|
|
2288
|
+
@top_group.command("alert", help="资源阈值告警检查")
|
|
2289
|
+
@click.option("--cpu", default=80, type=int, help="CPU 阈值 %")
|
|
2290
|
+
@click.option("--mem", default=80, type=int, help="内存阈值 %")
|
|
2291
|
+
@click.option("--disk", default=90, type=int, help="磁盘阈值 %")
|
|
2292
|
+
def top_alert(cpu, mem, disk):
|
|
2293
|
+
"""检查 CPU/内存/磁盘是否超过阈值"""
|
|
2294
|
+
from rich.table import Table
|
|
2295
|
+
|
|
2296
|
+
from devmate_monitor import alert_check
|
|
2297
|
+
|
|
2298
|
+
result = alert_check(cpu_limit=cpu, mem_limit=mem, disk_limit=disk)
|
|
2299
|
+
if not result.success:
|
|
2300
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
2301
|
+
return
|
|
2302
|
+
|
|
2303
|
+
data = result.data
|
|
2304
|
+
status_icon = "[red]🚨 告警[/red]" if data["status"] == "ALERT" else "[green]✅ 正常[/green]"
|
|
2305
|
+
console.print(f"\n{status_icon}({data['alert_count']} 项告警)\n")
|
|
2306
|
+
|
|
2307
|
+
table = Table(title=f"阈值: CPU>{cpu}% 内存>{mem}% 磁盘>{disk}%")
|
|
2308
|
+
table.add_column("指标", style="cyan")
|
|
2309
|
+
table.add_column("当前值", style="white")
|
|
2310
|
+
table.add_column("阈值", style="dim")
|
|
2311
|
+
table.add_column("状态", style="bold")
|
|
2312
|
+
for name, info in data["summary"].items():
|
|
2313
|
+
icon = "[red]🚨[/red]" if info["alert"] else "[green]✅[/green]"
|
|
2314
|
+
table.add_row(name, f"{info['value']:.1f}{info['unit']}",
|
|
2315
|
+
f"{info['limit']}{info['unit']}", icon)
|
|
2316
|
+
console.print(table)
|
|
2317
|
+
|
|
2318
|
+
if data["alerts"]:
|
|
2319
|
+
console.print("\n[bold red]告警详情:[/bold red]")
|
|
2320
|
+
for a in data["alerts"]:
|
|
2321
|
+
console.print(f" [red]⚠️ {a}[/red]")
|
|
2322
|
+
|
|
2323
|
+
|
|
2324
|
+
@top_group.command("battery", help="电池状态")
|
|
2325
|
+
def top_battery():
|
|
2326
|
+
"""电池状态(笔记本;台式机可能不可用)"""
|
|
2327
|
+
from devmate_monitor import battery_status
|
|
2328
|
+
|
|
2329
|
+
result = battery_status()
|
|
2330
|
+
if not result.success:
|
|
2331
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
2332
|
+
return
|
|
2333
|
+
|
|
2334
|
+
data = result.data
|
|
2335
|
+
if not data.get("available"):
|
|
2336
|
+
console.print(f"[yellow]🔋 {data.get('message', '电池不可用')}[/yellow]")
|
|
2337
|
+
return
|
|
2338
|
+
|
|
2339
|
+
percent = data["percent"]
|
|
2340
|
+
bar = "█" * (percent // 5) + "░" * (20 - percent // 5)
|
|
2341
|
+
color = "green" if percent > 50 else ("yellow" if percent > 20 else "red")
|
|
2342
|
+
console.print(f"\n🔋 [bold]电池: {percent}%[/bold] [{color}]{bar}[/{color}]")
|
|
2343
|
+
console.print(f"状态: {data['status']}")
|
|
2344
|
+
secs = data.get("remaining_sec")
|
|
2345
|
+
if isinstance(secs, int) and secs >= 0:
|
|
2346
|
+
mins, _ = divmod(secs, 60)
|
|
2347
|
+
console.print(f"剩余时间: ~{mins} 分钟")
|
|
2348
|
+
|
|
2349
|
+
|
|
2068
2350
|
@cli.group("report", help="日报/周报自动生成")
|
|
2069
2351
|
def report_group():
|
|
2070
2352
|
"""reporter — 报告生成"""
|
|
@@ -2919,6 +3201,58 @@ def wf_rm_step(name, index):
|
|
|
2919
3201
|
else f"[red]❌ {result.error}[/red]")
|
|
2920
3202
|
|
|
2921
3203
|
|
|
3204
|
+
@wf_group.command("template", help="管理工作流模板")
|
|
3205
|
+
@click.argument("action", type=click.Choice(["list", "new"]))
|
|
3206
|
+
@click.argument("name", required=False)
|
|
3207
|
+
@click.argument("template", required=False)
|
|
3208
|
+
@click.option("--param", "-p", multiple=True, help="模板参数 key=value")
|
|
3209
|
+
def wf_template(action, name, template, param):
|
|
3210
|
+
"""列出内置模板 / 从模板创建工作流(闭环编排)"""
|
|
3211
|
+
from rich.table import Table
|
|
3212
|
+
|
|
3213
|
+
from devmate.workflow import create_from_template, list_templates
|
|
3214
|
+
|
|
3215
|
+
if action == "list":
|
|
3216
|
+
result = list_templates()
|
|
3217
|
+
if not result.success:
|
|
3218
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
3219
|
+
return
|
|
3220
|
+
data = result.data
|
|
3221
|
+
table = Table(title=f"内置工作流模板({data['total']} 个)——跨模块闭环")
|
|
3222
|
+
table.add_column("模板", style="cyan")
|
|
3223
|
+
table.add_column("步骤", style="yellow", justify="right")
|
|
3224
|
+
table.add_column("工具链", style="green")
|
|
3225
|
+
table.add_column("描述", style="white")
|
|
3226
|
+
for t in data["templates"]:
|
|
3227
|
+
table.add_row(t["name"], str(t["step_count"]),
|
|
3228
|
+
" → ".join(t["tools"]), t["description"])
|
|
3229
|
+
console.print(table)
|
|
3230
|
+
console.print("\n[dim]使用: devmate wf template new <工作流名> <模板名> -p 参数=值[/dim]")
|
|
3231
|
+
console.print("[dim]示例: devmate wf template new 每日巡检 daily-check -p topic=数据库 -p output=~/reports[/dim]") # noqa: E501
|
|
3232
|
+
return
|
|
3233
|
+
|
|
3234
|
+
# action == "new"
|
|
3235
|
+
if not name or not template:
|
|
3236
|
+
console.print("[red]❌ new 需要 <工作流名> <模板名>[/red]")
|
|
3237
|
+
return
|
|
3238
|
+
params = {}
|
|
3239
|
+
for p in param:
|
|
3240
|
+
if "=" in p:
|
|
3241
|
+
k, v = p.split("=", 1)
|
|
3242
|
+
params[k.strip()] = v.strip()
|
|
3243
|
+
result = create_from_template(name, template, params)
|
|
3244
|
+
if not result.success:
|
|
3245
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
3246
|
+
return
|
|
3247
|
+
data = result.data
|
|
3248
|
+
console.print(f"\n[green]{data['message']}[/green]")
|
|
3249
|
+
if data.get("unresolved_params"):
|
|
3250
|
+
console.print(f"[yellow]未提供参数(用默认值): {', '.join(data['unresolved_params'])}[/yellow]") # noqa: E501
|
|
3251
|
+
tool_chain = " → ".join(s["tool"] for s in data["steps"])
|
|
3252
|
+
console.print(f"[dim]步骤: {tool_chain}[/dim]")
|
|
3253
|
+
console.print("[dim]执行: devmate wf run <工作流名>[/dim]")
|
|
3254
|
+
|
|
3255
|
+
|
|
2922
3256
|
@cli.group("knowledge", help="RAG 知识库管理")
|
|
2923
3257
|
def knowledge_group():
|
|
2924
3258
|
"""RAG 知识库——向量检索笔记"""
|
|
@@ -3146,6 +3480,203 @@ def mcp_disconnect_cmd(server_url):
|
|
|
3146
3480
|
console.print(f"[green]{result.data['message']}[/green]")
|
|
3147
3481
|
|
|
3148
3482
|
|
|
3483
|
+
@mcp_group.command("list", help="列出已连接的 MCP 服务器")
|
|
3484
|
+
def mcp_list_cmd():
|
|
3485
|
+
"""查看所有已保存的外部 MCP 服务器连接"""
|
|
3486
|
+
import json as _json
|
|
3487
|
+
from pathlib import Path as _Path
|
|
3488
|
+
|
|
3489
|
+
from rich.table import Table
|
|
3490
|
+
|
|
3491
|
+
config_path = _Path.home() / ".devmate" / "mcp_servers.json"
|
|
3492
|
+
if not config_path.exists():
|
|
3493
|
+
console.print("[yellow]暂无已连接的 MCP 服务器[/yellow]")
|
|
3494
|
+
console.print("[dim]连接: devmate mcp connect <url>[/dim]")
|
|
3495
|
+
return
|
|
3496
|
+
|
|
3497
|
+
try:
|
|
3498
|
+
servers = _json.loads(config_path.read_text())
|
|
3499
|
+
except Exception:
|
|
3500
|
+
servers = []
|
|
3501
|
+
if not servers:
|
|
3502
|
+
console.print("[yellow]暂无已连接的 MCP 服务器[/yellow]")
|
|
3503
|
+
return
|
|
3504
|
+
|
|
3505
|
+
table = Table(title=f"已连接的 MCP 服务器({len(servers)} 个)")
|
|
3506
|
+
table.add_column("#", style="dim")
|
|
3507
|
+
table.add_column("服务器地址", style="cyan")
|
|
3508
|
+
for i, s in enumerate(servers, 1):
|
|
3509
|
+
table.add_row(str(i), s)
|
|
3510
|
+
console.print(table)
|
|
3511
|
+
console.print("\n[dim]Agent 集成: devmate agent chat \"...\" --mcp 可让 Agent 使用这些外部工具[/dim]") # noqa: E501
|
|
3512
|
+
|
|
3513
|
+
|
|
3514
|
+
@mcp_group.command("agent-tools", help="查看 Agent 可用的 MCP 工具")
|
|
3515
|
+
def mcp_agent_tools_cmd():
|
|
3516
|
+
"""查看 Agent 动态注册的外部 MCP 工具(双向兼容)"""
|
|
3517
|
+
from rich.table import Table
|
|
3518
|
+
|
|
3519
|
+
from devmate_agent import AgentRuntime
|
|
3520
|
+
|
|
3521
|
+
runtime = AgentRuntime(connect_mcp=True)
|
|
3522
|
+
tools = runtime.list_available_tools()
|
|
3523
|
+
external = [t for t in tools if t["name"].startswith("external_")]
|
|
3524
|
+
|
|
3525
|
+
if not external:
|
|
3526
|
+
console.print("[yellow]当前没有外部 MCP 工具(先 devmate mcp connect <url>)[/yellow]")
|
|
3527
|
+
return
|
|
3528
|
+
|
|
3529
|
+
table = Table(title=f"Agent 已注册的外部 MCP 工具({len(external)} 个)")
|
|
3530
|
+
table.add_column("工具名", style="cyan")
|
|
3531
|
+
table.add_column("说明", style="white")
|
|
3532
|
+
for t in external:
|
|
3533
|
+
table.add_row(t["name"], t["description"].replace("[外部MCP] ", ""))
|
|
3534
|
+
console.print(table)
|
|
3535
|
+
|
|
3536
|
+
|
|
3537
|
+
# ═══════════════════════════════════════════════════
|
|
3538
|
+
# 3 分钟 Aha 场景(devmate demo)
|
|
3539
|
+
# ═══════════════════════════════════════════════════
|
|
3540
|
+
|
|
3541
|
+
_DEMO_SCENARIOS = [
|
|
3542
|
+
{
|
|
3543
|
+
"id": "daily",
|
|
3544
|
+
"title": "📊 每日巡检(3 个工具一次搞定)",
|
|
3545
|
+
"desc": "系统监控 → 检索笔记 → 自动生成日报",
|
|
3546
|
+
"tools": "top dashboard + note search + 报告生成",
|
|
3547
|
+
"workflow": "daily-check",
|
|
3548
|
+
"params": {"topic": "开发", "output": "~/.devmate/demo"},
|
|
3549
|
+
},
|
|
3550
|
+
{
|
|
3551
|
+
"id": "api",
|
|
3552
|
+
"title": "🌐 API 调试(替代 Postman)",
|
|
3553
|
+
"desc": "发送请求 → 保存到集合 → 复用执行",
|
|
3554
|
+
"tools": "api request + api collection + api env",
|
|
3555
|
+
"workflow": "api-health",
|
|
3556
|
+
"params": {"url": "https://httpbin.org/get", "url2": "https://httpbin.org/status/500",
|
|
3557
|
+
"output": "~/.devmate/demo"},
|
|
3558
|
+
},
|
|
3559
|
+
{
|
|
3560
|
+
"id": "files",
|
|
3561
|
+
"title": "📁 文件整理(替代手动拖拽)",
|
|
3562
|
+
"desc": "扫描目录 → 按类型整理 → 生成清理报告",
|
|
3563
|
+
"tools": "tk size + tk organize + 报告生成",
|
|
3564
|
+
"workflow": "disk-cleanup",
|
|
3565
|
+
"params": {"path": "~/Downloads", "output": "~/.devmate/demo"},
|
|
3566
|
+
},
|
|
3567
|
+
]
|
|
3568
|
+
|
|
3569
|
+
|
|
3570
|
+
@cli.command("demo", help="3 分钟体验 DevMate(Aha 场景)")
|
|
3571
|
+
@click.option("--scenario", type=click.Choice(["daily", "api", "files"]),
|
|
3572
|
+
help="指定场景(默认交互选择)")
|
|
3573
|
+
@click.option("--list", "list_only", is_flag=True, help="列出可用场景")
|
|
3574
|
+
def demo_cmd(scenario, list_only):
|
|
3575
|
+
"""3 分钟完成一个"原来要开 3 个工具"的真实任务闭环
|
|
3576
|
+
|
|
3577
|
+
零配置、无需 LLM:每个场景自动调用 DevMate 模块完成真实操作
|
|
3578
|
+
"""
|
|
3579
|
+
import time as _time
|
|
3580
|
+
|
|
3581
|
+
from rich.table import Table as _Table
|
|
3582
|
+
|
|
3583
|
+
if list_only:
|
|
3584
|
+
table = _Table(title="DevMate Aha 场景(3 分钟体验)")
|
|
3585
|
+
table.add_column("ID", style="cyan")
|
|
3586
|
+
table.add_column("场景", style="bold")
|
|
3587
|
+
table.add_column("说明", style="white")
|
|
3588
|
+
table.add_column("工具链", style="green")
|
|
3589
|
+
for s in _DEMO_SCENARIOS:
|
|
3590
|
+
table.add_row(s["id"], s["title"], s["desc"], s["tools"])
|
|
3591
|
+
console.print(table)
|
|
3592
|
+
console.print("\n[dim]体验: devmate demo --scenario daily | api | files[/dim]")
|
|
3593
|
+
return
|
|
3594
|
+
|
|
3595
|
+
# 选择场景
|
|
3596
|
+
selected = None
|
|
3597
|
+
if scenario:
|
|
3598
|
+
selected = next((s for s in _DEMO_SCENARIOS if s["id"] == scenario), None)
|
|
3599
|
+
else:
|
|
3600
|
+
console.print(Panel(
|
|
3601
|
+
"[bold green]⚡ 3 分钟体验 DevMate[/bold green]\n"
|
|
3602
|
+
"[dim]选一个场景,自动完成一个原来要开 3 个工具才能完成的任务[/dim]",
|
|
3603
|
+
border_style="green", padding=(1, 2)))
|
|
3604
|
+
table = _Table(show_header=False, box=None)
|
|
3605
|
+
table.add_column("#", style="dim", width=4)
|
|
3606
|
+
table.add_column("场景", style="bold cyan")
|
|
3607
|
+
table.add_column("说明", style="white")
|
|
3608
|
+
for i, s in enumerate(_DEMO_SCENARIOS, 1):
|
|
3609
|
+
table.add_row(str(i), s["title"], s["desc"])
|
|
3610
|
+
console.print(table)
|
|
3611
|
+
console.print()
|
|
3612
|
+
choice = click.prompt("选择场景编号", type=int, default=1)
|
|
3613
|
+
if choice < 1 or choice > len(_DEMO_SCENARIOS):
|
|
3614
|
+
console.print(f"[red]无效编号: {choice}[/red]")
|
|
3615
|
+
return
|
|
3616
|
+
selected = _DEMO_SCENARIOS[choice - 1]
|
|
3617
|
+
|
|
3618
|
+
wf_name = f"demo-{selected['id']}-{int(_time.time())}"
|
|
3619
|
+
|
|
3620
|
+
console.print(f"\n[bold cyan]{selected['title']}[/bold cyan]")
|
|
3621
|
+
console.print(f"[dim]{selected['desc']}[/dim]")
|
|
3622
|
+
console.print(f"[green]工具链: {selected['tools']}[/green]\n")
|
|
3623
|
+
|
|
3624
|
+
# 步骤 1: 从模板创建
|
|
3625
|
+
console.print("[dim]① 创建场景工作流…[/dim]")
|
|
3626
|
+
from devmate.workflow import create_from_template
|
|
3627
|
+
from devmate.workflow import run as run_workflow
|
|
3628
|
+
result = create_from_template(wf_name, selected["workflow"],
|
|
3629
|
+
params=selected["params"])
|
|
3630
|
+
if not result.success:
|
|
3631
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
3632
|
+
return
|
|
3633
|
+
|
|
3634
|
+
# 步骤 2: 执行闭环
|
|
3635
|
+
console.print(f"[dim]② 执行 {len(result.data['steps'])} 步闭环任务…[/dim]\n")
|
|
3636
|
+
run_result = run_workflow(wf_name)
|
|
3637
|
+
|
|
3638
|
+
# 步骤 3: 展示结果
|
|
3639
|
+
if not run_result.success:
|
|
3640
|
+
console.print(f"[red]❌ 执行失败: {run_result.error}[/red]")
|
|
3641
|
+
return
|
|
3642
|
+
data = run_result.data
|
|
3643
|
+
|
|
3644
|
+
steps_table = _Table(title=f"✅ 任务完成({data['success_count']} 成功 / {data['fail_count']} 失败,{data['duration_ms']}ms)") # noqa: E501
|
|
3645
|
+
steps_table.add_column("#", style="dim")
|
|
3646
|
+
steps_table.add_column("步骤", style="cyan")
|
|
3647
|
+
steps_table.add_column("工具", style="yellow")
|
|
3648
|
+
steps_table.add_column("结果", style="white", width=55)
|
|
3649
|
+
icons = {"success": "[green]✅[/green]", "error": "[red]❌[/red]"}
|
|
3650
|
+
for i, s in enumerate(data["steps"], 1):
|
|
3651
|
+
icon = icons.get(s["status"], "[dim]⏭️[/dim]")
|
|
3652
|
+
text = s.get("error", "") or s.get("result", "")[:55]
|
|
3653
|
+
steps_table.add_row(str(i), s["step_id"], s["tool"], f"{icon} {text}")
|
|
3654
|
+
console.print(steps_table)
|
|
3655
|
+
|
|
3656
|
+
# 报告产物路径
|
|
3657
|
+
from pathlib import Path as _Path
|
|
3658
|
+
report_dir = _Path(selected["params"]["output"]).expanduser()
|
|
3659
|
+
if report_dir.exists():
|
|
3660
|
+
reports = sorted(report_dir.glob("*.md"))
|
|
3661
|
+
if reports:
|
|
3662
|
+
console.print("\n[bold green]📄 生成报告:[/bold green]")
|
|
3663
|
+
for rp in reports[-3:]:
|
|
3664
|
+
console.print(f" [cyan]{rp}[/cyan]")
|
|
3665
|
+
try:
|
|
3666
|
+
first_lines = rp.read_text()[:120]
|
|
3667
|
+
console.print(f" [dim]{first_lines.replace(chr(10), chr(10)+' ')}[/dim]")
|
|
3668
|
+
except Exception:
|
|
3669
|
+
pass
|
|
3670
|
+
|
|
3671
|
+
# 清理临时工作流
|
|
3672
|
+
from devmate.workflow import delete as delete_wf
|
|
3673
|
+
delete_wf(wf_name)
|
|
3674
|
+
|
|
3675
|
+
console.print("\n[bold green]🎉 3 分钟体验完成![/bold green]")
|
|
3676
|
+
console.print(f"[dim]真实工具链: {selected['tools']}[/dim]")
|
|
3677
|
+
console.print("[dim]继续探索: devmate --help | devmate wf template list[/dim]\n")
|
|
3678
|
+
|
|
3679
|
+
|
|
3149
3680
|
@cli.command("web", help="启动 Web 面板")
|
|
3150
3681
|
@click.option("--host", default="127.0.0.1", help="监听地址")
|
|
3151
3682
|
@click.option("--port", default=8600, help="监听端口")
|