chore: make skill listing generic (#39)

This commit is contained in:
Josh McKinney
2026-01-08 16:21:33 -08:00
committed by GitHub
parent d3966156c8
commit e911ae9feb
2 changed files with 19 additions and 13 deletions
+7 -5
View File
@@ -7,10 +7,10 @@ metadata:
# Skill Installer
Helps install skills. By default these are from https://github.com/openai/skills/tree/main/skills/.curated, but users can also provide other locations.
Helps install skills. By default these are from https://github.com/openai/skills/tree/main/skills/.curated, but users can also provide other locations. Experimental skills live in https://github.com/openai/skills/tree/main/skills/.experimental and can be installed the same way.
Use the helper scripts based on the task:
- List curated skills when the user asks what is available, or if the user uses this skill without specifying what to do.
- List skills when the user asks what is available, or if the user uses this skill without specifying what to do. Default listing is `.curated`, but you can pass `--path skills/.experimental` when they ask about experimental skills.
- Install from the curated list when the user provides a skill name.
- Install from another repo when the user provides a GitHub repo/path (including private repos).
@@ -18,7 +18,7 @@ Install skills with the helper scripts.
## Communication
When listing curated skills, output approximately as follows, depending on the context of the user's request:
When listing skills, output approximately as follows, depending on the context of the user's request. If they ask about experimental skills, list from `.experimental` instead of `.curated` and label the source accordingly:
"""
Skills from {repo}:
1. skill-1
@@ -33,10 +33,12 @@ After installing a skill, tell the user: "Restart Codex to pick up new skills."
All of these scripts use network, so when running in the sandbox, request escalation when running them.
- `scripts/list-curated-skills.py` (prints curated list with installed annotations)
- `scripts/list-curated-skills.py --format json`
- `scripts/list-skills.py` (prints skills list with installed annotations)
- `scripts/list-skills.py --format json`
- Example (experimental list): `scripts/list-skills.py --path skills/.experimental`
- `scripts/install-skill-from-github.py --repo <owner>/<repo> --path <path/to/skill> [<path/to/skill> ...]`
- `scripts/install-skill-from-github.py --url https://github.com/<owner>/<repo>/tree/<ref>/<path>`
- Example (experimental skill): `scripts/install-skill-from-github.py --repo openai/skills --path skills/.experimental/<skill-name>`
## Behavior and Options
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""List curated skills from a GitHub repo path."""
"""List skills from a GitHub repo path."""
from __future__ import annotations
@@ -47,28 +47,32 @@ def _installed_skills() -> set[str]:
return entries
def _list_curated(repo: str, path: str, ref: str) -> list[str]:
def _list_skills(repo: str, path: str, ref: str) -> list[str]:
api_url = github_api_contents_url(repo, path, ref)
try:
payload = _request(api_url)
except urllib.error.HTTPError as exc:
if exc.code == 404:
raise ListError(
"Curated skills path not found: "
"Skills path not found: "
f"https://github.com/{repo}/tree/{ref}/{path}"
) from exc
raise ListError(f"Failed to fetch curated skills: HTTP {exc.code}") from exc
raise ListError(f"Failed to fetch skills: HTTP {exc.code}") from exc
data = json.loads(payload.decode("utf-8"))
if not isinstance(data, list):
raise ListError("Unexpected curated listing response.")
raise ListError("Unexpected skills listing response.")
skills = [item["name"] for item in data if item.get("type") == "dir"]
return sorted(skills)
def _parse_args(argv: list[str]) -> Args:
parser = argparse.ArgumentParser(description="List curated skills.")
parser = argparse.ArgumentParser(description="List skills.")
parser.add_argument("--repo", default=DEFAULT_REPO)
parser.add_argument("--path", default=DEFAULT_PATH)
parser.add_argument(
"--path",
default=DEFAULT_PATH,
help="Repo path to list (default: skills/.curated)",
)
parser.add_argument("--ref", default=DEFAULT_REF)
parser.add_argument(
"--format",
@@ -82,7 +86,7 @@ def _parse_args(argv: list[str]) -> Args:
def main(argv: list[str]) -> int:
args = _parse_args(argv)
try:
skills = _list_curated(args.repo, args.path, args.ref)
skills = _list_skills(args.repo, args.path, args.ref)
installed = _installed_skills()
if args.format == "json":
payload = [