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,366 @@
|
|
|
1
|
+
"""
|
|
2
|
+
代码生成命令实现
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import questionary
|
|
9
|
+
|
|
10
|
+
from mpdt.templates import prepare_component_context
|
|
11
|
+
from mpdt.utils.color_printer import (
|
|
12
|
+
console,
|
|
13
|
+
print_error,
|
|
14
|
+
print_step,
|
|
15
|
+
print_success,
|
|
16
|
+
print_warning,
|
|
17
|
+
)
|
|
18
|
+
from mpdt.utils.file_ops import (
|
|
19
|
+
ensure_dir,
|
|
20
|
+
get_git_user_info,
|
|
21
|
+
safe_write_file,
|
|
22
|
+
to_snake_case,
|
|
23
|
+
validate_component_name,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def generate_component(
|
|
28
|
+
component_type: str | None = None,
|
|
29
|
+
component_name: str | None = None,
|
|
30
|
+
description: str | None = None,
|
|
31
|
+
output_dir: str | None = None,
|
|
32
|
+
force: bool = False,
|
|
33
|
+
verbose: bool = False,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""
|
|
36
|
+
生成插件组件(始终生成异步方法)
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
component_type: 组件类型 (None 表示交互式询问)
|
|
40
|
+
component_name: 组件名称 (None 表示交互式询问)
|
|
41
|
+
description: 组件描述
|
|
42
|
+
output_dir: 输出目录
|
|
43
|
+
force: 是否覆盖
|
|
44
|
+
verbose: 详细输出
|
|
45
|
+
"""
|
|
46
|
+
# 交互式获取组件信息
|
|
47
|
+
if not component_type or not component_name:
|
|
48
|
+
component_info = _interactive_generate()
|
|
49
|
+
component_type = component_info["component_type"]
|
|
50
|
+
component_name = component_info["component_name"]
|
|
51
|
+
description = component_info.get("description") or description
|
|
52
|
+
force = component_info.get("force", force)
|
|
53
|
+
|
|
54
|
+
# 此时 component_type 和 component_name 必定不为 None
|
|
55
|
+
assert component_type is not None
|
|
56
|
+
assert component_name is not None
|
|
57
|
+
|
|
58
|
+
print_step(f"生成 {component_type.upper()} 组件: {component_name}")
|
|
59
|
+
|
|
60
|
+
# 验证组件名称
|
|
61
|
+
if not validate_component_name(component_name):
|
|
62
|
+
print_error("组件名称无效!必须使用小写字母、数字和下划线,以字母开头")
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
# 确定工作目录
|
|
66
|
+
if output_dir:
|
|
67
|
+
work_dir = Path(output_dir)
|
|
68
|
+
else:
|
|
69
|
+
work_dir = Path.cwd()
|
|
70
|
+
|
|
71
|
+
# 检查是否在插件目录中
|
|
72
|
+
plugin_name = _detect_plugin_name(work_dir)
|
|
73
|
+
if not plugin_name:
|
|
74
|
+
print_error("未检测到插件目录!请在插件根目录下运行此命令")
|
|
75
|
+
print_warning("提示: 插件目录应包含 plugin.py 文件")
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
if verbose:
|
|
79
|
+
console.print(f"[dim]检测到插件: {plugin_name}[/dim]")
|
|
80
|
+
|
|
81
|
+
# 确保组件名称为 snake_case
|
|
82
|
+
component_name = to_snake_case(component_name)
|
|
83
|
+
|
|
84
|
+
# 标准化组件类型(命令行参数 plus-command -> plus_command)
|
|
85
|
+
normalized_type = component_type.replace("-", "_")
|
|
86
|
+
|
|
87
|
+
# 准备上下文
|
|
88
|
+
git_info = get_git_user_info()
|
|
89
|
+
context = prepare_component_context(
|
|
90
|
+
component_type=normalized_type,
|
|
91
|
+
component_name=component_name,
|
|
92
|
+
plugin_name=plugin_name,
|
|
93
|
+
author=git_info.get("name", ""),
|
|
94
|
+
description=description or f"{component_name} 组件",
|
|
95
|
+
is_async=True, # 始终生成异步方法
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# 生成组件文件
|
|
99
|
+
component_file = _generate_component_file(
|
|
100
|
+
work_dir=work_dir,
|
|
101
|
+
component_type=normalized_type, # 使用标准化的类型
|
|
102
|
+
component_name=component_name,
|
|
103
|
+
context=context,
|
|
104
|
+
force=force,
|
|
105
|
+
verbose=verbose,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if not component_file:
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
# 更新插件注册
|
|
112
|
+
if not _update_plugin_registration(
|
|
113
|
+
work_dir=work_dir,
|
|
114
|
+
component_type=normalized_type, # 使用标准化的类型
|
|
115
|
+
component_name=component_name,
|
|
116
|
+
context=context,
|
|
117
|
+
verbose=verbose,
|
|
118
|
+
):
|
|
119
|
+
print_warning("⚠️ 自动更新插件注册失败,请手动添加到 plugin.py")
|
|
120
|
+
|
|
121
|
+
# 打印成功信息
|
|
122
|
+
print_success(f"✨ {context['class_name']} 生成成功!")
|
|
123
|
+
console.print("\n[bold cyan]生成的文件:[/bold cyan]")
|
|
124
|
+
console.print(f" 📄 {component_file.relative_to(work_dir)}")
|
|
125
|
+
|
|
126
|
+
console.print("\n[bold cyan]下一步:[/bold cyan]")
|
|
127
|
+
console.print(f" 1. 编辑 {component_file.name} 实现具体逻辑")
|
|
128
|
+
console.print(" 2. 运行 mpdt check 检查代码")
|
|
129
|
+
console.print(" 3. 运行 mpdt test 测试功能")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _interactive_generate() -> dict[str, Any]:
|
|
133
|
+
"""交互式生成组件"""
|
|
134
|
+
console.print("\n[bold cyan]🔧 组件生成向导[/bold cyan]\n")
|
|
135
|
+
|
|
136
|
+
answers = questionary.form(
|
|
137
|
+
component_type=questionary.select(
|
|
138
|
+
"选择组件类型:",
|
|
139
|
+
choices=[
|
|
140
|
+
questionary.Choice("Action 组件", value="action"),
|
|
141
|
+
questionary.Choice("Tool 组件", value="tool"),
|
|
142
|
+
questionary.Choice("Event 事件", value="event"),
|
|
143
|
+
questionary.Choice("Adapter 适配器", value="adapter"),
|
|
144
|
+
questionary.Choice("Prompt 提示词", value="prompt"),
|
|
145
|
+
questionary.Choice("Plus Command 命令", value="plus-command"),
|
|
146
|
+
questionary.Choice("Chatter 聊天组件", value="chatter"),
|
|
147
|
+
questionary.Choice("Router 路由组件", value="router"),
|
|
148
|
+
],
|
|
149
|
+
),
|
|
150
|
+
component_name=questionary.text(
|
|
151
|
+
"组件名称 (使用下划线命名):",
|
|
152
|
+
validate=lambda x: validate_component_name(x) or "组件名称格式无效",
|
|
153
|
+
),
|
|
154
|
+
description=questionary.text(
|
|
155
|
+
"组件描述 (可选):",
|
|
156
|
+
default="",
|
|
157
|
+
),
|
|
158
|
+
force=questionary.confirm(
|
|
159
|
+
"如果文件存在,是否覆盖?",
|
|
160
|
+
default=False,
|
|
161
|
+
),
|
|
162
|
+
).ask()
|
|
163
|
+
|
|
164
|
+
return answers
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _detect_plugin_name(work_dir: Path) -> str | None:
|
|
168
|
+
"""
|
|
169
|
+
检测插件名称
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
work_dir: 工作目录
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
插件名称,未检测到则返回 None
|
|
176
|
+
"""
|
|
177
|
+
# 检查 plugin.py 文件
|
|
178
|
+
plugin_file = work_dir / "plugin.py"
|
|
179
|
+
if not plugin_file.exists():
|
|
180
|
+
# 尝试在父目录查找
|
|
181
|
+
plugin_file = work_dir.parent / "plugin.py"
|
|
182
|
+
if not plugin_file.exists():
|
|
183
|
+
return None
|
|
184
|
+
work_dir = work_dir.parent
|
|
185
|
+
|
|
186
|
+
# 从目录名推断插件名
|
|
187
|
+
return work_dir.name
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _generate_component_file(
|
|
191
|
+
work_dir: Path,
|
|
192
|
+
component_type: str,
|
|
193
|
+
component_name: str,
|
|
194
|
+
context: dict,
|
|
195
|
+
force: bool,
|
|
196
|
+
verbose: bool,
|
|
197
|
+
) -> Path | None:
|
|
198
|
+
"""
|
|
199
|
+
生成组件文件
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
work_dir: 工作目录
|
|
203
|
+
component_type: 组件类型
|
|
204
|
+
component_name: 组件名称
|
|
205
|
+
context: 模板上下文
|
|
206
|
+
force: 是否覆盖
|
|
207
|
+
verbose: 详细输出
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
生成的文件路径,失败返回 None
|
|
211
|
+
"""
|
|
212
|
+
# 确定组件目录
|
|
213
|
+
component_dir = work_dir / "components" / f"{component_type}s"
|
|
214
|
+
ensure_dir(component_dir)
|
|
215
|
+
|
|
216
|
+
# 确保 __init__.py 存在
|
|
217
|
+
init_file = component_dir / "__init__.py"
|
|
218
|
+
if not init_file.exists():
|
|
219
|
+
safe_write_file(init_file, f'"""\n{component_type.title()}s 组件\n"""\n')
|
|
220
|
+
|
|
221
|
+
# 生成组件文件
|
|
222
|
+
component_file = component_dir / f"{component_name}.py"
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# 组件类型到模板 key 的映射(此时 component_type 已经是标准化的下划线格式)
|
|
226
|
+
type_map = {
|
|
227
|
+
"action": "action",
|
|
228
|
+
"tool": "tool",
|
|
229
|
+
"event": "event",
|
|
230
|
+
"adapter": "adapter",
|
|
231
|
+
"prompt": "prompt",
|
|
232
|
+
"plus_command": "plus_command",
|
|
233
|
+
"chatter":"chatter",
|
|
234
|
+
"router":"router"
|
|
235
|
+
}
|
|
236
|
+
template_key = type_map.get(component_type)
|
|
237
|
+
if not template_key:
|
|
238
|
+
print_error(f"不支持的组件类型: {component_type}")
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
from mpdt.templates import get_component_template
|
|
242
|
+
template = get_component_template(template_key)
|
|
243
|
+
content = template.format(**context)
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
safe_write_file(component_file, content, force=force)
|
|
247
|
+
if verbose:
|
|
248
|
+
console.print(f"[dim]✓ 生成文件: {component_file}[/dim]")
|
|
249
|
+
return component_file
|
|
250
|
+
except FileExistsError:
|
|
251
|
+
print_error(f"文件已存在: {component_file}")
|
|
252
|
+
print_warning("使用 --force 选项覆盖已存在的文件")
|
|
253
|
+
return None
|
|
254
|
+
except Exception as e:
|
|
255
|
+
print_error(f"生成文件失败: {e}")
|
|
256
|
+
return None
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _update_plugin_registration(
|
|
260
|
+
work_dir: Path,
|
|
261
|
+
component_type: str,
|
|
262
|
+
component_name: str,
|
|
263
|
+
context: dict,
|
|
264
|
+
verbose: bool,
|
|
265
|
+
) -> bool:
|
|
266
|
+
"""
|
|
267
|
+
更新插件注册代码
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
work_dir: 工作目录
|
|
271
|
+
component_type: 组件类型
|
|
272
|
+
component_name: 组件名称
|
|
273
|
+
context: 模板上下文
|
|
274
|
+
verbose: 详细输出
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
是否更新成功
|
|
278
|
+
"""
|
|
279
|
+
plugin_file = work_dir / "plugin.py"
|
|
280
|
+
if not plugin_file.exists():
|
|
281
|
+
return False
|
|
282
|
+
|
|
283
|
+
try:
|
|
284
|
+
content = plugin_file.read_text(encoding="utf-8")
|
|
285
|
+
|
|
286
|
+
# 添加 import 语句
|
|
287
|
+
import_line = f"from {context['plugin_name']}.components.{component_type}s.{component_name} import {context['class_name']}\n"
|
|
288
|
+
|
|
289
|
+
# 检查是否已导入
|
|
290
|
+
if import_line.strip() not in content:
|
|
291
|
+
# 找到合适的位置插入 import (在最后一个 import 之后,class 定义之前)
|
|
292
|
+
lines = content.split("\n")
|
|
293
|
+
import_insert_index = -1
|
|
294
|
+
last_import_index = -1
|
|
295
|
+
|
|
296
|
+
for i, line in enumerate(lines):
|
|
297
|
+
if line.startswith("from") or line.startswith("import"):
|
|
298
|
+
last_import_index = i
|
|
299
|
+
elif line.startswith("class") or line.startswith("@"):
|
|
300
|
+
# 在 class 或装饰器之前插入
|
|
301
|
+
import_insert_index = last_import_index + 1 if last_import_index >= 0 else i
|
|
302
|
+
break
|
|
303
|
+
|
|
304
|
+
if import_insert_index > 0:
|
|
305
|
+
# 确保插入位置后有空行
|
|
306
|
+
if import_insert_index < len(lines) and lines[import_insert_index].strip():
|
|
307
|
+
lines.insert(import_insert_index, "")
|
|
308
|
+
lines.insert(import_insert_index, import_line.rstrip())
|
|
309
|
+
content = "\n".join(lines)
|
|
310
|
+
|
|
311
|
+
# 在 get_plugin_components 中添加组件注册
|
|
312
|
+
# 根据组件类型生成正确的注册代码
|
|
313
|
+
registration_code = _generate_registration_code(component_type, context)
|
|
314
|
+
|
|
315
|
+
if "get_plugin_components" in content and registration_code not in content:
|
|
316
|
+
# 找到 return components 前插入注册代码
|
|
317
|
+
content = content.replace(
|
|
318
|
+
"return components",
|
|
319
|
+
f"{registration_code}\n return components"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
plugin_file.write_text(content, encoding="utf-8")
|
|
323
|
+
|
|
324
|
+
if verbose:
|
|
325
|
+
console.print(f"[dim]✓ 更新插件注册: {plugin_file}[/dim]")
|
|
326
|
+
|
|
327
|
+
return True
|
|
328
|
+
|
|
329
|
+
except Exception as e:
|
|
330
|
+
if verbose:
|
|
331
|
+
console.print(f"[dim]⚠ 自动更新插件注册失败: {e}[/dim]")
|
|
332
|
+
return False
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _generate_registration_code(component_type: str, context: dict) -> str:
|
|
336
|
+
"""
|
|
337
|
+
根据组件类型生成正确的注册代码
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
component_type: 组件类型
|
|
341
|
+
context: 模板上下文
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
注册代码字符串
|
|
345
|
+
"""
|
|
346
|
+
class_name = context['class_name']
|
|
347
|
+
|
|
348
|
+
# 根据组件类型生成对应的 get_xxx_info() 方法调用
|
|
349
|
+
info_method_map = {
|
|
350
|
+
"action": "get_action_info",
|
|
351
|
+
"tool": "get_tool_info",
|
|
352
|
+
"event": "get_event_handler_info",
|
|
353
|
+
"adapter": "get_adapter_info",
|
|
354
|
+
"prompt": "get_prompt_info",
|
|
355
|
+
"plus_command": "get_command_info",
|
|
356
|
+
"chatter": "get_chatter_info",
|
|
357
|
+
"router": "get_router_info",
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
info_method = info_method_map.get(component_type, "get_component_info")
|
|
361
|
+
|
|
362
|
+
registration = f"""
|
|
363
|
+
# 注册 {class_name}
|
|
364
|
+
components.append(({class_name}.{info_method}(), {class_name}))"""
|
|
365
|
+
|
|
366
|
+
return registration
|