mofox-plugin-dev-toolkit 0.5.1__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.
Files changed (65) hide show
  1. {mofox_plugin_dev_toolkit-0.5.1/mofox_plugin_dev_toolkit.egg-info → mofox_plugin_dev_toolkit-0.5.2}/PKG-INFO +2 -1
  2. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2/mofox_plugin_dev_toolkit.egg-info}/PKG-INFO +2 -1
  3. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mofox_plugin_dev_toolkit.egg-info/requires.txt +1 -0
  4. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/commands/build.py +3 -3
  5. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/commands/init.py +2 -0
  6. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/commands/market.py +3 -3
  7. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/market/manifest.py +58 -2
  8. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/manifest_metadata.py +62 -10
  9. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/metadata_validator.py +2 -2
  10. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/pyproject.toml +2 -1
  11. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/LICENSE +0 -0
  12. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/MANIFEST.in +0 -0
  13. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/README.md +0 -0
  14. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mofox_plugin_dev_toolkit.egg-info/SOURCES.txt +0 -0
  15. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mofox_plugin_dev_toolkit.egg-info/dependency_links.txt +0 -0
  16. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mofox_plugin_dev_toolkit.egg-info/entry_points.txt +0 -0
  17. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mofox_plugin_dev_toolkit.egg-info/top_level.txt +0 -0
  18. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/__init__.py +0 -0
  19. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/__main__.py +0 -0
  20. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/cli.py +0 -0
  21. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/commands/__init__.py +0 -0
  22. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/commands/check.py +0 -0
  23. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/commands/dev.py +0 -0
  24. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/commands/generate.py +0 -0
  25. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/dev/bridge_plugin/__init__.py +0 -0
  26. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/dev/bridge_plugin/cleanup_handler.py +0 -0
  27. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/dev/bridge_plugin/dev_config.py +0 -0
  28. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/dev/bridge_plugin/file_watcher.py +0 -0
  29. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/dev/bridge_plugin/manifest.json +0 -0
  30. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/dev/bridge_plugin/plugin.py +0 -0
  31. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/market/__init__.py +0 -0
  32. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/market/client.py +0 -0
  33. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/market/config.py +0 -0
  34. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/market/git.py +0 -0
  35. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/market/github.py +0 -0
  36. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/__init__.py +0 -0
  37. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/action_template.py +0 -0
  38. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/adapter_template.py +0 -0
  39. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/chatter_template.py +0 -0
  40. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/collection_template.py +0 -0
  41. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/config_template.py +0 -0
  42. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/event_template.py +0 -0
  43. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/plus_command_template.py +0 -0
  44. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/prompt_template.py +0 -0
  45. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/router_template.py +0 -0
  46. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/service_template.py +0 -0
  47. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/templates/tool_template.py +0 -0
  48. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/__init__.py +0 -0
  49. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/code_parser.py +0 -0
  50. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/color_printer.py +0 -0
  51. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/config_loader.py +0 -0
  52. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/config_manager.py +0 -0
  53. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/file_ops.py +0 -0
  54. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/license_generator.py +0 -0
  55. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/plugin_parser.py +0 -0
  56. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/utils/template_engine.py +0 -0
  57. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/__init__.py +0 -0
  58. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/auto_fix_validator.py +0 -0
  59. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/base.py +0 -0
  60. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/component_validator.py +0 -0
  61. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/config_validator.py +0 -0
  62. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/structure_validator.py +0 -0
  63. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/style_validator.py +0 -0
  64. {mofox_plugin_dev_toolkit-0.5.1 → mofox_plugin_dev_toolkit-0.5.2}/mpdt/validators/type_validator.py +0 -0
  65. {mofox_plugin_dev_toolkit-0.5.1 → 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.1
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.1
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
@@ -10,6 +10,7 @@ watchdog>=3.0.0
10
10
  websockets>=12.0
11
11
  libcst>=1.8.6
12
12
  aiohttp>=3.9.0
13
+ pillow>=11.2.0
13
14
  uvicorn>=0.24.0
14
15
  fastapi>=0.104.0
15
16
  ruff>=0.1.6
@@ -181,14 +181,14 @@ def _save_manifest(plugin_dir: Path, manifest: dict) -> None:
181
181
  json.dump(manifest, f, ensure_ascii=False, indent=4)
182
182
 
183
183
 
184
- def _validate_manifest_metadata(manifest: dict) -> None:
184
+ def _validate_manifest_metadata(manifest: dict, plugin_dir: Path) -> None:
185
185
  """验证打包所需的 manifest 元数据。"""
186
186
  required = ["name", "version", "description", "author", "entry_point"]
187
187
  for field in required:
188
188
  if field not in manifest:
189
189
  raise ValueError(f"manifest.json 缺少必需字段: '{field}'")
190
190
 
191
- errors = metadata_errors(manifest)
191
+ errors = metadata_errors(manifest, plugin_dir)
192
192
  if errors:
193
193
  raise ValueError(errors[0])
194
194
 
@@ -279,7 +279,7 @@ def build_package(
279
279
 
280
280
  manifest = ensure_manifest_metadata_interactive(plugin_dir, manifest)
281
281
 
282
- _validate_manifest_metadata(manifest)
282
+ _validate_manifest_metadata(manifest, plugin_dir)
283
283
 
284
284
  plugin_name: str = manifest["name"]
285
285
  plugin_version: str = manifest["version"]
@@ -369,9 +369,11 @@ def _generate_manifest_file(
369
369
 
370
370
  manifest = {
371
371
  "name": plugin_name,
372
+ "display_name": plugin_name,
372
373
  "version": "1.0.0",
373
374
  "description": description or f"{plugin_name} 插件",
374
375
  "author": author or "Your Name",
376
+ "icon": "icon.png",
375
377
  "categories": categories or ["tool"],
376
378
  "tags": tags or [plugin_name],
377
379
  "dependencies": {"plugins": [], "components": []},
@@ -51,7 +51,7 @@ def market_register(plugin_path: str = ".", market_url: str | None = None, token
51
51
 
52
52
  async def run() -> None:
53
53
  manifest = await _load_manifest_for_market(plugin_path)
54
- payload = plugin_payload(manifest, repository_url=repository_url)
54
+ payload = plugin_payload(manifest, repository_url=repository_url, plugin_dir=plugin_path)
55
55
  result = await _client(market_url, token).register_plugin(payload)
56
56
  _print_ok(f"Plugin registered: {result['plugin_id']} ({result['status']})")
57
57
 
@@ -63,7 +63,7 @@ def market_update(plugin_path: str = ".", market_url: str | None = None, token:
63
63
 
64
64
  async def run() -> None:
65
65
  manifest = await _load_manifest_for_market(plugin_path)
66
- payload = plugin_payload(manifest, repository_url=repository_url)
66
+ payload = plugin_payload(manifest, repository_url=repository_url, plugin_dir=plugin_path)
67
67
  plugin_id = payload.pop("plugin_id")
68
68
  result = await _client(market_url, token).update_plugin(plugin_id, payload)
69
69
  _print_ok(f"Plugin updated: {result['plugin_id']} ({result['status']})")
@@ -232,7 +232,7 @@ def market_publish(
232
232
  asset_url = str(asset.get("browser_download_url") or asset_download_url_for(plugin_id, version, package.package_path.name))
233
233
 
234
234
  market = _client(market_url, token or github_api_token)
235
- 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)
236
236
  try:
237
237
  plugin = await market.register_plugin(plugin_registration_payload)
238
238
  _print_ok(f"Plugin registered: {plugin['plugin_id']} ({plugin['status']})")
@@ -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(manifest: dict[str, Any], repository_url: str | None = None) -> dict[str, Any]:
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": str(manifest.get("display_name") or plugin_id),
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
@@ -15,6 +15,15 @@ from .color_printer import print_success, print_warning
15
15
  ALLOWED_CATEGORIES = ("tool", "chat", "fun", "information", "moderation")
16
16
 
17
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
+
18
27
  def get_categories(manifest: dict[str, Any]) -> list[str]:
19
28
  """Return normalized categories from manifest."""
20
29
  value = manifest.get("categories")
@@ -60,14 +69,19 @@ def tags_are_valid(manifest: dict[str, Any]) -> bool:
60
69
  return bool(get_tags(manifest))
61
70
 
62
71
 
63
- def metadata_errors(manifest: dict[str, Any]) -> list[str]:
72
+ def metadata_errors(manifest: dict[str, Any], plugin_dir: Path | None = None) -> list[str]:
64
73
  """Return validation errors for required manifest metadata."""
65
74
  errors: list[str] = []
75
+ if not get_display_name(manifest):
76
+ errors.append("manifest.json 的 display_name 必须是非空字符串")
66
77
  if not categories_are_valid(manifest):
67
78
  allowed = ", ".join(ALLOWED_CATEGORIES)
68
79
  errors.append(f"manifest.json 的 categories 必须是只包含一个值的数组,且取值只能是: {allowed}")
69
80
  if not tags_are_valid(manifest):
70
81
  errors.append("manifest.json 的 tags 必须是至少包含一个非空字符串的数组")
82
+ icon_error = _icon_error(manifest, plugin_dir)
83
+ if icon_error:
84
+ errors.append(icon_error)
71
85
  return errors
72
86
 
73
87
 
@@ -99,13 +113,22 @@ def _save_manifest(plugin_dir: Path, manifest: dict[str, Any]) -> None:
99
113
  json.dump(manifest, f, ensure_ascii=False, indent=4)
100
114
 
101
115
 
102
- def prompt_manifest_metadata(existing_manifest: dict[str, Any] | None = None) -> dict[str, list[str]]:
116
+ def prompt_manifest_metadata(existing_manifest: dict[str, Any] | None = None) -> dict[str, Any]:
103
117
  """Collect required manifest metadata from the console."""
104
118
  manifest = existing_manifest or {}
119
+ display_name = get_display_name(manifest) or str(manifest.get("name") or "").strip()
105
120
  existing_categories = get_categories(manifest)
106
121
  default_category = existing_categories[0] if existing_categories else ALLOWED_CATEGORIES[0]
107
122
  existing_tags = ", ".join(get_tags(manifest))
108
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
+
109
132
  category = questionary.select(
110
133
  "选择插件分类 categories(只能选一个):",
111
134
  choices=[questionary.Choice(item, value=item) for item in ALLOWED_CATEGORIES],
@@ -123,18 +146,28 @@ def prompt_manifest_metadata(existing_manifest: dict[str, Any] | None = None) ->
123
146
  raise ValueError("已取消填写 tags")
124
147
 
125
148
  return {
149
+ "display_name": str(resolved_display_name).strip(),
126
150
  "categories": [category],
127
151
  "tags": normalize_tags(tags_text),
128
152
  }
129
153
 
130
154
 
131
- async def prompt_manifest_metadata_async(existing_manifest: dict[str, Any] | None = None) -> dict[str, list[str]]:
155
+ async def prompt_manifest_metadata_async(existing_manifest: dict[str, Any] | None = None) -> dict[str, Any]:
132
156
  """Collect required manifest metadata from the console inside an event loop."""
133
157
  manifest = existing_manifest or {}
158
+ display_name = get_display_name(manifest) or str(manifest.get("name") or "").strip()
134
159
  existing_categories = get_categories(manifest)
135
160
  default_category = existing_categories[0] if existing_categories else ALLOWED_CATEGORIES[0]
136
161
  existing_tags = ", ".join(get_tags(manifest))
137
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
+
138
171
  category = await questionary.select(
139
172
  "选择插件分类 categories(只能选一个):",
140
173
  choices=[questionary.Choice(item, value=item) for item in ALLOWED_CATEGORIES],
@@ -152,6 +185,7 @@ async def prompt_manifest_metadata_async(existing_manifest: dict[str, Any] | Non
152
185
  raise ValueError("已取消填写 tags")
153
186
 
154
187
  return {
188
+ "display_name": str(resolved_display_name).strip(),
155
189
  "categories": [category],
156
190
  "tags": normalize_tags(tags_text),
157
191
  }
@@ -159,14 +193,14 @@ async def prompt_manifest_metadata_async(existing_manifest: dict[str, Any] | Non
159
193
 
160
194
  def ensure_manifest_metadata_interactive(plugin_dir: Path, manifest: dict[str, Any]) -> dict[str, Any]:
161
195
  """Prompt for missing manifest metadata and persist it when running in a TTY."""
162
- errors = metadata_errors(manifest)
196
+ errors = metadata_errors(manifest, plugin_dir)
163
197
  if not errors:
164
198
  return _finalize_manifest_metadata(manifest)
165
199
 
166
200
  if not _is_interactive_terminal() or _has_running_event_loop():
167
201
  return manifest
168
202
 
169
- print_warning("检测到 manifest.json 缺少或无效的 categories/tags,进入交互补全...")
203
+ print_warning("检测到 manifest.json 缺少或无效的市场元数据,进入交互补全...")
170
204
  for error in errors:
171
205
  print_warning(error)
172
206
 
@@ -174,20 +208,20 @@ def ensure_manifest_metadata_interactive(plugin_dir: Path, manifest: dict[str, A
174
208
  _finalize_manifest_metadata(manifest)
175
209
  _save_manifest(plugin_dir, manifest)
176
210
 
177
- print_success("已更新 manifest.json 中的 categories 和 tags")
211
+ print_success("已更新 manifest.json 中的市场元数据")
178
212
  return manifest
179
213
 
180
214
 
181
215
  async def ensure_manifest_metadata_interactive_async(plugin_dir: Path, manifest: dict[str, Any]) -> dict[str, Any]:
182
216
  """Async prompt for missing manifest metadata and persist it when running in a TTY."""
183
- errors = metadata_errors(manifest)
217
+ errors = metadata_errors(manifest, plugin_dir)
184
218
  if not errors:
185
219
  return _finalize_manifest_metadata(manifest)
186
220
 
187
221
  if not _is_interactive_terminal():
188
222
  return manifest
189
223
 
190
- print_warning("检测到 manifest.json 缺少或无效的 categories/tags,进入交互补全...")
224
+ print_warning("检测到 manifest.json 缺少或无效的市场元数据,进入交互补全...")
191
225
  for error in errors:
192
226
  print_warning(error)
193
227
 
@@ -195,5 +229,23 @@ async def ensure_manifest_metadata_interactive_async(plugin_dir: Path, manifest:
195
229
  _finalize_manifest_metadata(manifest)
196
230
  _save_manifest(plugin_dir, manifest)
197
231
 
198
- print_success("已更新 manifest.json 中的 categories 和 tags")
199
- return manifest
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
@@ -72,11 +72,11 @@ class MetadataValidator(BaseValidator):
72
72
  else:
73
73
  self.result.add_info("所有必需的元数据字段都已提供")
74
74
 
75
- for error in metadata_errors(manifest_data):
75
+ for error in metadata_errors(manifest_data, self.plugin_path):
76
76
  self.result.add_error(
77
77
  error,
78
78
  file_path="manifest.json",
79
- suggestion='请填写 "categories": ["tool|chat|fun|information|moderation"] "tags": ["..."]',
79
+ suggestion='请填写 display_name,并补全 categories/tags;如果配置了 icon,请确保它指向有效的 PNG 文件',
80
80
  )
81
81
 
82
82
  # 检查推荐字段
@@ -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.1"
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",