| 1 | #!/usr/bin/env python3 |
| 2 | """ |
| 3 | Skill Initializer - Creates a new skill from template |
| 4 | |
| 5 | Usage: |
| 6 | init_skill.py <skill-name> --path <path> [--resources scripts,references,assets] [--examples] |
| 7 | |
| 8 | Examples: |
| 9 | init_skill.py my-new-skill --path skills/public |
| 10 | init_skill.py my-new-skill --path skills/public --resources scripts,references |
| 11 | init_skill.py my-api-helper --path skills/private --resources scripts --examples |
| 12 | init_skill.py custom-skill --path /custom/location |
| 13 | """ |
| 14 | |
| 15 | import argparse |
| 16 | import re |
| 17 | import sys |
| 18 | from pathlib import Path |
| 19 | |
| 20 | MAX_SKILL_NAME_LENGTH = 64 |
| 21 | ALLOWED_RESOURCES = {"scripts", "references", "assets"} |
| 22 | |
| 23 | SKILL_TEMPLATE = """--- |
| 24 | name: {skill_name} |
| 25 | description: [TODO: Complete and informative explanation of what the skill does and when to use it. Include WHEN to use this skill - specific scenarios, file types, or tasks that trigger it.] |
| 26 | --- |
| 27 | |
| 28 | # {skill_title} |
| 29 | |
| 30 | ## Overview |
| 31 | |
| 32 | [TODO: 1-2 sentences explaining what this skill enables] |
| 33 | |
| 34 | ## Structuring This Skill |
| 35 | |
| 36 | [TODO: Choose the structure that best fits this skill's purpose. Common patterns: |
| 37 | |
| 38 | **1. Workflow-Based** (best for sequential processes) |
| 39 | - Works well when there are clear step-by-step procedures |
| 40 | - Example: DOCX skill with "Workflow Decision Tree" -> "Reading" -> "Creating" -> "Editing" |
| 41 | - Structure: ## Overview -> ## Workflow Decision Tree -> ## Step 1 -> ## Step 2... |
| 42 | |
| 43 | **2. Task-Based** (best for tool collections) |
| 44 | - Works well when the skill offers different operations/capabilities |
| 45 | - Example: PDF skill with "Quick Start" -> "Merge PDFs" -> "Split PDFs" -> "Extract Text" |
| 46 | - Structure: ## Overview -> ## Quick Start -> ## Task Category 1 -> ## Task Category 2... |
| 47 | |
| 48 | **3. Reference/Guidelines** (best for standards or specifications) |
| 49 | - Works well for brand guidelines, coding standards, or requirements |
| 50 | - Example: Brand styling with "Brand Guidelines" -> "Colors" -> "Typography" -> "Features" |
| 51 | - Structure: ## Overview -> ## Guidelines -> ## Specifications -> ## Usage... |
| 52 | |
| 53 | **4. Capabilities-Based** (best for integrated systems) |
| 54 | - Works well when the skill provides multiple interrelated features |
| 55 | - Example: Product Management with "Core Capabilities" -> numbered capability list |
| 56 | - Structure: ## Overview -> ## Core Capabilities -> ### 1. Feature -> ### 2. Feature... |
| 57 | |
| 58 | Patterns can be mixed and matched as needed. Most skills combine patterns (e.g., start with task-based, add workflow for complex operations). |
| 59 | |
| 60 | Delete this entire "Structuring This Skill" section when done - it's just guidance.] |
| 61 | |
| 62 | ## [TODO: Replace with the first main section based on chosen structure] |
| 63 | |
| 64 | [TODO: Add content here. See examples in existing skills: |
| 65 | - Code samples for technical skills |
| 66 | - Decision trees for complex workflows |
| 67 | - Concrete examples with realistic user requests |
| 68 | - References to scripts/templates/references as needed] |
| 69 | |
| 70 | ## Resources (optional) |
| 71 | |
| 72 | Create only the resource directories this skill actually needs. Delete this section if no resources are required. |
| 73 | |
| 74 | ### scripts/ |
| 75 | Executable code (Python/Bash/etc.) that can be run directly to perform specific operations. |
| 76 | |
| 77 | **Examples from other skills:** |
| 78 | - PDF skill: `fill_fillable_fields.py`, `extract_form_field_info.py` - utilities for PDF manipulation |
| 79 | - DOCX skill: `document.py`, `utilities.py` - Python modules for document processing |
| 80 | |
| 81 | **Appropriate for:** Python scripts, shell scripts, or any executable code that performs automation, data processing, or specific operations. |
| 82 | |
| 83 | **Note:** Scripts may be executed without loading into context, but can still be read by Codex for patching or environment adjustments. |
| 84 | |
| 85 | ### references/ |
| 86 | Documentation and reference material intended to be loaded into context to inform Codex's process and thinking. |
| 87 | |
| 88 | **Examples from other skills:** |
| 89 | - Product management: `communication.md`, `context_building.md` - detailed workflow guides |
| 90 | - BigQuery: API reference documentation and query examples |
| 91 | - Finance: Schema documentation, company policies |
| 92 | |
| 93 | **Appropriate for:** In-depth documentation, API references, database schemas, comprehensive guides, or any detailed information that Codex should reference while working. |
| 94 | |
| 95 | ### assets/ |
| 96 | Files not intended to be loaded into context, but rather used within the output Codex produces. |
| 97 | |
| 98 | **Examples from other skills:** |
| 99 | - Brand styling: PowerPoint template files (.pptx), logo files |
| 100 | - Frontend builder: HTML/React boilerplate project directories |
| 101 | - Typography: Font files (.ttf, .woff2) |
| 102 | |
| 103 | **Appropriate for:** Templates, boilerplate code, document templates, images, icons, fonts, or any files meant to be copied or used in the final output. |
| 104 | |
| 105 | --- |
| 106 | |
| 107 | **Not every skill requires all three types of resources.** |
| 108 | """ |
| 109 | |
| 110 | EXAMPLE_SCRIPT = '''#!/usr/bin/env python3 |
| 111 | """ |
| 112 | Example helper script for {skill_name} |
| 113 | |
| 114 | This is a placeholder script that can be executed directly. |
| 115 | Replace with actual implementation or delete if not needed. |
| 116 | |
| 117 | Example real scripts from other skills: |
| 118 | - pdf/scripts/fill_fillable_fields.py - Fills PDF form fields |
| 119 | - pdf/scripts/convert_pdf_to_images.py - Converts PDF pages to images |
| 120 | """ |
| 121 | |
| 122 | def main(): |
| 123 | print("This is an example script for {skill_name}") |
| 124 | # TODO: Add actual script logic here |
| 125 | # This could be data processing, file conversion, API calls, etc. |
| 126 | |
| 127 | if __name__ == "__main__": |
| 128 | main() |
| 129 | ''' |
| 130 | |
| 131 | EXAMPLE_REFERENCE = """# Reference Documentation for {skill_title} |
| 132 | |
| 133 | This is a placeholder for detailed reference documentation. |
| 134 | Replace with actual reference content or delete if not needed. |
| 135 | |
| 136 | Example real reference docs from other skills: |
| 137 | - product-management/references/communication.md - Comprehensive guide for status updates |
| 138 | - product-management/references/context_building.md - Deep-dive on gathering context |
| 139 | - bigquery/references/ - API references and query examples |
| 140 | |
| 141 | ## When Reference Docs Are Useful |
| 142 | |
| 143 | Reference docs are ideal for: |
| 144 | - Comprehensive API documentation |
| 145 | - Detailed workflow guides |
| 146 | - Complex multi-step processes |
| 147 | - Information too lengthy for main SKILL.md |
| 148 | - Content that's only needed for specific use cases |
| 149 | |
| 150 | ## Structure Suggestions |
| 151 | |
| 152 | ### API Reference Example |
| 153 | - Overview |
| 154 | - Authentication |
| 155 | - Endpoints with examples |
| 156 | - Error codes |
| 157 | - Rate limits |
| 158 | |
| 159 | ### Workflow Guide Example |
| 160 | - Prerequisites |
| 161 | - Step-by-step instructions |
| 162 | - Common patterns |
| 163 | - Troubleshooting |
| 164 | - Best practices |
| 165 | """ |
| 166 | |
| 167 | EXAMPLE_ASSET = """# Example Asset File |
| 168 | |
| 169 | This placeholder represents where asset files would be stored. |
| 170 | Replace with actual asset files (templates, images, fonts, etc.) or delete if not needed. |
| 171 | |
| 172 | Asset files are NOT intended to be loaded into context, but rather used within |
| 173 | the output Codex produces. |
| 174 | |
| 175 | Example asset files from other skills: |
| 176 | - Brand guidelines: logo.png, slides_template.pptx |
| 177 | - Frontend builder: hello-world/ directory with HTML/React boilerplate |
| 178 | - Typography: custom-font.ttf, font-family.woff2 |
| 179 | - Data: sample_data.csv, test_dataset.json |
| 180 | |
| 181 | ## Common Asset Types |
| 182 | |
| 183 | - Templates: .pptx, .docx, boilerplate directories |
| 184 | - Images: .png, .jpg, .svg, .gif |
| 185 | - Fonts: .ttf, .otf, .woff, .woff2 |
| 186 | - Boilerplate code: Project directories, starter files |
| 187 | - Icons: .ico, .svg |
| 188 | - Data files: .csv, .json, .xml, .yaml |
| 189 | |
| 190 | Note: This is a text placeholder. Actual assets can be any file type. |
| 191 | """ |
| 192 | |
| 193 | |
| 194 | def normalize_skill_name(skill_name): |
| 195 | """Normalize a skill name to lowercase hyphen-case.""" |
| 196 | normalized = skill_name.strip().lower() |
| 197 | normalized = re.sub(r"[^a-z0-9]+", "-", normalized) |
| 198 | normalized = normalized.strip("-") |
| 199 | normalized = re.sub(r"-{2,}", "-", normalized) |
| 200 | return normalized |
| 201 | |
| 202 | |
| 203 | def title_case_skill_name(skill_name): |
| 204 | """Convert hyphenated skill name to Title Case for display.""" |
| 205 | return " ".join(word.capitalize() for word in skill_name.split("-")) |
| 206 | |
| 207 | |
| 208 | def parse_resources(raw_resources): |
| 209 | if not raw_resources: |
| 210 | return [] |
| 211 | resources = [item.strip() for item in raw_resources.split(",") if item.strip()] |
| 212 | invalid = sorted({item for item in resources if item not in ALLOWED_RESOURCES}) |
| 213 | if invalid: |
| 214 | allowed = ", ".join(sorted(ALLOWED_RESOURCES)) |
| 215 | print(f"[ERROR] Unknown resource type(s): {', '.join(invalid)}") |
| 216 | print(f" Allowed: {allowed}") |
| 217 | sys.exit(1) |
| 218 | deduped = [] |
| 219 | seen = set() |
| 220 | for resource in resources: |
| 221 | if resource not in seen: |
| 222 | deduped.append(resource) |
| 223 | seen.add(resource) |
| 224 | return deduped |
| 225 | |
| 226 | |
| 227 | def create_resource_dirs(skill_dir, skill_name, skill_title, resources, include_examples): |
| 228 | for resource in resources: |
| 229 | resource_dir = skill_dir / resource |
| 230 | resource_dir.mkdir(exist_ok=True) |
| 231 | if resource == "scripts": |
| 232 | if include_examples: |
| 233 | example_script = resource_dir / "example.py" |
| 234 | example_script.write_text(EXAMPLE_SCRIPT.format(skill_name=skill_name)) |
| 235 | example_script.chmod(0o755) |
| 236 | print("[OK] Created scripts/example.py") |
| 237 | else: |
| 238 | print("[OK] Created scripts/") |
| 239 | elif resource == "references": |
| 240 | if include_examples: |
| 241 | example_reference = resource_dir / "api_reference.md" |
| 242 | example_reference.write_text(EXAMPLE_REFERENCE.format(skill_title=skill_title)) |
| 243 | print("[OK] Created references/api_reference.md") |
| 244 | else: |
| 245 | print("[OK] Created references/") |
| 246 | elif resource == "assets": |
| 247 | if include_examples: |
| 248 | example_asset = resource_dir / "example_asset.txt" |
| 249 | example_asset.write_text(EXAMPLE_ASSET) |
| 250 | print("[OK] Created assets/example_asset.txt") |
| 251 | else: |
| 252 | print("[OK] Created assets/") |
| 253 | |
| 254 | |
| 255 | def init_skill(skill_name, path, resources, include_examples): |
| 256 | """ |
| 257 | Initialize a new skill directory with template SKILL.md. |
| 258 | |
| 259 | Args: |
| 260 | skill_name: Name of the skill |
| 261 | path: Path where the skill directory should be created |
| 262 | resources: Resource directories to create |
| 263 | include_examples: Whether to create example files in resource directories |
| 264 | |
| 265 | Returns: |
| 266 | Path to created skill directory, or None if error |
| 267 | """ |
| 268 | # Determine skill directory path |
| 269 | skill_dir = Path(path).resolve() / skill_name |
| 270 | |
| 271 | # Check if directory already exists |
| 272 | if skill_dir.exists(): |
| 273 | print(f"[ERROR] Skill directory already exists: {skill_dir}") |
| 274 | return None |
| 275 | |
| 276 | # Create skill directory |
| 277 | try: |
| 278 | skill_dir.mkdir(parents=True, exist_ok=False) |
| 279 | print(f"[OK] Created skill directory: {skill_dir}") |
| 280 | except Exception as e: |
| 281 | print(f"[ERROR] Error creating directory: {e}") |
| 282 | return None |
| 283 | |
| 284 | # Create SKILL.md from template |
| 285 | skill_title = title_case_skill_name(skill_name) |
| 286 | skill_content = SKILL_TEMPLATE.format(skill_name=skill_name, skill_title=skill_title) |
| 287 | |
| 288 | skill_md_path = skill_dir / "SKILL.md" |
| 289 | try: |
| 290 | skill_md_path.write_text(skill_content) |
| 291 | print("[OK] Created SKILL.md") |
| 292 | except Exception as e: |
| 293 | print(f"[ERROR] Error creating SKILL.md: {e}") |
| 294 | return None |
| 295 | |
| 296 | # Create resource directories if requested |
| 297 | if resources: |
| 298 | try: |
| 299 | create_resource_dirs(skill_dir, skill_name, skill_title, resources, include_examples) |
| 300 | except Exception as e: |
| 301 | print(f"[ERROR] Error creating resource directories: {e}") |
| 302 | return None |
| 303 | |
| 304 | # Print next steps |
| 305 | print(f"\n[OK] Skill '{skill_name}' initialized successfully at {skill_dir}") |
| 306 | print("\nNext steps:") |
| 307 | print("1. Edit SKILL.md to complete the TODO items and update the description") |
| 308 | if resources: |
| 309 | if include_examples: |
| 310 | print("2. Customize or delete the example files in scripts/, references/, and assets/") |
| 311 | else: |
| 312 | print("2. Add resources to scripts/, references/, and assets/ as needed") |
| 313 | else: |
| 314 | print("2. Create resource directories only if needed (scripts/, references/, assets/)") |
| 315 | print("3. Run the validator when ready to check the skill structure") |
| 316 | |
| 317 | return skill_dir |
| 318 | |
| 319 | |
| 320 | def main(): |
| 321 | parser = argparse.ArgumentParser( |
| 322 | description="Create a new skill directory with a SKILL.md template.", |
| 323 | ) |
| 324 | parser.add_argument("skill_name", help="Skill name (normalized to hyphen-case)") |
| 325 | parser.add_argument("--path", required=True, help="Output directory for the skill") |
| 326 | parser.add_argument( |
| 327 | "--resources", |
| 328 | default="", |
| 329 | help="Comma-separated list: scripts,references,assets", |
| 330 | ) |
| 331 | parser.add_argument( |
| 332 | "--examples", |
| 333 | action="store_true", |
| 334 | help="Create example files inside the selected resource directories", |
| 335 | ) |
| 336 | args = parser.parse_args() |
| 337 | |
| 338 | raw_skill_name = args.skill_name |
| 339 | skill_name = normalize_skill_name(raw_skill_name) |
| 340 | if not skill_name: |
| 341 | print("[ERROR] Skill name must include at least one letter or digit.") |
| 342 | sys.exit(1) |
| 343 | if len(skill_name) > MAX_SKILL_NAME_LENGTH: |
| 344 | print( |
| 345 | f"[ERROR] Skill name '{skill_name}' is too long ({len(skill_name)} characters). " |
| 346 | f"Maximum is {MAX_SKILL_NAME_LENGTH} characters." |
| 347 | ) |
| 348 | sys.exit(1) |
| 349 | if skill_name != raw_skill_name: |
| 350 | print(f"Note: Normalized skill name from '{raw_skill_name}' to '{skill_name}'.") |
| 351 | |
| 352 | resources = parse_resources(args.resources) |
| 353 | if args.examples and not resources: |
| 354 | print("[ERROR] --examples requires --resources to be set.") |
| 355 | sys.exit(1) |
| 356 | |
| 357 | path = args.path |
| 358 | |
| 359 | print(f"Initializing skill: {skill_name}") |
| 360 | print(f" Location: {path}") |
| 361 | if resources: |
| 362 | print(f" Resources: {', '.join(resources)}") |
| 363 | if args.examples: |
| 364 | print(" Examples: enabled") |
| 365 | else: |
| 366 | print(" Resources: none (create as needed)") |
| 367 | print() |
| 368 | |
| 369 | result = init_skill(skill_name, path, resources, args.examples) |
| 370 | |
| 371 | if result: |
| 372 | sys.exit(0) |
| 373 | else: |
| 374 | sys.exit(1) |
| 375 | |
| 376 | |
| 377 | if __name__ == "__main__": |
| 378 | main() |
| 379 |