devmate 1.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.
- devmate/__init__.py +6 -0
- devmate/__main__.py +5 -0
- devmate/app/__init__.py +1 -0
- devmate/app/cli.py +1663 -0
- devmate/app/tui.py +291 -0
- devmate/app/web/__init__.py +95 -0
- devmate/bus/__init__.py +1 -0
- devmate/bus/bus.py +151 -0
- devmate/bus/command.py +84 -0
- devmate/core/__init__.py +1 -0
- devmate/core/config.py +115 -0
- devmate/core/logging.py +54 -0
- devmate/core/security.py +61 -0
- devmate/events/__init__.py +1 -0
- devmate/events/bus.py +97 -0
- devmate/events/domain_event.py +37 -0
- devmate/module/__init__.py +1 -0
- devmate/module/registry.py +74 -0
- devmate/security/audit.py +96 -0
- devmate/security/vault.py +221 -0
- devmate-1.0.0.dist-info/METADATA +116 -0
- devmate-1.0.0.dist-info/RECORD +37 -0
- devmate-1.0.0.dist-info/WHEEL +5 -0
- devmate-1.0.0.dist-info/entry_points.txt +18 -0
- devmate-1.0.0.dist-info/top_level.txt +13 -0
- devmate_agent/__init__.py +440 -0
- devmate_apidev/__init__.py +271 -0
- devmate_apihub/__init__.py +313 -0
- devmate_dbadmin/__init__.py +263 -0
- devmate_gitflow/__init__.py +247 -0
- devmate_monitor/__init__.py +112 -0
- devmate_notekeeper/__init__.py +280 -0
- devmate_regexlab/__init__.py +212 -0
- devmate_reporter/__init__.py +170 -0
- devmate_scaffold/__init__.py +181 -0
- devmate_sshman/__init__.py +330 -0
- devmate_toolkit/__init__.py +223 -0
devmate/app/tui.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""DevMate TUI 仪表盘——全功能版"""
|
|
2
|
+
|
|
3
|
+
from textual.app import App, ComposeResult
|
|
4
|
+
from textual.binding import Binding
|
|
5
|
+
from textual.containers import Horizontal, ScrollableContainer, Vertical
|
|
6
|
+
from textual.screen import Screen
|
|
7
|
+
from textual.widgets import (
|
|
8
|
+
Footer,
|
|
9
|
+
Header,
|
|
10
|
+
Input,
|
|
11
|
+
ListItem,
|
|
12
|
+
ListView,
|
|
13
|
+
Rule,
|
|
14
|
+
Static,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
MODULES = [
|
|
18
|
+
("📁 toolkit", "文件整理、大小排行、缓存清理"),
|
|
19
|
+
("🌐 apidev", "HTTP 请求、Mock Server、请求历史"),
|
|
20
|
+
("🗄 dbadmin", "SQL 查询、表浏览、保存的查询"),
|
|
21
|
+
("🤖 apihub", "AI 对话、模型配置、对话历史"),
|
|
22
|
+
("⚙️ agent", "Agent 执行引擎、代码审查、文件管家"),
|
|
23
|
+
("🔐 vault", "加密密钥存储、安全管理"),
|
|
24
|
+
("📝 notekeeper","笔记创建、全文搜索、标签管理"),
|
|
25
|
+
("📊 top", "CPU/内存、进程列表、日志查看"),
|
|
26
|
+
("📋 report", "日报/周报自动生成"),
|
|
27
|
+
("🏗 scaf", "项目脚手架、代码片段"),
|
|
28
|
+
("🔧 git", "Commit 校验、CHANGELOG、仓库统计"),
|
|
29
|
+
("🔍 regexlab", "正则测试、文件搜索、正则库"),
|
|
30
|
+
("🔗 ssh", "SSH 连接、命令执行、端口转发"),
|
|
31
|
+
("📜 audit", "审计日志查看"),
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ModulePanel(Vertical):
|
|
36
|
+
"""模块功能面板"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, title: str, description: str, commands: list[str]):
|
|
39
|
+
super().__init__()
|
|
40
|
+
self._title = title
|
|
41
|
+
self._description = description
|
|
42
|
+
self._commands = commands
|
|
43
|
+
|
|
44
|
+
def compose(self) -> ComposeResult:
|
|
45
|
+
yield Static(f"[bold cyan]{self._title}[/bold cyan]", id="panel_title")
|
|
46
|
+
yield Static(f"[dim]{self._description}[/dim]\n", id="panel_desc")
|
|
47
|
+
yield Rule()
|
|
48
|
+
for cmd in self._commands:
|
|
49
|
+
yield Static(f" [yellow]$ {cmd}[/yellow]")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class DashboardScreen(Screen):
|
|
53
|
+
"""主仪表盘——系统状态概览"""
|
|
54
|
+
|
|
55
|
+
def compose(self) -> ComposeResult:
|
|
56
|
+
with Vertical(id="dashboard"):
|
|
57
|
+
yield Static("[bold cyan]📊 DevMate 仪表盘[/bold cyan]", id="dash_title")
|
|
58
|
+
yield Rule()
|
|
59
|
+
|
|
60
|
+
# 快速统计卡片
|
|
61
|
+
with Horizontal(id="stat_cards"):
|
|
62
|
+
with Vertical(classes="stat_card"):
|
|
63
|
+
yield Static("📁", classes="stat_icon")
|
|
64
|
+
yield Static("文件管理", classes="stat_label")
|
|
65
|
+
yield Static("organize / size / cleanup", classes="stat_cmd")
|
|
66
|
+
with Vertical(classes="stat_card"):
|
|
67
|
+
yield Static("🗄", classes="stat_icon")
|
|
68
|
+
yield Static("数据库", classes="stat_label")
|
|
69
|
+
yield Static("connect / query / show", classes="stat_cmd")
|
|
70
|
+
with Vertical(classes="stat_card"):
|
|
71
|
+
yield Static("🌐", classes="stat_icon")
|
|
72
|
+
yield Static("API", classes="stat_label")
|
|
73
|
+
yield Static("request / mock / history", classes="stat_cmd")
|
|
74
|
+
with Vertical(classes="stat_card"):
|
|
75
|
+
yield Static("🤖", classes="stat_icon")
|
|
76
|
+
yield Static("AI", classes="stat_label")
|
|
77
|
+
yield Static("add / chat / list", classes="stat_cmd")
|
|
78
|
+
|
|
79
|
+
yield Rule()
|
|
80
|
+
yield Static("[bold]快速命令[/bold]\n", id="quick_title")
|
|
81
|
+
yield Static(" Ctrl+P 打开命令面板")
|
|
82
|
+
yield Static(" Ctrl+K 搜索命令")
|
|
83
|
+
yield Static(" Ctrl+Q 退出")
|
|
84
|
+
yield Static(" Enter 查看模块详情")
|
|
85
|
+
|
|
86
|
+
yield Rule()
|
|
87
|
+
yield Static("[bold]最近模块[/bold]", id="recent_title")
|
|
88
|
+
for name, desc in MODULES[:8]:
|
|
89
|
+
yield Static(f" [cyan]•[/cyan] {name}[dim] {desc}[/dim]")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class ModuleDetailScreen(Screen):
|
|
93
|
+
"""模块详情页面"""
|
|
94
|
+
|
|
95
|
+
def __init__(self, module_name: str, module_desc: str):
|
|
96
|
+
super().__init__()
|
|
97
|
+
self.module_name = module_name
|
|
98
|
+
self.module_desc = module_desc
|
|
99
|
+
|
|
100
|
+
def compose(self) -> ComposeResult:
|
|
101
|
+
yield Header(show_clock=True)
|
|
102
|
+
with ScrollableContainer():
|
|
103
|
+
yield Static(f"[bold cyan]{self.module_name}[/bold cyan]", id="mod_title")
|
|
104
|
+
yield Static(f"[dim]{self.module_desc}[/dim]\n")
|
|
105
|
+
yield Rule()
|
|
106
|
+
yield Static("\n在终端中使用:\n")
|
|
107
|
+
parts = self.module_name.split()
|
|
108
|
+
if len(parts) >= 2:
|
|
109
|
+
cli_name = parts[1]
|
|
110
|
+
yield Static(f"[yellow]$ devmate {cli_name} --help[/yellow]")
|
|
111
|
+
yield Footer()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class CommandPalette(Vertical):
|
|
115
|
+
"""命令面板"""
|
|
116
|
+
|
|
117
|
+
def compose(self) -> ComposeResult:
|
|
118
|
+
yield Static("[bold]命令面板[/bold]", id="palette_title")
|
|
119
|
+
yield Input(placeholder="搜索命令...", id="command_input")
|
|
120
|
+
yield Static("[dim]输入命令名称快速跳转[/dim]")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class DevMateTUI(App):
|
|
124
|
+
"""DevMate TUI 主应用——全功能仪表盘"""
|
|
125
|
+
|
|
126
|
+
CSS = """
|
|
127
|
+
Screen {
|
|
128
|
+
layout: horizontal;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#sidebar {
|
|
132
|
+
width: 30;
|
|
133
|
+
dock: left;
|
|
134
|
+
background: $surface;
|
|
135
|
+
border: solid $primary;
|
|
136
|
+
height: 100%;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
#sidebar_title {
|
|
140
|
+
padding: 1;
|
|
141
|
+
text-align: center;
|
|
142
|
+
background: $primary;
|
|
143
|
+
color: $text;
|
|
144
|
+
text-style: bold;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#module_list {
|
|
148
|
+
height: 1fr;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
ListView {
|
|
152
|
+
border: none;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
ListItem {
|
|
156
|
+
padding: 0 1;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
ListItem:hover {
|
|
160
|
+
background: $accent 20%;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
ListItem:focus {
|
|
164
|
+
background: $accent;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#main_content {
|
|
168
|
+
width: 1fr;
|
|
169
|
+
padding: 1 2;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
#dashboard {
|
|
173
|
+
padding: 1;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
#dash_title {
|
|
177
|
+
text-align: center;
|
|
178
|
+
padding: 1;
|
|
179
|
+
text-style: bold;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#stat_cards {
|
|
183
|
+
height: 7;
|
|
184
|
+
margin: 1 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.stat_card {
|
|
188
|
+
width: 1fr;
|
|
189
|
+
height: 100%;
|
|
190
|
+
border: solid $primary;
|
|
191
|
+
margin: 0 1;
|
|
192
|
+
padding: 1;
|
|
193
|
+
align: center middle;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.stat_icon {
|
|
197
|
+
text-style: bold;
|
|
198
|
+
content-align: center middle;
|
|
199
|
+
height: 2;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.stat_label {
|
|
203
|
+
text-align: center;
|
|
204
|
+
color: $text;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.stat_cmd {
|
|
208
|
+
text-align: center;
|
|
209
|
+
color: $accent;
|
|
210
|
+
dim: true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
#panel_title {
|
|
214
|
+
text-style: bold;
|
|
215
|
+
padding: 1 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
#mod_title {
|
|
219
|
+
text-style: bold;
|
|
220
|
+
padding: 1 0;
|
|
221
|
+
text-align: center;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#palette_title {
|
|
225
|
+
text-style: bold;
|
|
226
|
+
padding: 1;
|
|
227
|
+
text-align: center;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
#command_input {
|
|
231
|
+
margin: 1;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
StatusBar {
|
|
235
|
+
dock: bottom;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.active_module {
|
|
239
|
+
background: $accent;
|
|
240
|
+
}
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
BINDINGS = [
|
|
244
|
+
Binding("ctrl+q", "quit", "退出", priority=True),
|
|
245
|
+
Binding("ctrl+p", "command_palette", "命令面板"),
|
|
246
|
+
Binding("ctrl+k", "search", "搜索"),
|
|
247
|
+
Binding("escape", "back_to_dashboard", "返回仪表盘"),
|
|
248
|
+
]
|
|
249
|
+
|
|
250
|
+
def compose(self) -> ComposeResult:
|
|
251
|
+
yield Header(show_clock=True)
|
|
252
|
+
with Vertical(id="sidebar"):
|
|
253
|
+
yield Static("[bold]DevMate 仪表盘[/bold]", id="sidebar_title")
|
|
254
|
+
with ListView(id="module_list"):
|
|
255
|
+
yield ListItem(Static("🏠 仪表盘"), id="mod_dashboard")
|
|
256
|
+
for name, desc in MODULES:
|
|
257
|
+
yield ListItem(Static(name), id=f"mod_{name.split()[-1].strip()}")
|
|
258
|
+
yield DashboardScreen(id="main_content")
|
|
259
|
+
yield Footer()
|
|
260
|
+
|
|
261
|
+
def on_list_view_selected(self, event: ListView.Selected):
|
|
262
|
+
"""处理侧栏导航"""
|
|
263
|
+
list_view = self.query_one("#module_list", ListView)
|
|
264
|
+
list_view.remove_class("active_module")
|
|
265
|
+
|
|
266
|
+
item_id = event.item.id or ""
|
|
267
|
+
if item_id == "mod_dashboard":
|
|
268
|
+
self.pop_screen() if self.screen != self else None
|
|
269
|
+
self.switch_screen(DashboardScreen())
|
|
270
|
+
elif item_id.startswith("mod_"):
|
|
271
|
+
mod_key = item_id[4:]
|
|
272
|
+
for name, desc in MODULES:
|
|
273
|
+
parts = name.split()
|
|
274
|
+
if len(parts) >= 2 and parts[1] == mod_key:
|
|
275
|
+
self.switch_screen(ModuleDetailScreen(name, desc))
|
|
276
|
+
break
|
|
277
|
+
|
|
278
|
+
def action_command_palette(self):
|
|
279
|
+
"""打开命令面板"""
|
|
280
|
+
main = self.query_one("#main_content")
|
|
281
|
+
main.mount(CommandPalette())
|
|
282
|
+
|
|
283
|
+
def action_search(self):
|
|
284
|
+
"""搜索"""
|
|
285
|
+
self.notify("搜索功能即将在 Phase 3.5 实现", severity="information")
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def run_tui():
|
|
289
|
+
"""启动 TUI 仪表盘"""
|
|
290
|
+
app = DevMateTUI()
|
|
291
|
+
app.run()
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DevMate Web 面板——FastAPI + HTMX 架构
|
|
3
|
+
|
|
4
|
+
提供仪表盘、模块管理、Token 认证、Swagger 文档。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from fastapi import FastAPI, HTTPException, Request
|
|
10
|
+
from fastapi.responses import HTMLResponse
|
|
11
|
+
from fastapi.staticfiles import StaticFiles
|
|
12
|
+
from fastapi.templating import Jinja2Templates
|
|
13
|
+
|
|
14
|
+
from devmate import __version__
|
|
15
|
+
|
|
16
|
+
HERE = Path(__file__).parent
|
|
17
|
+
templates = Jinja2Templates(directory=str(HERE / "templates"))
|
|
18
|
+
|
|
19
|
+
app = FastAPI(
|
|
20
|
+
title="DevMate Web",
|
|
21
|
+
version=__version__,
|
|
22
|
+
description="DevMate Web 管理面板",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# ── 静态文件 ──────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
app.mount("/static", StaticFiles(directory=str(HERE / "static")), name="static")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ── 页面路由 ───────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
MODULES = [
|
|
33
|
+
{"name": "toolkit", "icon": "📁", "desc": "文件整理/大小排行/缓存清理", "cli": "devmate tk"},
|
|
34
|
+
{"name": "apidev", "icon": "🌐", "desc": "HTTP 请求、Mock Server", "cli": "devmate api"},
|
|
35
|
+
{"name": "dbadmin", "icon": "🗄", "desc": "SQL 查询、表浏览", "cli": "devmate db"},
|
|
36
|
+
{"name": "apihub", "icon": "🤖", "desc": "AI 对话、模型管理", "cli": "devmate ai"},
|
|
37
|
+
{"name": "agent", "icon": "⚙️", "desc": "Agent 执行引擎", "cli": "devmate agent"},
|
|
38
|
+
{"name": "sshman", "icon": "🔗", "desc": "SSH 连接管理", "cli": "devmate ssh"},
|
|
39
|
+
{"name": "gitflow", "icon": "🔧", "desc": "Git 工作流", "cli": "devmate git"},
|
|
40
|
+
{"name": "regexlab", "icon": "🔍", "desc": "正则表达式测试", "cli": "devmate re"},
|
|
41
|
+
{"name": "vault", "icon": "🔐", "desc": "加密密钥管理", "cli": "devmate vault"},
|
|
42
|
+
{"name": "notekeeper","icon": "📝", "desc": "笔记知识库", "cli": "devmate note"},
|
|
43
|
+
{"name": "monitor", "icon": "📊", "desc": "系统资源监控", "cli": "devmate top"},
|
|
44
|
+
{"name": "reporter", "icon": "📋", "desc": "日报/周报", "cli": "devmate report"},
|
|
45
|
+
{"name": "scaffold", "icon": "🏗", "desc": "项目脚手架", "cli": "devmate scaf"},
|
|
46
|
+
{"name": "audit", "icon": "📜", "desc": "审计日志", "cli": "devmate audit"},
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@app.get("/", response_class=HTMLResponse)
|
|
51
|
+
async def dashboard(request: Request):
|
|
52
|
+
"""仪表盘主页"""
|
|
53
|
+
return templates.TemplateResponse(
|
|
54
|
+
"dashboard.html",
|
|
55
|
+
{"request": request, "modules": MODULES, "version": __version__},
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@app.get("/module/{name}", response_class=HTMLResponse)
|
|
60
|
+
async def module_detail(request: Request, name: str):
|
|
61
|
+
"""模块详情页"""
|
|
62
|
+
mod = next((m for m in MODULES if m["name"] == name), None)
|
|
63
|
+
if mod is None:
|
|
64
|
+
raise HTTPException(status_code=404, detail=f"模块不存在: {name}")
|
|
65
|
+
|
|
66
|
+
return templates.TemplateResponse(
|
|
67
|
+
"module.html",
|
|
68
|
+
{"request": request, "mod": mod, "version": __version__},
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ── API 路由 ────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
@app.get("/api/health")
|
|
75
|
+
async def health():
|
|
76
|
+
"""健康检查"""
|
|
77
|
+
return {"status": "ok", "version": __version__}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@app.get("/api/stats")
|
|
81
|
+
async def stats():
|
|
82
|
+
"""快速统计"""
|
|
83
|
+
return {
|
|
84
|
+
"modules": len(MODULES),
|
|
85
|
+
"toolkit_available": True,
|
|
86
|
+
"dbadmin_available": True,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ── 启动入口 ──────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
def run_web(host: str = "127.0.0.1", port: int = 8600):
|
|
93
|
+
"""启动 Web 服务器"""
|
|
94
|
+
import uvicorn
|
|
95
|
+
uvicorn.run(app, host=host, port=port, log_level="info")
|
devmate/bus/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Command Bus — 命令路由、中间件、三端适配器"""
|
devmate/bus/bus.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Command Bus——命令路由、中间件链、Handler 注册"""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
|
|
6
|
+
from devmate.bus.command import Command, CommandResult
|
|
7
|
+
from devmate.core.logging import logger
|
|
8
|
+
|
|
9
|
+
# ── Handler 类型 ────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
HandlerFunc = Callable[..., CommandResult]
|
|
12
|
+
HandlerMap = dict[str, dict[str, HandlerFunc]]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# ── 中间件 ──────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
class Middleware:
|
|
18
|
+
"""中间件基类"""
|
|
19
|
+
|
|
20
|
+
def before(self, command: Command) -> Command:
|
|
21
|
+
"""命令执行前"""
|
|
22
|
+
return command
|
|
23
|
+
|
|
24
|
+
def after(self, command: Command, result: CommandResult) -> CommandResult:
|
|
25
|
+
"""命令执行后"""
|
|
26
|
+
return result
|
|
27
|
+
|
|
28
|
+
def on_error(self, command: Command, error: Exception) -> CommandResult:
|
|
29
|
+
"""命令执行出错"""
|
|
30
|
+
return CommandResult(
|
|
31
|
+
success=False,
|
|
32
|
+
error=str(error),
|
|
33
|
+
suggestion="请检查参数或重试",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class LoggingMiddleware(Middleware):
|
|
38
|
+
"""日志中间件"""
|
|
39
|
+
|
|
40
|
+
def before(self, command: Command) -> Command:
|
|
41
|
+
logger.info(f"[{command.module}.{command.action}] args={command.args}")
|
|
42
|
+
return command
|
|
43
|
+
|
|
44
|
+
def after(self, command: Command, result: CommandResult) -> CommandResult:
|
|
45
|
+
msg = f"[{command.module}.{command.action}] success={result.success}"
|
|
46
|
+
logger.info(f"{msg} ({result.duration_ms}ms)")
|
|
47
|
+
return result
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TimingMiddleware(Middleware):
|
|
51
|
+
"""计时中间件"""
|
|
52
|
+
|
|
53
|
+
def before(self, command: Command) -> Command:
|
|
54
|
+
command._start_time = time.time()
|
|
55
|
+
return command
|
|
56
|
+
|
|
57
|
+
def after(self, command: Command, result: CommandResult) -> CommandResult:
|
|
58
|
+
if hasattr(command, "_start_time"):
|
|
59
|
+
result.duration_ms = round((time.time() - command._start_time) * 1000, 2)
|
|
60
|
+
return result
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# ── Command Bus ─────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
class CommandBus:
|
|
66
|
+
"""命令总线——路由 Command 到 Handler,执行中间件链"""
|
|
67
|
+
|
|
68
|
+
def __init__(self):
|
|
69
|
+
self._handlers: HandlerMap = {}
|
|
70
|
+
self._middlewares: list[Middleware] = []
|
|
71
|
+
|
|
72
|
+
def register(self, module: str, action: str, handler: HandlerFunc | None = None):
|
|
73
|
+
"""注册命令处理器——支持直接调用和装饰器两种用法
|
|
74
|
+
|
|
75
|
+
装饰器用法:
|
|
76
|
+
@bus.register("module", "action")
|
|
77
|
+
def handler(...): ...
|
|
78
|
+
|
|
79
|
+
直接调用:
|
|
80
|
+
bus.register("module", "action", handler_func)
|
|
81
|
+
"""
|
|
82
|
+
def _register(h: HandlerFunc) -> HandlerFunc:
|
|
83
|
+
self._handlers.setdefault(module, {})[action] = h
|
|
84
|
+
return h
|
|
85
|
+
|
|
86
|
+
if handler is not None:
|
|
87
|
+
_register(handler)
|
|
88
|
+
return handler
|
|
89
|
+
return _register
|
|
90
|
+
|
|
91
|
+
def register_module(self, module: str, handlers: dict[str, HandlerFunc]):
|
|
92
|
+
"""批量注册模块的所有命令处理器"""
|
|
93
|
+
self._handlers.setdefault(module, {}).update(handlers)
|
|
94
|
+
|
|
95
|
+
def add_middleware(self, middleware: Middleware):
|
|
96
|
+
"""添加中间件"""
|
|
97
|
+
self._middlewares.append(middleware)
|
|
98
|
+
|
|
99
|
+
def execute(self, command: Command) -> CommandResult:
|
|
100
|
+
"""同步执行命令"""
|
|
101
|
+
# 中间件链:before
|
|
102
|
+
for mw in self._middlewares:
|
|
103
|
+
command = mw.before(command)
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
# 查找 Handler
|
|
107
|
+
module_handlers = self._handlers.get(command.module)
|
|
108
|
+
if module_handlers is None:
|
|
109
|
+
return CommandResult(
|
|
110
|
+
success=False,
|
|
111
|
+
error=f"未知模块: {command.module}",
|
|
112
|
+
suggestion="运行 devmate --help 查看可用模块",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
handler = module_handlers.get(command.action)
|
|
116
|
+
if handler is None:
|
|
117
|
+
return CommandResult(
|
|
118
|
+
success=False,
|
|
119
|
+
error=f"未知命令: {command.module}.{command.action}",
|
|
120
|
+
suggestion=f"运行 devmate {command.module} --help 查看可用命令",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# 执行 Handler
|
|
124
|
+
result = handler(**command.args)
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
logger.exception(f"命令执行失败: {command.module}.{command.action}")
|
|
128
|
+
for mw in reversed(self._middlewares):
|
|
129
|
+
result = mw.on_error(command, e)
|
|
130
|
+
break
|
|
131
|
+
else:
|
|
132
|
+
result = CommandResult(
|
|
133
|
+
success=False,
|
|
134
|
+
error=str(e),
|
|
135
|
+
suggestion="请检查参数或重试",
|
|
136
|
+
)
|
|
137
|
+
else:
|
|
138
|
+
if not isinstance(result, CommandResult):
|
|
139
|
+
result = CommandResult(success=True, data=result)
|
|
140
|
+
|
|
141
|
+
# 中间件链:after(逆序)
|
|
142
|
+
for mw in reversed(self._middlewares):
|
|
143
|
+
result = mw.after(command, result)
|
|
144
|
+
|
|
145
|
+
return result
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# ── 全局总线实例 ─────────────────────────────────────
|
|
149
|
+
bus = CommandBus()
|
|
150
|
+
bus.add_middleware(LoggingMiddleware())
|
|
151
|
+
bus.add_middleware(TimingMiddleware())
|
devmate/bus/command.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Command 数据类和 CommandResult"""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from datetime import UTC, datetime
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SourceEndpoint(StrEnum):
|
|
10
|
+
"""命令来源终端"""
|
|
11
|
+
CLI = "cli"
|
|
12
|
+
TUI = "tui"
|
|
13
|
+
WEB = "web"
|
|
14
|
+
VSCODE = "vscode"
|
|
15
|
+
CI = "ci"
|
|
16
|
+
REMOTE = "remote"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class OutputFormat(StrEnum):
|
|
20
|
+
"""输出格式"""
|
|
21
|
+
RICH = "rich"
|
|
22
|
+
JSON = "json"
|
|
23
|
+
QUIET = "quiet"
|
|
24
|
+
HTML = "html"
|
|
25
|
+
TUI = "tui"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ── Command 选项 ────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class CommandOptions:
|
|
32
|
+
"""跨端命令选项"""
|
|
33
|
+
format: OutputFormat = OutputFormat.RICH
|
|
34
|
+
quiet: bool = False
|
|
35
|
+
timeout_seconds: float = 30.0
|
|
36
|
+
dry_run: bool = False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# ── Command 上下文 ──────────────────────────────────
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class CommandContext:
|
|
43
|
+
"""命令执行上下文"""
|
|
44
|
+
source: SourceEndpoint = SourceEndpoint.CLI
|
|
45
|
+
user: str = ""
|
|
46
|
+
session_id: str = ""
|
|
47
|
+
correlation_id: str = ""
|
|
48
|
+
timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ── Command 对象 ────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class Command:
|
|
55
|
+
"""三端统一的命令对象
|
|
56
|
+
|
|
57
|
+
CLI / TUI / Web 都生成同一个 Command 对象,交给 CommandBus 路由。
|
|
58
|
+
"""
|
|
59
|
+
module: str # 模块名,如 "toolkit"
|
|
60
|
+
action: str # 动作名,如 "organize"
|
|
61
|
+
args: dict[str, Any] = field(default_factory=dict)
|
|
62
|
+
options: CommandOptions = field(default_factory=CommandOptions)
|
|
63
|
+
context: CommandContext = field(default_factory=CommandContext)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ── Command 执行结果 ────────────────────────────────
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class CommandResult:
|
|
70
|
+
"""命令执行结果——支持多格式渲染"""
|
|
71
|
+
success: bool
|
|
72
|
+
data: Any = None
|
|
73
|
+
error: str | None = None
|
|
74
|
+
suggestion: str | None = None # 错误时的建议操作
|
|
75
|
+
duration_ms: float | None = None
|
|
76
|
+
|
|
77
|
+
def to_dict(self) -> dict:
|
|
78
|
+
return {
|
|
79
|
+
"success": self.success,
|
|
80
|
+
"data": self.data,
|
|
81
|
+
"error": self.error,
|
|
82
|
+
"suggestion": self.suggestion,
|
|
83
|
+
"duration_ms": self.duration_ms,
|
|
84
|
+
}
|
devmate/core/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core infrastructure — Config, Logging, Security"""
|