devmate 2.1.0__tar.gz → 2.3.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.1.0 → devmate-2.3.0}/PKG-INFO +1 -1
- {devmate-2.1.0 → devmate-2.3.0}/pyproject.toml +1 -1
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/__init__.py +1 -1
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/app/cli.py +458 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/app/web/__init__.py +8 -2
- devmate-2.3.0/src/devmate/app/web/cli_docs.py +217 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/security/audit.py +29 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate.egg-info/PKG-INFO +1 -1
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate.egg-info/SOURCES.txt +1 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate_apidev/__init__.py +151 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate_dbadmin/__init__.py +133 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate_gitflow/__init__.py +165 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate_monitor/__init__.py +87 -3
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate_regexlab/__init__.py +105 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate_toolkit/__init__.py +191 -0
- {devmate-2.1.0 → devmate-2.3.0}/README.md +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/setup.cfg +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/__main__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/ai_config/__init__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/ai_scenarios/__init__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/app/__init__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/app/tui.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/bus/__init__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/bus/bus.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/bus/command.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/core/__init__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/core/config.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/core/logging.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/core/security.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/events/__init__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/events/bus.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/events/domain_event.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/mcp/__init__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/module/__init__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/module/registry.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/plugin/__init__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/plugin_market/__init__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/plugin_market/ratings.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/plugin_market/remote.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/rag/__init__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/security/vault.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate/workflow/__init__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate.egg-info/dependency_links.txt +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate.egg-info/entry_points.txt +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate.egg-info/requires.txt +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate.egg-info/top_level.txt +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate_agent/__init__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate_apihub/__init__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate_notekeeper/__init__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate_publisher/__init__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate_reporter/__init__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate_scaffold/__init__.py +0 -0
- {devmate-2.1.0 → devmate-2.3.0}/src/devmate_sshman/__init__.py +0 -0
|
@@ -221,6 +221,102 @@ def tk_cleanup(path, dry_run):
|
|
|
221
221
|
console.print(table)
|
|
222
222
|
|
|
223
223
|
|
|
224
|
+
@tk_group.command("rename", help="批量重命名文件")
|
|
225
|
+
@click.argument("path", default=".")
|
|
226
|
+
@click.option("--pattern", "-p", help="正则匹配模式")
|
|
227
|
+
@click.option("--replacement", "-r", default="", help="替换为")
|
|
228
|
+
@click.option("--prefix", help="统一前缀")
|
|
229
|
+
@click.option("--suffix", help="统一后缀(插入扩展名前)")
|
|
230
|
+
@click.option("--apply", is_flag=True, help="实际执行(默认预览)")
|
|
231
|
+
def tk_rename(path, pattern, replacement, prefix, suffix, apply):
|
|
232
|
+
"""批量重命名文件(支持正则替换/前缀/后缀),默认预览"""
|
|
233
|
+
from rich.table import Table
|
|
234
|
+
|
|
235
|
+
from devmate_toolkit import rename as _rename
|
|
236
|
+
|
|
237
|
+
result = _rename(path=path, pattern=pattern or "", replacement=replacement,
|
|
238
|
+
prefix=prefix or "", suffix=suffix or "", dry_run=not apply)
|
|
239
|
+
if not result.success:
|
|
240
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
data = result.data
|
|
244
|
+
console.print(f"\n[bold cyan]{data['message']}[/bold cyan]\n")
|
|
245
|
+
|
|
246
|
+
if data["results"]:
|
|
247
|
+
table = Table(show_header=True)
|
|
248
|
+
table.add_column("原文件名", style="dim")
|
|
249
|
+
table.add_column("新文件名", style="cyan")
|
|
250
|
+
table.add_column("状态", style="yellow")
|
|
251
|
+
for r in data["results"]:
|
|
252
|
+
table.add_row(r["old"], r["new"], r["status"])
|
|
253
|
+
console.print(table)
|
|
254
|
+
console.print("[dim]提示: 预览确认后加 --apply 实际执行[/dim]")
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@tk_group.command("dup", help="查找重复文件")
|
|
258
|
+
@click.argument("path", default=".")
|
|
259
|
+
@click.option("--min-size", default=1, type=int, help="最小文件大小(字节)")
|
|
260
|
+
def tk_dup(path, min_size):
|
|
261
|
+
"""扫描目录下重复文件(按大小分组后 MD5 校验)"""
|
|
262
|
+
|
|
263
|
+
from devmate_toolkit import find_duplicates
|
|
264
|
+
|
|
265
|
+
result = find_duplicates(path, min_size=min_size)
|
|
266
|
+
if not result.success:
|
|
267
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
data = result.data
|
|
271
|
+
console.print(f"\n[bold cyan]{data['message']}[/bold cyan]\n")
|
|
272
|
+
|
|
273
|
+
for i, dup in enumerate(data["duplicates"], 1):
|
|
274
|
+
console.print(f"[yellow]组 {i}[/yellow]({dup['size']},hash {dup['hash']})")
|
|
275
|
+
for f in dup["files"]:
|
|
276
|
+
console.print(f" [dim]{f}[/dim]")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@tk_group.command("json", help="JSON 格式化/校验/压缩")
|
|
280
|
+
@click.option("--path", type=click.Path(exists=False), help="JSON 文件路径")
|
|
281
|
+
@click.option("--text", "-t", help="JSON 字符串")
|
|
282
|
+
@click.option("--indent", default=2, type=int, help="缩进空格数")
|
|
283
|
+
@click.option("--compress", is_flag=True, help="压缩模式(去除空白)")
|
|
284
|
+
@click.option("--check", is_flag=True, help="仅校验,不输出格式化结果")
|
|
285
|
+
def tk_json(path, text, indent, compress, check):
|
|
286
|
+
"""格式化/校验/压缩 JSON"""
|
|
287
|
+
from devmate_toolkit import json_format
|
|
288
|
+
|
|
289
|
+
result = json_format(path=path or "", text=text or "", indent=indent,
|
|
290
|
+
compress=compress)
|
|
291
|
+
if not result.success:
|
|
292
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
data = result.data
|
|
296
|
+
if check:
|
|
297
|
+
console.print(f"[green]✅ {data['message']}[/green]")
|
|
298
|
+
return
|
|
299
|
+
console.print(data["output"])
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
@tk_group.command("hash", help="计算文件哈希")
|
|
303
|
+
@click.argument("path")
|
|
304
|
+
@click.option("--algorithm", "-a", default="md5",
|
|
305
|
+
type=click.Choice(["md5", "sha1", "sha256", "sha512"]))
|
|
306
|
+
def tk_hash(path, algorithm):
|
|
307
|
+
"""计算文件哈希(md5/sha1/sha256/sha512)"""
|
|
308
|
+
from devmate_toolkit import hash_file
|
|
309
|
+
|
|
310
|
+
result = hash_file(path, algorithm=algorithm)
|
|
311
|
+
if not result.success:
|
|
312
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
313
|
+
return
|
|
314
|
+
data = result.data
|
|
315
|
+
console.print(f"[bold]文件:[/bold] [cyan]{data['file']}[/cyan]")
|
|
316
|
+
console.print(f"[bold]{algorithm}:[/bold] [yellow]{data['hash']}[/yellow]")
|
|
317
|
+
console.print(f"[dim]大小: {data['size']}[/dim]")
|
|
318
|
+
|
|
319
|
+
|
|
224
320
|
@cli.group("api", help="HTTP 客户端、Mock Server")
|
|
225
321
|
def api_group():
|
|
226
322
|
"""apidev — API 开发工具"""
|
|
@@ -332,6 +428,112 @@ def api_history(limit):
|
|
|
332
428
|
console.print(table)
|
|
333
429
|
|
|
334
430
|
|
|
431
|
+
@api_group.command("collection", help="管理请求集合")
|
|
432
|
+
@click.argument("action", type=click.Choice(["add", "list", "run", "remove"]))
|
|
433
|
+
@click.argument("name", required=False)
|
|
434
|
+
@click.option("--method", "-m", default="GET", type=click.Choice(_HTTP_METHODS))
|
|
435
|
+
@click.option("--url", "-u", help="请求 URL")
|
|
436
|
+
@click.option("--body", "-b", help="请求体")
|
|
437
|
+
@click.option("--desc", help="描述")
|
|
438
|
+
def api_collection(action, name, method, url, body, desc):
|
|
439
|
+
"""保存/查看/执行/删除请求集合(支持 {{var}} 环境变量)"""
|
|
440
|
+
from rich.table import Table
|
|
441
|
+
|
|
442
|
+
from devmate_apidev import (
|
|
443
|
+
collection_add,
|
|
444
|
+
collection_list,
|
|
445
|
+
collection_remove,
|
|
446
|
+
collection_run,
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
if action == "add":
|
|
450
|
+
if not name or not url:
|
|
451
|
+
console.print("[red]❌ add 需要 --name 和 --url[/red]")
|
|
452
|
+
return
|
|
453
|
+
result = collection_add(name, method, url, body=body or "",
|
|
454
|
+
description=desc or "")
|
|
455
|
+
elif action == "list":
|
|
456
|
+
result = collection_list()
|
|
457
|
+
if result.success:
|
|
458
|
+
items = result.data["collections"]
|
|
459
|
+
if not items:
|
|
460
|
+
console.print("[yellow]集合为空[/yellow]")
|
|
461
|
+
return
|
|
462
|
+
table = Table(title=f"请求集合({len(items)} 条)")
|
|
463
|
+
table.add_column("名称", style="cyan")
|
|
464
|
+
table.add_column("方法", style="bold")
|
|
465
|
+
table.add_column("URL", style="green")
|
|
466
|
+
table.add_column("描述", style="dim")
|
|
467
|
+
for c in items:
|
|
468
|
+
table.add_row(c["name"], c["method"], c["url"], c.get("description", ""))
|
|
469
|
+
console.print(table)
|
|
470
|
+
return
|
|
471
|
+
elif action == "run":
|
|
472
|
+
if not name:
|
|
473
|
+
console.print("[red]❌ run 需要集合名[/red]")
|
|
474
|
+
return
|
|
475
|
+
result = collection_run(name)
|
|
476
|
+
elif action == "remove":
|
|
477
|
+
if not name:
|
|
478
|
+
console.print("[red]❌ remove 需要集合名[/red]")
|
|
479
|
+
return
|
|
480
|
+
result = collection_remove(name)
|
|
481
|
+
else:
|
|
482
|
+
console.print(f"[red]未知动作: {action}[/red]")
|
|
483
|
+
return
|
|
484
|
+
|
|
485
|
+
if result.success:
|
|
486
|
+
console.print(f"[green]{result.data.get('message', '✅ 完成')}[/green]")
|
|
487
|
+
else:
|
|
488
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
@api_group.command("env", help="管理请求环境变量")
|
|
492
|
+
@click.argument("action", type=click.Choice(["set", "list", "remove"]))
|
|
493
|
+
@click.argument("key", required=False)
|
|
494
|
+
@click.option("--value", "-v", help="变量值")
|
|
495
|
+
def api_env(action, key, value):
|
|
496
|
+
"""设置/查看/删除环境变量(在请求中通过 {{key}} 引用)"""
|
|
497
|
+
from rich.table import Table
|
|
498
|
+
|
|
499
|
+
from devmate_apidev import env_list, env_remove, env_set
|
|
500
|
+
|
|
501
|
+
if action == "set":
|
|
502
|
+
if not key or value is None:
|
|
503
|
+
console.print("[red]❌ set 需要 KEY 和 --value[/red]")
|
|
504
|
+
return
|
|
505
|
+
result = env_set(key, value)
|
|
506
|
+
elif action == "list":
|
|
507
|
+
result = env_list()
|
|
508
|
+
if result.success:
|
|
509
|
+
envs = result.data["envs"]
|
|
510
|
+
masked = result.data["masked"]
|
|
511
|
+
if not envs:
|
|
512
|
+
console.print("[yellow]暂无环境变量[/yellow]")
|
|
513
|
+
return
|
|
514
|
+
table = Table(title=f"环境变量({len(envs)} 个)")
|
|
515
|
+
table.add_column("键", style="cyan")
|
|
516
|
+
table.add_column("值", style="dim")
|
|
517
|
+
table.add_column("使用方式", style="green")
|
|
518
|
+
for k in envs:
|
|
519
|
+
table.add_row(k, masked.get(k, ""), f"{{{{{k}}}}}")
|
|
520
|
+
console.print(table)
|
|
521
|
+
return
|
|
522
|
+
elif action == "remove":
|
|
523
|
+
if not key:
|
|
524
|
+
console.print("[red]❌ remove 需要 KEY[/red]")
|
|
525
|
+
return
|
|
526
|
+
result = env_remove(key)
|
|
527
|
+
else:
|
|
528
|
+
console.print(f"[red]未知动作: {action}[/red]")
|
|
529
|
+
return
|
|
530
|
+
|
|
531
|
+
if result.success:
|
|
532
|
+
console.print(f"[green]{result.data.get('message', '✅ 完成')}[/green]")
|
|
533
|
+
else:
|
|
534
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
535
|
+
|
|
536
|
+
|
|
335
537
|
@cli.group("db", help="SQL 编辑器、表浏览器")
|
|
336
538
|
def db_group():
|
|
337
539
|
"""dbadmin — 数据库管理"""
|
|
@@ -418,6 +620,82 @@ def db_show(table_name, name):
|
|
|
418
620
|
console.print(pt)
|
|
419
621
|
|
|
420
622
|
|
|
623
|
+
@db_group.command("describe", help="查看表结构详情")
|
|
624
|
+
@click.argument("table_name")
|
|
625
|
+
@click.option("--name", default="default", help="连接名称")
|
|
626
|
+
def db_describe(table_name, name):
|
|
627
|
+
"""查看表结构详情:列/索引/外键/行数"""
|
|
628
|
+
from rich.table import Table
|
|
629
|
+
|
|
630
|
+
from devmate_dbadmin import describe
|
|
631
|
+
|
|
632
|
+
result = describe(table_name, name=name)
|
|
633
|
+
if not result.success:
|
|
634
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
635
|
+
return
|
|
636
|
+
|
|
637
|
+
data = result.data
|
|
638
|
+
console.print(f"\n[bold cyan]表: {data['table']}[/bold cyan] "
|
|
639
|
+
f"[dim]({data['row_count']} 行)[/dim]\n")
|
|
640
|
+
|
|
641
|
+
# 列
|
|
642
|
+
col_table = Table(title="列")
|
|
643
|
+
col_table.add_column("列名", style="cyan")
|
|
644
|
+
col_table.add_column("类型", style="yellow")
|
|
645
|
+
col_table.add_column("可空", style="green")
|
|
646
|
+
col_table.add_column("主键", style="bold")
|
|
647
|
+
col_table.add_column("默认值", style="dim")
|
|
648
|
+
for col in data["columns"]:
|
|
649
|
+
col_table.add_row(col["name"], col["type"],
|
|
650
|
+
"✅" if col["nullable"] else "❌",
|
|
651
|
+
"🔑" if col["primary_key"] else "",
|
|
652
|
+
col["default"])
|
|
653
|
+
console.print(col_table)
|
|
654
|
+
|
|
655
|
+
# 索引
|
|
656
|
+
if data["indexes"]:
|
|
657
|
+
idx_table = Table(title="索引")
|
|
658
|
+
idx_table.add_column("名称", style="cyan")
|
|
659
|
+
idx_table.add_column("列", style="yellow")
|
|
660
|
+
idx_table.add_column("唯一", style="green")
|
|
661
|
+
for idx in data["indexes"]:
|
|
662
|
+
idx_table.add_row(idx["name"], ", ".join(idx["columns"]),
|
|
663
|
+
"✅" if idx["unique"] else "❌")
|
|
664
|
+
console.print(idx_table)
|
|
665
|
+
|
|
666
|
+
# 外键
|
|
667
|
+
if data["foreign_keys"]:
|
|
668
|
+
fk_table = Table(title="外键")
|
|
669
|
+
fk_table.add_column("名称", style="cyan")
|
|
670
|
+
fk_table.add_column("列", style="yellow")
|
|
671
|
+
fk_table.add_column("引用表", style="green")
|
|
672
|
+
fk_table.add_column("引用列", style="dim")
|
|
673
|
+
for fk in data["foreign_keys"]:
|
|
674
|
+
fk_table.add_row(fk["name"], ", ".join(fk["columns"]),
|
|
675
|
+
fk["ref_table"], ", ".join(fk["ref_columns"]))
|
|
676
|
+
console.print(fk_table)
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
@db_group.command("export", help="导出表数据")
|
|
680
|
+
@click.argument("table_name")
|
|
681
|
+
@click.argument("format", type=click.Choice(["csv", "json", "sql"]))
|
|
682
|
+
@click.option("--output", "-o", help="输出文件路径(默认当前目录 表名.格式)")
|
|
683
|
+
@click.option("--limit", default=1000, type=int, help="最大行数")
|
|
684
|
+
@click.option("--name", default="default", help="连接名称")
|
|
685
|
+
def db_export(table_name, format, output, limit, name):
|
|
686
|
+
"""导出表数据为 CSV/JSON/SQL"""
|
|
687
|
+
from devmate_dbadmin import export_table
|
|
688
|
+
|
|
689
|
+
result = export_table(table_name, format=format, output=output or "",
|
|
690
|
+
limit=limit, name=name)
|
|
691
|
+
if not result.success:
|
|
692
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
693
|
+
return
|
|
694
|
+
data = result.data
|
|
695
|
+
console.print(f"[green]{data['message']}[/green]")
|
|
696
|
+
console.print(f"[dim]列: {', '.join(data['columns'])}[/dim]")
|
|
697
|
+
|
|
698
|
+
|
|
421
699
|
@db_group.command("query", help="执行 SQL 查询")
|
|
422
700
|
@click.argument("sql")
|
|
423
701
|
@click.option("--name", default="default", help="连接名称")
|
|
@@ -1243,6 +1521,89 @@ def git_stats(path):
|
|
|
1243
1521
|
console.print(ct)
|
|
1244
1522
|
|
|
1245
1523
|
|
|
1524
|
+
@git_group.command("status", help="仓库概览")
|
|
1525
|
+
@click.option("--path", default=".", help="Git 仓库路径")
|
|
1526
|
+
def git_status(path):
|
|
1527
|
+
"""仓库概览:分支/未提交/领先落后"""
|
|
1528
|
+
from rich.table import Table
|
|
1529
|
+
|
|
1530
|
+
from devmate_gitflow import overview
|
|
1531
|
+
|
|
1532
|
+
result = overview(path=path)
|
|
1533
|
+
if not result.success:
|
|
1534
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
1535
|
+
return
|
|
1536
|
+
|
|
1537
|
+
data = result.data
|
|
1538
|
+
console.print(f"\n[bold]📂 仓库: {data['path']}[/bold]\n")
|
|
1539
|
+
|
|
1540
|
+
table = Table(show_header=False, box=None)
|
|
1541
|
+
table.add_column("指标", style="cyan", width=18)
|
|
1542
|
+
table.add_column("值", style="yellow")
|
|
1543
|
+
table.add_row("当前分支", f"[bold]{data['branch']}[/bold]")
|
|
1544
|
+
table.add_row("未提交变更", str(data["uncommitted"]))
|
|
1545
|
+
table.add_row("领先远程", data["ahead"] or "-")
|
|
1546
|
+
table.add_row("落后远程", data["behind"] or "-")
|
|
1547
|
+
table.add_row("总提交数", str(data["commits"]))
|
|
1548
|
+
console.print(table)
|
|
1549
|
+
|
|
1550
|
+
if data["status_lines"]:
|
|
1551
|
+
console.print("\n[bold]未提交变更:[/bold]")
|
|
1552
|
+
for line in data["status_lines"][:15]:
|
|
1553
|
+
console.print(f" [dim]{line}[/dim]")
|
|
1554
|
+
|
|
1555
|
+
|
|
1556
|
+
@git_group.command("br", help="分支列表")
|
|
1557
|
+
@click.option("--path", default=".", help="Git 仓库路径")
|
|
1558
|
+
def git_br(path):
|
|
1559
|
+
"""列出本地分支 + 识别已合并分支"""
|
|
1560
|
+
from rich.table import Table
|
|
1561
|
+
|
|
1562
|
+
from devmate_gitflow import branches
|
|
1563
|
+
|
|
1564
|
+
result = branches(path=path)
|
|
1565
|
+
if not result.success:
|
|
1566
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
1567
|
+
return
|
|
1568
|
+
|
|
1569
|
+
data = result.data
|
|
1570
|
+
console.print(f"\n[bold]🌿 分支列表({data['total']} 个)[/bold]\n")
|
|
1571
|
+
|
|
1572
|
+
table = Table()
|
|
1573
|
+
table.add_column("分支", style="cyan")
|
|
1574
|
+
table.add_column("状态", style="yellow")
|
|
1575
|
+
for b in data["branches"]:
|
|
1576
|
+
if b["current"]:
|
|
1577
|
+
table.add_row(b["name"], "✅ 当前")
|
|
1578
|
+
elif b["name"] in data["merged"]:
|
|
1579
|
+
table.add_row(b["name"], "[dim]已合并[/dim]")
|
|
1580
|
+
else:
|
|
1581
|
+
table.add_row(b["name"], "[green]未合并[/green]")
|
|
1582
|
+
console.print(table)
|
|
1583
|
+
|
|
1584
|
+
if data["merged"]:
|
|
1585
|
+
console.print(f"\n[dim]提示: 已合并分支可清理 → git branch -d {' '.join(data['merged'][:5])}[/dim]") # noqa: E501
|
|
1586
|
+
|
|
1587
|
+
|
|
1588
|
+
@git_group.command("pr", help="生成 PR 描述")
|
|
1589
|
+
@click.option("--base", default="main", help="基础分支")
|
|
1590
|
+
@click.option("--head", default="", help="当前分支(默认自动检测)")
|
|
1591
|
+
@click.option("--path", default=".", help="Git 仓库路径")
|
|
1592
|
+
def git_pr(base, head, path):
|
|
1593
|
+
"""基于分支提交自动生成 PR 描述(按类型聚合)"""
|
|
1594
|
+
from devmate_gitflow import pr_description
|
|
1595
|
+
|
|
1596
|
+
result = pr_description(path=path, base=base, head=head)
|
|
1597
|
+
if not result.success:
|
|
1598
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
1599
|
+
return
|
|
1600
|
+
|
|
1601
|
+
data = result.data
|
|
1602
|
+
console.print(f"\n[bold cyan]{data['description']}[/bold cyan]")
|
|
1603
|
+
console.print(f"\n[dim]提交数: {data['commit_count']},"
|
|
1604
|
+
f"类型分布: {', '.join(f'{k}:{v}' for k, v in data['types'].items())}[/dim]")
|
|
1605
|
+
|
|
1606
|
+
|
|
1246
1607
|
@cli.group("re", help="正则测试器、常用正则库")
|
|
1247
1608
|
def re_group():
|
|
1248
1609
|
"""regexlab — 正则实验室"""
|
|
@@ -1312,6 +1673,63 @@ def re_search(pattern, path, flags, max):
|
|
|
1312
1673
|
console.print(table)
|
|
1313
1674
|
|
|
1314
1675
|
|
|
1676
|
+
@re_group.command("replace", help="正则替换")
|
|
1677
|
+
@click.argument("pattern")
|
|
1678
|
+
@click.argument("replacement")
|
|
1679
|
+
@click.option("--text", "-t", help="输入文本")
|
|
1680
|
+
@click.option("--path", help="输入文件路径(就地替换)")
|
|
1681
|
+
@click.option("--flags", "-f", help="正则标志(i=忽略大小写, m=多行)")
|
|
1682
|
+
@click.option("--count", "-c", default=0, type=int, help="替换次数(0=全部)")
|
|
1683
|
+
def re_replace(pattern, replacement, text, path, flags, count):
|
|
1684
|
+
"""正则替换(支持字符串或文件就地替换)"""
|
|
1685
|
+
from devmate_regexlab import replace
|
|
1686
|
+
|
|
1687
|
+
result = replace(pattern, replacement, text=text or "", path=path or "",
|
|
1688
|
+
flags=flags or "", count=count)
|
|
1689
|
+
if not result.success:
|
|
1690
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
1691
|
+
return
|
|
1692
|
+
data = result.data
|
|
1693
|
+
if path:
|
|
1694
|
+
console.print(f"[green]{data['message']}[/green]")
|
|
1695
|
+
else:
|
|
1696
|
+
console.print(f"[bold]替换结果({data['matches']} 处):[/bold]")
|
|
1697
|
+
console.print(data["result"])
|
|
1698
|
+
|
|
1699
|
+
|
|
1700
|
+
@re_group.command("extract", help="提取所有匹配")
|
|
1701
|
+
@click.argument("pattern")
|
|
1702
|
+
@click.option("--text", "-t", help="输入文本")
|
|
1703
|
+
@click.option("--path", help="输入文件路径")
|
|
1704
|
+
@click.option("--flags", "-f", help="正则标志")
|
|
1705
|
+
@click.option("--max", default=100, type=int, help="最大提取数")
|
|
1706
|
+
def re_extract(pattern, text, path, flags, max):
|
|
1707
|
+
"""提取所有匹配结果(支持分组提取)"""
|
|
1708
|
+
from rich.table import Table
|
|
1709
|
+
|
|
1710
|
+
from devmate_regexlab import extract
|
|
1711
|
+
|
|
1712
|
+
result = extract(pattern, text=text or "", path=path or "",
|
|
1713
|
+
flags=flags or "", max_matches=max)
|
|
1714
|
+
if not result.success:
|
|
1715
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
1716
|
+
return
|
|
1717
|
+
data = result.data
|
|
1718
|
+
console.print(f"\n[bold]提取 {data['pattern']}[/bold] → "
|
|
1719
|
+
f"[yellow]{data['total']}[/yellow] 个匹配\n")
|
|
1720
|
+
|
|
1721
|
+
if data["matches"]:
|
|
1722
|
+
table = Table(show_header=True)
|
|
1723
|
+
table.add_column("#", style="dim", width=4)
|
|
1724
|
+
table.add_column("匹配", style="cyan")
|
|
1725
|
+
table.add_column("分组", style="yellow")
|
|
1726
|
+
table.add_column("位置", style="dim")
|
|
1727
|
+
for i, m in enumerate(data["matches"], 1):
|
|
1728
|
+
groups = ", ".join(str(g) for g in m["groups"]) if m["groups"] else "-"
|
|
1729
|
+
table.add_row(str(i), m["full"], groups, str(m["position"]))
|
|
1730
|
+
console.print(table)
|
|
1731
|
+
|
|
1732
|
+
|
|
1315
1733
|
@re_group.command("lib", help="管理正则库")
|
|
1316
1734
|
@click.argument("action", type=click.Choice(["list", "add", "remove"]))
|
|
1317
1735
|
@click.argument("name", required=False)
|
|
@@ -1620,6 +2038,33 @@ def top_log(path, lines):
|
|
|
1620
2038
|
console.print(data["content"])
|
|
1621
2039
|
|
|
1622
2040
|
|
|
2041
|
+
@top_group.command("port", help="查询端口占用")
|
|
2042
|
+
@click.argument("port", required=False, type=int)
|
|
2043
|
+
def top_port(port):
|
|
2044
|
+
"""查询端口占用情况(跨平台)"""
|
|
2045
|
+
from rich.table import Table
|
|
2046
|
+
|
|
2047
|
+
from devmate_monitor import port_usage
|
|
2048
|
+
|
|
2049
|
+
result = port_usage(port)
|
|
2050
|
+
if not result.success:
|
|
2051
|
+
console.print(f"[red]❌ {result.error}[/red]")
|
|
2052
|
+
return
|
|
2053
|
+
|
|
2054
|
+
data = result.data
|
|
2055
|
+
console.print(f"\n[bold cyan]{data['message']}[/bold cyan]\n")
|
|
2056
|
+
|
|
2057
|
+
if data["listeners"]:
|
|
2058
|
+
table = Table(show_header=True)
|
|
2059
|
+
table.add_column("端口", style="yellow")
|
|
2060
|
+
table.add_column("PID", style="cyan")
|
|
2061
|
+
table.add_column("进程", style="green")
|
|
2062
|
+
table.add_column("地址", style="dim")
|
|
2063
|
+
for l in data["listeners"][:30]: # noqa: E741
|
|
2064
|
+
table.add_row(str(l["port"]), str(l["pid"]), l["process"], l["host"])
|
|
2065
|
+
console.print(table)
|
|
2066
|
+
|
|
2067
|
+
|
|
1623
2068
|
@cli.group("report", help="日报/周报自动生成")
|
|
1624
2069
|
def report_group():
|
|
1625
2070
|
"""reporter — 报告生成"""
|
|
@@ -2321,6 +2766,19 @@ def audit_count():
|
|
|
2321
2766
|
console.print(f"审计记录总数:[cyan]{_audit.count}[/cyan] 条")
|
|
2322
2767
|
|
|
2323
2768
|
|
|
2769
|
+
@audit_group.command("export", help="导出审计记录 CSV")
|
|
2770
|
+
@click.option("--output", "-o", help="输出文件路径(默认 ~/.devmate/audit_export.csv)")
|
|
2771
|
+
@click.option("--level", help="过滤级别(SAFE/SENSITIVE/HIGH)")
|
|
2772
|
+
@click.option("--module", "-m", help="过滤模块")
|
|
2773
|
+
def audit_export(output, level, module):
|
|
2774
|
+
"""导出审计记录为 CSV(支持级别/模块过滤)"""
|
|
2775
|
+
from devmate.security.audit import audit as _audit
|
|
2776
|
+
|
|
2777
|
+
count = _audit.export(output=output or "", level=level, module=module)
|
|
2778
|
+
out = output or str(Path.home() / ".devmate" / "audit_export.csv")
|
|
2779
|
+
console.print(f"[green]✅ 已导出 {count} 条审计记录到 {out}[/green]")
|
|
2780
|
+
|
|
2781
|
+
|
|
2324
2782
|
# ═══════════════════════════════════════════════════
|
|
2325
2783
|
# 顶层命令:mcp / web / upgrade
|
|
2326
2784
|
# ═══════════════════════════════════════════════════
|
|
@@ -78,15 +78,21 @@ async def dashboard(request: Request, _: str = Depends(require_auth)):
|
|
|
78
78
|
|
|
79
79
|
@app.get("/module/{name}", response_class=HTMLResponse)
|
|
80
80
|
async def module_detail(request: Request, name: str, _: str = Depends(require_auth)):
|
|
81
|
-
"""
|
|
81
|
+
"""模块详情页——含完整命令文档"""
|
|
82
|
+
from devmate.app.web.cli_docs import CLI_ALIASES, collect_cli_docs
|
|
83
|
+
|
|
82
84
|
mod = next((m for m in MODULES if m["name"] == name), None)
|
|
83
85
|
if mod is None:
|
|
84
86
|
raise HTTPException(status_code=404, detail=f"模块不存在: {name}")
|
|
85
87
|
|
|
88
|
+
# 自动收集该模块的 click 命令文档
|
|
89
|
+
cli_name = next((k for k, v in CLI_ALIASES.items() if v == name), name)
|
|
90
|
+
commands = collect_cli_docs(cli_name)
|
|
91
|
+
|
|
86
92
|
return templates.TemplateResponse(
|
|
87
93
|
"module.html",
|
|
88
94
|
{"request": request, "mod": mod, "modules": MODULES,
|
|
89
|
-
"active_module": name, "version": __version__},
|
|
95
|
+
"active_module": name, "commands": commands, "version": __version__},
|
|
90
96
|
)
|
|
91
97
|
|
|
92
98
|
|