
Codex Cli Specialist
- 123 installs
- 451 repo stars
- Updated July 21, 2026
- borghei/claude-skills
Configure and operate Codex CLI for agentic coding sessions, prompt workflows, repo context, and repeatable terminal-driven development tasks.
About
Specializes in OpenAI Codex CLI for agentic software development. Helps configure CLI sessions, structure prompts, leverage repository context, and run repeatable coding automations so developers integrate Codex effectively into terminal-based build workflows.
- Covers Codex CLI setup and invocation patterns
- Optimizes repo-aware agent coding sessions
- Guides prompt and context strategies for CLI agents
- Supports repeatable terminal automation flows
- Bridges local dev habits with agent execution
Codex Cli Specialist by the numbers
- 123 all-time installs (skills.sh)
- Ranked #3,795 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/borghei/claude-skills --skill codex-cli-specialistAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 123 |
|---|---|
| repo stars | ★ 451 |
| Last updated | July 21, 2026 |
| Repository | borghei/claude-skills ↗ |
What it does
Configure and operate Codex CLI for agentic coding sessions, prompt workflows, repo context, and repeatable terminal-driven development tasks.
Files
Codex CLI Specialist
The agent converts Claude Code skills to Codex-compatible format, validates cross-platform compatibility, and builds skill registry manifests. It generates agents/openai.yaml configurations from SKILL.md frontmatter, runs 17 compatibility checks across both platforms, and produces skills-index.json for discovery systems.
Table of Contents
- Quick Start
- Tools Overview
- Core Workflows
- Codex CLI Configuration Deep Dive
- Cross-Platform Skill Patterns
- Skill Installation and Management
- Integration Points
- Best Practices
- Reference Documentation
- Common Patterns Quick Reference
---
Quick Start
# Install Codex CLI
npm install -g @openai/codex
# Verify installation
codex --version
# Convert an existing Claude Code skill to Codex format
python scripts/codex_skill_converter.py path/to/SKILL.md --output-dir ./converted
# Validate a skill works on both Claude Code and Codex
python scripts/cross_platform_validator.py path/to/skill-dir
# Build a skills index from a directory of skills
python scripts/skills_index_builder.py /path/to/skills --output skills-index.json---
Tools Overview
1. Codex Skill Converter
Converts a Claude Code SKILL.md into Codex-compatible format by generating an agents/openai.yaml configuration and restructuring metadata.
Input: Path to a Claude Code SKILL.md file Output: Codex-compatible skill directory with agents/openai.yaml
Usage:
# Convert a single skill
python scripts/codex_skill_converter.py my-skill/SKILL.md
# Specify output directory
python scripts/codex_skill_converter.py my-skill/SKILL.md --output-dir ./codex-skills/my-skill
# JSON output for automation
python scripts/codex_skill_converter.py my-skill/SKILL.md --jsonWhat it does:
- Parses YAML frontmatter from SKILL.md
- Extracts name, description, and metadata
- Generates agents/openai.yaml with proper schema
- Copies scripts, references, and assets
- Reports conversion status and any warnings
---
2. Cross-Platform Validator
Validates that a skill directory is compatible with both Claude Code and Codex CLI environments.
Input: Path to a skill directory Output: Validation report with pass/fail status and recommendations
Usage:
# Validate a skill directory
python scripts/cross_platform_validator.py my-skill/
# Strict mode - treat warnings as errors
python scripts/cross_platform_validator.py my-skill/ --strict
# JSON output
python scripts/cross_platform_validator.py my-skill/ --jsonChecks performed:
- SKILL.md exists and has valid YAML frontmatter
- Required frontmatter fields present (name, description)
- Description uses third-person format for auto-discovery
- agents/openai.yaml exists and is valid YAML
- scripts/ directory contains executable Python files
- No external dependencies beyond standard library
- File structure matches expected patterns
---
3. Skills Index Builder
Builds a skills-index.json manifest from a directory of skills, useful for skill registries and discovery systems.
Input: Path to a directory containing skill subdirectories Output: JSON manifest with skill metadata
Usage:
# Build index from skills directory
python scripts/skills_index_builder.py /path/to/skills
# Custom output file
python scripts/skills_index_builder.py /path/to/skills --output my-index.json
# Human-readable output
python scripts/skills_index_builder.py /path/to/skills --format human
# Include only specific categories
python scripts/skills_index_builder.py /path/to/skills --category engineeringOutput includes:
- Skill name, description, version
- Available scripts and tools
- Category and domain classification
- File counts and sizes
- Platform compatibility flags
---
Core Workflows
Workflow 1: Install and Configure Codex CLI
Step 1: Install Codex CLI
# Install globally via npm
npm install -g @openai/codex
# Verify installation
codex --version
codex --helpStep 2: Configure API access
# Set your OpenAI API key
export OPENAI_API_KEY="sk-..."
# Or configure via the CLI
codex configureStep 3: Choose an approval mode and run
# suggest (default) - you approve each change
codex --approval-mode suggest "refactor the auth module"
# auto-edit - auto-applies file edits, asks before shell commands
codex --approval-mode auto-edit "add input validation"
# full-auto - fully autonomous (use in sandboxed environments)
codex --approval-mode full-auto "set up test infrastructure"---
Workflow 2: Author a Codex Skill from Scratch
Step 1: Create directory structure
mkdir -p my-skill/agents
mkdir -p my-skill/scripts
mkdir -p my-skill/references
mkdir -p my-skill/assetsStep 2: Write SKILL.md with compatible frontmatter
---
name: my-skill
description: This skill should be used when the user asks to "do X",
"perform Y", or "analyze Z". Use for domain expertise, automation,
and best practice enforcement.
license: MIT + Commons Clause
metadata:
version: 1.0.0
category: engineering
domain: development-tools
---
# My Skill
Description and workflows here...Step 3: Create agents/openai.yaml
# Use the template from assets/openai-yaml-template.yaml
name: my-skill
description: >
Expert guidance for X, Y, and Z.
instructions: |
You are an expert at X. When the user asks about Y,
follow these steps...
tools:
- name: my_tool
description: Runs the my_tool.py script
command: python scripts/my_tool.pyStep 4: Add Python tools
# Create your script
touch my-skill/scripts/my_tool.py
chmod +x my-skill/scripts/my_tool.pyStep 5: Validate the skill
python cross_platform_validator.py my-skill/---
Workflow 3: Convert Claude Code Skills to Codex
Step 1: Identify skills to convert
# List all skills in a directory
find engineering/ -name "SKILL.md" -type fStep 2: Run the converter
# Convert a single skill
python scripts/codex_skill_converter.py engineering/code-reviewer/SKILL.md \
--output-dir ./codex-ready/code-reviewer
# Batch convert (shell loop)
for skill_md in engineering/*/SKILL.md; do
skill_name=$(basename $(dirname "$skill_md"))
python scripts/codex_skill_converter.py "$skill_md" \
--output-dir "./codex-ready/$skill_name"
doneStep 3: Review and adjust generated openai.yaml
The converter generates a baseline agents/openai.yaml. Review it for:
- Accuracy of the instructions field
- Completeness of the tools list
- Correct command paths for scripts
Step 4: Validate the converted skill
python scripts/cross_platform_validator.py ./codex-ready/code-reviewer---
Workflow 4: Validate Cross-Platform Compatibility
# Run validator on a skill (outputs PASS/WARN/FAIL for each check)
python scripts/cross_platform_validator.py my-skill/
# Strict mode (warnings become errors)
python scripts/cross_platform_validator.py my-skill/ --strict --jsonThe validator checks both Claude Code compatibility (SKILL.md, frontmatter, scripts) and Codex CLI compatibility (agents/openai.yaml, tool references), plus cross-platform checks (UTF-8 encoding, skill size, name consistency).
---
Workflow 5: Build and Publish a Skills Index
# Build index from a directory of skills
python scripts/skills_index_builder.py ./engineering --output skills-index.json
# Human-readable summary
python scripts/skills_index_builder.py ./engineering --format human---
Codex CLI Configuration Deep Dive
agents/openai.yaml Structure
The agents/openai.yaml file is the primary configuration for Codex CLI skills. It tells Codex how to discover, describe, and invoke the skill.
# Required fields
name: skill-name # Unique identifier (kebab-case)
description: > # What the skill does (for discovery)
Expert guidance for X. Analyzes Y and generates Z.
# Instructions define the skill's behavior
instructions: |
You are a senior X specialist. When the user asks about Y:
1. First, analyze the context
2. Then, apply framework Z
3. Finally, produce output in format W
Always follow these principles:
- Principle A
- Principle B
# Tools expose scripts to the agent
tools:
- name: tool_name # Tool identifier (snake_case)
description: > # When to use this tool
Analyzes X and produces Y report
command: python scripts/tool.py # Execution command
args: # Optional: define accepted arguments
- name: input_path
description: Path to input file
required: true
- name: output_format
description: Output format (json or text)
required: false
default: text
# Optional metadata
model: o4-mini # Preferred model
version: 1.0.0 # Skill versionSkill Discovery and Locations
Codex CLI discovers skills from these locations (in priority order):
1. Project-local: .codex/skills/ in the current working directory 2. User-global: ~/.codex/skills/ for user-wide skills 3. System-wide: /usr/local/share/codex/skills/ (rare, admin-managed) 4. Registry: Remote skills index (when configured)
Precedence rule: Project-local overrides user-global overrides system-wide.
# Install a skill locally to a project
cp -r my-skill/ .codex/skills/my-skill/
# Install globally for all projects
cp -r my-skill/ ~/.codex/skills/my-skill/Invocation Patterns
# Direct invocation by name
codex --skill code-reviewer "review the latest PR"
# Codex auto-discovers relevant skills from context
codex "analyze code quality of the auth module"
# Chain with specific approval mode
codex --approval-mode auto-edit --skill senior-fullstack \
"scaffold a Next.js app with GraphQL"
# Pass files as context
codex --skill code-reviewer --file src/auth.ts "review this file"---
Cross-Platform Skill Patterns
Shared Structure Convention
A skill that works on both Claude Code and Codex CLI follows this layout:
my-skill/
├── SKILL.md # Claude Code reads this (primary documentation)
├── agents/
│ └── openai.yaml # Codex CLI reads this (agent configuration)
├── scripts/ # Shared - both platforms execute these
│ ├── tool_a.py
│ └── tool_b.py
├── references/ # Shared - knowledge base
│ └── guide.md
└── assets/ # Shared - templates and resources
└── template.yamlKey insight: SKILL.md and agents/openai.yaml serve the same purpose (skill definition) for different platforms. The scripts/, references/, and assets/ directories are fully shared.
Frontmatter Compatibility
Claude Code and Codex use different frontmatter fields. A cross-platform SKILL.md should include all relevant fields:
---
# Claude Code fields (required)
name: my-skill
description: This skill should be used when the user asks to "do X"...
# Extended metadata (optional, used by both)
license: MIT + Commons Clause
metadata:
version: 1.0.0
category: engineering
domain: development-tools
# Codex-specific hints (optional, ignored by Claude Code)
codex:
model: o4-mini
approval_mode: suggest
---Dual-Target Skill Layout
When writing instructions in SKILL.md, structure them so they work regardless of platform:
1. Use standard markdown - both platforms parse markdown well 2. Reference scripts by relative path - scripts/tool.py works everywhere 3. Show both invocation patterns - document Claude Code natural language and Codex CLI command-line usage side by side
---
Skill Installation and Management
Installing Skills Locally
# Clone a skill into your project
git clone https://github.com/org/skills-repo.git /tmp/skills
cp -r /tmp/skills/code-reviewer .codex/skills/code-reviewer
# Or use a git submodule for version tracking
git submodule add https://github.com/org/skills-repo.git .codex/skills-repoManaging and Versioning Skills
# List installed skills
ls -d .codex/skills/*/
# Update all skills from source
cd .codex/skills-repo && git pull origin mainUse skills-index.json for version pinning across team members. The index builder tool generates this manifest automatically.
---
Integration Points
Syncing Skills Between Claude Code and Codex
Strategy 1: Shared repository (recommended) - Keep all skills in one repo with both SKILL.md and agents/openai.yaml. Both platforms read from the same source.
Strategy 2: CI/CD conversion - Maintain Claude Code skills as source of truth. Use a GitHub Actions workflow that triggers on **/SKILL.md changes to auto-run codex_skill_converter.py and commit the generated agents/openai.yaml files.
Strategy 3: Git hooks - Add a pre-commit hook that detects modified SKILL.md files and regenerates agents/openai.yaml automatically before each commit.
CI/CD for Skill Libraries
Add a validation workflow that runs cross_platform_validator.py --strict --json on all skill directories during push/PR, and uses skills_index_builder.py to generate and upload an updated skills-index.json artifact.
GitHub-Based Skill Distribution
# Tag, build index, and create release
git tag v1.0.0 && git push origin v1.0.0
python skills_index_builder.py . --output skills-index.json
gh release create v1.0.0 skills-index.json --title "Skills v1.0.0"---
Best Practices
Skill Authoring
1. Keep descriptions discovery-friendly - Use third-person, keyword-rich descriptions that start with "This skill should be used when..." 2. One skill, one concern - Each skill should cover a coherent domain, not an entire discipline 3. Scripts use standard library only - No pip install requirements for core functionality 4. Include both SKILL.md and agents/openai.yaml - Makes the skill usable on any platform immediately 5. Test scripts independently - Every Python tool should work standalone via python script.py --help
Codex CLI Usage
1. Start with suggest mode - Use --approval-mode suggest until you trust the skill 2. Scope skill contexts narrowly - Pass specific files with --file instead of entire directories 3. Use project-local skills - Avoid global installation for project-specific skills 4. Pin versions in teams - Use skills-index.json for version consistency across team members 5. Review generated configs - Always review auto-generated agents/openai.yaml before deploying
Cross-Platform Compatibility
1. Relative paths everywhere - Scripts reference scripts/, references/, assets/ with relative paths 2. No shell-specific syntax - Avoid bash-isms in scripts; stick to Python for portability 3. Standard YAML only - No YAML extensions or anchors that might confuse parsers 4. UTF-8 encoding - All files should be UTF-8 encoded 5. Unix line endings - Use LF, not CRLF (configure .gitattributes)
Performance
1. Keep skills small - Under 1MB total for fast loading and distribution 2. Minimize reference files - Include only essential knowledge, not entire docs 3. Lazy-load expensive tools - Split heavy scripts into separate files 4. Cache tool outputs - Use --json output for piping into other tools
---
Reference Documentation
| Resource | Location | Description |
|---|---|---|
| Codex CLI Guide | references/codex-cli-guide.md | Installation, configuration, features |
| Cross-Platform Skills | references/cross-platform-skills.md | Multi-agent compatibility guide |
| openai.yaml Template | assets/openai-yaml-template.yaml | Ready-to-use Codex config template |
---
Common Patterns Quick Reference
Pattern: Quick Skill Conversion
# One-liner: convert and validate
python scripts/codex_skill_converter.py skill/SKILL.md && \
python scripts/cross_platform_validator.py skill/Pattern: Batch Validation
# Validate all skills in a directory
for d in */; do
[ -f "$d/SKILL.md" ] && python scripts/cross_platform_validator.py "$d"
donePattern: Generate Index for Registry
python scripts/skills_index_builder.py . --output skills-index.json --format jsonPattern: Codex Quick Task
# Run a quick task with a skill
codex --approval-mode auto-edit --skill codex-cli-specialist \
"convert all skills in engineering/ to Codex format"Pattern: Minimal Codex Skill
# agents/openai.yaml - absolute minimum
name: my-skill
description: Does X for Y
instructions: You are an expert at X. Help the user with Y.Pattern: Full-Featured Codex Skill
See the complete production-grade template at assets/openai-yaml-template.yaml, which includes instructions, tools, model selection, and versioning.
---
Anti-Patterns
- Converting without reviewing -- auto-generated
agents/openai.yamlneeds human review for instruction accuracy and tool command paths - Global skill installation -- project-specific skills should stay in
.codex/skills/, not~/.codex/skills/, to avoid version conflicts across projects - Duplicating logic in SKILL.md and openai.yaml -- keep
SKILL.mdas source of truth;openai.yamlshould reference shared scripts, not rewrite instructions - Shell-specific syntax in scripts -- bash-isms break on Windows; stick to Python for all automation logic
- Ignoring strict validation warnings -- optional directories (
references/,assets/) that are missing degrade skill quality even if not required - Skipping version pinning -- teams without
skills-index.jsonversion pinning get inconsistent behavior across members
---
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
Converter produces empty instructions field | SKILL.md has no ## Best Practices or ### Workflow headings for the parser to extract | Add clearly labeled ### Workflow N: and ## Best Practices sections with bulleted items in the source SKILL.md |
| Validator fails with "No valid YAML frontmatter" | SKILL.md does not start with --- on the very first line, or the closing --- delimiter is missing | Ensure the file begins with --- on line 1, followed by frontmatter fields, followed by a closing --- line with no leading whitespace |
agents/openai.yaml tool references show "missing script" error | The command field path in openai.yaml does not match the actual filename in scripts/ | Verify that each tool's command value uses the exact filename (case-sensitive) under scripts/ and uses the prefix python scripts/ |
| Index builder returns 0 skills | Subdirectories scanned do not contain a SKILL.md file, or the target path points to a single skill instead of a parent directory | Pass the parent directory that contains skill subdirectories, not a single skill folder. Hidden directories (dot-prefixed) are also skipped |
| Validator warns "Description should use third-person, discovery-friendly format" | The description frontmatter field does not contain recognized discovery patterns like "This skill should be used when" | Rewrite the description to begin with "This skill should be used when the user asks to..." or include verbs like "analyzes", "generates", "provides" |
Converter overwrites existing agents/openai.yaml without backup | Running the converter with output-dir set to the same directory as the source skill | Use --output-dir to write to a separate directory, or manually back up the existing agents/openai.yaml before converting |
| Strict validation fails on optional missing directories | Running --strict treats warnings (missing references/, assets/, license field) as errors | Either create the missing optional directories and fields, or run without --strict to allow warnings |
---
Success Criteria
- Converted skills pass
cross_platform_validator.py --strictwith zero errors and zero warnings - Generated
agents/openai.yamlcontains a validname,description,instructions, andtoolssection that matches the source SKILL.md - Skills index built from 50+ skill directories completes in under 10 seconds with accurate metadata extraction
- All three Python tools exit with code 0 on valid input and exit with code 1 on invalid input, enabling reliable CI/CD integration
- Batch conversion of an entire skill domain (e.g., all
engineering/skills) produces Codex-compatible output with no manual edits required for structure - Cross-platform skills load and function correctly in both Claude Code (via SKILL.md) and Codex CLI (via
agents/openai.yaml) without platform-specific workarounds - Generated
skills-index.jsonis valid JSON parseable by any standard JSON parser and includes complete metadata for every scanned skill
---
Scope & Limitations
This skill covers:
- Installing, configuring, and operating OpenAI Codex CLI
- Converting Claude Code SKILL.md files into Codex-compatible format with
agents/openai.yaml - Validating skill directories for dual-platform (Claude Code + Codex CLI) compatibility
- Building skill registry manifests (
skills-index.json) for discovery and distribution
This skill does NOT cover:
- Writing the actual domain logic inside Python tool scripts (see senior-fullstack, code-reviewer, or the relevant domain skill)
- Cursor, Windsurf, Cline, or Aider platform-specific configuration (see standards/ and root-level dotfiles like
.cursorrules,.windsurfrules) - OpenAI API key management, billing, or rate-limit troubleshooting (out of scope -- refer to OpenAI documentation)
- Automated testing or CI/CD pipeline authoring beyond skill validation (see senior-devops and templates/)
---
Integration Points
| Skill | Integration | Data Flow |
|---|---|---|
| code-reviewer | Convert code-reviewer's SKILL.md to Codex format so it can run in Codex CLI | codex_skill_converter.py reads code-reviewer's SKILL.md and generates agents/openai.yaml |
| senior-fullstack | Validate fullstack skill's cross-platform compatibility after adding Codex support | cross_platform_validator.py checks both SKILL.md frontmatter and openai.yaml structure |
| senior-devops | Embed skill validation and index building into CI/CD pipelines | DevOps workflows call cross_platform_validator.py --strict --json and skills_index_builder.py as pipeline steps |
| tech-stack-evaluator | Evaluate whether Codex CLI fits a project's AI tooling stack | Tech stack evaluator references Codex CLI capabilities and configuration patterns from this skill |
| senior-architect | Architect multi-agent skill systems that span Claude Code and Codex CLI | Architect uses cross-platform skill patterns and index manifests to plan skill distribution |
---
Tool Reference
codex_skill_converter.py
Purpose: Converts a Claude Code SKILL.md into Codex-compatible format by parsing YAML frontmatter, extracting scripts, building instructions, and generating an agents/openai.yaml configuration file.
Usage:
python scripts/codex_skill_converter.py <skill_md> [--output-dir DIR] [--json]Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
skill_md | positional | Yes | -- | Path to the Claude Code SKILL.md file to convert |
--output-dir | string | No | Same as source directory | Output directory for the converted skill. If different from source, copies scripts/, references/, assets/, and SKILL.md alongside the generated agents/openai.yaml |
--json | flag | No | Off (human-readable) | Output results in JSON format instead of human-readable text |
Example:
python scripts/codex_skill_converter.py engineering/code-reviewer/SKILL.md \
--output-dir ./codex-ready/code-reviewer --jsonOutput Formats:
- Human-readable (default): Displays source path, output path, status (SUCCESS/ERROR), lists of generated files, copied files, warnings, and errors
- JSON (`--json`): Structured object with keys:
status,source,output_dir,files_generated,files_copied,warnings,errors
---
cross_platform_validator.py
Purpose: Validates that a skill directory is compatible with both Claude Code and Codex CLI by running 17 checks across three categories: Claude Code compatibility, Codex CLI compatibility, and cross-platform checks.
Usage:
python scripts/cross_platform_validator.py <skill_dir> [--strict] [--json]Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
skill_dir | positional | Yes | -- | Path to the skill directory to validate |
--strict | flag | No | Off | Treat warnings as errors -- the skill is marked NOT COMPATIBLE if any warnings exist |
--json | flag | No | Off (human-readable) | Output results in JSON format instead of human-readable text |
Example:
python scripts/cross_platform_validator.py engineering/codex-cli-specialist/ --strict --jsonOutput Formats:
- Human-readable (default): Groups checks by platform (Claude Code Compatibility, Codex CLI Compatibility, Cross-Platform Checks) with
[PASS],[WARN],[FAIL], or[INFO]status per check, plus an overall compatibility verdict and pass/total count - JSON (`--json`): Structured object with keys:
skill_name,skill_path,compatible(boolean),summary(total_checks, passed, errors, warnings, info),checks(array of check objects withcheck,platform,passed,message,severity)
---
skills_index_builder.py
Purpose: Scans a directory of skill subdirectories, extracts metadata from each SKILL.md, and builds a skills-index.json manifest for skill registries, discovery systems, and version pinning.
Usage:
python scripts/skills_index_builder.py <skills_dir> [--output FILE] [--format FORMAT] [--category CATEGORY]Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
skills_dir | positional | Yes | -- | Path to the directory containing skill subdirectories (each with a SKILL.md) |
--output, -o | string | No | stdout | Output file path. If omitted, prints to stdout |
--format, -f | choice | No | json | Output format: json (structured manifest) or human (tabular summary) |
--category, -c | string | No | None (all categories) | Filter skills by category (matches the metadata.category frontmatter field, case-insensitive) |
Example:
python scripts/skills_index_builder.py ./engineering \
--output skills-index.json --format json --category engineeringOutput Formats:
- JSON (`json`, default): Full index object with keys:
version,generated_at(UTC ISO 8601),source_directory,skills_count,summary(total_tools, total_references, total_size, categories, domains, platforms),skills(array of skill objects with name, title, description, version, license, category, domain, keywords, tools, references, assets, platforms, size_bytes, size_human, path) - Human-readable (`human`): Tabular display with source, generation timestamp, skill count, totals, category breakdown, platform support counts, and a table of skills with name, version, tool count, and platforms
# =============================================================================
# agents/openai.yaml - Codex CLI Skill Configuration Template
# =============================================================================
#
# This template defines a skill for OpenAI Codex CLI.
# Copy this file to your-skill/agents/openai.yaml and customize it.
#
# Documentation: references/codex-cli-guide.md
# Converter tool: python scripts/codex_skill_converter.py
# Validator tool: python scripts/cross_platform_validator.py
#
# =============================================================================
# ---------------------------------------------------------------------------
# REQUIRED: Skill identity
# ---------------------------------------------------------------------------
# Unique identifier for the skill (kebab-case, lowercase)
name: my-skill-name
# Short description for skill discovery and matching.
# Codex uses this to decide when to activate the skill.
# Be specific about what the skill does and when to use it.
description: >
Expert guidance for [DOMAIN]. Analyzes [X], generates [Y],
and enforces [Z] best practices. Use when working with [CONTEXT].
# ---------------------------------------------------------------------------
# RECOMMENDED: Behavioral instructions
# ---------------------------------------------------------------------------
# Instructions define how the agent behaves when this skill is active.
# Write in second person ("You are...") with clear structure.
# Keep under 2000 words for optimal context usage.
instructions: |
You are a senior [DOMAIN] specialist with deep expertise in [AREAS].
## Core Responsibilities
- [Responsibility 1: what you help with]
- [Responsibility 2: what you analyze]
- [Responsibility 3: what you generate]
## Process
When the user asks about [PRIMARY TASK]:
1. Understand the context and requirements
2. Run the appropriate analysis tool
3. Apply best practices from references/
4. Provide structured recommendations
When the user asks about [SECONDARY TASK]:
1. [Step 1]
2. [Step 2]
3. [Step 3]
## Quality Standards
- Always explain your reasoning
- Provide concrete, actionable recommendations
- Reference specific files and line numbers when relevant
- Use the available tools for data-driven analysis
- Follow the patterns in references/ for domain best practices
## Output Format
Structure your responses with:
- A brief summary of findings
- Detailed analysis with evidence
- Prioritized recommendations
- Next steps for the user
# ---------------------------------------------------------------------------
# RECOMMENDED: Tool definitions
# ---------------------------------------------------------------------------
# Tools expose Python scripts to the Codex agent.
# Each tool maps to a command that Codex can execute.
tools:
# Tool 1: Primary analysis tool
- name: primary_analyzer
description: >
Analyzes [X] and produces a structured assessment report.
Use this when the user asks to review, audit, or evaluate [X].
command: python scripts/primary_analyzer.py
# Optional: define accepted arguments
# args:
# - name: input_path
# description: Path to the file or directory to analyze
# required: true
# - name: format
# description: Output format (json or text)
# required: false
# default: text
# Tool 2: Generator tool
- name: artifact_generator
description: >
Generates [Y] artifacts from templates and configuration.
Use this when the user asks to create, scaffold, or produce [Y].
command: python scripts/artifact_generator.py
# Tool 3: Validator tool (optional)
# - name: validator
# description: >
# Validates [Z] against best practices and standards.
# Use this when the user asks to check, verify, or validate [Z].
# command: python scripts/validator.py
# ---------------------------------------------------------------------------
# OPTIONAL: Model and version
# ---------------------------------------------------------------------------
# Preferred model for this skill (optional - uses user default if omitted)
# Options: o4-mini (fast, cheap), o3 (complex reasoning), gpt-4.1 (broad)
# model: o4-mini
# Skill version (follows semver)
version: 1.0.0
Codex CLI Reference Guide
Comprehensive reference for OpenAI Codex CLI: installation, configuration, skill system, invocation patterns, and advanced features.
---
Table of Contents
- Overview
- Installation
- Configuration
- Approval Modes
- Skill System
- Skill Locations
- Skill Discovery
- agents/openai.yaml Schema
- Tool Definitions
- Invocation Patterns
- Built-in Features
- Environment Variables
- Sandboxing and Security
- UI Metadata and Output
- Troubleshooting
---
Overview
Codex CLI is OpenAI's terminal-native coding agent. It connects to OpenAI models (o4-mini, o3, GPT-4.1) and executes tasks autonomously or with human approval. Codex reads, writes, and executes code directly in your local environment.
Key capabilities:
- File creation and modification
- Shell command execution
- Multi-step task planning and execution
- Skill-based specialization
- Sandboxed execution for safety
---
Installation
Prerequisites
- Node.js 22 or newer
- npm (comes with Node.js)
- Git (recommended)
- An OpenAI API key
Install via npm
npm install -g @openai/codexVerify installation
codex --version
codex --helpUpdate to latest
npm update -g @openai/codexFirst-time setup
# Set API key
export OPENAI_API_KEY="sk-..."
# Or add to shell profile for persistence
echo 'export OPENAI_API_KEY="sk-..."' >> ~/.zshrc
source ~/.zshrc
# Run initial configuration
codex configure---
Configuration
Configuration file location
Codex CLI reads configuration from:
1. ~/.codex/config.yaml - Global user config 2. .codex/config.yaml - Project-local config (overrides global)
Configuration options
# ~/.codex/config.yaml
model: o4-mini # Default model
approval_mode: suggest # Default approval mode
history: true # Enable conversation history
notify: true # Desktop notifications on completionModel selection
| Model | Best For | Speed | Cost |
|---|---|---|---|
| o4-mini | General coding tasks | Fast | Low |
| o3 | Complex reasoning, architecture | Slower | Higher |
| gpt-4.1 | Broad knowledge, writing | Fast | Medium |
# Override model per invocation
codex --model o3 "design the database schema"---
Approval Modes
Codex CLI supports three approval modes that control how much autonomy the agent has.
suggest (default)
The agent proposes changes. You approve or reject each one.
codex --approval-mode suggest "add input validation"Use when: Learning the tool, working on critical code, reviewing each step.
auto-edit
The agent automatically applies file edits but asks before executing shell commands.
codex --approval-mode auto-edit "refactor auth module"Use when: You trust the agent with file changes but want to control command execution.
full-auto
The agent executes everything autonomously. All file edits and shell commands run without approval.
codex --approval-mode full-auto "set up test infrastructure"Use when: Working in sandboxed environments, CI/CD pipelines, or trusted automated workflows. Codex applies network-disabled sandboxing by default in this mode.
---
Skill System
Skills are modular packages that give Codex specialized capabilities and domain knowledge.
Skill Locations
Codex discovers skills from these directories (in priority order):
| Priority | Location | Scope |
|---|---|---|
| 1 | .codex/skills/ | Project-local |
| 2 | ~/.codex/skills/ | User-global |
| 3 | /usr/local/share/codex/skills/ | System-wide |
Priority rule: Project-local skills override global skills with the same name.
Skill Discovery
When invoked, Codex:
1. Scans skill directories for agents/openai.yaml files 2. Reads skill names and descriptions 3. Matches user queries to relevant skills 4. Loads matched skill instructions and tools
Explicit invocation:
codex --skill code-reviewer "review the latest PR"Auto-discovery: Codex matches the user prompt against skill descriptions:
codex "analyze code quality" # Matches skills with "code quality" in descriptionagents/openai.yaml Schema
The agents/openai.yaml file is the primary skill configuration for Codex CLI.
Required fields
name: my-skill # Unique identifier (kebab-case)
description: > # What this skill does (discovery text)
Expert guidance for X. Analyzes Y, generates Z.Recommended fields
instructions: | # Behavioral instructions for the agent
You are a senior X specialist.
When the user asks about Y:
1. First, analyze the context
2. Apply framework Z
3. Produce structured output
tools: # Tool definitions (see below)
- name: analyzer
description: Analyzes X
command: python scripts/analyzer.pyOptional fields
model: o4-mini # Preferred model for this skill
version: 1.0.0 # Skill versionComplete example
name: code-reviewer
description: >
Automated code review. Analyzes pull requests for complexity,
risk, and quality. Generates review reports with prioritized findings.
instructions: |
You are an expert code reviewer with deep knowledge of software
engineering best practices, security patterns, and code quality.
## Review Process
1. Understand the PR context (what changed and why)
2. Run the PR analyzer tool for automated checks
3. Review findings and add human-level insights
4. Generate a structured review report
## Priorities
- Security vulnerabilities (critical)
- Logic errors (high)
- Performance issues (medium)
- Style and conventions (low)
## Output Format
Always provide:
- Summary of changes
- Risk assessment (low/medium/high/critical)
- Specific findings with file and line references
- Actionable recommendations
tools:
- name: pr_analyzer
description: >
Analyzes git diff between branches for review complexity,
risk patterns, and generates review priorities
command: python scripts/pr_analyzer.py
- name: code_quality_checker
description: >
Checks source code for SOLID violations, code smells,
complexity metrics, and structural issues
command: python scripts/code_quality_checker.py
- name: review_report_generator
description: >
Generates a formatted review report from analysis results
command: python scripts/review_report_generator.py
model: o4-mini
version: 1.0.0Tool Definitions
Tools expose scripts to the Codex agent. Each tool maps to a command that Codex can execute.
tools:
- name: tool_name # Identifier (snake_case)
description: > # When and why to use this tool
Detailed description of what the tool does and
what output to expect
command: python scripts/tool.py # Execution command
args: # Optional argument definitions
- name: input_path
description: Path to input file or directory
required: true
- name: format
description: Output format
required: false
default: textTool naming conventions:
- Use snake_case for tool names
- Match the Python script name (without .py extension)
- Be descriptive:
pr_analyzernotanalyze
Command path rules:
- Paths are relative to the skill directory
python scripts/tool.pyresolves to<skill-dir>/scripts/tool.py- Ensure scripts are executable and have proper shebangs
---
Invocation Patterns
Basic usage
# Simple task
codex "add error handling to the API routes"
# With specific model
codex --model o3 "design the caching architecture"
# With specific skill
codex --skill senior-fullstack "scaffold a Next.js app"File context
# Pass specific files as context
codex --file src/auth.ts --file src/types.ts "add JWT validation"
# Work in a specific directory
cd my-project && codex "fix the failing tests"Multi-turn conversation
Codex maintains conversation context within a session:
codex
> Create a User model with email and name fields
> Add validation for the email field
> Now create a migration for this model
> exitPiping and integration
# Pipe file content as context
cat error.log | codex "explain these errors and suggest fixes"
# Use output in scripts
codex --quiet "generate a .gitignore for a Node.js project" > .gitignore---
Built-in Features
Conversation history
Codex saves conversation history for context continuity:
# Continue previous conversation
codex --resume "now add the tests we discussed"File watching
Codex monitors file changes in the working directory to maintain up-to-date context.
Multimodal input
# Pass an image for analysis
codex --image screenshot.png "implement this UI design"Quiet mode
# Suppress UI, output only the result
codex --quiet "what is the main entry point of this project"---
Environment Variables
| Variable | Description | Default |
|---|---|---|
OPENAI_API_KEY | API key for authentication | (required) |
CODEX_HOME | Override config directory | ~/.codex |
CODEX_MODEL | Default model | o4-mini |
CODEX_APPROVAL_MODE | Default approval mode | suggest |
CODEX_QUIET | Suppress UI output | false |
---
Sandboxing and Security
Default sandbox behavior
In full-auto mode, Codex applies sandboxing by default:
- Network disabled - no outbound connections
- Filesystem restricted - writes only to project directory
- No secret access - environment variables filtered
macOS sandbox
On macOS, Codex uses Apple's sandbox-exec to enforce restrictions.
Linux sandbox
On Linux, Codex uses a combination of namespace isolation and seccomp filters.
Disabling sandbox (advanced)
# Only when you explicitly need network access
codex --approval-mode full-auto --no-sandbox "install dependencies and run tests"Warning: Disabling the sandbox removes safety guardrails. Only use in trusted environments.
---
UI Metadata and Output
Terminal UI
Codex renders a rich terminal UI showing:
- Current task and progress
- File modifications (diff view)
- Command execution output
- Approval prompts
Structured output
# Get JSON-structured output
codex --output-format json "list all TODO comments in the codebase"Notification support
# Desktop notification on task completion
codex --notify "run the full test suite"---
Troubleshooting
Common issues
"API key not found"
# Verify key is set
echo $OPENAI_API_KEY
# Set it
export OPENAI_API_KEY="sk-...""Skill not found"
# Check skill location
ls .codex/skills/
ls ~/.codex/skills/
# Verify agents/openai.yaml exists
cat .codex/skills/my-skill/agents/openai.yaml"Permission denied" when executing tools
# Ensure scripts are executable
chmod +x .codex/skills/my-skill/scripts/*.py"Model not available"
# Check available models
codex models list
# Fall back to default
codex --model o4-mini "your task"Debug mode
# Verbose output for debugging
codex --verbose "your task"
# Show full request/response logs
CODEX_DEBUG=1 codex "your task"Reset configuration
# Reset global config
rm ~/.codex/config.yaml
codex configure
# Clear conversation history
rm -rf ~/.codex/history/Cross-Platform Skills Guide
How to write skills compatible with multiple AI coding agents: Claude Code, OpenAI Codex CLI, Cursor, VS Code Copilot, and Goose.
---
Table of Contents
- Overview
- Platform Comparison
- Universal Skill Structure
- Platform-Specific Configuration Files
- Claude Code: SKILL.md
- Codex CLI: agents/openai.yaml
- Cursor: .cursorrules
- VS Code Copilot: .github/copilot-instructions.md
- Goose: .goosehints
- Shared Components
- Writing Portable Instructions
- Cross-Platform Skill Template
- Conversion Strategies
- Testing Across Platforms
- Platform Feature Matrix
---
Overview
AI coding agents are converging on similar skill/instruction patterns but use different configuration formats. A well-designed skill separates its core knowledge (instructions, references, tools) from platform-specific configuration, making it straightforward to support multiple agents from a single source.
Design principle: Write once, configure per platform. The domain expertise lives in shared markdown and scripts. Only the entry-point configuration differs.
---
Platform Comparison
| Feature | Claude Code | Codex CLI | Cursor | VS Code Copilot | Goose |
|---|---|---|---|---|---|
| Config file | SKILL.md | agents/openai.yaml | .cursorrules | copilot-instructions.md | .goosehints |
| Format | Markdown + YAML FM | YAML | Plain text | Markdown | Markdown |
| Tool support | Python scripts | CLI commands | Limited | Extensions | Python plugins |
| Skill discovery | YAML frontmatter | YAML fields | N/A | N/A | N/A |
| Auto-discovery | By description | By description | No | No | No |
| Max context | Large | Large | Medium | Medium | Large |
| Sandboxing | Yes | Yes (full-auto) | No | No | Yes |
---
Universal Skill Structure
The recommended cross-platform skill layout:
my-skill/
├── SKILL.md # Claude Code entry point
├── agents/
│ └── openai.yaml # Codex CLI entry point
├── .cursorrules # Cursor rules (optional)
├── .github/
│ └── copilot-instructions.md # VS Code Copilot (optional)
├── .goosehints # Goose hints (optional)
├── scripts/ # Shared tools (all platforms)
│ ├── tool_a.py
│ └── tool_b.py
├── references/ # Shared knowledge base
│ ├── guide.md
│ └── patterns.md
└── assets/ # Shared templates
└── template.yamlShared directories (scripts/, references/, assets/) are platform-agnostic. Every platform can reference these files. Only the top-level config files differ.
---
Platform-Specific Configuration Files
Claude Code: SKILL.md
Claude Code reads SKILL.md as the skill definition. It uses YAML frontmatter for metadata and the markdown body for instructions and documentation.
---
name: my-skill
description: This skill should be used when the user asks to "do X",
"perform Y", or "analyze Z". Use for domain expertise and automation.
license: MIT
metadata:
version: 1.0.0
category: engineering
domain: development-tools
---
# My Skill
Expert guidance for X domain.
## Quick Start
python scripts/tool_a.py --help
## Workflows
### Workflow 1: Analyze X
...
## Best Practices
...Key characteristics:
- YAML frontmatter with
nameanddescription(required) - Description uses third-person, keyword-rich format for auto-discovery
- Markdown body serves as both documentation and instructions
- Tool usage documented inline with bash code blocks
---
Codex CLI: agents/openai.yaml
Codex CLI reads agents/openai.yaml for skill configuration.
name: my-skill
description: >
Expert guidance for X domain. Analyzes Y, generates Z,
and enforces best practices.
instructions: |
You are a senior X specialist.
When the user asks about Y:
1. Analyze the context using the analyzer tool
2. Apply framework Z
3. Generate structured output
Always reference the knowledge in references/ for details.
tools:
- name: tool_a
description: Analyzes X and produces assessment
command: python scripts/tool_a.py
- name: tool_b
description: Generates Y artifacts
command: python scripts/tool_b.py
model: o4-mini
version: 1.0.0Key characteristics:
- YAML format with structured fields
- Separate
instructionsfield (not embedded in docs) - Explicit
toolsarray with command mappings - Optional
modelpreference
---
Cursor: .cursorrules
Cursor reads .cursorrules from the project root. It is a plain text file with instructions.
You are a senior X specialist with deep expertise in Y and Z.
## Rules
- Always validate input before processing
- Use TypeScript strict mode
- Follow the patterns in references/guide.md
## Workflow
When asked to analyze code:
1. Check the file structure
2. Run scripts/tool_a.py for automated analysis
3. Provide recommendations based on references/patterns.md
## Code Style
- Use camelCase for variables
- Use PascalCase for types
- Prefer const over letKey characteristics:
- Plain text (no YAML, no frontmatter)
- Rules-oriented format
- No formal tool definition (tools referenced informally)
- Applies to entire project (not modular)
---
VS Code Copilot: .github/copilot-instructions.md
GitHub Copilot reads .github/copilot-instructions.md for workspace-level instructions.
# Copilot Instructions
You are a senior X specialist. Follow these guidelines when generating code.
## Standards
- Use TypeScript strict mode
- Follow SOLID principles
- Write tests for all new functions
## Architecture
Follow the patterns documented in `references/guide.md`.
## Tools
When analysis is needed, suggest running:python scripts/tool_a.py <path>
Key characteristics:
- Standard markdown in
.github/directory - Read by GitHub Copilot in VS Code
- Instruction-focused (no tool execution)
- Informational only (Copilot suggests, does not execute)
---
Goose: .goosehints
Goose reads .goosehints from the project root for behavioral guidance.
# Goose Hints
You are a senior X specialist.
## Capabilities
- Run `python scripts/tool_a.py` for automated analysis
- Reference `references/guide.md` for domain knowledge
- Use templates in `assets/` for output formatting
## Workflow
1. Understand the user request
2. Check existing code context
3. Run tools as needed
4. Apply best practices from references
5. Generate clean, documented code
## Constraints
- Standard library Python only in scripts
- No network calls during analysis
- UTF-8 encoding for all filesKey characteristics:
- Markdown format with hints and guidance
- Supports tool execution (Goose runs commands)
- Flexible format, no strict schema
---
Shared Components
scripts/ directory
Python scripts are the most portable tool format. All platforms can execute Python scripts.
Portability rules: 1. Use standard library only (no pip dependencies) 2. Support --help via argparse 3. Support --json output for machine consumption 4. Use relative paths (resolve from script location) 5. Handle errors gracefully with clear messages 6. Work on macOS, Linux, and Windows (use pathlib)
#!/usr/bin/env python3
"""Tool description for discovery."""
import argparse
import json
import sys
from pathlib import Path
def main():
parser = argparse.ArgumentParser(description="Tool description")
parser.add_argument("input", help="Input path")
parser.add_argument("--json", action="store_true", help="JSON output")
args = parser.parse_args()
result = analyze(args.input)
if args.json:
print(json.dumps(result, indent=2))
else:
print(format_human(result))
if __name__ == "__main__":
main()references/ directory
Knowledge base files in markdown. These are referenced by instructions on all platforms.
Best practices:
- Use standard markdown (no platform-specific extensions)
- Keep files focused (one topic per file)
- Use relative links between reference files
- Include a table of contents for files longer than 100 lines
assets/ directory
Templates, configuration samples, and other reusable resources.
Best practices:
- Use YAML or JSON for structured templates (both are widely supported)
- Include comments explaining each field
- Provide both minimal and full-featured examples
---
Writing Portable Instructions
Instructions are the core of any skill. Write them so they translate cleanly to any platform:
Do
- Use imperative mood: "Analyze the code" not "This skill analyzes the code"
- Reference shared files by path: "See references/guide.md for details"
- Describe tools generically: "Run the analyzer tool" not "Use codex --skill"
- Structure with markdown headers: Universally parsed
- Number steps clearly: "1. First... 2. Then... 3. Finally..."
Avoid
- Platform-specific invocations: "Ask Claude to..." or "Run codex --skill..."
- Embedded tool definitions: Keep tool configs in platform-specific files
- Assumptions about UI: Not all platforms have the same approval flow
- Inline YAML in markdown: Keep YAML in dedicated files
- Overly long instructions: Keep under 2000 words for best context use
Instruction template
You are a senior [DOMAIN] specialist with expertise in [AREAS].
## Core Responsibilities
- [Responsibility 1]
- [Responsibility 2]
- [Responsibility 3]
## Process
When asked to [PRIMARY TASK]:
1. [Step 1]
2. [Step 2]
3. [Step 3]
## Tools
- [tool_name]: [what it does and when to use it]
## Quality Standards
- [Standard 1]
- [Standard 2]
## References
- references/[file].md: [what it covers]---
Cross-Platform Skill Template
Use this template when creating a new skill that targets all platforms:
Step 1: Create shared content first
Write references, scripts, and assets before platform configs.
Step 2: Write SKILL.md (Claude Code)
The most detailed format. Use this as the source of truth.
Step 3: Generate agents/openai.yaml (Codex CLI)
Extract instructions and tool definitions from SKILL.md. Use the codex_skill_converter.py tool.
Step 4: Create .cursorrules (Cursor)
Distill instructions to rules and coding standards. Keep under 500 lines.
Step 5: Create copilot-instructions.md (Copilot)
Focus on code generation guidelines. Omit tool execution details.
Step 6: Create .goosehints (Goose)
Similar to SKILL.md but more concise. Include tool paths.
---
Conversion Strategies
Source of truth: SKILL.md
Maintain SKILL.md as the canonical skill definition. Generate other formats from it.
SKILL.md (source) ──┬──> agents/openai.yaml (Codex)
├──> .cursorrules (Cursor)
├──> copilot-instructions.md (Copilot)
└──> .goosehints (Goose)Automated conversion
Use the codex_skill_converter.py script for SKILL.md to agents/openai.yaml conversion. For other platforms, a general conversion follows this pattern:
1. Parse SKILL.md frontmatter and body 2. Extract instructions, tools, and references 3. Format into the target platform syntax 4. Write the platform-specific file
Manual conversion checklist
When converting a skill to a new platform:
- [ ] Core instructions preserved
- [ ] Tool references updated to match platform syntax
- [ ] File paths are correct (relative to skill or project root)
- [ ] Platform-specific features leveraged (e.g., Codex tool args)
- [ ] Validated on target platform
---
Testing Across Platforms
Validation approach
1. Structural validation: Use cross_platform_validator.py to check file presence and format 2. Functional testing: Run each script independently with --help and sample input 3. Integration testing: Test the skill on each target platform with a standard prompt
Standard test prompts
Use these prompts to verify a skill works correctly:
# Basic functionality
"Explain what this skill does and what tools are available"
# Tool execution
"Run the [primary tool] on [sample input]"
# Workflow execution
"Walk me through the [primary workflow]"
# Edge case
"What happens when [unusual situation]?"Platform-specific verification
| Platform | How to test |
|---|---|
| Claude Code | Load SKILL.md in project, ask Claude about it |
| Codex CLI | Install skill, run codex --skill name "test prompt" |
| Cursor | Place .cursorrules in project root, test in IDE |
| VS Code Copilot | Add copilot-instructions.md, test suggestions |
| Goose | Add .goosehints, run goose with test prompt |
---
Platform Feature Matrix
Detailed comparison of what each platform supports:
Skill Discovery
| Capability | Claude Code | Codex CLI | Cursor | Copilot | Goose |
|---|---|---|---|---|---|
| Auto-discovery by description | Yes | Yes | No | No | No |
| Explicit invocation | N/A | --skill flag | N/A | N/A | N/A |
| Multiple skills per project | Yes | Yes | No (1 file) | No (1 file) | No (1 file) |
| Skill versioning | Via metadata | Via YAML | No | No | No |
Tool Execution
| Capability | Claude Code | Codex CLI | Cursor | Copilot | Goose |
|---|---|---|---|---|---|
| Run Python scripts | Yes | Yes | No | No | Yes |
| Run shell commands | Yes | Yes | No | No | Yes |
| Tool argument schemas | No | Yes (args) | No | No | No |
| Sandboxed execution | Yes | Yes (full-auto) | No | No | Yes |
Instruction Format
| Capability | Claude Code | Codex CLI | Cursor | Copilot | Goose |
|---|---|---|---|---|---|
| Markdown support | Full | In instructions | Partial | Full | Full |
| YAML frontmatter | Yes | N/A | No | No | No |
| Max instruction size | Large | Large | ~5000 chars | Medium | Large |
| Structured sections | Via markdown | Via YAML | Free-form | Via markdown | Via markdown |
Distribution
| Capability | Claude Code | Codex CLI | Cursor | Copilot | Goose |
|---|---|---|---|---|---|
| Directory-based skills | Yes | Yes | No | No | No |
| Registry support | No (manual) | Planned | No | No | No |
| Git-based distribution | Yes | Yes | Yes | Yes | Yes |
| Skills index/manifest | Via builder tool | Via builder tool | No | No | No |
#!/usr/bin/env python3
"""
Codex Skill Converter
Converts a Claude Code SKILL.md into Codex-compatible format by generating
an agents/openai.yaml configuration file alongside the existing skill.
Usage:
python codex_skill_converter.py path/to/SKILL.md
python codex_skill_converter.py path/to/SKILL.md --output-dir ./converted
python codex_skill_converter.py path/to/SKILL.md --json
"""
import argparse
import json
import os
import re
import shutil
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
def parse_yaml_frontmatter(content: str) -> Tuple[Dict[str, Any], str]:
"""Parse YAML frontmatter from markdown content.
Returns a tuple of (frontmatter_dict, body_content).
Uses a simple parser to avoid external dependencies.
"""
frontmatter: Dict[str, Any] = {}
body = content
if not content.startswith("---"):
return frontmatter, body
lines = content.split("\n")
end_index = -1
for i in range(1, len(lines)):
if lines[i].strip() == "---":
end_index = i
break
if end_index == -1:
return frontmatter, body
fm_lines = lines[1:end_index]
body = "\n".join(lines[end_index + 1:]).lstrip("\n")
# Simple YAML key-value parser (handles flat keys and nested metadata)
current_key = None
current_indent = 0
for line in fm_lines:
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
indent = len(line) - len(line.lstrip())
# Top-level key: value
match = re.match(r'^(\w[\w-]*)\s*:\s*(.*)', stripped)
if match and indent == 0:
key = match.group(1)
value = match.group(2).strip()
if value:
# Remove surrounding quotes if present
if (value.startswith('"') and value.endswith('"')) or \
(value.startswith("'") and value.endswith("'")):
value = value[1:-1]
frontmatter[key] = value
else:
frontmatter[key] = {}
current_key = key
current_indent = indent
continue
# Nested key: value (under metadata, codex, etc.)
if indent > 0 and current_key and isinstance(frontmatter.get(current_key), dict):
nested_match = re.match(r'^(\w[\w-]*)\s*:\s*(.*)', stripped)
if nested_match:
nkey = nested_match.group(1)
nval = nested_match.group(2).strip()
if (nval.startswith('"') and nval.endswith('"')) or \
(nval.startswith("'") and nval.endswith("'")):
nval = nval[1:-1]
frontmatter[current_key][nkey] = nval
continue
# Multi-line description continuation
if current_key == "description" and indent > 0 and isinstance(frontmatter.get("description"), str):
frontmatter["description"] += " " + stripped
return frontmatter, body
def extract_title(body: str) -> str:
"""Extract the first H1 title from markdown body."""
for line in body.split("\n"):
line = line.strip()
if line.startswith("# "):
return line[2:].strip()
return ""
def extract_scripts(skill_dir: Path) -> List[Dict[str, str]]:
"""Find Python scripts in the skill's scripts/ directory."""
scripts_dir = skill_dir / "scripts"
tools = []
if not scripts_dir.is_dir():
return tools
for script_path in sorted(scripts_dir.glob("*.py")):
name = script_path.stem
# Read first docstring for description
description = f"Runs {script_path.name}"
try:
with open(script_path, "r", encoding="utf-8") as f:
content = f.read()
# Extract module docstring
doc_match = re.search(r'^"""(.*?)"""', content, re.DOTALL)
if not doc_match:
doc_match = re.search(r"^'''(.*?)'''", content, re.DOTALL)
if doc_match:
doc_text = doc_match.group(1).strip()
# Take just the first line or sentence
first_line = doc_text.split("\n")[0].strip()
if first_line:
description = first_line
except (OSError, UnicodeDecodeError):
pass
tools.append({
"name": name,
"description": description,
"command": f"python scripts/{script_path.name}",
})
return tools
def build_instructions(frontmatter: Dict[str, Any], body: str, title: str) -> str:
"""Build Codex instructions from the skill's content."""
lines = []
lines.append(f"You are an expert {title.lower()} specialist.")
lines.append("")
# Extract description for context
desc = frontmatter.get("description", "")
if desc:
lines.append(f"## Purpose")
lines.append(desc)
lines.append("")
# Extract key sections from the body for instructions
sections = extract_key_sections(body)
if sections.get("workflows"):
lines.append("## Key Workflows")
for wf in sections["workflows"][:5]: # Limit to 5 workflows
lines.append(f"- {wf}")
lines.append("")
if sections.get("best_practices"):
lines.append("## Best Practices")
for bp in sections["best_practices"][:8]:
lines.append(f"- {bp}")
lines.append("")
lines.append("## Output Standards")
lines.append("- Provide clear, actionable guidance")
lines.append("- Show concrete examples when possible")
lines.append("- Reference available tools when relevant")
lines.append("- Use the scripts in the scripts/ directory for automation")
return "\n".join(lines)
def extract_key_sections(body: str) -> Dict[str, List[str]]:
"""Extract workflow names and best practices from markdown body."""
result: Dict[str, List[str]] = {"workflows": [], "best_practices": []}
lines = body.split("\n")
in_best_practices = False
for line in lines:
stripped = line.strip()
# Find workflow section headers (### Workflow N: Title)
wf_match = re.match(r'^###\s+(?:Workflow\s+\d+[:.]\s*)?(.+)', stripped)
if wf_match and "workflow" in stripped.lower():
result["workflows"].append(wf_match.group(1).strip())
# Detect best practices sections
if re.match(r'^#{1,3}\s+[Bb]est\s+[Pp]ractices', stripped):
in_best_practices = True
continue
# Next heading exits best practices
if in_best_practices and re.match(r'^#{1,3}\s+', stripped):
in_best_practices = False
continue
# Collect numbered or bulleted items in best practices
if in_best_practices:
bp_match = re.match(r'^[\d]+[.)]\s+\*\*(.+?)\*\*', stripped)
if bp_match:
result["best_practices"].append(bp_match.group(1).strip())
elif stripped.startswith("- ") or stripped.startswith("* "):
text = stripped.lstrip("-* ").strip()
if text and not text.startswith("```"):
result["best_practices"].append(text)
return result
def generate_openai_yaml(
name: str,
description: str,
instructions: str,
tools: List[Dict[str, str]],
version: str = "1.0.0",
model: Optional[str] = None,
) -> str:
"""Generate the agents/openai.yaml content."""
lines = []
lines.append(f"name: {name}")
lines.append("description: >")
# Wrap description at ~78 chars
desc_words = description.split()
current_line = " "
for word in desc_words:
if len(current_line) + len(word) + 1 > 78:
lines.append(current_line.rstrip())
current_line = " " + word
else:
current_line += (" " if len(current_line.strip()) > 0 else "") + word
if current_line.strip():
lines.append(current_line.rstrip())
lines.append("instructions: |")
for inst_line in instructions.split("\n"):
if inst_line:
lines.append(f" {inst_line}")
else:
lines.append("")
if tools:
lines.append("tools:")
for tool in tools:
lines.append(f" - name: {tool['name']}")
lines.append(f" description: >")
lines.append(f" {tool['description']}")
lines.append(f" command: {tool['command']}")
if model:
lines.append(f"model: {model}")
lines.append(f"version: {version}")
return "\n".join(lines) + "\n"
def convert_skill(
skill_md_path: str,
output_dir: Optional[str] = None,
) -> Dict[str, Any]:
"""Convert a Claude Code SKILL.md to Codex-compatible format.
Returns a result dict with status, warnings, and generated files.
"""
result: Dict[str, Any] = {
"status": "success",
"source": str(skill_md_path),
"output_dir": "",
"files_generated": [],
"files_copied": [],
"warnings": [],
"errors": [],
}
skill_md = Path(skill_md_path).resolve()
if not skill_md.is_file():
result["status"] = "error"
result["errors"].append(f"File not found: {skill_md}")
return result
if skill_md.name != "SKILL.md":
result["warnings"].append(
f"Expected filename 'SKILL.md', got '{skill_md.name}'. Proceeding anyway."
)
skill_dir = skill_md.parent
# Determine output directory
if output_dir:
out_path = Path(output_dir).resolve()
else:
out_path = skill_dir
result["output_dir"] = str(out_path)
# Read and parse SKILL.md
try:
with open(skill_md, "r", encoding="utf-8") as f:
content = f.read()
except (OSError, UnicodeDecodeError) as e:
result["status"] = "error"
result["errors"].append(f"Cannot read {skill_md}: {e}")
return result
frontmatter, body = parse_yaml_frontmatter(content)
if not frontmatter:
result["warnings"].append("No YAML frontmatter found. Using defaults.")
# Extract key fields
name = frontmatter.get("name", skill_dir.name)
description = frontmatter.get("description", "")
title = extract_title(body) or name
if not description:
description = f"Expert guidance for {title}."
result["warnings"].append("No description in frontmatter. Generated a default.")
# Extract metadata
metadata = frontmatter.get("metadata", {})
version = "1.0.0"
if isinstance(metadata, dict):
version = metadata.get("version", "1.0.0")
# Check for Codex-specific hints
codex_hints = frontmatter.get("codex", {})
model = None
if isinstance(codex_hints, dict):
model = codex_hints.get("model")
# Find scripts
tools = extract_scripts(skill_dir)
# Build instructions
instructions = build_instructions(frontmatter, body, title)
# Generate openai.yaml
yaml_content = generate_openai_yaml(
name=name,
description=description,
instructions=instructions,
tools=tools,
version=version,
model=model,
)
# Create output directory structure
try:
agents_dir = out_path / "agents"
agents_dir.mkdir(parents=True, exist_ok=True)
yaml_path = agents_dir / "openai.yaml"
with open(yaml_path, "w", encoding="utf-8") as f:
f.write(yaml_content)
result["files_generated"].append(str(yaml_path))
except OSError as e:
result["status"] = "error"
result["errors"].append(f"Cannot write output: {e}")
return result
# If output dir differs from source, copy other files
if out_path != skill_dir:
for subdir in ["scripts", "references", "assets"]:
src = skill_dir / subdir
dst = out_path / subdir
if src.is_dir():
try:
if dst.exists():
shutil.rmtree(dst)
shutil.copytree(src, dst)
result["files_copied"].append(str(dst))
except OSError as e:
result["warnings"].append(f"Could not copy {subdir}/: {e}")
# Copy SKILL.md
dst_skill = out_path / "SKILL.md"
try:
shutil.copy2(skill_md, dst_skill)
result["files_copied"].append(str(dst_skill))
except OSError as e:
result["warnings"].append(f"Could not copy SKILL.md: {e}")
# Check for existing openai.yaml that was overwritten
if (skill_dir / "agents" / "openai.yaml").is_file() and out_path == skill_dir:
result["warnings"].append(
"Overwrote existing agents/openai.yaml. Previous version not backed up."
)
return result
def format_human_output(result: Dict[str, Any]) -> str:
"""Format result as human-readable text."""
lines = []
lines.append("Codex Skill Converter")
lines.append("=" * 40)
lines.append("")
lines.append(f"Source: {result['source']}")
lines.append(f"Output: {result['output_dir']}")
lines.append(f"Status: {result['status'].upper()}")
lines.append("")
if result["files_generated"]:
lines.append("Files Generated:")
for f in result["files_generated"]:
lines.append(f" + {f}")
lines.append("")
if result["files_copied"]:
lines.append("Files Copied:")
for f in result["files_copied"]:
lines.append(f" > {f}")
lines.append("")
if result["warnings"]:
lines.append("Warnings:")
for w in result["warnings"]:
lines.append(f" ! {w}")
lines.append("")
if result["errors"]:
lines.append("Errors:")
for e in result["errors"]:
lines.append(f" X {e}")
lines.append("")
if result["status"] == "success":
lines.append("Conversion complete. Review agents/openai.yaml before deploying.")
else:
lines.append("Conversion failed. See errors above.")
return "\n".join(lines)
def main() -> None:
parser = argparse.ArgumentParser(
description="Convert a Claude Code SKILL.md into Codex-compatible format.",
epilog="Example: python codex_skill_converter.py path/to/SKILL.md --output-dir ./converted",
)
parser.add_argument(
"skill_md",
help="Path to the Claude Code SKILL.md file to convert",
)
parser.add_argument(
"--output-dir",
default=None,
help="Output directory for the converted skill (default: same as source)",
)
parser.add_argument(
"--json",
action="store_true",
help="Output results in JSON format",
)
args = parser.parse_args()
result = convert_skill(args.skill_md, args.output_dir)
if args.json:
print(json.dumps(result, indent=2))
else:
print(format_human_output(result))
if result["status"] != "success":
sys.exit(1)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Cross-Platform Skill Validator
Validates that a skill directory is compatible with both Claude Code and
Codex CLI. Checks YAML frontmatter, file structure, description format,
and agents/openai.yaml configuration.
Usage:
python cross_platform_validator.py path/to/skill-dir
python cross_platform_validator.py path/to/skill-dir --strict
python cross_platform_validator.py path/to/skill-dir --json
"""
import argparse
import json
import os
import re
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
class ValidationResult:
"""Holds a single validation check result."""
def __init__(self, check: str, platform: str, passed: bool,
message: str, severity: str = "error"):
self.check = check
self.platform = platform
self.passed = passed
self.message = message
self.severity = severity # "error", "warning", "info"
def to_dict(self) -> Dict[str, Any]:
return {
"check": self.check,
"platform": self.platform,
"passed": self.passed,
"message": self.message,
"severity": self.severity,
}
def parse_yaml_frontmatter_simple(content: str) -> Tuple[Dict[str, str], bool]:
"""Simple YAML frontmatter parser. Returns (dict, is_valid)."""
result: Dict[str, str] = {}
if not content.startswith("---"):
return result, False
lines = content.split("\n")
end_index = -1
for i in range(1, len(lines)):
if lines[i].strip() == "---":
end_index = i
break
if end_index == -1:
return result, False
fm_lines = lines[1:end_index]
current_key = None
current_value = ""
for line in fm_lines:
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
indent = len(line) - len(line.lstrip())
# Top-level key: value
match = re.match(r'^(\w[\w-]*)\s*:\s*(.*)', stripped)
if match and indent == 0:
if current_key and current_value:
result[current_key] = current_value.strip()
current_key = match.group(1)
current_value = match.group(2).strip()
# Remove quotes
if (current_value.startswith('"') and current_value.endswith('"')) or \
(current_value.startswith("'") and current_value.endswith("'")):
current_value = current_value[1:-1]
continue
# Continuation of multi-line value
if indent > 0 and current_key:
if current_key in ("description",):
current_value += " " + stripped
elif ":" in stripped:
# Nested key - store parent as marker
if current_key not in result:
result[current_key] = "__nested__"
if current_key and current_value:
result[current_key] = current_value.strip()
return result, True
def parse_openai_yaml_simple(content: str) -> Tuple[Dict[str, Any], bool]:
"""Simple parser for agents/openai.yaml. Returns (dict, is_valid)."""
result: Dict[str, Any] = {}
lines = content.split("\n")
current_key = None
current_value = ""
tools_list: List[Dict[str, str]] = []
in_tools = False
current_tool: Dict[str, str] = {}
for line in lines:
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
indent = len(line) - len(line.lstrip())
# Top-level key
top_match = re.match(r'^(\w[\w-]*)\s*:\s*(.*)', stripped)
if top_match and indent == 0:
# Save previous key
if current_key and current_value and current_key != "tools":
result[current_key] = current_value.strip()
if in_tools and current_tool:
tools_list.append(current_tool)
current_tool = {}
current_key = top_match.group(1)
val = top_match.group(2).strip()
if current_key == "tools":
in_tools = True
current_value = ""
continue
in_tools = False
if val in (">", "|", "|-", ">-"):
current_value = ""
else:
current_value = val
continue
# Inside tools section
if in_tools:
if stripped.startswith("- name:"):
if current_tool:
tools_list.append(current_tool)
current_tool = {"name": stripped.split(":", 1)[1].strip()}
elif ":" in stripped and current_tool:
k, v = stripped.split(":", 1)
k = k.strip()
v = v.strip()
if v in (">", "|"):
current_tool[k] = ""
elif v:
current_tool[k] = v
elif k in current_tool and not current_tool[k]:
pass # Multi-line continuation handled below
elif current_tool:
# Continuation line for tool field
for tk in ("description", "command"):
if tk in current_tool and not current_tool[tk]:
current_tool[tk] = stripped
break
continue
# Multi-line value continuation
if indent > 0 and current_key:
if current_value:
current_value += " " + stripped
else:
current_value = stripped
# Finalize
if current_key and current_value and current_key != "tools":
result[current_key] = current_value.strip()
if in_tools and current_tool:
tools_list.append(current_tool)
if tools_list:
result["tools"] = tools_list
return result, bool(result)
def validate_skill(skill_dir: str, strict: bool = False) -> Dict[str, Any]:
"""Validate a skill directory for cross-platform compatibility.
Returns a validation report dict.
"""
path = Path(skill_dir).resolve()
checks: List[ValidationResult] = []
skill_name = path.name
# =========================================================
# Claude Code Compatibility Checks
# =========================================================
# Check 1: SKILL.md exists
skill_md_path = path / "SKILL.md"
if skill_md_path.is_file():
checks.append(ValidationResult(
"skill_md_exists", "claude-code", True,
"SKILL.md exists"
))
else:
checks.append(ValidationResult(
"skill_md_exists", "claude-code", False,
"SKILL.md not found - required for Claude Code",
"error"
))
# Check 2: Valid YAML frontmatter
frontmatter: Dict[str, str] = {}
if skill_md_path.is_file():
try:
with open(skill_md_path, "r", encoding="utf-8") as f:
content = f.read()
frontmatter, fm_valid = parse_yaml_frontmatter_simple(content)
if fm_valid:
checks.append(ValidationResult(
"valid_frontmatter", "claude-code", True,
"Valid YAML frontmatter found"
))
else:
checks.append(ValidationResult(
"valid_frontmatter", "claude-code", False,
"No valid YAML frontmatter (must start with --- and end with ---)",
"error"
))
except (OSError, UnicodeDecodeError) as e:
checks.append(ValidationResult(
"valid_frontmatter", "claude-code", False,
f"Cannot read SKILL.md: {e}",
"error"
))
# Check 3: Name field present
if frontmatter.get("name"):
checks.append(ValidationResult(
"name_field", "claude-code", True,
f"Name field present: {frontmatter['name']}"
))
skill_name = frontmatter["name"]
else:
checks.append(ValidationResult(
"name_field", "claude-code", False,
"Name field missing in frontmatter",
"error"
))
# Check 4: Description field present and third-person
desc = frontmatter.get("description", "")
if desc:
checks.append(ValidationResult(
"description_exists", "claude-code", True,
"Description field present"
))
# Check third-person / discovery format
discovery_patterns = [
r"this skill",
r"should be used when",
r"use for",
r"use when",
r"analyzes",
r"generates",
r"provides",
r"automates",
]
has_discovery_pattern = any(
re.search(pat, desc, re.IGNORECASE) for pat in discovery_patterns
)
if has_discovery_pattern:
checks.append(ValidationResult(
"description_format", "claude-code", True,
"Description uses discovery-friendly format"
))
else:
checks.append(ValidationResult(
"description_format", "claude-code", False,
"Description should use third-person, discovery-friendly format "
"(e.g., 'This skill should be used when...')",
"warning"
))
else:
checks.append(ValidationResult(
"description_exists", "claude-code", False,
"Description field missing in frontmatter",
"error"
))
# Check 5: License field
if frontmatter.get("license"):
checks.append(ValidationResult(
"license_field", "claude-code", True,
f"License: {frontmatter['license']}"
))
else:
checks.append(ValidationResult(
"license_field", "claude-code", False,
"License field missing (recommended for distribution)",
"warning"
))
# Check 6: Scripts directory
scripts_dir = path / "scripts"
if scripts_dir.is_dir():
py_scripts = list(scripts_dir.glob("*.py"))
if py_scripts:
checks.append(ValidationResult(
"scripts_dir", "claude-code", True,
f"scripts/ directory found ({len(py_scripts)} Python tool(s))"
))
else:
checks.append(ValidationResult(
"scripts_dir", "claude-code", False,
"scripts/ directory exists but contains no Python files",
"warning"
))
else:
checks.append(ValidationResult(
"scripts_dir", "claude-code", False,
"scripts/ directory not found (optional but recommended)",
"warning"
))
# Check 7: References directory
refs_dir = path / "references"
if refs_dir.is_dir():
ref_files = list(refs_dir.glob("*.md"))
checks.append(ValidationResult(
"references_dir", "claude-code", True,
f"references/ directory found ({len(ref_files)} file(s))"
))
else:
checks.append(ValidationResult(
"references_dir", "claude-code", False,
"references/ directory not found (optional)",
"info"
))
# Check 8: Assets directory
assets_dir = path / "assets"
if assets_dir.is_dir():
asset_files = list(assets_dir.iterdir())
asset_count = len([f for f in asset_files if f.is_file()])
checks.append(ValidationResult(
"assets_dir", "claude-code", True,
f"assets/ directory found ({asset_count} file(s))"
))
else:
checks.append(ValidationResult(
"assets_dir", "claude-code", False,
"assets/ directory not found (optional)",
"info"
))
# Check 9: No requirements.txt or heavy dependencies
req_file = path / "requirements.txt"
if req_file.is_file():
try:
with open(req_file, "r", encoding="utf-8") as f:
deps = [l.strip() for l in f if l.strip() and not l.startswith("#")]
if deps:
checks.append(ValidationResult(
"no_heavy_deps", "claude-code", False,
f"requirements.txt found with {len(deps)} dependencies "
f"(skills should use standard library only)",
"warning"
))
except OSError:
pass
else:
checks.append(ValidationResult(
"no_heavy_deps", "claude-code", True,
"No requirements.txt (standard library only - good)"
))
# =========================================================
# Codex CLI Compatibility Checks
# =========================================================
# Check 10: agents/openai.yaml exists
yaml_path = path / "agents" / "openai.yaml"
if yaml_path.is_file():
checks.append(ValidationResult(
"openai_yaml_exists", "codex-cli", True,
"agents/openai.yaml exists"
))
else:
checks.append(ValidationResult(
"openai_yaml_exists", "codex-cli", False,
"agents/openai.yaml not found - required for Codex CLI",
"error"
))
# Check 11: Valid YAML structure
yaml_data: Dict[str, Any] = {}
if yaml_path.is_file():
try:
with open(yaml_path, "r", encoding="utf-8") as f:
yaml_content = f.read()
yaml_data, yaml_valid = parse_openai_yaml_simple(yaml_content)
if yaml_valid:
checks.append(ValidationResult(
"openai_yaml_valid", "codex-cli", True,
"agents/openai.yaml has valid structure"
))
else:
checks.append(ValidationResult(
"openai_yaml_valid", "codex-cli", False,
"agents/openai.yaml appears empty or invalid",
"error"
))
except (OSError, UnicodeDecodeError) as e:
checks.append(ValidationResult(
"openai_yaml_valid", "codex-cli", False,
f"Cannot read agents/openai.yaml: {e}",
"error"
))
# Check 12: Name field in openai.yaml
if yaml_data.get("name"):
checks.append(ValidationResult(
"yaml_name", "codex-cli", True,
f"Name field present: {yaml_data['name']}"
))
# Check name matches SKILL.md
if skill_name and yaml_data["name"] != skill_name:
checks.append(ValidationResult(
"name_match", "cross-platform", False,
f"Name mismatch: SKILL.md has '{skill_name}', "
f"openai.yaml has '{yaml_data['name']}'",
"warning"
))
else:
checks.append(ValidationResult(
"name_match", "cross-platform", True,
"Skill name matches across platforms"
))
elif yaml_path.is_file():
checks.append(ValidationResult(
"yaml_name", "codex-cli", False,
"Name field missing in agents/openai.yaml",
"error"
))
# Check 13: Description in openai.yaml
if yaml_data.get("description"):
checks.append(ValidationResult(
"yaml_description", "codex-cli", True,
"Description field present in openai.yaml"
))
elif yaml_path.is_file():
checks.append(ValidationResult(
"yaml_description", "codex-cli", False,
"Description field missing in agents/openai.yaml",
"error"
))
# Check 14: Instructions in openai.yaml
if yaml_data.get("instructions"):
inst_len = len(yaml_data["instructions"])
if inst_len > 50:
checks.append(ValidationResult(
"yaml_instructions", "codex-cli", True,
f"Instructions field present ({inst_len} chars)"
))
else:
checks.append(ValidationResult(
"yaml_instructions", "codex-cli", False,
f"Instructions field is very short ({inst_len} chars) - "
"consider adding more detail",
"warning"
))
elif yaml_path.is_file():
checks.append(ValidationResult(
"yaml_instructions", "codex-cli", False,
"Instructions field missing in agents/openai.yaml",
"warning"
))
# Check 15: Tools in openai.yaml reference existing scripts
if yaml_data.get("tools") and isinstance(yaml_data["tools"], list):
for tool in yaml_data["tools"]:
cmd = tool.get("command", "")
# Extract script path from command
script_match = re.search(r'scripts/(\S+)', cmd)
if script_match:
script_file = path / "scripts" / script_match.group(1)
if script_file.is_file():
checks.append(ValidationResult(
f"tool_script_{tool.get('name', 'unknown')}", "codex-cli", True,
f"Tool '{tool.get('name', 'unknown')}' references existing script"
))
else:
checks.append(ValidationResult(
f"tool_script_{tool.get('name', 'unknown')}", "codex-cli", False,
f"Tool '{tool.get('name', 'unknown')}' references "
f"missing script: {script_match.group(1)}",
"error"
))
# =========================================================
# Cross-Platform Checks
# =========================================================
# Check 16: File encoding (UTF-8)
for file_path in path.rglob("*"):
if file_path.is_file() and file_path.suffix in (".md", ".yaml", ".yml", ".py"):
try:
with open(file_path, "r", encoding="utf-8") as f:
f.read()
except UnicodeDecodeError:
checks.append(ValidationResult(
"utf8_encoding", "cross-platform", False,
f"File not UTF-8 encoded: {file_path.relative_to(path)}",
"error"
))
break
else:
checks.append(ValidationResult(
"utf8_encoding", "cross-platform", True,
"All text files are UTF-8 encoded"
))
# Check 17: Skill size
total_size = sum(
f.stat().st_size for f in path.rglob("*") if f.is_file()
)
size_kb = total_size / 1024
if size_kb < 1024:
checks.append(ValidationResult(
"skill_size", "cross-platform", True,
f"Skill size: {size_kb:.1f} KB (under 1 MB - good)"
))
else:
checks.append(ValidationResult(
"skill_size", "cross-platform", False,
f"Skill size: {size_kb:.1f} KB (over 1 MB - consider reducing)",
"warning"
))
# Build summary
errors = [c for c in checks if not c.passed and c.severity == "error"]
warnings = [c for c in checks if not c.passed and c.severity == "warning"]
infos = [c for c in checks if not c.passed and c.severity == "info"]
passed = [c for c in checks if c.passed]
if strict:
is_compatible = len(errors) == 0 and len(warnings) == 0
else:
is_compatible = len(errors) == 0
report = {
"skill_name": skill_name,
"skill_path": str(path),
"compatible": is_compatible,
"summary": {
"total_checks": len(checks),
"passed": len(passed),
"errors": len(errors),
"warnings": len(warnings),
"info": len(infos),
},
"checks": [c.to_dict() for c in checks],
}
return report
def format_human_output(report: Dict[str, Any]) -> str:
"""Format validation report as human-readable text."""
lines = []
lines.append("Cross-Platform Skill Validator")
lines.append("=" * 40)
lines.append(f"Skill: {report['skill_name']}")
lines.append(f"Path: {report['skill_path']}")
lines.append("")
# Group by platform
platforms: Dict[str, List[Dict[str, Any]]] = {}
for check in report["checks"]:
platform = check["platform"]
if platform not in platforms:
platforms[platform] = []
platforms[platform].append(check)
platform_labels = {
"claude-code": "Claude Code Compatibility",
"codex-cli": "Codex CLI Compatibility",
"cross-platform": "Cross-Platform Checks",
}
for platform_key in ["claude-code", "codex-cli", "cross-platform"]:
if platform_key not in platforms:
continue
label = platform_labels.get(platform_key, platform_key)
lines.append(f"{label}:")
for check in platforms[platform_key]:
if check["passed"]:
status = "[PASS]"
elif check["severity"] == "warning":
status = "[WARN]"
elif check["severity"] == "info":
status = "[INFO]"
else:
status = "[FAIL]"
lines.append(f" {status} {check['message']}")
lines.append("")
# Summary
s = report["summary"]
compat_str = "COMPATIBLE" if report["compatible"] else "NOT COMPATIBLE"
detail_parts = []
if s["errors"] > 0:
detail_parts.append(f"{s['errors']} error(s)")
if s["warnings"] > 0:
detail_parts.append(f"{s['warnings']} warning(s)")
detail = f" ({', '.join(detail_parts)})" if detail_parts else ""
lines.append(f"Overall: {compat_str}{detail}")
lines.append(f"Checks: {s['passed']}/{s['total_checks']} passed")
return "\n".join(lines)
def main() -> None:
parser = argparse.ArgumentParser(
description="Validate a skill directory for Claude Code and Codex CLI compatibility.",
epilog="Example: python cross_platform_validator.py path/to/skill-dir",
)
parser.add_argument(
"skill_dir",
help="Path to the skill directory to validate",
)
parser.add_argument(
"--strict",
action="store_true",
help="Treat warnings as errors (fail if any warnings)",
)
parser.add_argument(
"--json",
action="store_true",
help="Output results in JSON format",
)
args = parser.parse_args()
if not Path(args.skill_dir).is_dir():
print(f"Error: '{args.skill_dir}' is not a directory", file=sys.stderr)
sys.exit(1)
report = validate_skill(args.skill_dir, strict=args.strict)
if args.json:
print(json.dumps(report, indent=2))
else:
print(format_human_output(report))
if not report["compatible"]:
sys.exit(1)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Skills Index Builder
Builds a skills-index.json manifest from a directory of skills. Scans for
skill directories containing SKILL.md files, extracts metadata, and produces
a structured index for skill registries and discovery systems.
Usage:
python skills_index_builder.py /path/to/skills
python skills_index_builder.py /path/to/skills --output skills-index.json
python skills_index_builder.py /path/to/skills --format human
python skills_index_builder.py /path/to/skills --category engineering
"""
import argparse
import json
import os
import re
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
INDEX_VERSION = "1.0.0"
def parse_yaml_frontmatter(content: str) -> Dict[str, Any]:
"""Parse YAML frontmatter from markdown content.
Returns a flat-ish dict of frontmatter fields.
"""
result: Dict[str, Any] = {}
if not content.startswith("---"):
return result
lines = content.split("\n")
end_index = -1
for i in range(1, len(lines)):
if lines[i].strip() == "---":
end_index = i
break
if end_index == -1:
return result
fm_lines = lines[1:end_index]
current_key: Optional[str] = None
current_value = ""
nested_dict: Dict[str, str] = {}
nested_key: Optional[str] = None
for line in fm_lines:
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
indent = len(line) - len(line.lstrip())
# Top-level key
match = re.match(r'^(\w[\w-]*)\s*:\s*(.*)', stripped)
if match and indent == 0:
# Save previous
if current_key:
if nested_key and nested_dict:
result[current_key] = dict(nested_dict)
nested_dict = {}
nested_key = None
elif current_value:
result[current_key] = current_value.strip()
current_key = match.group(1)
val = match.group(2).strip()
# Remove YAML flow indicators
if val in (">", "|", "|-", ">-"):
current_value = ""
elif val:
if (val.startswith('"') and val.endswith('"')) or \
(val.startswith("'") and val.endswith("'")):
val = val[1:-1]
current_value = val
else:
current_value = ""
nested_key = current_key
continue
# Nested key: value
if indent > 0 and nested_key:
nested_match = re.match(r'^(\w[\w-]*)\s*:\s*(.*)', stripped)
if nested_match:
nkey = nested_match.group(1)
nval = nested_match.group(2).strip()
if (nval.startswith('"') and nval.endswith('"')) or \
(nval.startswith("'") and nval.endswith("'")):
nval = nval[1:-1]
nested_dict[nkey] = nval
continue
# Multi-line continuation
if indent > 0 and current_key:
if current_value:
current_value += " " + stripped
else:
current_value = stripped
# Final key
if current_key:
if nested_key and nested_dict:
result[current_key] = dict(nested_dict)
elif current_value:
result[current_key] = current_value.strip()
return result
def detect_platforms(skill_dir: Path) -> List[str]:
"""Detect which platforms a skill supports."""
platforms = []
if (skill_dir / "SKILL.md").is_file():
platforms.append("claude-code")
if (skill_dir / "agents" / "openai.yaml").is_file():
platforms.append("codex-cli")
# Check for other platform markers
if (skill_dir / ".cursorrules").is_file():
platforms.append("cursor")
if (skill_dir / ".github" / "copilot-instructions.md").is_file():
platforms.append("github-copilot")
return platforms
def get_directory_size(dir_path: Path) -> int:
"""Calculate total size of all files in a directory."""
total = 0
for f in dir_path.rglob("*"):
if f.is_file():
try:
total += f.stat().st_size
except OSError:
pass
return total
def count_files_by_type(dir_path: Path) -> Dict[str, int]:
"""Count files grouped by extension."""
counts: Dict[str, int] = {}
for f in dir_path.rglob("*"):
if f.is_file():
ext = f.suffix.lower() or "(no extension)"
counts[ext] = counts.get(ext, 0) + 1
return counts
def scan_skill(skill_dir: Path) -> Optional[Dict[str, Any]]:
"""Scan a single skill directory and extract metadata.
Returns None if the directory is not a valid skill.
"""
skill_md_path = skill_dir / "SKILL.md"
if not skill_md_path.is_file():
return None
try:
with open(skill_md_path, "r", encoding="utf-8") as f:
content = f.read()
except (OSError, UnicodeDecodeError):
return None
frontmatter = parse_yaml_frontmatter(content)
name = frontmatter.get("name", skill_dir.name)
description = frontmatter.get("description", "")
license_val = frontmatter.get("license", "")
# Extract metadata
metadata = frontmatter.get("metadata", {})
version = "0.0.0"
category = ""
domain = ""
if isinstance(metadata, dict):
version = metadata.get("version", "0.0.0")
category = metadata.get("category", "")
domain = metadata.get("domain", "")
# Find scripts
scripts_dir = skill_dir / "scripts"
tools: List[str] = []
if scripts_dir.is_dir():
tools = sorted([f.name for f in scripts_dir.glob("*.py")])
# Find references
refs_dir = skill_dir / "references"
references: List[str] = []
if refs_dir.is_dir():
references = sorted([f.name for f in refs_dir.glob("*.md")])
# Find assets
assets_dir = skill_dir / "assets"
assets: List[str] = []
if assets_dir.is_dir():
assets = sorted([f.name for f in assets_dir.iterdir() if f.is_file()])
# Detect platforms
platforms = detect_platforms(skill_dir)
# Calculate size
total_size = get_directory_size(skill_dir)
# Extract title from markdown body
title = ""
body_start = content.find("\n---\n")
if body_start >= 0:
body = content[body_start + 5:]
else:
body = content
for line in body.split("\n"):
if line.strip().startswith("# "):
title = line.strip()[2:].strip()
break
# Extract keywords if present
keywords: List[str] = []
kw_match = re.search(
r'##\s+Keywords\s*\n+(.+?)(?:\n\n|\n##)',
content,
re.DOTALL
)
if kw_match:
kw_text = kw_match.group(1).strip()
keywords = [k.strip() for k in kw_text.split(",") if k.strip()]
return {
"name": name,
"title": title or name,
"description": description,
"version": version,
"license": license_val,
"category": category,
"domain": domain,
"keywords": keywords,
"tools": tools,
"tools_count": len(tools),
"references": references,
"references_count": len(references),
"assets": assets,
"assets_count": len(assets),
"platforms": platforms,
"size_bytes": total_size,
"size_human": format_size(total_size),
"path": str(skill_dir.relative_to(skill_dir.parent)),
}
def format_size(size_bytes: int) -> str:
"""Format byte size to human readable string."""
if size_bytes < 1024:
return f"{size_bytes} B"
elif size_bytes < 1024 * 1024:
return f"{size_bytes / 1024:.1f} KB"
else:
return f"{size_bytes / (1024 * 1024):.1f} MB"
def build_index(
skills_dir: str,
category_filter: Optional[str] = None,
) -> Dict[str, Any]:
"""Build a skills index from a directory of skill subdirectories.
Returns the complete index dict.
"""
path = Path(skills_dir).resolve()
if not path.is_dir():
return {
"error": f"Directory not found: {skills_dir}",
"version": INDEX_VERSION,
"skills": [],
"skills_count": 0,
}
skills: List[Dict[str, Any]] = []
# Scan all subdirectories for SKILL.md
for item in sorted(path.iterdir()):
if not item.is_dir():
continue
if item.name.startswith("."):
continue
skill_data = scan_skill(item)
if skill_data is None:
continue
# Apply category filter
if category_filter:
if skill_data.get("category", "").lower() != category_filter.lower():
continue
skills.append(skill_data)
# Build summary stats
total_tools = sum(s["tools_count"] for s in skills)
total_refs = sum(s["references_count"] for s in skills)
total_size = sum(s["size_bytes"] for s in skills)
categories: Dict[str, int] = {}
domains: Dict[str, int] = {}
platform_counts: Dict[str, int] = {}
for s in skills:
cat = s.get("category") or "uncategorized"
categories[cat] = categories.get(cat, 0) + 1
dom = s.get("domain") or "unspecified"
domains[dom] = domains.get(dom, 0) + 1
for p in s.get("platforms", []):
platform_counts[p] = platform_counts.get(p, 0) + 1
index = {
"version": INDEX_VERSION,
"generated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"source_directory": str(path),
"skills_count": len(skills),
"summary": {
"total_tools": total_tools,
"total_references": total_refs,
"total_size": format_size(total_size),
"total_size_bytes": total_size,
"categories": categories,
"domains": domains,
"platforms": platform_counts,
},
"skills": skills,
}
return index
def format_human_output(index: Dict[str, Any]) -> str:
"""Format index as human-readable text."""
lines = []
lines.append("Skills Index")
lines.append("=" * 60)
lines.append(f"Source: {index.get('source_directory', 'N/A')}")
lines.append(f"Generated: {index.get('generated_at', 'N/A')}")
lines.append(f"Skills: {index.get('skills_count', 0)}")
lines.append("")
summary = index.get("summary", {})
lines.append(f"Total Tools: {summary.get('total_tools', 0)}")
lines.append(f"Total References: {summary.get('total_references', 0)}")
lines.append(f"Total Size: {summary.get('total_size', '0 B')}")
lines.append("")
cats = summary.get("categories", {})
if cats:
lines.append("Categories:")
for cat, count in sorted(cats.items()):
lines.append(f" {cat}: {count}")
lines.append("")
platforms = summary.get("platforms", {})
if platforms:
lines.append("Platform Support:")
for plat, count in sorted(platforms.items()):
lines.append(f" {plat}: {count} skill(s)")
lines.append("")
lines.append("-" * 60)
lines.append(f"{'Name':<30} {'Version':<10} {'Tools':<8} {'Platforms'}")
lines.append("-" * 60)
for skill in index.get("skills", []):
name = skill.get("name", "unknown")
version = skill.get("version", "?")
tools = str(skill.get("tools_count", 0))
platforms_str = ", ".join(skill.get("platforms", []))
# Truncate name if too long
if len(name) > 28:
name = name[:25] + "..."
lines.append(f"{name:<30} {version:<10} {tools:<8} {platforms_str}")
lines.append("-" * 60)
lines.append(f"Total: {index.get('skills_count', 0)} skills")
return "\n".join(lines)
def main() -> None:
parser = argparse.ArgumentParser(
description="Build a skills-index.json manifest from a directory of skills.",
epilog="Example: python skills_index_builder.py /path/to/skills --output skills-index.json",
)
parser.add_argument(
"skills_dir",
help="Path to the directory containing skill subdirectories",
)
parser.add_argument(
"--output", "-o",
default=None,
help="Output file path (default: print to stdout)",
)
parser.add_argument(
"--format", "-f",
choices=["json", "human"],
default="json",
help="Output format (default: json)",
)
parser.add_argument(
"--category", "-c",
default=None,
help="Filter skills by category",
)
args = parser.parse_args()
if not Path(args.skills_dir).is_dir():
print(f"Error: '{args.skills_dir}' is not a directory", file=sys.stderr)
sys.exit(1)
index = build_index(args.skills_dir, category_filter=args.category)
if "error" in index and index["skills_count"] == 0:
print(f"Error: {index['error']}", file=sys.stderr)
sys.exit(1)
if args.format == "human":
output_text = format_human_output(index)
else:
output_text = json.dumps(index, indent=2)
if args.output:
output_path = Path(args.output)
try:
output_path.parent.mkdir(parents=True, exist_ok=True)
with open(output_path, "w", encoding="utf-8") as f:
f.write(output_text)
f.write("\n")
if args.format == "human":
print(f"Index written to {output_path}")
else:
print(f"Index written to {output_path} ({index['skills_count']} skills)")
except OSError as e:
print(f"Error writing output: {e}", file=sys.stderr)
sys.exit(1)
else:
print(output_text)
if __name__ == "__main__":
main()