mofox-plugin-dev-toolkit 0.2.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.
Files changed (43) hide show
  1. mofox_plugin_dev_toolkit-0.2.1.dist-info/METADATA +409 -0
  2. mofox_plugin_dev_toolkit-0.2.1.dist-info/RECORD +43 -0
  3. mofox_plugin_dev_toolkit-0.2.1.dist-info/WHEEL +5 -0
  4. mofox_plugin_dev_toolkit-0.2.1.dist-info/entry_points.txt +2 -0
  5. mofox_plugin_dev_toolkit-0.2.1.dist-info/licenses/LICENSE +674 -0
  6. mofox_plugin_dev_toolkit-0.2.1.dist-info/top_level.txt +1 -0
  7. mpdt/__init__.py +15 -0
  8. mpdt/__main__.py +8 -0
  9. mpdt/cli.py +314 -0
  10. mpdt/commands/__init__.py +9 -0
  11. mpdt/commands/check.py +316 -0
  12. mpdt/commands/dev.py +550 -0
  13. mpdt/commands/generate.py +366 -0
  14. mpdt/commands/init.py +487 -0
  15. mpdt/dev/bridge_plugin/__init__.py +17 -0
  16. mpdt/dev/bridge_plugin/discovery_server.py +126 -0
  17. mpdt/dev/bridge_plugin/plugin.py +258 -0
  18. mpdt/templates/__init__.py +165 -0
  19. mpdt/templates/action_template.py +102 -0
  20. mpdt/templates/adapter_template.py +129 -0
  21. mpdt/templates/chatter_template.py +103 -0
  22. mpdt/templates/event_template.py +116 -0
  23. mpdt/templates/plus_command_template.py +150 -0
  24. mpdt/templates/prompt_template.py +92 -0
  25. mpdt/templates/router_template.py +175 -0
  26. mpdt/templates/tool_template.py +98 -0
  27. mpdt/utils/__init__.py +10 -0
  28. mpdt/utils/color_printer.py +99 -0
  29. mpdt/utils/config_loader.py +171 -0
  30. mpdt/utils/config_manager.py +297 -0
  31. mpdt/utils/file_ops.py +203 -0
  32. mpdt/utils/license_generator.py +980 -0
  33. mpdt/utils/plugin_parser.py +196 -0
  34. mpdt/utils/template_engine.py +112 -0
  35. mpdt/validators/__init__.py +26 -0
  36. mpdt/validators/auto_fix_validator.py +182 -0
  37. mpdt/validators/base.py +121 -0
  38. mpdt/validators/component_validator.py +415 -0
  39. mpdt/validators/config_validator.py +173 -0
  40. mpdt/validators/metadata_validator.py +125 -0
  41. mpdt/validators/structure_validator.py +70 -0
  42. mpdt/validators/style_validator.py +125 -0
  43. mpdt/validators/type_validator.py +223 -0
mpdt/commands/dev.py ADDED
@@ -0,0 +1,550 @@
1
+ """
2
+ mpdt dev 命令实现
3
+ 提供热重载开发模式
4
+ """
5
+
6
+ import asyncio
7
+ import json
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ import time
12
+ from pathlib import Path
13
+ from typing import Optional
14
+
15
+ import aiohttp
16
+ import websockets
17
+ from rich.console import Console
18
+ from rich.live import Live
19
+ from rich.panel import Panel
20
+ from rich.text import Text
21
+ from watchdog.events import FileSystemEvent, FileSystemEventHandler
22
+ from watchdog.observers import Observer
23
+
24
+ from mpdt.utils.config_manager import MPDTConfig, interactive_config
25
+ from mpdt.utils.plugin_parser import extract_plugin_name, get_plugin_info
26
+
27
+ console = Console()
28
+
29
+ # 发现服务器固定端口
30
+ DISCOVERY_PORT = 12318
31
+
32
+
33
+ class PluginFileWatcher(FileSystemEventHandler):
34
+ """插件文件监控"""
35
+
36
+ def __init__(self, plugin_path: Path, callback, loop):
37
+ self.plugin_path = plugin_path
38
+ self.callback = callback
39
+ self.loop = loop # 主事件循环
40
+ self.last_modified = {}
41
+ self.debounce_delay = 0.3 # 防抖延迟(秒)
42
+
43
+ def on_modified(self, event: FileSystemEvent):
44
+ if event.is_directory:
45
+ return
46
+
47
+ # 只监控 Python 文件
48
+ if not event.src_path.endswith(".py"):
49
+ return
50
+
51
+ # 防抖处理
52
+ now = time.time()
53
+ if event.src_path in self.last_modified:
54
+ if now - self.last_modified[event.src_path] < self.debounce_delay:
55
+ return
56
+
57
+ self.last_modified[event.src_path] = now
58
+
59
+ # 获取相对路径
60
+ rel_path = Path(event.src_path).relative_to(self.plugin_path)
61
+
62
+ # 在主事件循环中调度协程
63
+ asyncio.run_coroutine_threadsafe(self.callback(str(rel_path)), self.loop)
64
+
65
+ def on_created(self, event: FileSystemEvent):
66
+ self.on_modified(event)
67
+
68
+
69
+ class DevServer:
70
+ """开发服务器 - 监控文件并通过 WebSocket 控制主程序"""
71
+
72
+ def __init__(self, plugin_path: Path, config: MPDTConfig, mmc_path: Path | None = None):
73
+ self.plugin_path = plugin_path.absolute()
74
+ self.config = config
75
+ self.mmc_path = mmc_path or config.mmc_path
76
+
77
+ if not self.mmc_path:
78
+ raise ValueError("未配置 mmc 主程序路径")
79
+
80
+ self.plugin_name: str | None = None
81
+ self.process: subprocess.Popen | None = None
82
+ self.websocket: websockets.WebSocketClientProtocol | None = None
83
+ self.observer: Observer | None = None
84
+ self.main_host = "127.0.0.1"
85
+ self.main_port = 8000
86
+ self.running = False
87
+
88
+ async def start(self):
89
+ """启动开发服务器"""
90
+ try:
91
+ # 1. 解析插件名称
92
+ await self._parse_plugin_info()
93
+
94
+ # 2. 注入 DevBridge 插件
95
+ await self._inject_bridge_plugin()
96
+
97
+ # 3. 启动主程序
98
+ await self._start_main_process()
99
+
100
+ # 4. 等待主程序启动
101
+ await asyncio.sleep(3)
102
+
103
+ # 5. 发现主程序端口
104
+ await self._discover_main_server()
105
+
106
+ # 6. 连接 WebSocket
107
+ await self._connect_websocket()
108
+
109
+ # 7. 等待插件加载通知
110
+ await self._wait_for_plugin_loaded()
111
+
112
+ # 8. 启动文件监控
113
+ await self._start_file_watcher()
114
+
115
+ console.print("\n[bold green]✨ 开发服务器就绪![/bold green]")
116
+ console.print("监控文件变化中... (Ctrl+C 退出)\n")
117
+
118
+ self.running = True
119
+
120
+ # 保持运行
121
+ await self._keep_alive()
122
+
123
+ except KeyboardInterrupt:
124
+ console.print("\n[yellow]正在退出...[/yellow]")
125
+ except Exception as e:
126
+ console.print(f"[red]错误: {e}[/red]")
127
+ import traceback
128
+
129
+ traceback.print_exc()
130
+ finally:
131
+ await self.stop()
132
+
133
+ async def stop(self):
134
+ """停止开发服务器"""
135
+ self.running = False
136
+
137
+ # 停止文件监控
138
+ if self.observer:
139
+ self.observer.stop()
140
+ self.observer.join()
141
+
142
+ # 关闭 WebSocket
143
+ if self.websocket:
144
+ try:
145
+ await self.websocket.close()
146
+ except Exception:
147
+ pass
148
+
149
+ # 停止主程序 - 确保一定被关闭(包括所有子进程)
150
+ if self.process:
151
+ console.print("[cyan]🛑 正在关闭主程序...[/cyan]")
152
+ try:
153
+ import os
154
+
155
+ # Windows: 使用 taskkill 杀死整个进程树
156
+ if os.name == "nt":
157
+ try:
158
+ # /F 强制终止 /T 终止子进程树 /PID 指定进程ID
159
+ subprocess.run(
160
+ ["taskkill", "/F", "/T", "/PID", str(self.process.pid)],
161
+ capture_output=True,
162
+ timeout=5
163
+ )
164
+ console.print("[green]✓ 主程序及所有子进程已关闭[/green]")
165
+ except Exception as e:
166
+ console.print(f"[yellow]taskkill 失败: {e},尝试其他方法...[/yellow]")
167
+ # 降级到常规方法
168
+ self.process.terminate()
169
+ try:
170
+ self.process.wait(timeout=3)
171
+ except subprocess.TimeoutExpired:
172
+ self.process.kill()
173
+ self.process.wait()
174
+ else:
175
+ # Linux/Mac: 尝试优雅终止
176
+ self.process.terminate()
177
+ try:
178
+ self.process.wait(timeout=3)
179
+ console.print("[green]✓ 主程序已优雅关闭[/green]")
180
+ except subprocess.TimeoutExpired:
181
+ # 超时则强制杀死进程组
182
+ console.print("[yellow]主程序未响应,强制关闭...[/yellow]")
183
+ try:
184
+ # 杀死整个进程组
185
+ os.killpg(os.getpgid(self.process.pid), 9)
186
+ except Exception:
187
+ self.process.kill()
188
+ self.process.wait()
189
+ console.print("[green]✓ 主程序已强制关闭[/green]")
190
+ except Exception as e:
191
+ console.print(f"[yellow]警告: 关闭主程序时出错: {e}[/yellow]")
192
+ # 最后的尝试:直接 kill
193
+ try:
194
+ self.process.kill()
195
+ self.process.wait()
196
+ except Exception:
197
+ pass
198
+
199
+ # 清理 DevBridge 插件
200
+ await self._cleanup_bridge_plugin()
201
+
202
+ console.print("[green]开发服务器已停止[/green]")
203
+
204
+ async def _parse_plugin_info(self):
205
+ """解析插件信息"""
206
+ console.print(
207
+ Panel.fit(
208
+ f"[bold cyan]🚀 MoFox Plugin Dev Server[/bold cyan]\n\n"
209
+ f"📂 目录: {self.plugin_path.name}\n"
210
+ f"📍 路径: {self.plugin_path}"
211
+ )
212
+ )
213
+
214
+ # 提取插件名称
215
+ self.plugin_name = extract_plugin_name(self.plugin_path)
216
+
217
+ if not self.plugin_name:
218
+ console.print("[red]❌ 无法读取插件名称[/red]")
219
+ console.print("\n请确保 plugin.py 中有:")
220
+ console.print("```python")
221
+ console.print("class YourPlugin(BasePlugin):")
222
+ console.print(' plugin_name = "your_plugin"')
223
+ console.print("```")
224
+ raise ValueError("无法解析插件名称")
225
+
226
+ console.print(f"[green]✓ 插件名: {self.plugin_name}[/green]")
227
+
228
+ async def _inject_bridge_plugin(self):
229
+ """注入 DevBridge 插件到主程序"""
230
+ console.print("[cyan]🔗 注入开发模式插件...[/cyan]")
231
+
232
+ # DevBridge 插件源路径
233
+ bridge_source = Path(__file__).parent.parent / "dev" / "bridge_plugin"
234
+
235
+ if not bridge_source.exists():
236
+ raise FileNotFoundError(f"DevBridge 插件源不存在: {bridge_source}")
237
+
238
+ # 目标路径
239
+ bridge_target = self.mmc_path / "plugins" / "dev_bridge"
240
+
241
+ # 如果已存在,先删除
242
+ if bridge_target.exists():
243
+ shutil.rmtree(bridge_target)
244
+
245
+ # 复制插件
246
+ shutil.copytree(bridge_source, bridge_target)
247
+
248
+ console.print(f"[green]✓ DevBridge 插件已注入: {bridge_target}[/green]")
249
+
250
+ async def _cleanup_bridge_plugin(self):
251
+ """清理 DevBridge 插件"""
252
+ bridge_target = self.mmc_path / "plugins" / "dev_bridge"
253
+
254
+ if bridge_target.exists():
255
+ try:
256
+ shutil.rmtree(bridge_target)
257
+ console.print("[cyan]🧹 DevBridge 插件已清理[/cyan]")
258
+ except Exception as e:
259
+ console.print(f"[yellow]警告: 清理 DevBridge 插件失败: {e}[/yellow]")
260
+
261
+ async def _start_main_process(self):
262
+ """启动主程序"""
263
+ console.print(f"[cyan]🚀 启动主程序: {self.mmc_path / 'bot.py'}[/cyan]")
264
+
265
+ # 获取 Python 命令
266
+ python_cmd = self.config.get_python_command()
267
+ venv_type = self.config.venv_type
268
+ venv_path = self.config.venv_path
269
+
270
+ # 启动进程
271
+ try:
272
+ import os
273
+ import sys
274
+
275
+ # Windows 下打开新窗口
276
+ if os.name == "nt":
277
+ # 根据虚拟环境类型构建启动命令
278
+ if venv_type in ["venv", "uv"] and venv_path:
279
+ # venv/uv: 先激活环境再启动
280
+ activate_script = venv_path / "Scripts" / "activate.bat"
281
+ if activate_script.exists():
282
+ # 使用 cmd /k 保持窗口打开,先设置编码再激活和启动
283
+ cmd = ["cmd", "/k", f"chcp 65001 && cd /d {self.mmc_path} && {activate_script} && python bot.py"]
284
+ console.print(f"[dim]命令: 激活 {venv_type} 环境并启动[/dim]")
285
+ else:
286
+ # 降级到直接使用 Python 可执行文件
287
+ cmd = ["cmd", "/k", f"chcp 65001 && cd /d {self.mmc_path} && {python_cmd[0]} bot.py"]
288
+ console.print("[yellow]警告: 未找到激活脚本,使用直接启动[/yellow]")
289
+ elif venv_type == "conda" and venv_path:
290
+ # conda: 使用 conda activate
291
+ cmd = ["cmd", "/k", f"chcp 65001 && cd /d {self.mmc_path} && conda activate {venv_path} && python bot.py"]
292
+ console.print("[dim]命令: 激活 conda 环境并启动[/dim]")
293
+ elif venv_type == "poetry":
294
+ # poetry: 使用 poetry run
295
+ cmd = ["cmd", "/k", f"chcp 65001 && cd /d {self.mmc_path} && poetry run python bot.py"]
296
+ console.print("[dim]命令: 使用 poetry run 启动[/dim]")
297
+ else:
298
+ # 无虚拟环境或其他情况
299
+ cmd = ["cmd", "/k", f"chcp 65001 && cd /d {self.mmc_path} && python bot.py"]
300
+ console.print("[dim]命令: 使用系统 Python 启动[/dim]")
301
+
302
+ self.process = subprocess.Popen(cmd, creationflags=subprocess.CREATE_NEW_CONSOLE)
303
+ else:
304
+ # Linux/Mac 打开新终端窗口
305
+ if venv_type in ["venv", "uv"] and venv_path:
306
+ # venv/uv: 先激活环境再启动
307
+ activate_script = venv_path / "bin" / "activate"
308
+ if activate_script.exists():
309
+ shell_cmd = f"cd {self.mmc_path} && source {activate_script} && python bot.py; exec $SHELL"
310
+ else:
311
+ # 降级到直接使用 Python 可执行文件
312
+ shell_cmd = f"cd {self.mmc_path} && {python_cmd[0]} bot.py; exec $SHELL"
313
+ console.print("[yellow]警告: 未找到激活脚本,使用直接启动[/yellow]")
314
+ console.print(f"[dim]命令: 激活 {venv_type} 环境并启动[/dim]")
315
+ elif venv_type == "conda" and venv_path:
316
+ # conda: 使用 conda activate
317
+ shell_cmd = f"cd {self.mmc_path} && conda activate {venv_path} && python bot.py; exec $SHELL"
318
+ console.print("[dim]命令: 激活 conda 环境并启动[/dim]")
319
+ elif venv_type == "poetry":
320
+ # poetry: 使用 poetry run
321
+ shell_cmd = f"cd {self.mmc_path} && poetry run python bot.py; exec $SHELL"
322
+ console.print("[dim]命令: 使用 poetry run 启动[/dim]")
323
+ else:
324
+ # 无虚拟环境
325
+ shell_cmd = f"cd {self.mmc_path} && python bot.py; exec $SHELL"
326
+ console.print("[dim]命令: 使用系统 Python 启动[/dim]")
327
+
328
+ # 检测桌面环境并使用相应的终端
329
+ if sys.platform == "darwin":
330
+ # macOS: 使用 osascript 打开 Terminal.app
331
+ cmd = [
332
+ "osascript",
333
+ "-e",
334
+ f'tell application "Terminal" to do script "{shell_cmd}"',
335
+ ]
336
+ else:
337
+ # Linux: 尝试常见的终端模拟器
338
+ terminals = [
339
+ ("gnome-terminal", ["gnome-terminal", "--", "bash", "-c", shell_cmd]),
340
+ ("konsole", ["konsole", "-e", "bash", "-c", shell_cmd]),
341
+ ("xfce4-terminal", ["xfce4-terminal", "-e", f"bash -c '{shell_cmd}'"]),
342
+ ("xterm", ["xterm", "-e", f"bash -c '{shell_cmd}'"]),
343
+ ]
344
+
345
+ cmd = None
346
+ for term_name, term_cmd in terminals:
347
+ # 检查终端是否可用
348
+ if subprocess.run(["which", term_name], capture_output=True).returncode == 0:
349
+ cmd = term_cmd
350
+ break
351
+
352
+ if cmd is None:
353
+ # 降级到不打开新窗口
354
+ console.print("[yellow]警告: 未找到支持的终端模拟器,使用后台启动[/yellow]")
355
+ cmd = ["bash", "-c", f"cd {self.mmc_path} && source {activate_script} && python bot.py" if venv_type in ["venv", "uv"] and activate_script.exists() else f"cd {self.mmc_path} && python bot.py"]
356
+ self.process = subprocess.Popen(
357
+ cmd,
358
+ stdout=subprocess.PIPE,
359
+ stderr=subprocess.PIPE,
360
+ text=True
361
+ )
362
+ console.print("[green]✓ 主程序已启动(后台)[/green]")
363
+ return
364
+
365
+ self.process = subprocess.Popen(cmd)
366
+ console.print("[green]✓ 主程序已启动(新窗口)[/green]")
367
+ except Exception as e:
368
+ raise RuntimeError(f"启动主程序失败: {e}")
369
+
370
+ async def _discover_main_server(self):
371
+ """通过发现服务器获取主程序端口"""
372
+ console.print("[cyan]⏳ 等待主程序就绪...[/cyan]")
373
+
374
+ max_retries = 10
375
+ retry_delay = 1.0
376
+
377
+ await asyncio.sleep(10)
378
+ for i in range(max_retries):
379
+ try:
380
+ async with aiohttp.ClientSession() as session:
381
+ async with session.get(
382
+ f"http://127.0.0.1:{DISCOVERY_PORT}/api/server-info", timeout=aiohttp.ClientTimeout(total=2)
383
+ ) as resp:
384
+ if resp.status == 200:
385
+ data = await resp.json()
386
+ self.main_host = data["host"]
387
+ self.main_port = data["port"]
388
+ console.print(f"[green]✓ 发现主程序: http://{self.main_host}:{self.main_port}[/green]")
389
+ return
390
+ except Exception as e:
391
+ if i < max_retries - 1:
392
+ console.print(f"[dim]重试 {i + 1}/{max_retries}...[/dim]")
393
+ await asyncio.sleep(retry_delay)
394
+ else:
395
+ raise RuntimeError(f"无法连接到发现服务器: {e}")
396
+
397
+ async def _connect_websocket(self):
398
+ """连接 WebSocket"""
399
+ console.print("[cyan]🔌 连接开发模式接口...[/cyan]")
400
+
401
+ ws_url = f"ws://{self.main_host}:{self.main_port}/plugins/dev_bridge/dev_bridge_router/ws"
402
+
403
+ max_retries = 5
404
+ retry_delay = 1.0
405
+
406
+ for i in range(max_retries):
407
+ try:
408
+ self.websocket = await websockets.connect(ws_url)
409
+ console.print("[green]✓ 已连接到主程序[/green]")
410
+ return
411
+ except Exception as e:
412
+ if i < max_retries - 1:
413
+ console.print(f"[dim]重试 {i + 1}/{max_retries}...[/dim]")
414
+ await asyncio.sleep(retry_delay)
415
+ else:
416
+ raise RuntimeError(f"无法连接到 WebSocket: {e}")
417
+
418
+ async def _wait_for_plugin_loaded(self):
419
+ """等待插件加载通知"""
420
+ console.print("[cyan]⏳ 等待插件加载...[/cyan]")
421
+
422
+ try:
423
+ # 设置超时
424
+ async with asyncio.timeout(10):
425
+ while True:
426
+ message = await self.websocket.recv()
427
+ data = json.loads(message)
428
+
429
+ if data.get("type") == "plugins_loaded":
430
+ loaded = data.get("loaded", [])
431
+ failed = data.get("failed", [])
432
+
433
+ if self.plugin_name in loaded:
434
+ console.print(f"[green]✓ 插件已加载: {self.plugin_name}[/green]")
435
+ return
436
+ elif self.plugin_name in failed:
437
+ console.print(f"[red]❌ 插件加载失败: {self.plugin_name}[/red]")
438
+ raise RuntimeError(f"插件加载失败: {self.plugin_name}")
439
+ else:
440
+ console.print(f"[yellow]⚠️ 插件未找到: {self.plugin_name}[/yellow]")
441
+ raise RuntimeError(f"插件未找到: {self.plugin_name}")
442
+ except asyncio.TimeoutError:
443
+ console.print("[yellow]⚠️ 等待插件加载超时[/yellow]")
444
+ raise RuntimeError("等待插件加载超时")
445
+
446
+ async def _start_file_watcher(self):
447
+ """启动文件监控"""
448
+ console.print(f"[cyan]👀 开始监控: {self.plugin_path}[/cyan]")
449
+
450
+ handler = PluginFileWatcher(
451
+ self.plugin_path,
452
+ self._on_file_changed,
453
+ asyncio.get_running_loop() # 传递当前事件循环
454
+ )
455
+
456
+ self.observer = Observer()
457
+ self.observer.schedule(handler, str(self.plugin_path), recursive=True)
458
+ self.observer.start()
459
+
460
+ async def _on_file_changed(self, rel_path: str):
461
+ """文件变化回调"""
462
+ if not self.running or not self.websocket:
463
+ return
464
+
465
+ console.print(f"[yellow]📝 检测到变化: {rel_path}[/yellow]")
466
+ console.print(f"[cyan]🔄 重新加载 {self.plugin_name}...[/cyan]")
467
+
468
+ try:
469
+ # 只发送重载命令,不等待响应
470
+ # 响应将由 _keep_alive 统一处理
471
+ await self.websocket.send(json.dumps({"command": "reload", "plugin_name": self.plugin_name}))
472
+
473
+ except Exception as e:
474
+ console.print(f"[red]❌ 发送重载命令失败: {e}[/red]\n")
475
+
476
+ async def _keep_alive(self):
477
+ """保持运行并处理 WebSocket 消息"""
478
+ try:
479
+ while self.running:
480
+ try:
481
+ # 接收 WebSocket 消息
482
+ message = await asyncio.wait_for(self.websocket.recv(), timeout=1.0)
483
+
484
+ # 处理消息
485
+ data = json.loads(message)
486
+ msg_type = data.get("type")
487
+
488
+ if msg_type == "reload_result":
489
+ # 重载结果
490
+ plugin_name = data.get("plugin_name")
491
+ if data.get("success"):
492
+ console.print(f"[green]✅ 插件 {plugin_name} 重载成功[/green]\n")
493
+ else:
494
+ console.print(f"[red]❌ 插件重载失败: {data.get('message')}[/red]\n")
495
+ elif msg_type == "plugin_reloaded":
496
+ # 广播的重载消息
497
+ pass
498
+ elif msg_type == "pong":
499
+ # 心跳响应
500
+ pass
501
+
502
+ except TimeoutError:
503
+ # 超时是正常的,继续循环
504
+ continue
505
+ except websockets.exceptions.ConnectionClosed:
506
+ console.print("[red]WebSocket 连接已断开[/red]")
507
+ break
508
+
509
+ except KeyboardInterrupt:
510
+ pass
511
+
512
+
513
+ async def dev_command(
514
+ plugin_path: Path | None = None,
515
+ mmc_path: Path | None = None,
516
+ ):
517
+ """启动开发模式
518
+
519
+ Args:
520
+ plugin_path: 插件路径,默认为当前目录
521
+ mmc_path: mmc 主程序路径,默认从配置读取
522
+ """
523
+ # 确定插件路径
524
+ if plugin_path is None:
525
+ plugin_path = Path.cwd()
526
+
527
+ # 加载配置
528
+ config = MPDTConfig()
529
+
530
+ # 如果未配置,运行配置向导
531
+ if not config.is_configured() and mmc_path is None:
532
+ console.print("[yellow]未找到配置,启动配置向导...[/yellow]\n")
533
+ config = interactive_config()
534
+
535
+ # 如果提供了 mmc_path,使用它
536
+ if mmc_path:
537
+ config.mmc_path = mmc_path
538
+
539
+ # 验证配置
540
+ valid, errors = config.validate()
541
+ if not valid:
542
+ console.print("[red]配置验证失败:[/red]")
543
+ for error in errors:
544
+ console.print(f" - {error}")
545
+ console.print("\n请运行 [cyan]mpdt config init[/cyan] 重新配置")
546
+ return
547
+
548
+ # 创建并启动开发服务器
549
+ server = DevServer(plugin_path, config, mmc_path)
550
+ await server.start()