mirror of
https://github.com/openai/codex.git
synced 2026-02-01 22:47:52 +00:00
Sync system skills from public repo (#10320)
Syncs the system skills included in Codex with the updates in https://github.com/openai/skills/tree/main/skills/.system
This commit is contained in:
@@ -11,7 +11,7 @@ This skill provides guidance for creating effective skills.
|
||||
|
||||
## About Skills
|
||||
|
||||
Skills are modular, self-contained packages that extend Codex's capabilities by providing
|
||||
Skills are modular, self-contained folders that extend Codex's capabilities by providing
|
||||
specialized knowledge, workflows, and tools. Think of them as "onboarding guides" for specific
|
||||
domains or tasks—they transform Codex from a general-purpose agent into a specialized agent
|
||||
equipped with procedural knowledge that no model can fully possess.
|
||||
@@ -56,6 +56,8 @@ skill-name/
|
||||
│ │ ├── name: (required)
|
||||
│ │ └── description: (required)
|
||||
│ └── Markdown instructions (required)
|
||||
├── agents/ (recommended)
|
||||
│ └── openai.yaml - UI metadata for skill lists and chips
|
||||
└── Bundled Resources (optional)
|
||||
├── scripts/ - Executable code (Python/Bash/etc.)
|
||||
├── references/ - Documentation intended to be loaded into context as needed
|
||||
@@ -69,6 +71,16 @@ Every SKILL.md consists of:
|
||||
- **Frontmatter** (YAML): Contains `name` and `description` fields. These are the only fields that Codex reads to determine when the skill gets used, thus it is very important to be clear and comprehensive in describing what the skill is, and when it should be used.
|
||||
- **Body** (Markdown): Instructions and guidance for using the skill. Only loaded AFTER the skill triggers (if at all).
|
||||
|
||||
#### Agents metadata (recommended)
|
||||
|
||||
- UI-facing metadata for skill lists and chips
|
||||
- Read references/openai_yaml.md before generating values and follow its descriptions and constraints
|
||||
- Create: human-facing `display_name`, `short_description`, and `default_prompt` by reading the skill
|
||||
- Generate deterministically by passing the values as `--interface key=value` to `scripts/generate_openai_yaml.py` or `scripts/init_skill.py`
|
||||
- On updates: validate `agents/openai.yaml` still matches SKILL.md; regenerate if stale
|
||||
- Only include other optional interface fields (icons, brand color) if explicitly provided
|
||||
- See references/openai_yaml.md for field definitions and examples
|
||||
|
||||
#### Bundled Resources (optional)
|
||||
|
||||
##### Scripts (`scripts/`)
|
||||
@@ -208,7 +220,7 @@ Skill creation involves these steps:
|
||||
2. Plan reusable skill contents (scripts, references, assets)
|
||||
3. Initialize the skill (run init_skill.py)
|
||||
4. Edit the skill (implement resources and write SKILL.md)
|
||||
5. Package the skill (run package_skill.py)
|
||||
5. Validate the skill (run quick_validate.py)
|
||||
6. Iterate based on real usage
|
||||
|
||||
Follow these steps in order, skipping only if there is a clear reason why they are not applicable.
|
||||
@@ -266,7 +278,7 @@ To establish the skill's contents, analyze each concrete example to create a lis
|
||||
|
||||
At this point, it is time to actually create the skill.
|
||||
|
||||
Skip this step only if the skill being developed already exists, and iteration or packaging is needed. In this case, continue to the next step.
|
||||
Skip this step only if the skill being developed already exists. In this case, continue to the next step.
|
||||
|
||||
When creating a new skill from scratch, always run the `init_skill.py` script. The script conveniently generates a new template skill directory that automatically includes everything a skill requires, making the skill creation process much more efficient and reliable.
|
||||
|
||||
@@ -288,11 +300,20 @@ The script:
|
||||
|
||||
- Creates the skill directory at the specified path
|
||||
- Generates a SKILL.md template with proper frontmatter and TODO placeholders
|
||||
- Creates `agents/openai.yaml` using agent-generated `display_name`, `short_description`, and `default_prompt` passed via `--interface key=value`
|
||||
- Optionally creates resource directories based on `--resources`
|
||||
- Optionally adds example files when `--examples` is set
|
||||
|
||||
After initialization, customize the SKILL.md and add resources as needed. If you used `--examples`, replace or delete placeholder files.
|
||||
|
||||
Generate `display_name`, `short_description`, and `default_prompt` by reading the skill, then pass them as `--interface key=value` to `init_skill.py` or regenerate with:
|
||||
|
||||
```bash
|
||||
scripts/generate_openai_yaml.py <path/to/skill-folder> --interface key=value
|
||||
```
|
||||
|
||||
Only include other optional interface fields when the user explicitly provides them. For full field descriptions and examples, see references/openai_yaml.md.
|
||||
|
||||
### Step 4: Edit the Skill
|
||||
|
||||
When editing the (newly-generated or existing) skill, remember that the skill is being created for another instance of Codex to use. Include information that would be beneficial and non-obvious to Codex. Consider what procedural knowledge, domain-specific details, or reusable assets would help another Codex instance execute these tasks more effectively.
|
||||
@@ -328,40 +349,21 @@ Write the YAML frontmatter with `name` and `description`:
|
||||
- Include all "when to use" information here - Not in the body. The body is only loaded after triggering, so "When to Use This Skill" sections in the body are not helpful to Codex.
|
||||
- Example description for a `docx` skill: "Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. Use when Codex needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks"
|
||||
|
||||
Ensure the frontmatter is valid YAML. Keep `name` and `description` as single-line scalars. If either could be interpreted as YAML syntax, wrap it in quotes.
|
||||
|
||||
Do not include any other fields in YAML frontmatter.
|
||||
|
||||
##### Body
|
||||
|
||||
Write instructions for using the skill and its bundled resources.
|
||||
|
||||
### Step 5: Packaging a Skill
|
||||
### Step 5: Validate the Skill
|
||||
|
||||
Once development of the skill is complete, it must be packaged into a distributable .skill file that gets shared with the user. The packaging process automatically validates the skill first to ensure it meets all requirements:
|
||||
Once development of the skill is complete, validate the skill folder to catch basic issues early:
|
||||
|
||||
```bash
|
||||
scripts/package_skill.py <path/to/skill-folder>
|
||||
scripts/quick_validate.py <path/to/skill-folder>
|
||||
```
|
||||
|
||||
Optional output directory specification:
|
||||
|
||||
```bash
|
||||
scripts/package_skill.py <path/to/skill-folder> ./dist
|
||||
```
|
||||
|
||||
The packaging script will:
|
||||
|
||||
1. **Validate** the skill automatically, checking:
|
||||
|
||||
- YAML frontmatter format and required fields
|
||||
- Skill naming conventions and directory structure
|
||||
- Description completeness and quality
|
||||
- File organization and resource references
|
||||
|
||||
2. **Package** the skill if validation passes, creating a .skill file named after the skill (e.g., `my-skill.skill`) that includes all files and maintains the proper directory structure for distribution. The .skill file is a zip file with a .skill extension.
|
||||
|
||||
If validation fails, the script will report the errors and exit without creating a package. Fix any validation errors and run the packaging command again.
|
||||
The validation script checks YAML frontmatter format, required fields, and naming rules. If validation fails, fix the reported issues and run the command again.
|
||||
|
||||
### Step 6: Iterate
|
||||
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
Skill Initializer - Creates a new skill from template
|
||||
|
||||
Usage:
|
||||
init_skill.py <skill-name> --path <path> [--resources scripts,references,assets] [--examples]
|
||||
init_skill.py <skill-name> --path <path> [--resources scripts,references,assets] [--examples] [--interface key=value]
|
||||
|
||||
Examples:
|
||||
init_skill.py my-new-skill --path skills/public
|
||||
init_skill.py my-new-skill --path skills/public --resources scripts,references
|
||||
init_skill.py my-api-helper --path skills/private --resources scripts --examples
|
||||
init_skill.py custom-skill --path /custom/location
|
||||
init_skill.py my-skill --path skills/public --interface short_description="Short UI label"
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -17,6 +18,8 @@ import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from generate_openai_yaml import write_openai_yaml
|
||||
|
||||
MAX_SKILL_NAME_LENGTH = 64
|
||||
ALLOWED_RESOURCES = {"scripts", "references", "assets"}
|
||||
|
||||
@@ -252,7 +255,7 @@ def create_resource_dirs(skill_dir, skill_name, skill_title, resources, include_
|
||||
print("[OK] Created assets/")
|
||||
|
||||
|
||||
def init_skill(skill_name, path, resources, include_examples):
|
||||
def init_skill(skill_name, path, resources, include_examples, interface_overrides):
|
||||
"""
|
||||
Initialize a new skill directory with template SKILL.md.
|
||||
|
||||
@@ -293,6 +296,15 @@ def init_skill(skill_name, path, resources, include_examples):
|
||||
print(f"[ERROR] Error creating SKILL.md: {e}")
|
||||
return None
|
||||
|
||||
# Create agents/openai.yaml
|
||||
try:
|
||||
result = write_openai_yaml(skill_dir, skill_name, interface_overrides)
|
||||
if not result:
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Error creating agents/openai.yaml: {e}")
|
||||
return None
|
||||
|
||||
# Create resource directories if requested
|
||||
if resources:
|
||||
try:
|
||||
@@ -312,7 +324,8 @@ def init_skill(skill_name, path, resources, include_examples):
|
||||
print("2. Add resources to scripts/, references/, and assets/ as needed")
|
||||
else:
|
||||
print("2. Create resource directories only if needed (scripts/, references/, assets/)")
|
||||
print("3. Run the validator when ready to check the skill structure")
|
||||
print("3. Update agents/openai.yaml if the UI metadata should differ")
|
||||
print("4. Run the validator when ready to check the skill structure")
|
||||
|
||||
return skill_dir
|
||||
|
||||
@@ -333,6 +346,12 @@ def main():
|
||||
action="store_true",
|
||||
help="Create example files inside the selected resource directories",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--interface",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Interface override in key=value format (repeatable)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
raw_skill_name = args.skill_name
|
||||
@@ -366,7 +385,7 @@ def main():
|
||||
print(" Resources: none (create as needed)")
|
||||
print()
|
||||
|
||||
result = init_skill(skill_name, path, resources, args.examples)
|
||||
result = init_skill(skill_name, path, resources, args.examples, args.interface)
|
||||
|
||||
if result:
|
||||
sys.exit(0)
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Skill Packager - Creates a distributable .skill file of a skill folder
|
||||
|
||||
Usage:
|
||||
python utils/package_skill.py <path/to/skill-folder> [output-directory]
|
||||
|
||||
Example:
|
||||
python utils/package_skill.py skills/public/my-skill
|
||||
python utils/package_skill.py skills/public/my-skill ./dist
|
||||
"""
|
||||
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from quick_validate import validate_skill
|
||||
|
||||
|
||||
def package_skill(skill_path, output_dir=None):
|
||||
"""
|
||||
Package a skill folder into a .skill file.
|
||||
|
||||
Args:
|
||||
skill_path: Path to the skill folder
|
||||
output_dir: Optional output directory for the .skill file (defaults to current directory)
|
||||
|
||||
Returns:
|
||||
Path to the created .skill file, or None if error
|
||||
"""
|
||||
skill_path = Path(skill_path).resolve()
|
||||
|
||||
# Validate skill folder exists
|
||||
if not skill_path.exists():
|
||||
print(f"[ERROR] Skill folder not found: {skill_path}")
|
||||
return None
|
||||
|
||||
if not skill_path.is_dir():
|
||||
print(f"[ERROR] Path is not a directory: {skill_path}")
|
||||
return None
|
||||
|
||||
# Validate SKILL.md exists
|
||||
skill_md = skill_path / "SKILL.md"
|
||||
if not skill_md.exists():
|
||||
print(f"[ERROR] SKILL.md not found in {skill_path}")
|
||||
return None
|
||||
|
||||
# Run validation before packaging
|
||||
print("Validating skill...")
|
||||
valid, message = validate_skill(skill_path)
|
||||
if not valid:
|
||||
print(f"[ERROR] Validation failed: {message}")
|
||||
print(" Please fix the validation errors before packaging.")
|
||||
return None
|
||||
print(f"[OK] {message}\n")
|
||||
|
||||
# Determine output location
|
||||
skill_name = skill_path.name
|
||||
if output_dir:
|
||||
output_path = Path(output_dir).resolve()
|
||||
output_path.mkdir(parents=True, exist_ok=True)
|
||||
else:
|
||||
output_path = Path.cwd()
|
||||
|
||||
skill_filename = output_path / f"{skill_name}.skill"
|
||||
|
||||
# Create the .skill file (zip format)
|
||||
try:
|
||||
with zipfile.ZipFile(skill_filename, "w", zipfile.ZIP_DEFLATED) as zipf:
|
||||
# Walk through the skill directory
|
||||
for file_path in skill_path.rglob("*"):
|
||||
if file_path.is_file():
|
||||
# Calculate the relative path within the zip
|
||||
arcname = file_path.relative_to(skill_path.parent)
|
||||
zipf.write(file_path, arcname)
|
||||
print(f" Added: {arcname}")
|
||||
|
||||
print(f"\n[OK] Successfully packaged skill to: {skill_filename}")
|
||||
return skill_filename
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Error creating .skill file: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python utils/package_skill.py <path/to/skill-folder> [output-directory]")
|
||||
print("\nExample:")
|
||||
print(" python utils/package_skill.py skills/public/my-skill")
|
||||
print(" python utils/package_skill.py skills/public/my-skill ./dist")
|
||||
sys.exit(1)
|
||||
|
||||
skill_path = sys.argv[1]
|
||||
output_dir = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
|
||||
print(f"Packaging skill: {skill_path}")
|
||||
if output_dir:
|
||||
print(f" Output directory: {output_dir}")
|
||||
print()
|
||||
|
||||
result = package_skill(skill_path, output_dir)
|
||||
|
||||
if result:
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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,103 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""List curated skills from a GitHub repo path."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.error
|
||||
|
||||
from github_utils import github_api_contents_url, github_request
|
||||
|
||||
DEFAULT_REPO = "openai/skills"
|
||||
DEFAULT_PATH = "skills/.curated"
|
||||
DEFAULT_REF = "main"
|
||||
|
||||
|
||||
class ListError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Args(argparse.Namespace):
|
||||
repo: str
|
||||
path: str
|
||||
ref: str
|
||||
format: str
|
||||
|
||||
|
||||
def _request(url: str) -> bytes:
|
||||
return github_request(url, "codex-skill-list")
|
||||
|
||||
|
||||
def _codex_home() -> str:
|
||||
return os.environ.get("CODEX_HOME", os.path.expanduser("~/.codex"))
|
||||
|
||||
|
||||
def _installed_skills() -> set[str]:
|
||||
root = os.path.join(_codex_home(), "skills")
|
||||
if not os.path.isdir(root):
|
||||
return set()
|
||||
entries = set()
|
||||
for name in os.listdir(root):
|
||||
path = os.path.join(root, name)
|
||||
if os.path.isdir(path):
|
||||
entries.add(name)
|
||||
return entries
|
||||
|
||||
|
||||
def _list_curated(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: "
|
||||
f"https://github.com/{repo}/tree/{ref}/{path}"
|
||||
) from exc
|
||||
raise ListError(f"Failed to fetch curated skills: HTTP {exc.code}") from exc
|
||||
data = json.loads(payload.decode("utf-8"))
|
||||
if not isinstance(data, list):
|
||||
raise ListError("Unexpected curated 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.add_argument("--repo", default=DEFAULT_REPO)
|
||||
parser.add_argument("--path", default=DEFAULT_PATH)
|
||||
parser.add_argument("--ref", default=DEFAULT_REF)
|
||||
parser.add_argument(
|
||||
"--format",
|
||||
choices=["text", "json"],
|
||||
default="text",
|
||||
help="Output format",
|
||||
)
|
||||
return parser.parse_args(argv, namespace=Args())
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
args = _parse_args(argv)
|
||||
try:
|
||||
skills = _list_curated(args.repo, args.path, args.ref)
|
||||
installed = _installed_skills()
|
||||
if args.format == "json":
|
||||
payload = [
|
||||
{"name": name, "installed": name in installed} for name in skills
|
||||
]
|
||||
print(json.dumps(payload))
|
||||
else:
|
||||
for idx, name in enumerate(skills, start=1):
|
||||
suffix = " (already installed)" if name in installed else ""
|
||||
print(f"{idx}. {name}{suffix}")
|
||||
return 0
|
||||
except ListError as exc:
|
||||
print(f"Error: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
Reference in New Issue
Block a user