Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,35 @@
"owner": {
"name": "AMD"
},
"description": "Agent Skills for AMD-optimized workflows.",
"metadata": {
"description": "Agent Skills for AMD-optimized workflows",
"description": "Agent Skills for AMD-optimized workflows.",
"version": "0.1.0"
},
"plugins": [
{
"name": "apu-memory-tuner",
"source": "./skills/apu-memory-tuner",
"skills": "./",
"description": "Inspect and tune the shared-vs-dedicated memory split (GTT / UMA Frame Buffer) on AMD Ryzen APUs so larger LLMs and image models fit on the iGPU."
},
{
"name": "local-ai-app-integration",
"source": "./skills/local-ai-app-integration",
"skills": "./",
"description": "Integrate local AI into cloud LLM apps for offline support, better privacy, and lower API costs."
},
{
"name": "local-ai-use",
"source": "./skills/local-ai-use",
"skills": "./",
"description": "Route image generation, text-to-speech, and speech-to-text through a local AI Server to reduce token/cost usage."
},
{
"name": "magpie",
"source": "./skills/magpie",
"skills": "./",
"description": "Performs GPU kernel correctness and performance evaluation and LLM inference benchmarking with Magpie. Analyzes single or multiple kernels (HIP/CUDA/PyTorch), compares kernel implementations, runs vLLM/SGLang benchmarks with profiling and TraceLens, and runs gap analysis on torch traces."
},
{
"name": "rocm-doctor",
"source": "./skills/rocm-doctor",
"skills": "./",
"description": "Diagnose why ROCm, PyTorch, or llama.cpp isn't working on an AMD GPU. Matches the symptom against a fixed list of twelve known misconfigurations and proposes the next step."
}
]
Expand Down
28 changes: 24 additions & 4 deletions .cursor-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,36 @@
"owner": {
"name": "AMD"
},
"description": "Agent Skills for AMD-optimized workflows.",
"metadata": {
"description": "Agent Skills for AMD-optimized workflows.",
"version": "0.1.0"
},
"plugins": [
{
"name": "amd-skills",
"source": ".",
"skills": "skills",
"description": "Agent Skills for AMD-optimized workflows."
"name": "apu-memory-tuner",
"source": "./skills/apu-memory-tuner",
"description": "Inspect and tune the shared-vs-dedicated memory split (GTT / UMA Frame Buffer) on AMD Ryzen APUs so larger LLMs and image models fit on the iGPU."
},
{
"name": "local-ai-app-integration",
"source": "./skills/local-ai-app-integration",
"description": "Integrate local AI into cloud LLM apps for offline support, better privacy, and lower API costs."
},
{
"name": "local-ai-use",
"source": "./skills/local-ai-use",
"description": "Route image generation, text-to-speech, and speech-to-text through a local AI Server to reduce token/cost usage."
},
{
"name": "magpie",
"source": "./skills/magpie",
"description": "Performs GPU kernel correctness and performance evaluation and LLM inference benchmarking with Magpie. Analyzes single or multiple kernels (HIP/CUDA/PyTorch), compares kernel implementations, runs vLLM/SGLang benchmarks with profiling and TraceLens, and runs gap analysis on torch traces."
},
{
"name": "rocm-doctor",
"source": "./skills/rocm-doctor",
"description": "Diagnose why ROCm, PyTorch, or llama.cpp isn't working on an AMD GPU. Matches the symptom against a fixed list of twelve known misconfigurations and proposes the next step."
}
]
}
22 changes: 0 additions & 22 deletions .cursor-plugin/plugin.json

This file was deleted.

4 changes: 2 additions & 2 deletions .github/workflows/import-external-skills.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ jobs:
uv run scripts/import_external_skills.py
fi

- name: Regenerate Cursor plugin manifest
- name: Regenerate Cursor marketplace manifest
if: ${{ inputs.dry_run != true }}
run: uv run scripts/generate_cursor_plugin.py
run: uv run scripts/generate_cursor_marketplace.py

- name: Validate skills and manifests
if: ${{ inputs.dry_run != true }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ jobs:
run: uv run scripts/validate_skills.py --marketplace-only

- name: Validate generated Cursor manifest
run: uv run scripts/generate_cursor_plugin.py --check
run: uv run scripts/generate_cursor_marketplace.py --check

# Deterministic, offline-only reference check: relative paths and
# heading anchors. External URLs are intentionally not checked here
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Best for cross-cutting skills that do not have a natural product home.
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).
5. Regenerate the Cursor manifest so it tracks the new skill:
```bash
./scripts/publish.sh # writes .cursor-plugin/plugin.json
./scripts/publish.sh # writes .cursor-plugin/marketplace.json
```
6. Validate the skill locally before pushing:
```bash
Expand Down Expand Up @@ -202,4 +202,4 @@ The validator checks every skill under `skills/` for:
It also checks the plugin manifests:

- 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`
- `.cursor-plugin/plugin.json` is up to date with `.claude-plugin/plugin.json` and the discovered skills (regenerate with `./scripts/publish.sh`)
- `.cursor-plugin/marketplace.json` is up to date — it mirrors `.claude-plugin/marketplace.json` and pulls shared identity (name, description, version, author) from `plugin-metadata.json` (regenerate with `./scripts/publish.sh`)
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,9 @@ This repo also acts as an **incubator**: a skill can start under `skills/` to it

```
skills/ # All skills the agent can load (in-repo + vendored copies of federated)
.cursor-plugin/ # Cursor plugin manifest
.claude-plugin/ # Claude Code marketplace manifest
.claude-plugin/ # Claude Code marketplace manifest (one installable plugin per skill)
.cursor-plugin/ # Cursor marketplace manifest (generated mirror of the Claude catalog)
plugin-metadata.json # Shared, vendor-neutral identity/discovery metadata for all manifests
.github/workflows/ # CI for validating skills and the `import-external-skills` workflow
scripts/ # Tooling for publishing, regenerating manifests, and importing
scripts/sources.yml # Master list of external skill sources for federation
Expand All @@ -149,7 +150,7 @@ AMD Skills are compatible with Cursor, Claude Code, OpenAI Codex, and Gemini CLI

### Cursor

Install the AMD plugin from this repository through the Cursor plugin flow. The repo ships a `.cursor-plugin/plugin.json` so skills are discoverable as soon as the plugin is enabled.
Add this repository as a plugin marketplace through the Cursor plugin flow, then enable the skills you want. The repo ships a `.cursor-plugin/marketplace.json` that lists every skill as its own installable plugin, mirroring the Claude Code catalog.

### Claude Code

Expand Down
1 change: 0 additions & 1 deletion .claude-plugin/plugin.json → plugin-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"rocm",
"hip",
"ryzen-ai",
"migraphx",
"vllm",
"lemonade",
"local-ai"
Expand Down
2 changes: 1 addition & 1 deletion scripts/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ usage() {
case "${1:-}" in
"")
uv run scripts/validate_skills.py
uv run scripts/generate_cursor_plugin.py --check
uv run scripts/generate_cursor_marketplace.py --check
;;
-h|--help)
usage
Expand Down
157 changes: 157 additions & 0 deletions scripts/generate_cursor_marketplace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#!/usr/bin/env -S uv run --quiet
# /// script
# requires-python = ">=3.10"
# dependencies = []
# ///
"""Generate the Cursor marketplace manifest from the canonical sources.

Both ecosystems use the same per-skill marketplace model: each skill under
`skills/` is published as its own installable plugin. To avoid drift, the
Cursor catalog is generated rather than hand-maintained.

Sources of truth:
- `plugin-metadata.json` (repo root): shared identity and discovery metadata
(name, description, version, author, homepage, repository, license,
keywords). This is the vendor-neutral metadata file, reused by every
marketplace/manifest target. It is NOT a plugin manifest.
- `.claude-plugin/marketplace.json`: the per-skill plugin entries and their
human-readable descriptions (hand-maintained, since the catalog blurbs
intentionally differ from the SKILL.md routing descriptions).

Output:
- `.cursor-plugin/marketplace.json`: a mirror of the Claude marketplace so
Cursor exposes exactly the same skills as Claude.

Usage:
uv run scripts/generate_cursor_marketplace.py # write
uv run scripts/generate_cursor_marketplace.py --check # validate only

`--check` fails if the generated file is stale or if the Claude marketplace
top-level identity has drifted from `plugin-metadata.json`.
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
PLUGIN_METADATA = ROOT / "plugin-metadata.json"
CLAUDE_MARKETPLACE = ROOT / ".claude-plugin" / "marketplace.json"
CURSOR_MARKETPLACE = ROOT / ".cursor-plugin" / "marketplace.json"


def load_json(path: Path) -> dict:
if not path.exists():
raise FileNotFoundError(f"Missing required file: {path}")
return json.loads(path.read_text(encoding="utf-8"))


def check_identity_consistency(metadata: dict, claude: dict) -> list[str]:
"""Return error strings if the Claude marketplace top-level identity has
drifted from the canonical `plugin-metadata.json`."""
errors: list[str] = []

name = metadata.get("name")
description = metadata.get("description")
version = metadata.get("version")

if claude.get("name") != name:
errors.append(
f".claude-plugin/marketplace.json `name` ({claude.get('name')!r}) "
f"must match plugin-metadata.json `name` ({name!r})."
)
if claude.get("description") != description:
errors.append(
".claude-plugin/marketplace.json `description` must match "
"plugin-metadata.json `description`."
)
claude_version = (claude.get("metadata") or {}).get("version")
if claude_version != version:
errors.append(
f".claude-plugin/marketplace.json metadata.version "
f"({claude_version!r}) must match plugin-metadata.json `version` "
f"({version!r})."
)
return errors


def build_cursor_marketplace(metadata: dict, claude: dict) -> dict:
author = metadata.get("author") or {}
owner_name = author.get("name") if isinstance(author, dict) else None

return {
"name": metadata["name"],
"owner": {"name": owner_name} if owner_name else {},
"description": metadata["description"],
"metadata": {
"description": metadata["description"],
"version": metadata["version"],
},
"plugins": claude.get("plugins", []),
}


def render_json(data: dict) -> str:
return json.dumps(data, indent=2, ensure_ascii=False) + "\n"


def write_or_check(path: Path, content: str, check: bool) -> bool:
"""Return True when the file is already up to date."""
current = path.read_text(encoding="utf-8") if path.exists() else None
if current == content:
return True
if check:
return False
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
return True


def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description="Generate .cursor-plugin/marketplace.json from the "
"canonical Claude marketplace and plugin-metadata.json."
)
parser.add_argument(
"--check",
action="store_true",
help="Validate the generated manifest is up to date without writing.",
)
args = parser.parse_args(argv)

metadata = load_json(PLUGIN_METADATA)
claude = load_json(CLAUDE_MARKETPLACE)

identity_errors = check_identity_consistency(metadata, claude)
if identity_errors:
print("Marketplace identity is inconsistent:", file=sys.stderr)
for err in identity_errors:
print(f" - {err}", file=sys.stderr)
return 1

content = render_json(build_cursor_marketplace(metadata, claude))
up_to_date = write_or_check(CURSOR_MARKETPLACE, content, check=args.check)

if args.check:
if not up_to_date:
print(
f"{CURSOR_MARKETPLACE.relative_to(ROOT)} is out of date.",
file=sys.stderr,
)
print(
"Run: uv run scripts/generate_cursor_marketplace.py",
file=sys.stderr,
)
return 1
print("Cursor marketplace manifest is up to date.")
return 0

print(f"Wrote {CURSOR_MARKETPLACE.relative_to(ROOT)}")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading
Loading