Skip to content

Commit 9f2930e

Browse files
committed
Initial marketplace entries
1 parent 454ef01 commit 9f2930e

11 files changed

Lines changed: 425 additions & 20 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "amd-skills",
3+
"owner": {
4+
"name": "AMD"
5+
},
6+
"metadata": {
7+
"description": "Agent Skills for AMD-optimized workflows",
8+
"version": "0.1.0"
9+
},
10+
"plugins": [
11+
{
12+
"name": "local-ai-app-integration",
13+
"source": "./skills/local-ai-app-integration",
14+
"skills": "./",
15+
"description": "Integrate local AI into cloud LLM apps for offline support, better privacy, and lower API costs."
16+
},
17+
{
18+
"name": "local-ai-use",
19+
"source": "./skills/local-ai-use",
20+
"skills": "./",
21+
"description": "Route image generation, text-to-speech, and speech-to-text through a local AI Server to reduce token/cost usage."
22+
}
23+
]
24+
}

.claude-plugin/plugin.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "amd-skills",
3+
"description": "Agent Skills for AMD-optimized workflows.",
4+
"version": "0.1.0",
5+
"author": {
6+
"name": "AMD"
7+
},
8+
"homepage": "https://github.com/amd/skills",
9+
"repository": "https://github.com/amd/skills",
10+
"license": "MIT",
11+
"keywords": [
12+
"amd",
13+
"rocm",
14+
"hip",
15+
"ryzen-ai",
16+
"migraphx",
17+
"vllm",
18+
"lemonade",
19+
"local-ai"
20+
]
21+
}

.cursor-plugin/marketplace.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "amd-skills",
3+
"owner": {
4+
"name": "AMD"
5+
},
6+
"metadata": {
7+
"description": "Agent Skills for AMD-optimized workflows.",
8+
"version": "0.1.0"
9+
},
10+
"plugins": [
11+
{
12+
"name": "amd-skills",
13+
"source": ".",
14+
"skills": "skills",
15+
"description": "Agent Skills for AMD-optimized workflows."
16+
}
17+
]
18+
}

.cursor-plugin/plugin.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "amd-skills",
3+
"skills": "skills",
4+
"mcpServers": ".mcp.json",
5+
"description": "Agent Skills for AMD-optimized workflows.",
6+
"version": "0.1.0",
7+
"author": {
8+
"name": "AMD"
9+
},
10+
"homepage": "https://github.com/amd/skills",
11+
"repository": "https://github.com/amd/skills",
12+
"license": "MIT",
13+
"keywords": [
14+
"amd",
15+
"rocm",
16+
"hip",
17+
"ryzen-ai",
18+
"migraphx",
19+
"vllm",
20+
"lemonade",
21+
"local-ai"
22+
]
23+
}

.github/workflows/validate.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ on:
88
- "scripts/**"
99
- "**/SKILL.md"
1010
- "skills/**"
11+
- ".claude-plugin/**"
12+
- ".cursor-plugin/**"
13+
- ".mcp.json"
1114
- ".github/workflows/validate.yml"
1215
workflow_dispatch:
1316

1417
jobs:
1518
validate:
16-
name: Validate skills
19+
name: Validate skills and plugin manifests
1720
runs-on: ubuntu-latest
1821
steps:
1922
- name: Check out repository
@@ -22,5 +25,5 @@ jobs:
2225
- name: Set up uv
2326
uses: astral-sh/setup-uv@v7
2427

25-
- name: Validate skills (size + standardized format)
28+
- name: Validate skills and generated manifests
2629
run: ./scripts/check.sh

AUTHORING.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,17 @@ Test the skill the way users will hit it:
155155
The structural rules from this guide (frontmatter shape, name format, description length, and `SKILL.md` body size) are enforced by `scripts/validate_skills.py` and run on every pull request. Run them locally before pushing:
156156
157157
```bash
158-
./scripts/check.sh # validates every skill (same command CI runs)
158+
./scripts/check.sh # validates every skill and plugin manifests (same command CI runs)
159159
```
160160

161161
The validator checks every skill under `skills/` for:
162162

163163
- a `SKILL.md` file with a valid YAML frontmatter block
164164
- `name`: lowercase-with-hyphens, ≤ 64 characters, no `anthropic` / `claude` substrings, matches the directory name
165165
- `description`: non-empty, ≤ 1024 characters
166-
- `SKILL.md` body: ≤ 500 lines (push longer reference material into sibling files)
166+
- `SKILL.md` body: ≤ 500 lines
167+
168+
It also checks the plugin manifests:
169+
170+
- every skill under `skills/` has a matching entry in `.claude-plugin/marketplace.json` (and vice versa), with `source` set to `./skills/<name>` and a non-empty human-readable `description`
171+
- `.cursor-plugin/plugin.json` is up to date with `.claude-plugin/plugin.json` and the discovered skills (regenerate with `./scripts/publish.sh`) (push longer reference material into sibling files)

README.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ Embed AMD-optimized AI into end-user applications.
6464

6565
| Skill | What it does |
6666
| --- | --- |
67-
| `local-ai-app-integration` | Add private, on-device AI to apps that use OpenAI, Anthropic, or Ollama APIs by bundling Embeddable Lemonade as a subprocess. |
68-
| `local-ai-use` | Apply a Lemonade-first strategy so agents default to local image generation, text-to-speech, and speech-to-text to reduce token/cost usage before any cloud fallback. |
67+
| `local-ai-app-integration` | Integrate local AI into cloud LLM apps for offline support, better privacy, and lower API costs. |
68+
| `local-ai-use` | Route image generation, text-to-speech, and speech-to-text through a local AI Server to reduce token/cost usage. |
6969

7070
### Cross-stack porting
7171

@@ -151,7 +151,7 @@ scripts/ # Tooling for publishing and regenerating manifests
151151

152152
## Installation
153153

154-
Detailed install steps for each supported agent will land alongside the first published skills. The general flow:
154+
AMD Skills are compatible with Cursor, Claude Code, OpenAI Codex, and Gemini CLI. The general flow:
155155

156156
### Cursor
157157

@@ -172,7 +172,7 @@ Copy or symlink the desired folders from `skills/` into one of Codex's standard
172172

173173
### Gemini CLI
174174

175-
A `gemini-extension.json` is provided so the repo can be installed as a Gemini CLI extension:
175+
A `gemini-extension.json` will be provided so the repo can be installed as a Gemini CLI extension:
176176

177177
```bash
178178
gemini extensions install https://github.com/amd/skills.git --consent
@@ -201,12 +201,16 @@ Best for cross-cutting skills that do not have a natural product home.
201201
1. Copy an existing skill folder under `skills/` as a starting point and rename it.
202202
2. Update the `SKILL.md` frontmatter so the `name` and `description` clearly explain *what* the skill does and *when* an agent should reach for it.
203203
3. Add the supporting scripts, templates, and reference docs your instructions point to. Keep skills focused: one well-scoped task per skill is better than one mega-skill.
204-
4. Register the skill in `.claude-plugin/marketplace.json` with a human-readable description.
205-
5. Validate the skill locally before pushing:
204+
4. Register the skill in `.claude-plugin/marketplace.json` with a human-readable description (the marketplace description is for humans browsing the catalog; the `SKILL.md` description is what the agent uses for routing).
205+
5. Regenerate the Cursor manifest so it tracks the new skill:
206206
```bash
207-
./scripts/check.sh # validates every SKILL.md
207+
./scripts/publish.sh # writes .cursor-plugin/plugin.json
208208
```
209-
6. Open a pull request. The `validate` GitHub Actions workflow runs `./scripts/check.sh` and must pass before merge. See [AUTHORING.md](AUTHORING.md#validating-locally) for the full set of enforced rules.
209+
6. Validate the skill locally before pushing:
210+
```bash
211+
./scripts/check.sh # validates every SKILL.md and that manifests are in sync
212+
```
213+
7. Open a pull request. The `validate` GitHub Actions workflow runs `./scripts/check.sh` and must pass before merge. See [AUTHORING.md](AUTHORING.md#validating-locally) for the full set of enforced rules.
210214

211215
### Path B: Skills authored in a product repository
212216

scripts/check.sh

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
11
#!/usr/bin/env bash
2-
# Validate every SKILL.md in the catalog.
2+
# Validate every SKILL.md and that generated plugin manifests are up to date.
33
#
44
# Usage:
5-
# ./scripts/check.sh Validate every skill.
5+
# ./scripts/check.sh Validate every skill and check manifests.
66
# ./scripts/check.sh -h|--help Print this help.
77
#
88
# Requires `uv` (https://github.com/astral-sh/uv).
9-
#
10-
# Note: this repo does not publish anything yet. When we add catalog
11-
# manifests (.cursor-plugin/plugin.json, .claude-plugin/marketplace.json,
12-
# .mcp.json, etc.) we'll add a separate scripts/publish.sh for generation
13-
# and check.sh will gain a `--check` mode that diffs the regenerated
14-
# artifacts against the committed copy.
159

1610
set -euo pipefail
1711

@@ -25,6 +19,7 @@ usage() {
2519
case "${1:-}" in
2620
"")
2721
uv run scripts/validate_skills.py
22+
uv run scripts/generate_cursor_plugin.py --check
2823
;;
2924
-h|--help)
3025
usage

scripts/generate_cursor_plugin.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#!/usr/bin/env -S uv run --quiet
2+
# /// script
3+
# requires-python = ">=3.10"
4+
# dependencies = []
5+
# ///
6+
"""Generate Cursor plugin manifest from existing repo metadata.
7+
8+
Outputs:
9+
- .cursor-plugin/plugin.json
10+
11+
Design goals:
12+
- Keep Claude + Cursor metadata in sync.
13+
- Reuse `.claude-plugin/plugin.json` as the primary metadata source.
14+
- Discover skills from `skills/*/SKILL.md` so the manifest tracks the
15+
catalog automatically.
16+
- Treat `.mcp.json` as hand-maintained (no MCP server is generated here).
17+
18+
Usage:
19+
uv run scripts/generate_cursor_plugin.py # write
20+
uv run scripts/generate_cursor_plugin.py --check # validate only
21+
"""
22+
23+
from __future__ import annotations
24+
25+
import argparse
26+
import json
27+
import re
28+
import sys
29+
from pathlib import Path
30+
31+
ROOT = Path(__file__).resolve().parent.parent
32+
CLAUDE_PLUGIN_MANIFEST = ROOT / ".claude-plugin" / "plugin.json"
33+
CURSOR_PLUGIN_DIR = ROOT / ".cursor-plugin"
34+
CURSOR_PLUGIN_MANIFEST = CURSOR_PLUGIN_DIR / "plugin.json"
35+
MCP_CONFIG = ROOT / ".mcp.json"
36+
37+
# Fields copied verbatim from the Claude plugin manifest into the Cursor
38+
# manifest so the two stay in lock-step.
39+
COPIED_FIELDS = (
40+
"description",
41+
"version",
42+
"author",
43+
"homepage",
44+
"repository",
45+
"license",
46+
"keywords",
47+
"logo",
48+
)
49+
50+
PLUGIN_NAME_RE = re.compile(r"^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$")
51+
52+
53+
def load_json(path: Path) -> dict:
54+
if not path.exists():
55+
raise FileNotFoundError(f"Missing required file: {path}")
56+
return json.loads(path.read_text(encoding="utf-8"))
57+
58+
59+
def parse_frontmatter(text: str) -> dict[str, str]:
60+
"""Return the YAML frontmatter at the top of `text` as a flat mapping.
61+
62+
Only top-level scalar keys are extracted. That is sufficient for `name`,
63+
which is all this script needs.
64+
"""
65+
match = re.search(r"^---\s*\n(.*?)\n---\s*", text, re.DOTALL)
66+
if not match:
67+
return {}
68+
data: dict[str, str] = {}
69+
for line in match.group(1).splitlines():
70+
# Skip continuation lines so multi-line `description: >-` values
71+
# don't get parsed as keys.
72+
if ":" not in line or line.startswith((" ", "\t")):
73+
continue
74+
key, value = line.split(":", 1)
75+
data[key.strip()] = value.strip()
76+
return data
77+
78+
79+
def collect_skills() -> list[str]:
80+
skills: list[str] = []
81+
for skill_md in sorted(ROOT.glob("skills/*/SKILL.md")):
82+
meta = parse_frontmatter(skill_md.read_text(encoding="utf-8"))
83+
name = meta.get("name", "").strip()
84+
if not name:
85+
continue
86+
skills.append(name)
87+
return skills
88+
89+
90+
def validate_plugin_name(name: str) -> None:
91+
if not PLUGIN_NAME_RE.match(name):
92+
raise ValueError(
93+
"Invalid plugin name in .claude-plugin/plugin.json: "
94+
f"'{name}'. Must be lowercase and match {PLUGIN_NAME_RE.pattern}"
95+
)
96+
97+
98+
def build_cursor_plugin_manifest() -> dict:
99+
src = load_json(CLAUDE_PLUGIN_MANIFEST)
100+
101+
name = src.get("name")
102+
if not isinstance(name, str) or not name:
103+
raise ValueError(".claude-plugin/plugin.json must define a non-empty 'name'")
104+
validate_plugin_name(name)
105+
106+
skills = collect_skills()
107+
if not skills:
108+
raise ValueError("No skills discovered under skills/*/SKILL.md")
109+
110+
# `mcpServers` points at .mcp.json so future MCP servers added there
111+
# are picked up by Cursor without a manifest change.
112+
manifest: dict = {"name": name, "skills": "skills", "mcpServers": ".mcp.json"}
113+
for key in COPIED_FIELDS:
114+
if key in src:
115+
manifest[key] = src[key]
116+
117+
return manifest
118+
119+
120+
def render_json(data: dict) -> str:
121+
return json.dumps(data, indent=2, ensure_ascii=False) + "\n"
122+
123+
124+
def write_or_check(path: Path, content: str, check: bool) -> bool:
125+
"""Return True when the file is already up-to-date.
126+
127+
In write mode (check=False) the file is written first, so the return
128+
value is always True in that branch.
129+
"""
130+
current = path.read_text(encoding="utf-8") if path.exists() else None
131+
if current == content:
132+
return True
133+
134+
if check:
135+
return False
136+
137+
path.parent.mkdir(parents=True, exist_ok=True)
138+
path.write_text(content, encoding="utf-8")
139+
return True
140+
141+
142+
def validate_mcp_config() -> None:
143+
"""Make sure .mcp.json is at least valid JSON shaped like an MCP config."""
144+
if not MCP_CONFIG.exists():
145+
raise FileNotFoundError(
146+
f"Missing required file: {MCP_CONFIG.relative_to(ROOT)}. "
147+
'Create it with `{"mcpServers": {}}` if there are no servers yet.'
148+
)
149+
data = load_json(MCP_CONFIG)
150+
if not isinstance(data, dict) or "mcpServers" not in data:
151+
raise ValueError(
152+
f"{MCP_CONFIG.relative_to(ROOT)} must be a JSON object with an "
153+
"`mcpServers` key (use `{}` if no servers are configured)."
154+
)
155+
156+
157+
def main() -> None:
158+
parser = argparse.ArgumentParser(
159+
description="Generate Cursor plugin manifest from .claude-plugin/plugin.json"
160+
)
161+
parser.add_argument(
162+
"--check",
163+
action="store_true",
164+
help="Validate the generated manifest is up to date without writing changes.",
165+
)
166+
args = parser.parse_args()
167+
168+
validate_mcp_config()
169+
plugin_manifest = render_json(build_cursor_plugin_manifest())
170+
ok_plugin = write_or_check(CURSOR_PLUGIN_MANIFEST, plugin_manifest, check=args.check)
171+
172+
if args.check:
173+
if not ok_plugin:
174+
print("Generated Cursor manifest is out of date:", file=sys.stderr)
175+
print(f" - {CURSOR_PLUGIN_MANIFEST.relative_to(ROOT)}", file=sys.stderr)
176+
print("Run: uv run scripts/generate_cursor_plugin.py", file=sys.stderr)
177+
sys.exit(1)
178+
179+
print("Cursor plugin manifest is up to date.")
180+
return
181+
182+
print(f"Wrote {CURSOR_PLUGIN_MANIFEST.relative_to(ROOT)}")
183+
184+
185+
if __name__ == "__main__":
186+
main()

0 commit comments

Comments
 (0)