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
24 changes: 24 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "amd-skills",
"owner": {
"name": "AMD"
},
"metadata": {
"description": "Agent Skills for AMD-optimized workflows",
"version": "0.1.0"
},
"plugins": [
{
"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."
}
]
}
21 changes: 21 additions & 0 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "amd-skills",
"description": "Agent Skills for AMD-optimized workflows.",
"version": "0.1.0",
"author": {
"name": "AMD"
},
"homepage": "https://github.com/amd/skills",
"repository": "https://github.com/amd/skills",
"license": "MIT",
"keywords": [
"amd",
"rocm",
"hip",
"ryzen-ai",
"migraphx",
"vllm",
"lemonade",
"local-ai"
]
}
18 changes: 18 additions & 0 deletions .cursor-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "amd-skills",
"owner": {
"name": "AMD"
},
"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."
}
]
}
22 changes: 22 additions & 0 deletions .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "amd-skills",
"skills": "skills",
"description": "Agent Skills for AMD-optimized workflows.",
"version": "0.1.0",
"author": {
"name": "AMD"
},
"homepage": "https://github.com/amd/skills",
"repository": "https://github.com/amd/skills",
"license": "MIT",
"keywords": [
"amd",
"rocm",
"hip",
"ryzen-ai",
"migraphx",
"vllm",
"lemonade",
"local-ai"
]
}
6 changes: 4 additions & 2 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ on:
- "scripts/**"
- "**/SKILL.md"
- "skills/**"
- ".claude-plugin/**"
- ".cursor-plugin/**"
- ".github/workflows/validate.yml"
workflow_dispatch:

jobs:
validate:
name: Validate skills
name: Validate skills and plugin manifests
runs-on: ubuntu-latest
steps:
- name: Check out repository
Expand All @@ -22,5 +24,5 @@ jobs:
- name: Set up uv
uses: astral-sh/setup-uv@v7

- name: Validate skills (size + standardized format)
- name: Validate skills and generated manifests
run: ./scripts/check.sh
9 changes: 7 additions & 2 deletions AUTHORING.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,17 @@ Test the skill the way users will hit it:
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:

```bash
./scripts/check.sh # validates every skill (same command CI runs)
./scripts/check.sh # validates every skill and plugin manifests (same command CI runs)
```

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

- a `SKILL.md` file with a valid YAML frontmatter block
- `name`: lowercase-with-hyphens, ≤ 64 characters, no `anthropic` / `claude` substrings, matches the directory name
- `description`: non-empty, ≤ 1024 characters
- `SKILL.md` body: ≤ 500 lines (push longer reference material into sibling files)
- `SKILL.md` body: ≤ 500 lines

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`) (push longer reference material into sibling files)
22 changes: 13 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ Embed AMD-optimized AI into end-user applications.

| Skill | What it does |
| --- | --- |
| `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. |
| `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. |
| `local-ai-app-integration` | Integrate local AI into cloud LLM apps for offline support, better privacy, and lower API costs. |
| `local-ai-use` | Route image generation, text-to-speech, and speech-to-text through a local AI Server to reduce token/cost usage. |

### Cross-stack porting

Expand Down Expand Up @@ -151,11 +151,11 @@ scripts/ # Tooling for publishing and regenerating manifests

## Installation

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

### Cursor

Install the AMD plugin from this repository through the Cursor plugin flow. The repo ships a `.cursor-plugin/plugin.json` and an `.mcp.json` so skills are discoverable as soon as the plugin is enabled.
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.

### Claude Code

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

### Gemini CLI

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

```bash
gemini extensions install https://github.com/amd/skills.git --consent
Expand Down Expand Up @@ -201,12 +201,16 @@ Best for cross-cutting skills that do not have a natural product home.
1. Copy an existing skill folder under `skills/` as a starting point and rename it.
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.
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.
4. Register the skill in `.claude-plugin/marketplace.json` with a human-readable description.
5. Validate the skill locally before pushing:
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/check.sh # validates every SKILL.md
./scripts/publish.sh # writes .cursor-plugin/plugin.json
```
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.
6. Validate the skill locally before pushing:
```bash
./scripts/check.sh # validates every SKILL.md and that manifests are in sync
```
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.

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

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

set -euo pipefail

Expand All @@ -25,6 +19,7 @@ usage() {
case "${1:-}" in
"")
uv run scripts/validate_skills.py
uv run scripts/generate_cursor_plugin.py --check
;;
-h|--help)
usage
Expand Down
166 changes: 166 additions & 0 deletions scripts/generate_cursor_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#!/usr/bin/env -S uv run --quiet
# /// script
# requires-python = ">=3.10"
# dependencies = []
# ///
"""Generate Cursor plugin manifest from existing repo metadata.

Outputs:
- .cursor-plugin/plugin.json

Design goals:
- Keep Claude + Cursor metadata in sync.
- Reuse `.claude-plugin/plugin.json` as the primary metadata source.
- Discover skills from `skills/*/SKILL.md` so the manifest tracks the
catalog automatically.

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

from __future__ import annotations

import argparse
import json
import re
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
CLAUDE_PLUGIN_MANIFEST = ROOT / ".claude-plugin" / "plugin.json"
CURSOR_PLUGIN_DIR = ROOT / ".cursor-plugin"
CURSOR_PLUGIN_MANIFEST = CURSOR_PLUGIN_DIR / "plugin.json"

# Fields copied verbatim from the Claude plugin manifest into the Cursor
# manifest so the two stay in lock-step.
COPIED_FIELDS = (
"description",
"version",
"author",
"homepage",
"repository",
"license",
"keywords",
"logo",
)

PLUGIN_NAME_RE = re.compile(r"^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$")


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 parse_frontmatter(text: str) -> dict[str, str]:
"""Return the YAML frontmatter at the top of `text` as a flat mapping.

Only top-level scalar keys are extracted. That is sufficient for `name`,
which is all this script needs.
"""
match = re.search(r"^---\s*\n(.*?)\n---\s*", text, re.DOTALL)
if not match:
return {}
data: dict[str, str] = {}
for line in match.group(1).splitlines():
# Skip continuation lines so multi-line `description: >-` values
# don't get parsed as keys.
if ":" not in line or line.startswith((" ", "\t")):
continue
key, value = line.split(":", 1)
data[key.strip()] = value.strip()
return data


def collect_skills() -> list[str]:
skills: list[str] = []
for skill_md in sorted(ROOT.glob("skills/*/SKILL.md")):
meta = parse_frontmatter(skill_md.read_text(encoding="utf-8"))
name = meta.get("name", "").strip()
if not name:
continue
skills.append(name)
return skills


def validate_plugin_name(name: str) -> None:
if not PLUGIN_NAME_RE.match(name):
raise ValueError(
"Invalid plugin name in .claude-plugin/plugin.json: "
f"'{name}'. Must be lowercase and match {PLUGIN_NAME_RE.pattern}"
)


def build_cursor_plugin_manifest() -> dict:
src = load_json(CLAUDE_PLUGIN_MANIFEST)

name = src.get("name")
if not isinstance(name, str) or not name:
raise ValueError(".claude-plugin/plugin.json must define a non-empty 'name'")
validate_plugin_name(name)

skills = collect_skills()
if not skills:
raise ValueError("No skills discovered under skills/*/SKILL.md")

manifest: dict = {"name": name, "skills": "skills"}
for key in COPIED_FIELDS:
if key in src:
manifest[key] = src[key]

return manifest


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.

In write mode (check=False) the file is written first, so the return
value is always True in that branch.
"""
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() -> None:
parser = argparse.ArgumentParser(
description="Generate Cursor plugin manifest from .claude-plugin/plugin.json"
)
parser.add_argument(
"--check",
action="store_true",
help="Validate the generated manifest is up to date without writing changes.",
)
args = parser.parse_args()

plugin_manifest = render_json(build_cursor_plugin_manifest())
ok_plugin = write_or_check(CURSOR_PLUGIN_MANIFEST, plugin_manifest, check=args.check)

if args.check:
if not ok_plugin:
print("Generated Cursor manifest is out of date:", file=sys.stderr)
print(f" - {CURSOR_PLUGIN_MANIFEST.relative_to(ROOT)}", file=sys.stderr)
print("Run: uv run scripts/generate_cursor_plugin.py", file=sys.stderr)
sys.exit(1)

print("Cursor plugin manifest is up to date.")
return

print(f"Wrote {CURSOR_PLUGIN_MANIFEST.relative_to(ROOT)}")


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