feat(market): 为外部技能补充图标 (#50)

## Summary / 摘要

Add inline SVG icons to all 20 external pointer skills, so their cards
no longer fall back to an empty icon. The market catalog version is
bumped from `1.2.8` to `1.2.9`.

为全部 20 个外部 pointer skill 补齐内联 SVG 图标,市场卡片不再显示为空;市场版本由 `1.2.8` 升至
`1.2.9`。

## Compatibility / 兼容性

Depends on desirecore/desirecore#1177, which permits the `icon` field in
strict pointer-entry validation and forwards it through the offline
list/detail responses. That PR must merge first.

## Skill → icon screenshot / Skill 与图标截图

The preview labels every card with its display name and id.

![External skill icon
preview](https://raw.githubusercontent.com/desirecore/market/feat/external-skill-icons/docs/assets/external-skill-icons-v1.2.9.svg)

Covered skills: `agent-reach`, `ai-news-radar`, `amap-jsapi-skill`,
`dingtalk-api`, `flyai-skill`, `follow-builders`,
`ian-xiaohei-illustrations`, `karpathy-guidelines`, `khazix-skills`,
`larksuite-cli`, `last30days`, `luckin-my-coffee`, `mattpocock-skills`,
`minimax-image-gen`, `minimax-tts`, `mt-paotui-for-client`,
`netease-skills`, `taste-skill`, `wechatpay-skills`, and `wecom-cli`.

## Validation / 验证

- `uv run scripts/i18n/validate-i18n.py`
- `uv run scripts/i18n/translate.py --check`
- Parsed all 20 `entry.json` icons as SVG XML
- `git diff --check`
- Rendered the committed preview asset locally
This commit is contained in:
2026-07-16 19:59:41 +08:00
committed by GitHub
parent 73c94ab70e
commit 0ecc29c7ce
24 changed files with 164 additions and 4 deletions

View File

@@ -17,7 +17,7 @@ Checks:
8. Top-level description is 1-1024 chars (spec); top-level name is 1-64 chars (spec).
9. Skill/Agent counts and builtin skill index match the repository contents.
10. Skill, Agent, and entry.json category references exist in categories.json.
11. entry.json pointers have the required marketplace fields and safe source URLs.
11. entry.json pointers have the required marketplace fields, valid inline SVG icons, and safe source URLs.
12. Market Skills set `disable-model-invocation` to true or omit it; false is prohibited.
Exit codes:
@@ -38,6 +38,7 @@ import json
import re
import ssl
import sys
from xml.etree import ElementTree
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Iterable
@@ -389,7 +390,7 @@ def validate_entry_json(report: Report, entry_file: Path, category_ids: set[str]
if not entry:
return
required = ("id", "name", "category", "maintainer", "stewardship", "license", "redistribution", "source")
required = ("id", "name", "category", "icon", "maintainer", "stewardship", "license", "redistribution", "source")
for key in required:
if key not in entry:
report.add(Issue(rel, "entry-json", f"missing required field '{key}'"))
@@ -401,6 +402,19 @@ def validate_entry_json(report: Report, entry_file: Path, category_ids: set[str]
if not isinstance(category, str) or category not in category_ids:
report.add(Issue(rel, "entry-json", f"category '{category}' is not declared in categories.json"))
icon = entry.get("icon")
if not isinstance(icon, str) or not icon.strip():
report.add(Issue(rel, "entry-json", "icon must be a non-empty inline SVG string"))
else:
try:
root = ElementTree.fromstring(icon)
if root.tag != "{http://www.w3.org/2000/svg}svg":
report.add(Issue(rel, "entry-json", "icon root element must be svg in the SVG namespace"))
elif not root.get("viewBox"):
report.add(Issue(rel, "entry-json", "icon SVG must declare a viewBox"))
except ElementTree.ParseError as exc:
report.add(Issue(rel, "entry-json", f"icon must be valid SVG XML: {exc}"))
maintainer = entry.get("maintainer")
if not isinstance(maintainer, dict) or not isinstance(maintainer.get("name"), str):
report.add(Issue(rel, "entry-json", "maintainer.name is required"))