mofox-plugin-dev-toolkit 0.5.0__tar.gz → 0.5.2__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.
- {mofox_plugin_dev_toolkit-0.5.0/mofox_plugin_dev_toolkit.egg-info → mofox_plugin_dev_toolkit-0.5.2}/PKG-INFO +2 -1
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2/mofox_plugin_dev_toolkit.egg-info}/PKG-INFO +2 -1
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mofox_plugin_dev_toolkit.egg-info/SOURCES.txt +1 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mofox_plugin_dev_toolkit.egg-info/requires.txt +1 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/commands/build.py +17 -4
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/commands/check.py +14 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/commands/init.py +32 -2
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/commands/market.py +17 -8
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/market/manifest.py +58 -2
- mofox_plugin_dev_toolkit-0.5.2/mpdt/utils/manifest_metadata.py +251 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/metadata_validator.py +8 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/pyproject.toml +2 -1
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/LICENSE +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/MANIFEST.in +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/README.md +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mofox_plugin_dev_toolkit.egg-info/dependency_links.txt +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mofox_plugin_dev_toolkit.egg-info/entry_points.txt +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mofox_plugin_dev_toolkit.egg-info/top_level.txt +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/__init__.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/__main__.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/cli.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/commands/__init__.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/commands/dev.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/commands/generate.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/dev/bridge_plugin/__init__.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/dev/bridge_plugin/cleanup_handler.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/dev/bridge_plugin/dev_config.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/dev/bridge_plugin/file_watcher.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/dev/bridge_plugin/manifest.json +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/dev/bridge_plugin/plugin.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/market/__init__.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/market/client.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/market/config.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/market/git.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/market/github.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/__init__.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/action_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/adapter_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/chatter_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/collection_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/config_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/event_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/plus_command_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/prompt_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/router_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/service_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/tool_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/__init__.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/code_parser.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/color_printer.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/config_loader.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/config_manager.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/file_ops.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/license_generator.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/plugin_parser.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/template_engine.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/__init__.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/auto_fix_validator.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/base.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/component_validator.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/config_validator.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/structure_validator.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/style_validator.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/type_validator.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mofox-plugin-dev-toolkit
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: 开发工具集,用于快速创建、开发和测试 MoFox-Bot 插件
|
|
5
5
|
Author-email: MoFox-Studio <wwwww95915@qq.com>
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -30,6 +30,7 @@ Requires-Dist: watchdog>=3.0.0
|
|
|
30
30
|
Requires-Dist: websockets>=12.0
|
|
31
31
|
Requires-Dist: libcst>=1.8.6
|
|
32
32
|
Requires-Dist: aiohttp>=3.9.0
|
|
33
|
+
Requires-Dist: pillow>=11.2.0
|
|
33
34
|
Requires-Dist: uvicorn>=0.24.0
|
|
34
35
|
Requires-Dist: fastapi>=0.104.0
|
|
35
36
|
Requires-Dist: ruff>=0.1.6
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mofox-plugin-dev-toolkit
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: 开发工具集,用于快速创建、开发和测试 MoFox-Bot 插件
|
|
5
5
|
Author-email: MoFox-Studio <wwwww95915@qq.com>
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -30,6 +30,7 @@ Requires-Dist: watchdog>=3.0.0
|
|
|
30
30
|
Requires-Dist: websockets>=12.0
|
|
31
31
|
Requires-Dist: libcst>=1.8.6
|
|
32
32
|
Requires-Dist: aiohttp>=3.9.0
|
|
33
|
+
Requires-Dist: pillow>=11.2.0
|
|
33
34
|
Requires-Dist: uvicorn>=0.24.0
|
|
34
35
|
Requires-Dist: fastapi>=0.104.0
|
|
35
36
|
Requires-Dist: ruff>=0.1.6
|
|
@@ -16,6 +16,7 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
import json
|
|
18
18
|
import re
|
|
19
|
+
import sys
|
|
19
20
|
import zipfile
|
|
20
21
|
from dataclasses import dataclass
|
|
21
22
|
from hashlib import sha256
|
|
@@ -31,6 +32,7 @@ from mpdt.utils.color_printer import (
|
|
|
31
32
|
print_success,
|
|
32
33
|
print_warning,
|
|
33
34
|
)
|
|
35
|
+
from mpdt.utils.manifest_metadata import ensure_manifest_metadata_interactive, metadata_errors
|
|
34
36
|
|
|
35
37
|
# 构建时默认排除的文件/目录名称(精确匹配)
|
|
36
38
|
_EXCLUDE_NAMES: set[str] = {
|
|
@@ -179,6 +181,18 @@ def _save_manifest(plugin_dir: Path, manifest: dict) -> None:
|
|
|
179
181
|
json.dump(manifest, f, ensure_ascii=False, indent=4)
|
|
180
182
|
|
|
181
183
|
|
|
184
|
+
def _validate_manifest_metadata(manifest: dict, plugin_dir: Path) -> None:
|
|
185
|
+
"""验证打包所需的 manifest 元数据。"""
|
|
186
|
+
required = ["name", "version", "description", "author", "entry_point"]
|
|
187
|
+
for field in required:
|
|
188
|
+
if field not in manifest:
|
|
189
|
+
raise ValueError(f"manifest.json 缺少必需字段: '{field}'")
|
|
190
|
+
|
|
191
|
+
errors = metadata_errors(manifest, plugin_dir)
|
|
192
|
+
if errors:
|
|
193
|
+
raise ValueError(errors[0])
|
|
194
|
+
|
|
195
|
+
|
|
182
196
|
def build_plugin(
|
|
183
197
|
plugin_path: str = ".",
|
|
184
198
|
output_dir: str = "dist",
|
|
@@ -263,10 +277,9 @@ def build_package(
|
|
|
263
277
|
if manifest is None:
|
|
264
278
|
return
|
|
265
279
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
raise ValueError(f"manifest.json 缺少必需字段: '{field}'")
|
|
280
|
+
manifest = ensure_manifest_metadata_interactive(plugin_dir, manifest)
|
|
281
|
+
|
|
282
|
+
_validate_manifest_metadata(manifest, plugin_dir)
|
|
270
283
|
|
|
271
284
|
plugin_name: str = manifest["name"]
|
|
272
285
|
plugin_version: str = manifest["version"]
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
静态检查命令实现
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import json
|
|
6
|
+
|
|
5
7
|
from pathlib import Path
|
|
6
8
|
|
|
7
9
|
from rich.panel import Panel
|
|
8
10
|
from rich.table import Table
|
|
9
11
|
|
|
10
12
|
from mpdt.utils.color_printer import console, print_error, print_info, print_success, print_warning
|
|
13
|
+
from mpdt.utils.manifest_metadata import ensure_manifest_metadata_interactive
|
|
11
14
|
from mpdt.validators import (
|
|
12
15
|
AutoFixValidator,
|
|
13
16
|
ComponentValidator,
|
|
@@ -77,6 +80,17 @@ def check_plugin(
|
|
|
77
80
|
# 元数据验证
|
|
78
81
|
if not skip_metadata:
|
|
79
82
|
print_info("正在检查插件元数据...")
|
|
83
|
+
manifest_path = path / "manifest.json"
|
|
84
|
+
if manifest_path.exists():
|
|
85
|
+
try:
|
|
86
|
+
with open(manifest_path, encoding="utf-8") as f:
|
|
87
|
+
manifest_data = json.load(f)
|
|
88
|
+
ensure_manifest_metadata_interactive(path, manifest_data)
|
|
89
|
+
except json.JSONDecodeError:
|
|
90
|
+
pass
|
|
91
|
+
except ValueError as e:
|
|
92
|
+
print_error(str(e))
|
|
93
|
+
return
|
|
80
94
|
validator = MetadataValidator(path)
|
|
81
95
|
result = validator.validate()
|
|
82
96
|
all_results.append(result)
|
|
@@ -17,6 +17,7 @@ from typing import Any
|
|
|
17
17
|
|
|
18
18
|
import questionary
|
|
19
19
|
|
|
20
|
+
from mpdt.utils.manifest_metadata import prompt_manifest_metadata
|
|
20
21
|
from mpdt.utils.color_printer import (
|
|
21
22
|
console,
|
|
22
23
|
print_error,
|
|
@@ -64,12 +65,20 @@ def init_plugin(
|
|
|
64
65
|
if not plugin_name:
|
|
65
66
|
plugin_info = _interactive_init()
|
|
66
67
|
plugin_name = plugin_info["plugin_name"]
|
|
68
|
+
description = plugin_info.get("description", "")
|
|
67
69
|
template = plugin_info["template"]
|
|
68
70
|
author = plugin_info.get("author")
|
|
69
71
|
email = plugin_info.get("email")
|
|
70
72
|
license_type = plugin_info["license"]
|
|
71
73
|
with_docs = plugin_info.get("with_docs", False)
|
|
72
74
|
init_git = plugin_info.get("init_git", False)
|
|
75
|
+
categories = plugin_info["categories"]
|
|
76
|
+
tags = plugin_info["tags"]
|
|
77
|
+
else:
|
|
78
|
+
description = ""
|
|
79
|
+
metadata = prompt_manifest_metadata()
|
|
80
|
+
categories = metadata["categories"]
|
|
81
|
+
tags = metadata["tags"]
|
|
73
82
|
|
|
74
83
|
# 此时 plugin_name 必定不为 None
|
|
75
84
|
assert plugin_name is not None
|
|
@@ -96,11 +105,14 @@ def init_plugin(
|
|
|
96
105
|
_create_plugin_structure(
|
|
97
106
|
plugin_dir=plugin_dir,
|
|
98
107
|
plugin_name=plugin_name,
|
|
108
|
+
description=description,
|
|
99
109
|
template=template,
|
|
100
110
|
author=author,
|
|
101
111
|
email=email,
|
|
102
112
|
license_type=license_type,
|
|
103
113
|
with_docs=with_docs,
|
|
114
|
+
categories=categories,
|
|
115
|
+
tags=tags,
|
|
104
116
|
verbose=verbose,
|
|
105
117
|
)
|
|
106
118
|
|
|
@@ -194,6 +206,10 @@ def _interactive_init() -> dict[str, Any]:
|
|
|
194
206
|
),
|
|
195
207
|
).ask()
|
|
196
208
|
|
|
209
|
+
metadata = prompt_manifest_metadata()
|
|
210
|
+
answers["categories"] = metadata["categories"]
|
|
211
|
+
answers["tags"] = metadata["tags"]
|
|
212
|
+
|
|
197
213
|
return answers
|
|
198
214
|
|
|
199
215
|
# ============================================================================
|
|
@@ -204,11 +220,14 @@ def _interactive_init() -> dict[str, Any]:
|
|
|
204
220
|
def _create_plugin_structure(
|
|
205
221
|
plugin_dir: Path,
|
|
206
222
|
plugin_name: str,
|
|
223
|
+
description: str,
|
|
207
224
|
template: str,
|
|
208
225
|
author: str | None,
|
|
209
226
|
email: str | None,
|
|
210
227
|
license_type: str,
|
|
211
228
|
with_docs: bool,
|
|
229
|
+
categories: list[str],
|
|
230
|
+
tags: list[str],
|
|
212
231
|
verbose: bool,
|
|
213
232
|
) -> None:
|
|
214
233
|
"""创建插件目录结构"""
|
|
@@ -217,7 +236,7 @@ def _create_plugin_structure(
|
|
|
217
236
|
ensure_dir(plugin_dir)
|
|
218
237
|
|
|
219
238
|
# 创建 manifest.json
|
|
220
|
-
manifest_content = _generate_manifest_file(plugin_name, author, template)
|
|
239
|
+
manifest_content = _generate_manifest_file(plugin_name, author, template, description, categories, tags)
|
|
221
240
|
safe_write_file(plugin_dir / "manifest.json", manifest_content)
|
|
222
241
|
if verbose:
|
|
223
242
|
console.print("[dim]✓ 生成清单文件: manifest.json[/dim]")
|
|
@@ -281,7 +300,14 @@ def _create_plugin_structure(
|
|
|
281
300
|
console.print(f"[dim]✓ 生成许可证文件: {license_type}[/dim]")
|
|
282
301
|
|
|
283
302
|
|
|
284
|
-
def _generate_manifest_file(
|
|
303
|
+
def _generate_manifest_file(
|
|
304
|
+
plugin_name: str,
|
|
305
|
+
author: str | None,
|
|
306
|
+
template: str,
|
|
307
|
+
description: str = "",
|
|
308
|
+
categories: list[str] | None = None,
|
|
309
|
+
tags: list[str] | None = None,
|
|
310
|
+
) -> str:
|
|
285
311
|
"""生成 manifest.json 文件内容
|
|
286
312
|
|
|
287
313
|
Args:
|
|
@@ -343,9 +369,13 @@ def _generate_manifest_file(plugin_name: str, author: str | None, template: str,
|
|
|
343
369
|
|
|
344
370
|
manifest = {
|
|
345
371
|
"name": plugin_name,
|
|
372
|
+
"display_name": plugin_name,
|
|
346
373
|
"version": "1.0.0",
|
|
347
374
|
"description": description or f"{plugin_name} 插件",
|
|
348
375
|
"author": author or "Your Name",
|
|
376
|
+
"icon": "icon.png",
|
|
377
|
+
"categories": categories or ["tool"],
|
|
378
|
+
"tags": tags or [plugin_name],
|
|
349
379
|
"dependencies": {"plugins": [], "components": []},
|
|
350
380
|
"include": template_components.get(template, []),
|
|
351
381
|
"entry_point": "plugin.py",
|
|
@@ -30,6 +30,7 @@ from mpdt.market.manifest import (
|
|
|
30
30
|
version_payload,
|
|
31
31
|
)
|
|
32
32
|
from mpdt.utils.color_printer import console
|
|
33
|
+
from mpdt.utils.manifest_metadata import ensure_manifest_metadata_interactive_async
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
def market_doctor(market_url: str | None = None, token: str | None = None) -> None:
|
|
@@ -49,8 +50,8 @@ def market_register(plugin_path: str = ".", market_url: str | None = None, token
|
|
|
49
50
|
"""Register a plugin in the market."""
|
|
50
51
|
|
|
51
52
|
async def run() -> None:
|
|
52
|
-
manifest =
|
|
53
|
-
payload = plugin_payload(manifest, repository_url=repository_url)
|
|
53
|
+
manifest = await _load_manifest_for_market(plugin_path)
|
|
54
|
+
payload = plugin_payload(manifest, repository_url=repository_url, plugin_dir=plugin_path)
|
|
54
55
|
result = await _client(market_url, token).register_plugin(payload)
|
|
55
56
|
_print_ok(f"Plugin registered: {result['plugin_id']} ({result['status']})")
|
|
56
57
|
|
|
@@ -61,8 +62,8 @@ def market_update(plugin_path: str = ".", market_url: str | None = None, token:
|
|
|
61
62
|
"""Update plugin metadata in the market."""
|
|
62
63
|
|
|
63
64
|
async def run() -> None:
|
|
64
|
-
manifest =
|
|
65
|
-
payload = plugin_payload(manifest, repository_url=repository_url)
|
|
65
|
+
manifest = await _load_manifest_for_market(plugin_path)
|
|
66
|
+
payload = plugin_payload(manifest, repository_url=repository_url, plugin_dir=plugin_path)
|
|
66
67
|
plugin_id = payload.pop("plugin_id")
|
|
67
68
|
result = await _client(market_url, token).update_plugin(plugin_id, payload)
|
|
68
69
|
_print_ok(f"Plugin updated: {result['plugin_id']} ({result['status']})")
|
|
@@ -98,7 +99,7 @@ def market_submit_version(
|
|
|
98
99
|
"""Build and submit a plugin version to the market."""
|
|
99
100
|
|
|
100
101
|
async def run() -> None:
|
|
101
|
-
manifest =
|
|
102
|
+
manifest = await _load_manifest_for_market(plugin_path)
|
|
102
103
|
package = build_package(plugin_path=plugin_path, output_dir=output_dir, with_docs=with_docs, fmt="mfp", show_progress=False)
|
|
103
104
|
if package is None:
|
|
104
105
|
return
|
|
@@ -132,7 +133,7 @@ def market_sync(
|
|
|
132
133
|
"""Rebuild and sync version metadata."""
|
|
133
134
|
|
|
134
135
|
async def run() -> None:
|
|
135
|
-
manifest =
|
|
136
|
+
manifest = await _load_manifest_for_market(plugin_path)
|
|
136
137
|
package = build_package(plugin_path=plugin_path, output_dir=output_dir, with_docs=with_docs, fmt="mfp", show_progress=False)
|
|
137
138
|
if package is None:
|
|
138
139
|
return
|
|
@@ -173,7 +174,7 @@ def market_publish(
|
|
|
173
174
|
|
|
174
175
|
async def run() -> None:
|
|
175
176
|
plugin_dir = Path(plugin_path).resolve()
|
|
176
|
-
manifest =
|
|
177
|
+
manifest = await _load_manifest_for_market(str(plugin_dir))
|
|
177
178
|
package = build_package(plugin_path=str(plugin_dir), output_dir=output_dir, with_docs=with_docs, fmt="mfp", show_progress=False)
|
|
178
179
|
if package is None:
|
|
179
180
|
return
|
|
@@ -231,7 +232,7 @@ def market_publish(
|
|
|
231
232
|
asset_url = str(asset.get("browser_download_url") or asset_download_url_for(plugin_id, version, package.package_path.name))
|
|
232
233
|
|
|
233
234
|
market = _client(market_url, token or github_api_token)
|
|
234
|
-
plugin_registration_payload = plugin_payload(manifest, repository_url=repo_html_url)
|
|
235
|
+
plugin_registration_payload = plugin_payload(manifest, repository_url=repo_html_url, plugin_dir=plugin_dir)
|
|
235
236
|
try:
|
|
236
237
|
plugin = await market.register_plugin(plugin_registration_payload)
|
|
237
238
|
_print_ok(f"Plugin registered: {plugin['plugin_id']} ({plugin['status']})")
|
|
@@ -433,6 +434,14 @@ def _run_market(awaitable) -> None:
|
|
|
433
434
|
_print_error(f"Market server connection failed: {e}")
|
|
434
435
|
|
|
435
436
|
|
|
437
|
+
async def _load_manifest_for_market(plugin_path: str) -> dict:
|
|
438
|
+
"""Load manifest and repair required market metadata in async flows before proceeding."""
|
|
439
|
+
resolved_path = Path(plugin_path).resolve()
|
|
440
|
+
manifest = load_manifest(str(resolved_path))
|
|
441
|
+
await ensure_manifest_metadata_interactive_async(resolved_path, manifest)
|
|
442
|
+
return load_manifest(str(resolved_path))
|
|
443
|
+
|
|
444
|
+
|
|
436
445
|
def _print_ok(message: str) -> None:
|
|
437
446
|
"""Print an ASCII success message."""
|
|
438
447
|
|
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import base64
|
|
6
|
+
import io
|
|
5
7
|
import json
|
|
6
8
|
from pathlib import Path
|
|
7
9
|
from typing import Any
|
|
8
10
|
|
|
11
|
+
from PIL import Image, ImageOps
|
|
12
|
+
|
|
9
13
|
|
|
10
14
|
def load_manifest(plugin_path: str = ".") -> dict[str, Any]:
|
|
11
15
|
"""Load manifest.json from a plugin directory."""
|
|
@@ -20,16 +24,24 @@ def load_manifest(plugin_path: str = ".") -> dict[str, Any]:
|
|
|
20
24
|
return data
|
|
21
25
|
|
|
22
26
|
|
|
23
|
-
def plugin_payload(
|
|
27
|
+
def plugin_payload(
|
|
28
|
+
manifest: dict[str, Any],
|
|
29
|
+
repository_url: str | None = None,
|
|
30
|
+
plugin_dir: str | Path | None = None,
|
|
31
|
+
) -> dict[str, Any]:
|
|
24
32
|
"""Convert MPDT manifest.json to market plugin registration payload."""
|
|
25
33
|
|
|
26
34
|
plugin_id = str(manifest.get("name") or "").strip()
|
|
27
35
|
if not plugin_id:
|
|
28
36
|
raise ValueError("manifest.json 缺少 name 字段")
|
|
37
|
+
display_name = str(manifest.get("display_name") or "").strip()
|
|
38
|
+
if not display_name:
|
|
39
|
+
raise ValueError("manifest.json 缺少 display_name 字段")
|
|
29
40
|
repo = repository_url or manifest.get("repository_url") or f"https://github.com/MoFox-Studio/{plugin_id}"
|
|
41
|
+
resolved_plugin_dir = Path(plugin_dir).resolve() if plugin_dir is not None else None
|
|
30
42
|
return {
|
|
31
43
|
"plugin_id": plugin_id,
|
|
32
|
-
"display_name":
|
|
44
|
+
"display_name": display_name,
|
|
33
45
|
"summary": str(manifest.get("summary") or manifest.get("description") or f"{plugin_id} 插件"),
|
|
34
46
|
"description": str(manifest.get("description") or ""),
|
|
35
47
|
"homepage": manifest.get("homepage") or repo,
|
|
@@ -38,6 +50,9 @@ def plugin_payload(manifest: dict[str, Any], repository_url: str | None = None)
|
|
|
38
50
|
"categories": list(manifest.get("categories") or []),
|
|
39
51
|
"tags": list(manifest.get("tags") or []),
|
|
40
52
|
"maintainers": _maintainers(manifest),
|
|
53
|
+
"plugin_dependencies": list(((manifest.get("dependencies") or {}).get("plugins") or [])),
|
|
54
|
+
"icon_png_base64": _icon_png_base64(manifest, resolved_plugin_dir),
|
|
55
|
+
"readme_markdown": _readme_markdown(resolved_plugin_dir),
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
|
|
@@ -100,3 +115,44 @@ def _maintainers(manifest: dict[str, Any]) -> list[str]:
|
|
|
100
115
|
return [str(item) for item in maintainers]
|
|
101
116
|
author = str(manifest.get("author") or "mock-author").strip()
|
|
102
117
|
return [author or "mock-author"]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _icon_png_base64(manifest: dict[str, Any], plugin_dir: Path | None) -> str | None:
|
|
121
|
+
"""Return a normalized base64 PNG icon for upload, if configured."""
|
|
122
|
+
|
|
123
|
+
raw_icon = str(manifest.get("icon") or "").strip()
|
|
124
|
+
if not raw_icon:
|
|
125
|
+
return None
|
|
126
|
+
if plugin_dir is None:
|
|
127
|
+
raise ValueError("manifest.json 包含 icon 字段,但当前上下文缺少插件目录")
|
|
128
|
+
icon_path = (plugin_dir / raw_icon).resolve() if not Path(raw_icon).is_absolute() else Path(raw_icon)
|
|
129
|
+
if not icon_path.exists() or not icon_path.is_file():
|
|
130
|
+
raise ValueError(f"manifest.json 的 icon 指向的文件不存在: {icon_path}")
|
|
131
|
+
if icon_path.suffix.lower() != ".png":
|
|
132
|
+
raise ValueError("manifest.json 的 icon 必须指向 PNG 文件")
|
|
133
|
+
|
|
134
|
+
with Image.open(icon_path) as image:
|
|
135
|
+
if (image.format or "").upper() != "PNG":
|
|
136
|
+
raise ValueError("manifest.json 的 icon 必须是有效的 PNG 图片")
|
|
137
|
+
working = ImageOps.contain(image.convert("RGBA"), (512, 512), method=Image.Resampling.LANCZOS)
|
|
138
|
+
|
|
139
|
+
canvas = Image.new("RGBA", (512, 512), (0, 0, 0, 0))
|
|
140
|
+
offset = ((512 - working.width) // 2, (512 - working.height) // 2)
|
|
141
|
+
canvas.paste(working, offset, working)
|
|
142
|
+
normalized = canvas.convert("P", palette=Image.Palette.ADAPTIVE, colors=256)
|
|
143
|
+
|
|
144
|
+
buffer = io.BytesIO()
|
|
145
|
+
normalized.save(buffer, format="PNG", optimize=True)
|
|
146
|
+
return base64.b64encode(buffer.getvalue()).decode("ascii")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _readme_markdown(plugin_dir: Path | None) -> str | None:
|
|
150
|
+
"""Load README.md contents for market detail rendering when present."""
|
|
151
|
+
|
|
152
|
+
if plugin_dir is None:
|
|
153
|
+
return None
|
|
154
|
+
readme_path = plugin_dir / "README.md"
|
|
155
|
+
if not readme_path.exists() or not readme_path.is_file():
|
|
156
|
+
return None
|
|
157
|
+
content = readme_path.read_text(encoding="utf-8").strip()
|
|
158
|
+
return content or None
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Manifest metadata rules and interactive prompts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import questionary
|
|
12
|
+
|
|
13
|
+
from .color_printer import print_success, print_warning
|
|
14
|
+
|
|
15
|
+
ALLOWED_CATEGORIES = ("tool", "chat", "fun", "information", "moderation")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_display_name(manifest: dict[str, Any]) -> str:
|
|
19
|
+
"""Return normalized display_name from manifest."""
|
|
20
|
+
|
|
21
|
+
value = manifest.get("display_name")
|
|
22
|
+
if not isinstance(value, str):
|
|
23
|
+
return ""
|
|
24
|
+
return value.strip()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_categories(manifest: dict[str, Any]) -> list[str]:
|
|
28
|
+
"""Return normalized categories from manifest."""
|
|
29
|
+
value = manifest.get("categories")
|
|
30
|
+
if not isinstance(value, list):
|
|
31
|
+
return []
|
|
32
|
+
return [item.strip() for item in value if isinstance(item, str) and item.strip()]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_tags(manifest: dict[str, Any]) -> list[str]:
|
|
36
|
+
"""Return normalized tags from manifest."""
|
|
37
|
+
value = manifest.get("tags")
|
|
38
|
+
if not isinstance(value, list):
|
|
39
|
+
return []
|
|
40
|
+
normalized: list[str] = []
|
|
41
|
+
for item in value:
|
|
42
|
+
if not isinstance(item, str):
|
|
43
|
+
continue
|
|
44
|
+
tag = item.strip()
|
|
45
|
+
if tag and tag not in normalized:
|
|
46
|
+
normalized.append(tag)
|
|
47
|
+
return normalized
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def normalize_tags(raw_tags: str) -> list[str]:
|
|
51
|
+
"""Parse raw tag input into a de-duplicated tag list."""
|
|
52
|
+
tags: list[str] = []
|
|
53
|
+
normalized_input = raw_tags.replace(",", ",").replace("\n", ",")
|
|
54
|
+
for part in normalized_input.split(","):
|
|
55
|
+
tag = part.strip()
|
|
56
|
+
if tag and tag not in tags:
|
|
57
|
+
tags.append(tag)
|
|
58
|
+
return tags
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def categories_are_valid(manifest: dict[str, Any]) -> bool:
|
|
62
|
+
"""Return whether manifest categories satisfy the packaging rules."""
|
|
63
|
+
categories = get_categories(manifest)
|
|
64
|
+
return len(categories) == 1 and categories[0] in ALLOWED_CATEGORIES
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def tags_are_valid(manifest: dict[str, Any]) -> bool:
|
|
68
|
+
"""Return whether manifest tags satisfy the packaging rules."""
|
|
69
|
+
return bool(get_tags(manifest))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def metadata_errors(manifest: dict[str, Any], plugin_dir: Path | None = None) -> list[str]:
|
|
73
|
+
"""Return validation errors for required manifest metadata."""
|
|
74
|
+
errors: list[str] = []
|
|
75
|
+
if not get_display_name(manifest):
|
|
76
|
+
errors.append("manifest.json 的 display_name 必须是非空字符串")
|
|
77
|
+
if not categories_are_valid(manifest):
|
|
78
|
+
allowed = ", ".join(ALLOWED_CATEGORIES)
|
|
79
|
+
errors.append(f"manifest.json 的 categories 必须是只包含一个值的数组,且取值只能是: {allowed}")
|
|
80
|
+
if not tags_are_valid(manifest):
|
|
81
|
+
errors.append("manifest.json 的 tags 必须是至少包含一个非空字符串的数组")
|
|
82
|
+
icon_error = _icon_error(manifest, plugin_dir)
|
|
83
|
+
if icon_error:
|
|
84
|
+
errors.append(icon_error)
|
|
85
|
+
return errors
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _is_interactive_terminal() -> bool:
|
|
89
|
+
"""Return whether stdin/stdout are attached to a TTY."""
|
|
90
|
+
return sys.stdin.isatty() and sys.stdout.isatty()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _has_running_event_loop() -> bool:
|
|
94
|
+
"""Return whether the current thread is already inside a running event loop."""
|
|
95
|
+
try:
|
|
96
|
+
asyncio.get_running_loop()
|
|
97
|
+
except RuntimeError:
|
|
98
|
+
return False
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _finalize_manifest_metadata(manifest: dict[str, Any]) -> dict[str, Any]:
|
|
103
|
+
"""Normalize legacy single-value metadata keys."""
|
|
104
|
+
manifest.pop("category", None)
|
|
105
|
+
manifest.pop("tag", None)
|
|
106
|
+
return manifest
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _save_manifest(plugin_dir: Path, manifest: dict[str, Any]) -> None:
|
|
110
|
+
"""Persist manifest.json after interactive metadata repair."""
|
|
111
|
+
manifest_path = plugin_dir / "manifest.json"
|
|
112
|
+
with open(manifest_path, "w", encoding="utf-8") as f:
|
|
113
|
+
json.dump(manifest, f, ensure_ascii=False, indent=4)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def prompt_manifest_metadata(existing_manifest: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
117
|
+
"""Collect required manifest metadata from the console."""
|
|
118
|
+
manifest = existing_manifest or {}
|
|
119
|
+
display_name = get_display_name(manifest) or str(manifest.get("name") or "").strip()
|
|
120
|
+
existing_categories = get_categories(manifest)
|
|
121
|
+
default_category = existing_categories[0] if existing_categories else ALLOWED_CATEGORIES[0]
|
|
122
|
+
existing_tags = ", ".join(get_tags(manifest))
|
|
123
|
+
|
|
124
|
+
resolved_display_name = questionary.text(
|
|
125
|
+
"填写插件展示名 display_name:",
|
|
126
|
+
default=display_name,
|
|
127
|
+
validate=lambda value: bool(str(value).strip()) or "display_name 不能为空",
|
|
128
|
+
).ask()
|
|
129
|
+
if resolved_display_name is None:
|
|
130
|
+
raise ValueError("已取消填写 display_name")
|
|
131
|
+
|
|
132
|
+
category = questionary.select(
|
|
133
|
+
"选择插件分类 categories(只能选一个):",
|
|
134
|
+
choices=[questionary.Choice(item, value=item) for item in ALLOWED_CATEGORIES],
|
|
135
|
+
default=default_category,
|
|
136
|
+
).ask()
|
|
137
|
+
if category is None:
|
|
138
|
+
raise ValueError("已取消填写 categories")
|
|
139
|
+
|
|
140
|
+
tags_text = questionary.text(
|
|
141
|
+
"填写插件 tags(多个标签用逗号分隔):",
|
|
142
|
+
default=existing_tags,
|
|
143
|
+
validate=lambda value: bool(normalize_tags(value)) or "至少填写一个 tag",
|
|
144
|
+
).ask()
|
|
145
|
+
if tags_text is None:
|
|
146
|
+
raise ValueError("已取消填写 tags")
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
"display_name": str(resolved_display_name).strip(),
|
|
150
|
+
"categories": [category],
|
|
151
|
+
"tags": normalize_tags(tags_text),
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
async def prompt_manifest_metadata_async(existing_manifest: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
156
|
+
"""Collect required manifest metadata from the console inside an event loop."""
|
|
157
|
+
manifest = existing_manifest or {}
|
|
158
|
+
display_name = get_display_name(manifest) or str(manifest.get("name") or "").strip()
|
|
159
|
+
existing_categories = get_categories(manifest)
|
|
160
|
+
default_category = existing_categories[0] if existing_categories else ALLOWED_CATEGORIES[0]
|
|
161
|
+
existing_tags = ", ".join(get_tags(manifest))
|
|
162
|
+
|
|
163
|
+
resolved_display_name = await questionary.text(
|
|
164
|
+
"填写插件展示名 display_name:",
|
|
165
|
+
default=display_name,
|
|
166
|
+
validate=lambda value: bool(str(value).strip()) or "display_name 不能为空",
|
|
167
|
+
).ask_async()
|
|
168
|
+
if resolved_display_name is None:
|
|
169
|
+
raise ValueError("已取消填写 display_name")
|
|
170
|
+
|
|
171
|
+
category = await questionary.select(
|
|
172
|
+
"选择插件分类 categories(只能选一个):",
|
|
173
|
+
choices=[questionary.Choice(item, value=item) for item in ALLOWED_CATEGORIES],
|
|
174
|
+
default=default_category,
|
|
175
|
+
).ask_async()
|
|
176
|
+
if category is None:
|
|
177
|
+
raise ValueError("已取消填写 categories")
|
|
178
|
+
|
|
179
|
+
tags_text = await questionary.text(
|
|
180
|
+
"填写插件 tags(多个标签用逗号分隔):",
|
|
181
|
+
default=existing_tags,
|
|
182
|
+
validate=lambda value: bool(normalize_tags(value)) or "至少填写一个 tag",
|
|
183
|
+
).ask_async()
|
|
184
|
+
if tags_text is None:
|
|
185
|
+
raise ValueError("已取消填写 tags")
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
"display_name": str(resolved_display_name).strip(),
|
|
189
|
+
"categories": [category],
|
|
190
|
+
"tags": normalize_tags(tags_text),
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def ensure_manifest_metadata_interactive(plugin_dir: Path, manifest: dict[str, Any]) -> dict[str, Any]:
|
|
195
|
+
"""Prompt for missing manifest metadata and persist it when running in a TTY."""
|
|
196
|
+
errors = metadata_errors(manifest, plugin_dir)
|
|
197
|
+
if not errors:
|
|
198
|
+
return _finalize_manifest_metadata(manifest)
|
|
199
|
+
|
|
200
|
+
if not _is_interactive_terminal() or _has_running_event_loop():
|
|
201
|
+
return manifest
|
|
202
|
+
|
|
203
|
+
print_warning("检测到 manifest.json 缺少或无效的市场元数据,进入交互补全...")
|
|
204
|
+
for error in errors:
|
|
205
|
+
print_warning(error)
|
|
206
|
+
|
|
207
|
+
manifest.update(prompt_manifest_metadata(manifest))
|
|
208
|
+
_finalize_manifest_metadata(manifest)
|
|
209
|
+
_save_manifest(plugin_dir, manifest)
|
|
210
|
+
|
|
211
|
+
print_success("已更新 manifest.json 中的市场元数据")
|
|
212
|
+
return manifest
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
async def ensure_manifest_metadata_interactive_async(plugin_dir: Path, manifest: dict[str, Any]) -> dict[str, Any]:
|
|
216
|
+
"""Async prompt for missing manifest metadata and persist it when running in a TTY."""
|
|
217
|
+
errors = metadata_errors(manifest, plugin_dir)
|
|
218
|
+
if not errors:
|
|
219
|
+
return _finalize_manifest_metadata(manifest)
|
|
220
|
+
|
|
221
|
+
if not _is_interactive_terminal():
|
|
222
|
+
return manifest
|
|
223
|
+
|
|
224
|
+
print_warning("检测到 manifest.json 缺少或无效的市场元数据,进入交互补全...")
|
|
225
|
+
for error in errors:
|
|
226
|
+
print_warning(error)
|
|
227
|
+
|
|
228
|
+
manifest.update(await prompt_manifest_metadata_async(manifest))
|
|
229
|
+
_finalize_manifest_metadata(manifest)
|
|
230
|
+
_save_manifest(plugin_dir, manifest)
|
|
231
|
+
|
|
232
|
+
print_success("已更新 manifest.json 中的市场元数据")
|
|
233
|
+
return manifest
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _icon_error(manifest: dict[str, Any], plugin_dir: Path | None) -> str | None:
|
|
237
|
+
"""Return a validation error for the optional icon field, if any."""
|
|
238
|
+
|
|
239
|
+
icon = manifest.get("icon")
|
|
240
|
+
if icon is None or str(icon).strip() == "":
|
|
241
|
+
return None
|
|
242
|
+
if not isinstance(icon, str):
|
|
243
|
+
return "manifest.json 的 icon 必须是字符串路径"
|
|
244
|
+
if plugin_dir is None:
|
|
245
|
+
return None
|
|
246
|
+
icon_path = plugin_dir / icon
|
|
247
|
+
if not icon_path.exists() or not icon_path.is_file():
|
|
248
|
+
return f"manifest.json 的 icon 指向的文件不存在: {icon}"
|
|
249
|
+
if icon_path.suffix.lower() != ".png":
|
|
250
|
+
return "manifest.json 的 icon 必须指向 .png 文件"
|
|
251
|
+
return None
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
|
|
7
|
+
from ..utils.manifest_metadata import metadata_errors
|
|
7
8
|
from .base import BaseValidator, ValidationResult
|
|
8
9
|
|
|
9
10
|
|
|
@@ -71,6 +72,13 @@ class MetadataValidator(BaseValidator):
|
|
|
71
72
|
else:
|
|
72
73
|
self.result.add_info("所有必需的元数据字段都已提供")
|
|
73
74
|
|
|
75
|
+
for error in metadata_errors(manifest_data, self.plugin_path):
|
|
76
|
+
self.result.add_error(
|
|
77
|
+
error,
|
|
78
|
+
file_path="manifest.json",
|
|
79
|
+
suggestion='请填写 display_name,并补全 categories/tags;如果配置了 icon,请确保它指向有效的 PNG 文件',
|
|
80
|
+
)
|
|
81
|
+
|
|
74
82
|
# 检查推荐字段
|
|
75
83
|
missing_recommended = []
|
|
76
84
|
for field in self.RECOMMENDED_FIELDS:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mofox-plugin-dev-toolkit"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.2"
|
|
8
8
|
description = "开发工具集,用于快速创建、开发和测试 MoFox-Bot 插件"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "MoFox-Studio", email = "wwwww95915@qq.com"}
|
|
@@ -35,6 +35,7 @@ dependencies = [
|
|
|
35
35
|
"websockets>=12.0",
|
|
36
36
|
"libcst>=1.8.6",
|
|
37
37
|
"aiohttp>=3.9.0",
|
|
38
|
+
"pillow>=11.2.0",
|
|
38
39
|
"uvicorn>=0.24.0",
|
|
39
40
|
"fastapi>=0.104.0",
|
|
40
41
|
"ruff>=0.1.6",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/dev/bridge_plugin/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/dev/bridge_plugin/plugin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/__init__.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/action_template.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/adapter_template.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/chatter_template.py
RENAMED
|
File without changes
|
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/config_template.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/event_template.py
RENAMED
|
File without changes
|
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/prompt_template.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/router_template.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/service_template.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/tool_template.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/color_printer.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/config_loader.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/config_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/license_generator.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/plugin_parser.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/template_engine.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/style_validator.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/type_validator.py
RENAMED
|
File without changes
|
|
File without changes
|