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.
- mofox_plugin_dev_toolkit-0.2.1.dist-info/METADATA +409 -0
- mofox_plugin_dev_toolkit-0.2.1.dist-info/RECORD +43 -0
- mofox_plugin_dev_toolkit-0.2.1.dist-info/WHEEL +5 -0
- mofox_plugin_dev_toolkit-0.2.1.dist-info/entry_points.txt +2 -0
- mofox_plugin_dev_toolkit-0.2.1.dist-info/licenses/LICENSE +674 -0
- mofox_plugin_dev_toolkit-0.2.1.dist-info/top_level.txt +1 -0
- mpdt/__init__.py +15 -0
- mpdt/__main__.py +8 -0
- mpdt/cli.py +314 -0
- mpdt/commands/__init__.py +9 -0
- mpdt/commands/check.py +316 -0
- mpdt/commands/dev.py +550 -0
- mpdt/commands/generate.py +366 -0
- mpdt/commands/init.py +487 -0
- mpdt/dev/bridge_plugin/__init__.py +17 -0
- mpdt/dev/bridge_plugin/discovery_server.py +126 -0
- mpdt/dev/bridge_plugin/plugin.py +258 -0
- mpdt/templates/__init__.py +165 -0
- mpdt/templates/action_template.py +102 -0
- mpdt/templates/adapter_template.py +129 -0
- mpdt/templates/chatter_template.py +103 -0
- mpdt/templates/event_template.py +116 -0
- mpdt/templates/plus_command_template.py +150 -0
- mpdt/templates/prompt_template.py +92 -0
- mpdt/templates/router_template.py +175 -0
- mpdt/templates/tool_template.py +98 -0
- mpdt/utils/__init__.py +10 -0
- mpdt/utils/color_printer.py +99 -0
- mpdt/utils/config_loader.py +171 -0
- mpdt/utils/config_manager.py +297 -0
- mpdt/utils/file_ops.py +203 -0
- mpdt/utils/license_generator.py +980 -0
- mpdt/utils/plugin_parser.py +196 -0
- mpdt/utils/template_engine.py +112 -0
- mpdt/validators/__init__.py +26 -0
- mpdt/validators/auto_fix_validator.py +182 -0
- mpdt/validators/base.py +121 -0
- mpdt/validators/component_validator.py +415 -0
- mpdt/validators/config_validator.py +173 -0
- mpdt/validators/metadata_validator.py +125 -0
- mpdt/validators/structure_validator.py +70 -0
- mpdt/validators/style_validator.py +125 -0
- mpdt/validators/type_validator.py +223 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool 组件模板
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
TOOL_TEMPLATE = '''"""
|
|
6
|
+
{description}
|
|
7
|
+
|
|
8
|
+
Created by: {author}
|
|
9
|
+
Created at: {date}
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from src.common.logger import get_logger
|
|
15
|
+
from src.plugin_system import BaseTool, ToolParamType
|
|
16
|
+
|
|
17
|
+
logger = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class {class_name}(BaseTool):
|
|
21
|
+
"""
|
|
22
|
+
{description}
|
|
23
|
+
|
|
24
|
+
Tool 组件可以被 LLM 调用来执行特定功能。
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
# Tool 元数据
|
|
28
|
+
name: str = "{tool_name}"
|
|
29
|
+
description: str = "{description}"
|
|
30
|
+
available_for_llm: bool = True # 是否可供 LLM 使用
|
|
31
|
+
|
|
32
|
+
# 定义工具参数
|
|
33
|
+
# 格式: [("参数名", 参数类型, "参数描述", 是否必填, 枚举值列表)]
|
|
34
|
+
parameters = [
|
|
35
|
+
("query", ToolParamType.STRING, "查询内容", True, None),
|
|
36
|
+
("limit", ToolParamType.INTEGER, "返回结果数量限制", False, None),
|
|
37
|
+
("format", ToolParamType.STRING, "输出格式", False, ["json", "text", "markdown"]),
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
# 缓存配置(可选)
|
|
41
|
+
enable_cache: bool = False # 是否启用缓存
|
|
42
|
+
cache_ttl: int = 3600 # 缓存过期时间(秒)
|
|
43
|
+
|
|
44
|
+
async def execute(self, function_args: dict[str, Any]) -> dict[str, Any]:
|
|
45
|
+
"""
|
|
46
|
+
执行工具功能(供 LLM 调用)
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
function_args: LLM 传入的参数,格式符合 parameters 定义
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
执行结果字典
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
logger.info(f"执行 Tool: {{self.name}}")
|
|
56
|
+
logger.debug(f"参数: {{function_args}}")
|
|
57
|
+
|
|
58
|
+
# 获取参数
|
|
59
|
+
query = function_args.get("query")
|
|
60
|
+
limit = function_args.get("limit", 10)
|
|
61
|
+
output_format = function_args.get("format", "text")
|
|
62
|
+
|
|
63
|
+
# TODO: 实现工具的核心逻辑
|
|
64
|
+
result_data = self._process_query(query, limit)
|
|
65
|
+
|
|
66
|
+
# 格式化返回结果
|
|
67
|
+
return {{
|
|
68
|
+
"status": "success",
|
|
69
|
+
"data": result_data,
|
|
70
|
+
"message": "执行成功"
|
|
71
|
+
}}
|
|
72
|
+
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.error(f"Tool 执行失败: {{e}}")
|
|
75
|
+
return {{
|
|
76
|
+
"status": "error",
|
|
77
|
+
"message": str(e)
|
|
78
|
+
}}
|
|
79
|
+
|
|
80
|
+
def _process_query(self, query: str, limit: int) -> Any:
|
|
81
|
+
"""
|
|
82
|
+
处理查询的核心逻辑
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
query: 查询内容
|
|
86
|
+
limit: 结果数量限制
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
处理结果
|
|
90
|
+
"""
|
|
91
|
+
# TODO: 实现具体的处理逻辑
|
|
92
|
+
return {{"query": query, "count": limit}}
|
|
93
|
+
'''
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_tool_template() -> str:
|
|
97
|
+
"""获取 Tool 组件模板"""
|
|
98
|
+
return TOOL_TEMPLATE
|
mpdt/utils/__init__.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
彩色输出工具
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
from rich.tree import Tree
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def print_success(message: str) -> None:
|
|
17
|
+
"""打印成功消息"""
|
|
18
|
+
console.print(f"[bold green]✅ {message}[/bold green]")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def print_error(message: str) -> None:
|
|
22
|
+
"""打印错误消息"""
|
|
23
|
+
console.print(f"[bold red]❌ {message}[/bold red]")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def print_warning(message: str) -> None:
|
|
27
|
+
"""打印警告消息"""
|
|
28
|
+
console.print(f"[bold yellow]⚠️ {message}[/bold yellow]")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def print_info(message: str) -> None:
|
|
32
|
+
"""打印信息消息"""
|
|
33
|
+
console.print(f"[bold blue]ℹ️ {message}[/bold blue]")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def print_step(message: str) -> None:
|
|
37
|
+
"""打印步骤消息"""
|
|
38
|
+
console.print(f"[bold cyan]🔸 {message}[/bold cyan]")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def print_panel(title: str, content: str, style: str = "green") -> None:
|
|
42
|
+
"""打印面板"""
|
|
43
|
+
console.print(Panel(content, title=title, style=style))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def print_table(title: str, columns: list[str], rows: list[list[str]]) -> None:
|
|
47
|
+
"""打印表格"""
|
|
48
|
+
table = Table(title=title)
|
|
49
|
+
|
|
50
|
+
for col in columns:
|
|
51
|
+
table.add_column(col, style="cyan")
|
|
52
|
+
|
|
53
|
+
for row in rows:
|
|
54
|
+
table.add_row(*row)
|
|
55
|
+
|
|
56
|
+
console.print(table)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def print_tree(root_label: str, tree_data: dict[str, Any]) -> None:
|
|
60
|
+
"""
|
|
61
|
+
打印树形结构
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
root_label: 根节点标签
|
|
65
|
+
tree_data: 树形数据(字典或列表)
|
|
66
|
+
"""
|
|
67
|
+
tree = Tree(f"[bold blue]{root_label}[/bold blue]")
|
|
68
|
+
|
|
69
|
+
def add_branch(parent: Tree, data: Any) -> None:
|
|
70
|
+
if isinstance(data, dict):
|
|
71
|
+
for key, value in data.items():
|
|
72
|
+
if isinstance(value, (dict, list)):
|
|
73
|
+
branch = parent.add(f"[cyan]{key}/[/cyan]")
|
|
74
|
+
add_branch(branch, value)
|
|
75
|
+
else:
|
|
76
|
+
parent.add(f"[green]{key}[/green]")
|
|
77
|
+
elif isinstance(data, list):
|
|
78
|
+
for item in data:
|
|
79
|
+
if isinstance(item, str):
|
|
80
|
+
parent.add(f"[green]{item}[/green]")
|
|
81
|
+
else:
|
|
82
|
+
add_branch(parent, item)
|
|
83
|
+
|
|
84
|
+
add_branch(tree, tree_data)
|
|
85
|
+
console.print(tree)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def create_progress() -> Progress:
|
|
89
|
+
"""创建进度条"""
|
|
90
|
+
return Progress(
|
|
91
|
+
SpinnerColumn(),
|
|
92
|
+
TextColumn("[progress.description]{task.description}"),
|
|
93
|
+
console=console,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def print_divider(char: str = "━", length: int = 80) -> None:
|
|
98
|
+
"""打印分割线"""
|
|
99
|
+
console.print(char * length, style="dim")
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""
|
|
2
|
+
配置加载器
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import toml
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ConfigLoader:
|
|
12
|
+
"""配置加载器"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, config_path: Path | str | None = None):
|
|
15
|
+
"""
|
|
16
|
+
初始化配置加载器
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
config_path: 配置文件路径
|
|
20
|
+
"""
|
|
21
|
+
self.config_path = Path(config_path) if config_path else None
|
|
22
|
+
self.config: dict[str, Any] = {}
|
|
23
|
+
|
|
24
|
+
if self.config_path and self.config_path.exists():
|
|
25
|
+
self.load()
|
|
26
|
+
|
|
27
|
+
def load(self) -> dict[str, Any]:
|
|
28
|
+
"""
|
|
29
|
+
加载配置文件
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
配置字典
|
|
33
|
+
"""
|
|
34
|
+
if not self.config_path or not self.config_path.exists():
|
|
35
|
+
return {}
|
|
36
|
+
|
|
37
|
+
self.config = toml.load(str(self.config_path))
|
|
38
|
+
return self.config
|
|
39
|
+
|
|
40
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
41
|
+
"""
|
|
42
|
+
获取配置项
|
|
43
|
+
|
|
44
|
+
支持点号分隔的嵌套键,例如: "mpdt.check.level"
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
key: 配置键
|
|
48
|
+
default: 默认值
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
配置值
|
|
52
|
+
"""
|
|
53
|
+
keys = key.split(".")
|
|
54
|
+
value = self.config
|
|
55
|
+
|
|
56
|
+
for k in keys:
|
|
57
|
+
if isinstance(value, dict) and k in value:
|
|
58
|
+
value = value[k]
|
|
59
|
+
else:
|
|
60
|
+
return default
|
|
61
|
+
|
|
62
|
+
return value
|
|
63
|
+
|
|
64
|
+
def set(self, key: str, value: Any) -> None:
|
|
65
|
+
"""
|
|
66
|
+
设置配置项
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
key: 配置键
|
|
70
|
+
value: 配置值
|
|
71
|
+
"""
|
|
72
|
+
keys = key.split(".")
|
|
73
|
+
config = self.config
|
|
74
|
+
|
|
75
|
+
for k in keys[:-1]:
|
|
76
|
+
if k not in config:
|
|
77
|
+
config[k] = {}
|
|
78
|
+
config = config[k]
|
|
79
|
+
|
|
80
|
+
config[keys[-1]] = value
|
|
81
|
+
|
|
82
|
+
def save(self, path: Path | str | None = None) -> None:
|
|
83
|
+
"""
|
|
84
|
+
保存配置到文件
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
path: 保存路径,如果为 None 则使用初始化时的路径
|
|
88
|
+
"""
|
|
89
|
+
save_path = Path(path) if path else self.config_path
|
|
90
|
+
|
|
91
|
+
if not save_path:
|
|
92
|
+
raise ValueError("未指定配置文件路径")
|
|
93
|
+
|
|
94
|
+
save_path.parent.mkdir(parents=True, exist_ok=True)
|
|
95
|
+
|
|
96
|
+
with open(save_path, "w", encoding="utf-8") as f:
|
|
97
|
+
toml.dump(self.config, f)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def load_mpdt_config(project_dir: Path | str = ".") -> ConfigLoader:
|
|
101
|
+
"""
|
|
102
|
+
加载 MPDT 配置文件
|
|
103
|
+
|
|
104
|
+
优先级:
|
|
105
|
+
1. .mpdtrc.toml
|
|
106
|
+
2. pyproject.toml 中的 [tool.mpdt] 部分
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
project_dir: 项目目录
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
ConfigLoader 对象
|
|
113
|
+
"""
|
|
114
|
+
project_dir = Path(project_dir)
|
|
115
|
+
|
|
116
|
+
# 首先尝试加载 .mpdtrc.toml
|
|
117
|
+
mpdtrc_path = project_dir / ".mpdtrc.toml"
|
|
118
|
+
if mpdtrc_path.exists():
|
|
119
|
+
return ConfigLoader(mpdtrc_path)
|
|
120
|
+
|
|
121
|
+
# 然后尝试从 pyproject.toml 加载
|
|
122
|
+
pyproject_path = project_dir / "pyproject.toml"
|
|
123
|
+
if pyproject_path.exists():
|
|
124
|
+
config = toml.load(str(pyproject_path))
|
|
125
|
+
if "tool" in config and "mpdt" in config["tool"]:
|
|
126
|
+
loader = ConfigLoader()
|
|
127
|
+
loader.config = config["tool"]["mpdt"]
|
|
128
|
+
return loader
|
|
129
|
+
|
|
130
|
+
# 返回空配置
|
|
131
|
+
return ConfigLoader()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_default_config() -> dict[str, Any]:
|
|
135
|
+
"""
|
|
136
|
+
获取默认配置
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
默认配置字典
|
|
140
|
+
"""
|
|
141
|
+
return {
|
|
142
|
+
"mpdt": {
|
|
143
|
+
"version": "0.1.2",
|
|
144
|
+
},
|
|
145
|
+
"check": {
|
|
146
|
+
"level": "warning",
|
|
147
|
+
"auto_fix": False,
|
|
148
|
+
"ignore_patterns": ["tests/*", "*.pyc", "__pycache__/*"],
|
|
149
|
+
},
|
|
150
|
+
"test": {
|
|
151
|
+
"coverage_threshold": 80,
|
|
152
|
+
"pytest_args": ["-v", "--tb=short"],
|
|
153
|
+
},
|
|
154
|
+
"build": {
|
|
155
|
+
"output_dir": "dist",
|
|
156
|
+
"include_docs": True,
|
|
157
|
+
"include_tests": False,
|
|
158
|
+
"format": "zip",
|
|
159
|
+
},
|
|
160
|
+
"dev": {
|
|
161
|
+
"port": 8080,
|
|
162
|
+
"host": "127.0.0.1",
|
|
163
|
+
"reload": True,
|
|
164
|
+
"check_on_save": True,
|
|
165
|
+
},
|
|
166
|
+
"templates": {
|
|
167
|
+
"author": "",
|
|
168
|
+
"license": "GPL-v3.0",
|
|
169
|
+
"python_version": "^3.11",
|
|
170
|
+
},
|
|
171
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MPDT 配置管理器
|
|
3
|
+
管理 mmc 路径、虚拟环境等配置信息
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Literal, Optional
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
import tomli
|
|
12
|
+
import tomli_w
|
|
13
|
+
except ImportError:
|
|
14
|
+
# 如果没有安装 tomli,使用 toml
|
|
15
|
+
try:
|
|
16
|
+
import toml as tomli
|
|
17
|
+
import toml as tomli_w
|
|
18
|
+
except ImportError:
|
|
19
|
+
tomli = None
|
|
20
|
+
tomli_w = None
|
|
21
|
+
|
|
22
|
+
VenvType = Literal["venv", "uv", "conda", "poetry", "none"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MPDTConfig:
|
|
26
|
+
"""MPDT 配置管理器"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, config_path: Path | None = None):
|
|
29
|
+
"""初始化配置管理器
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
config_path: 配置文件路径,默认为 ~/.mpdt/config.toml
|
|
33
|
+
"""
|
|
34
|
+
if config_path is None:
|
|
35
|
+
config_path = Path.home() / ".mpdt" / "config.toml"
|
|
36
|
+
|
|
37
|
+
self.config_path = config_path
|
|
38
|
+
self._config: dict = {}
|
|
39
|
+
|
|
40
|
+
# 加载配置
|
|
41
|
+
if self.config_path.exists():
|
|
42
|
+
self.load()
|
|
43
|
+
|
|
44
|
+
def load(self) -> None:
|
|
45
|
+
"""加载配置文件"""
|
|
46
|
+
if not self.config_path.exists():
|
|
47
|
+
self._config = {}
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
if tomli is None:
|
|
51
|
+
raise ImportError("需要安装 tomli 库来读取配置文件")
|
|
52
|
+
|
|
53
|
+
with open(self.config_path, "rb") as f:
|
|
54
|
+
self._config = tomli.load(f)
|
|
55
|
+
|
|
56
|
+
def save(self) -> None:
|
|
57
|
+
"""保存配置文件"""
|
|
58
|
+
if tomli_w is None:
|
|
59
|
+
raise ImportError("需要安装 tomli-w 库来写入配置文件")
|
|
60
|
+
|
|
61
|
+
# 确保目录存在
|
|
62
|
+
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
63
|
+
|
|
64
|
+
with open(self.config_path, "wb") as f:
|
|
65
|
+
tomli_w.dump(self._config, f)
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def mmc_path(self) -> Path | None:
|
|
69
|
+
"""获取 mmc 主程序路径"""
|
|
70
|
+
path_str = self._config.get("mmc", {}).get("path")
|
|
71
|
+
return Path(path_str) if path_str else None
|
|
72
|
+
|
|
73
|
+
@mmc_path.setter
|
|
74
|
+
def mmc_path(self, path: Path | str) -> None:
|
|
75
|
+
"""设置 mmc 主程序路径"""
|
|
76
|
+
if "mmc" not in self._config:
|
|
77
|
+
self._config["mmc"] = {}
|
|
78
|
+
self._config["mmc"]["path"] = str(Path(path).absolute())
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def venv_path(self) -> Path | None:
|
|
82
|
+
"""获取虚拟环境路径"""
|
|
83
|
+
path_str = self._config.get("mmc", {}).get("venv_path")
|
|
84
|
+
return Path(path_str) if path_str else None
|
|
85
|
+
|
|
86
|
+
@venv_path.setter
|
|
87
|
+
def venv_path(self, path: Path | str | None) -> None:
|
|
88
|
+
"""设置虚拟环境路径"""
|
|
89
|
+
if "mmc" not in self._config:
|
|
90
|
+
self._config["mmc"] = {}
|
|
91
|
+
if path is None:
|
|
92
|
+
self._config["mmc"]["venv_path"] = None
|
|
93
|
+
else:
|
|
94
|
+
self._config["mmc"]["venv_path"] = str(Path(path).absolute())
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def venv_type(self) -> VenvType:
|
|
98
|
+
"""获取虚拟环境类型"""
|
|
99
|
+
return self._config.get("mmc", {}).get("venv_type", "venv")
|
|
100
|
+
|
|
101
|
+
@venv_type.setter
|
|
102
|
+
def venv_type(self, venv_type: VenvType) -> None:
|
|
103
|
+
"""设置虚拟环境类型"""
|
|
104
|
+
if "mmc" not in self._config:
|
|
105
|
+
self._config["mmc"] = {}
|
|
106
|
+
self._config["mmc"]["venv_type"] = venv_type
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def auto_reload(self) -> bool:
|
|
110
|
+
"""是否自动重载"""
|
|
111
|
+
return self._config.get("dev", {}).get("auto_reload", True)
|
|
112
|
+
|
|
113
|
+
@auto_reload.setter
|
|
114
|
+
def auto_reload(self, value: bool) -> None:
|
|
115
|
+
"""设置是否自动重载"""
|
|
116
|
+
if "dev" not in self._config:
|
|
117
|
+
self._config["dev"] = {}
|
|
118
|
+
self._config["dev"]["auto_reload"] = value
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def reload_delay(self) -> float:
|
|
122
|
+
"""重载延迟(秒)"""
|
|
123
|
+
return self._config.get("dev", {}).get("reload_delay", 0.3)
|
|
124
|
+
|
|
125
|
+
@reload_delay.setter
|
|
126
|
+
def reload_delay(self, value: float) -> None:
|
|
127
|
+
"""设置重载延迟"""
|
|
128
|
+
if "dev" not in self._config:
|
|
129
|
+
self._config["dev"] = {}
|
|
130
|
+
self._config["dev"]["reload_delay"] = value
|
|
131
|
+
|
|
132
|
+
def get_python_command(self) -> list[str]:
|
|
133
|
+
"""获取 Python 启动命令
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Python 命令列表,例如:
|
|
137
|
+
- ["E:/venv/Scripts/python.exe"]
|
|
138
|
+
- ["conda", "run", "-p", "E:/conda_env", "python"]
|
|
139
|
+
- ["poetry", "run", "python"]
|
|
140
|
+
"""
|
|
141
|
+
venv_type = self.venv_type
|
|
142
|
+
venv_path = self.venv_path
|
|
143
|
+
|
|
144
|
+
if venv_type == "none" or not venv_path:
|
|
145
|
+
return ["python"]
|
|
146
|
+
|
|
147
|
+
if venv_type == "venv" or venv_type == "uv":
|
|
148
|
+
# venv 和 uv 使用相同的结构
|
|
149
|
+
if os.name == "nt": # Windows
|
|
150
|
+
python_exe = venv_path / "Scripts" / "python.exe"
|
|
151
|
+
else: # Unix-like
|
|
152
|
+
python_exe = venv_path / "bin" / "python"
|
|
153
|
+
|
|
154
|
+
if python_exe.exists():
|
|
155
|
+
return [str(python_exe)]
|
|
156
|
+
else:
|
|
157
|
+
# 降级到系统 Python
|
|
158
|
+
return ["python"]
|
|
159
|
+
|
|
160
|
+
elif venv_type == "conda":
|
|
161
|
+
return ["conda", "run", "-p", str(venv_path), "python"]
|
|
162
|
+
|
|
163
|
+
elif venv_type == "poetry":
|
|
164
|
+
# poetry 需要在 mmc 目录中执行
|
|
165
|
+
return ["poetry", "run", "python"]
|
|
166
|
+
|
|
167
|
+
else:
|
|
168
|
+
return ["python"]
|
|
169
|
+
|
|
170
|
+
def validate(self) -> tuple[bool, list[str]]:
|
|
171
|
+
"""验证配置
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
(是否有效, 错误消息列表)
|
|
175
|
+
"""
|
|
176
|
+
errors = []
|
|
177
|
+
|
|
178
|
+
# 检查 mmc 路径
|
|
179
|
+
if not self.mmc_path:
|
|
180
|
+
errors.append("未配置 mmc 主程序路径")
|
|
181
|
+
elif not self.mmc_path.exists():
|
|
182
|
+
errors.append(f"mmc 路径不存在: {self.mmc_path}")
|
|
183
|
+
else:
|
|
184
|
+
# 检查是否有 bot.py
|
|
185
|
+
bot_file = self.mmc_path / "bot.py"
|
|
186
|
+
if not bot_file.exists():
|
|
187
|
+
errors.append(f"未找到 bot.py: {bot_file}")
|
|
188
|
+
|
|
189
|
+
# 检查虚拟环境
|
|
190
|
+
if self.venv_type != "none" and self.venv_path:
|
|
191
|
+
if not self.venv_path.exists():
|
|
192
|
+
errors.append(f"虚拟环境路径不存在: {self.venv_path}")
|
|
193
|
+
else:
|
|
194
|
+
# 检查 Python 可执行文件
|
|
195
|
+
python_cmd = self.get_python_command()
|
|
196
|
+
if self.venv_type in ["venv", "uv"]:
|
|
197
|
+
python_path = Path(python_cmd[0])
|
|
198
|
+
if not python_path.exists():
|
|
199
|
+
errors.append(f"Python 可执行文件不存在: {python_path}")
|
|
200
|
+
|
|
201
|
+
return len(errors) == 0, errors
|
|
202
|
+
|
|
203
|
+
def is_configured(self) -> bool:
|
|
204
|
+
"""检查是否已配置"""
|
|
205
|
+
return self.mmc_path is not None
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def get_default_config() -> MPDTConfig:
|
|
209
|
+
"""获取默认配置实例"""
|
|
210
|
+
return MPDTConfig()
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def interactive_config() -> MPDTConfig:
|
|
214
|
+
"""交互式配置向导
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
配置好的 MPDTConfig 实例
|
|
218
|
+
"""
|
|
219
|
+
from rich.console import Console
|
|
220
|
+
from rich.prompt import Prompt, Confirm
|
|
221
|
+
from rich.panel import Panel
|
|
222
|
+
|
|
223
|
+
console = Console()
|
|
224
|
+
config = MPDTConfig()
|
|
225
|
+
|
|
226
|
+
console.print(Panel.fit(
|
|
227
|
+
"[bold cyan]MPDT 配置向导[/bold cyan]\n\n"
|
|
228
|
+
"让我们配置 MoFox 主程序的路径和虚拟环境"
|
|
229
|
+
))
|
|
230
|
+
|
|
231
|
+
# 配置 mmc 路径
|
|
232
|
+
while True:
|
|
233
|
+
mmc_path_str = Prompt.ask(
|
|
234
|
+
"\n[bold]请输入 mmc 主程序路径[/bold]",
|
|
235
|
+
default=str(Path.cwd().parent / "mmc") if Path.cwd().parent.name != "mmc" else str(Path.cwd())
|
|
236
|
+
)
|
|
237
|
+
mmc_path = Path(mmc_path_str).expanduser().absolute()
|
|
238
|
+
|
|
239
|
+
if not mmc_path.exists():
|
|
240
|
+
console.print(f"[red]路径不存在: {mmc_path}[/red]")
|
|
241
|
+
if not Confirm.ask("重新输入?", default=True):
|
|
242
|
+
break
|
|
243
|
+
continue
|
|
244
|
+
|
|
245
|
+
bot_file = mmc_path / "bot.py"
|
|
246
|
+
if not bot_file.exists():
|
|
247
|
+
console.print(f"[yellow]警告: 未找到 bot.py[/yellow]")
|
|
248
|
+
if not Confirm.ask("仍然使用此路径?", default=False):
|
|
249
|
+
continue
|
|
250
|
+
|
|
251
|
+
config.mmc_path = mmc_path
|
|
252
|
+
console.print(f"[green]✓ mmc 路径已设置: {mmc_path}[/green]")
|
|
253
|
+
break
|
|
254
|
+
|
|
255
|
+
# 配置虚拟环境
|
|
256
|
+
console.print("\n[bold]虚拟环境配置[/bold]")
|
|
257
|
+
venv_type_choice = Prompt.ask(
|
|
258
|
+
"请选择虚拟环境类型",
|
|
259
|
+
choices=["venv", "uv", "conda", "poetry", "none"],
|
|
260
|
+
default="venv"
|
|
261
|
+
)
|
|
262
|
+
config.venv_type = venv_type_choice
|
|
263
|
+
|
|
264
|
+
if venv_type_choice != "none":
|
|
265
|
+
if venv_type_choice == "poetry":
|
|
266
|
+
console.print("[cyan]使用 poetry,将在 mmc 目录中执行命令[/cyan]")
|
|
267
|
+
config.venv_path = config.mmc_path
|
|
268
|
+
else:
|
|
269
|
+
default_venv_path = str(config.mmc_path.parent / "venv")
|
|
270
|
+
if venv_type_choice == "uv":
|
|
271
|
+
default_venv_path = str(config.mmc_path.parent / ".venv")
|
|
272
|
+
elif venv_type_choice == "conda":
|
|
273
|
+
default_venv_path = str(config.mmc_path.parent / "conda_env")
|
|
274
|
+
|
|
275
|
+
venv_path_str = Prompt.ask(
|
|
276
|
+
f"请输入 {venv_type_choice} 虚拟环境路径",
|
|
277
|
+
default=default_venv_path
|
|
278
|
+
)
|
|
279
|
+
venv_path = Path(venv_path_str).expanduser().absolute()
|
|
280
|
+
|
|
281
|
+
if not venv_path.exists():
|
|
282
|
+
console.print(f"[yellow]警告: 虚拟环境路径不存在: {venv_path}[/yellow]")
|
|
283
|
+
if not Confirm.ask("仍然使用此路径?", default=True):
|
|
284
|
+
config.venv_type = "none"
|
|
285
|
+
else:
|
|
286
|
+
config.venv_path = venv_path
|
|
287
|
+
else:
|
|
288
|
+
config.venv_path = venv_path
|
|
289
|
+
console.print(f"[green]✓ 虚拟环境路径已设置: {venv_path}[/green]")
|
|
290
|
+
else:
|
|
291
|
+
console.print("[cyan]将使用系统 Python[/cyan]")
|
|
292
|
+
|
|
293
|
+
# 保存配置
|
|
294
|
+
config.save()
|
|
295
|
+
console.print(f"\n[bold green]✓ 配置已保存: {config.config_path}[/bold green]")
|
|
296
|
+
|
|
297
|
+
return config
|