
Create Skill
- 43 installs
- 269 repo stars
- Updated June 11, 2026
- gupsammy/claudest
Scaffold and author new agent skills with proper structure.
About
An agent skill that scaffolds and authors new agent skills with the correct structure and metadata. A builder uses it to create reusable skills for an agent. Content is name-only, so the exact templates are inferred from the create-skill convention.
- Skill scaffolding
- Agent extension
- Skill authoring
Create Skill by the numbers
- 43 all-time installs (skills.sh)
- Ranked #345 of 782 Skill Development skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/gupsammy/claudest --skill create-skillAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 43 |
|---|---|
| repo stars | ★ 269 |
| Security audit | 2 / 3 scanners passed |
| Last updated | June 11, 2026 |
| Repository | gupsammy/claudest ↗ |
What it does
Scaffold and author new agent skills with proper structure.
Files
Skill & Command Generator
Generate well-structured skills or slash commands. Both are markdown files with YAML frontmatter—they share the same structure but differ in how they're triggered and described.
Phase 0: Understand Requirements
Parse $ARGUMENTS for type hint. If $ARGUMENTS is empty or insufficient, use AskUserQuestion to gather requirements — users are often unclear on what type of artifact they need or what the best design is.
Use AskUserQuestion to collect: 1. Primary objective — What should this do? 2. Trigger scenarios — When should it activate? 3. Inputs/outputs — What does it receive and produce? 4. Complexity — Simple, standard, or complex? 5. Execution needs — Isolated context? Delegated to specialized agent? Proceed to Phase 1 when at minimum Objective and Trigger Scenarios are established. Remaining dimensions can be resolved during generation.
Phase 1: Generate
Apply these principles throughout generation: use imperative voice and terse phrasing because every token in a generated skill body costs budget on every invocation, and Claude extrapolates well from precise nudges. Prefer instruction over example — state the rule with its reasoning so it generalizes to every input.
If creating a new skill directory (not editing an existing file):
python3 ${CLAUDE_PLUGIN_ROOT}/skills/create-skill/scripts/init_skill.py <name> --path <dir> [--resources scripts,references,assets] [--examples]Exit 0 = directory created, proceed to Step 1. Exit 1 = naming collision; ask user whether to overwrite or rename.
Step 1 — Choose type
- Skills: Trigger-rich, third-person description ("This skill should be used when..."); auto-triggered by routing
- Commands: Concise, verb-first description, under 60 chars; user-invoked via
/menu
Step 2 — Write frontmatter
Read ${CLAUDE_PLUGIN_ROOT}/skills/create-skill/references/frontmatter-options.md for the full field catalog, description patterns, tool selection framework, and execution modifiers.
Description density rules: Keep descriptions under 150 tokens (200 absolute max) — they load every session. Descriptions over 250 characters are truncated in the skill listing. Derive trigger phrases from the user's actual words in Phase 0, not paraphrases. See the token budget, pushy routing pattern, and trigger derivation principles in frontmatter-options.md.
Intensional over extensional — apply to all generated content. State the rule directly with its reasoning rather than listing examples that imply the rule. An intensional rule ("quoted phrases must be verbatim user speech because routing matches on literal tokens") generalizes to every input the skill will encounter. An extensional approach requires the reader to reverse-engineer the rule — two reasoning hops instead of one, covering only the shape of those specific examples. Since this skill generates instructions that will themselves guide further generation, the quality of reasoning propagates.
Step 3 — Validate description discoverability
Before writing the body, verify the description will route correctly. Mentally generate:
1. 3 should-trigger prompts — realistic user messages that should activate this skill. Include at least one naive phrasing from a user who has never heard of the skill. 2. 3 should-NOT-trigger prompts — messages in adjacent domains that are close but should not activate. These test whether the description is too broad.
Evaluate: does the description cover all should-trigger prompts? Would it plausibly reject the should-NOT-trigger prompts? If coverage is weak, revise the description — add missing trigger phrases, tighten language to exclude adjacent domains, or add a negative trigger ("Not for X").
This step catches routing misses before the rest of the skill is built. Proceed when description coverage is adequate.
Step 4 — Write body
Construction rules:
- State objective explicitly in first sentence
- Use imperative voice ("Analyze", "Generate", "Identify") — no first-person ("I will", "I am")
- Context only when necessary for understanding
- XML tags only for complex structured data
- No "When to Use This Skill" section — body loads only after triggering; routing guidance there is never read by the routing decision
- Avoid headers deeper than H3 — deep nesting signals content that belongs in
references/, notSKILL.md - Use bang-backtick syntax for dynamic context injection when real-time data (git status, file list, env vars) improves the skill without requiring a tool call
- Preserve variable bindings when collapsing code blocks to prose. Code blocks serve
two purposes: illustrating an operation and establishing workflow state. When a code block assigns variables (BASE=..., BRANCH=...) that later steps reference, collapsing it to prose without preserving the bindings leaves downstream $VAR references unbound. Add a "derive working variables" preamble that explicitly binds each variable in prose before the steps that use them
Both skills and commands follow the same body pattern:
# Name
Brief overview (1-2 sentences).
## Process
1. Step one (imperative voice)
2. Step two
3. Step threeDynamic Content:
| Syntax | Purpose |
|---|---|
$ARGUMENTS | All arguments as string |
$1, $2, $3 | Positional arguments (shell-style quoting for multi-word values) |
@path/file | Load file contents |
@$1 | Load file from argument |
| Exclamation + backticks | Execute bash command, include output |
${CLAUDE_SKILL_DIR} | Path to the skill's own directory (use for referencing bundled scripts/files) |
${CLAUDE_SESSION_ID} | Current session ID (useful for logging or session-specific output files) |
Example — injecting live context. In a skill body, write lines like:
- Current branch: !\
git branch --show-current\ - Recent commits: !\
git log --oneline -5\ - Changed files: !\
git diff --name-only\
Then add directives like "Summarize this pull request..."
These commands run when the skill is invoked. The model sees only the output — no tool calls needed. Use this for infallible probes (git status, env vars, file trees, process output) where failure is rare and the output is informational. Do not use for commands that may fail or need exit-code branching — those require Bash tool calls so the model can handle errors.
Note: The backslashes above escape the backticks so this documentation
doesn't execute — in a real skill, write !\cmd\ without the backslashes.Step 5 — Script opportunity scan
Read ${CLAUDE_PLUGIN_ROOT}/skills/create-skill/references/script-patterns.md and apply the five signal patterns to every workflow step in the skill being generated:
| Signal | Question | If yes → |
|---|---|---|
| Repeated Generation | Does any step produce the same structure with different params across invocations? | Parameterized script in scripts/ |
| Unclear Tool Choice | Does any step combine multiple tools in a fragile sequence naturally expressible as one function? | Script the procedure |
| Rigid Contract | Can you write --help text for this step right now without ambiguity? | CLI candidate — delegate design to create-cli |
| Dual-Use Potential | Would a user want to run this step from the terminal, outside the skill workflow? | Design as proper CLI from the start |
| Consistency Critical | Must this step produce bit-for-bit identical output for identical inputs? | Script — never LLM generation |
For each identified script candidate: 1. Choose the archetype from references/script-patterns.md (init/validate/transform/package/query) 2. If the interface is non-trivial, delegate to claude-skills:create-cli skill to design it 3. Scaffold the script in scripts/ using the Python template from references/script-patterns.md 4. Wire it into SKILL.md with: trigger condition, exact invocation, output interpretation
Wiring rule: A script reference must state when to invoke (trigger condition), how to invoke (exact command with flags), and what to do with the result (exit code handling, which output fields matter).
Step 6 — Check delegation
Scan for existing resources before finalizing:
Review available: skills, commands, agents, MCPs
For each workflow step, ask: "Do we already have this?"Common delegation patterns:
- Git commits →
Skill: claude-coding:commit
Always use fully qualified names:
Skill: plugin-dev:hook-development(not just "hook-development")SlashCommand: /plugin-dev:create-plugin(not just "create-plugin")Task: subagent_type=plugin-dev:agent-creator
Step 7 — Validate
python3 ${CLAUDE_PLUGIN_ROOT}/skills/create-skill/scripts/validate_skill.py <skill-directory> --output jsonExit 0 = proceed to Phase 2. Exit 1 = parse the errors array; each entry has field, message, severity. Resolve all critical and major items before writing to disk.
Phase 2: Deliver
Output Paths
| Type | Location |
|---|---|
| User skill | ~/.claude/skills/<name>/SKILL.md |
| User command | ~/.claude/commands/<name>.md |
| Project skill | .claude/skills/<name>/SKILL.md |
| Project command | .claude/commands/<name>.md |
Write and Confirm
Before writing:
Writing to: [path]
This will [create new / overwrite existing] file.
Proceed?Explain Your Choices
When presenting the generated skill/command to the user, briefly explain:
- What you set and why — "Added
allowed-toolsto scope Bash to git commands only, since the skill only needs git for commits" - What you excluded and why — "
hooksomitted (no validation needed),disable-model-invocationleft unset (auto-triggering is appropriate)" - Add more trigger phrases if routing misses expected inputs
Package for Distribution
Only when user explicitly requests a distributable file, run:
python3 ${CLAUDE_PLUGIN_ROOT}/skills/create-skill/scripts/package_skill.py <skill-directory> [output-dir]Exit 0 = .skill file created at output path. Exit 1 = validation failed; read stdout for details.
After Creation
Summarize what was created:
- Name and type
- Path
- How to invoke/trigger
- Suggested test scenario
Phase 3: Structural Lint
After writing the skill to disk, invoke the skill-lint agent to run a structural audit:
Use Task tool with subagent_type=claude-skills:skill-lint:
"Lint the skill at <path-to-skill-directory>. Auto-apply critical and major fixes, report
minor findings for user decision."Wait for the agent to complete. If it auto-applied fixes, note them in the Phase 4 summary. If it reports minor findings, include them in the evaluation output for the user to decide.
Proceed to Phase 4 when the lint agent returns.
Phase 4: Evaluate
Score the generated skill/command:
| Dimension | Criteria |
|---|---|
| Clarity (0-10) | Instructions unambiguous, objective clear |
| Precision (0-10) | Appropriate specificity without over-constraint |
| Efficiency (0-10) | Token economy—maximum value per token |
| Completeness (0-10) | Covers requirements without gaps or excess |
| Usability (0-10) | Practical, actionable, appropriate for target use |
Target: 9.0/10.0. If below, refine once addressing the weakest dimension, then deliver.
Before finalizing, load ${CLAUDE_PLUGIN_ROOT}/skills/create-skill/references/generation-standards.md and verify the validation checklist passes.
---
Execute phases sequentially.
Frontmatter Options & Patterns Reference
Authoritative source for skill/command frontmatter. Keep current with Claude Code releases — this file is the single source of truth used by create-skill. No live documentation fetch is performed; accuracy depends on this file being maintained.
Load before writing frontmatter in Phase 1, Step 2. Contains the full field catalog, description patterns, execution modifiers, tool selection framework, and progressive disclosure patterns.
---
Essential Frontmatter
Every skill needs these fields. Start here.
# Complete field catalog — most skills only need: name, description, allowed-tools
---
name: identifier # Required — unique skill identifier
description: > # How it's described/triggered (see patterns below)
[See description patterns below]
allowed-tools: # Restrict available tools (see Tool Selection below)
- Read
- Grep
- Bash(git:*)
# Lifecycle hooks (optional, scoped to this skill's lifetime)
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "scripts/validate-input.sh"
timeout: 10
statusMessage: "Validating..."
PostToolUse:
- hooks:
- type: command
command: "scripts/cleanup.sh"
Stop:
- hooks:
- type: command
command: "scripts/on-complete.sh"
once: true # Skills only: run once, then auto-remove
# Execution context
context: fork # Run in a subagent (isolates from conversation)
agent: Explore # Subagent type when context: fork (default: general-purpose)
effort: high # Override session effort: low | medium | high | max (max: Opus 4.6 only)
paths: "*.py,src/**/*.ts" # Glob patterns limiting auto-activation to matching files
shell: bash # Shell for bang+backtick inline-command blocks: bash (default) or powershell
# Behavior modifiers
user-invocable: true # Show in /command menu (default true)
disable-model-invocation: true # Prevent programmatic invocation (commands only)
argument-hint: "[arg1] [arg2]" # Document expected arguments; quote if value contains [...]
---`argument-hint` quoting rule: Values containing [...] must be quoted ("[arg]"), because YAML treats unquoted [ as the start of a flow sequence. Values using only <...> do not need quoting.
Advanced Frontmatter
Use when needed — most skills don't require these.
# Lifecycle hooks (scoped to this skill's lifetime)
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "scripts/validate-input.sh"
timeout: 10
statusMessage: "Validating..."
PostToolUse:
- hooks:
- type: command
command: "scripts/cleanup.sh"
Stop:
- hooks:
- type: command
command: "scripts/on-complete.sh"
once: true # Skills only: run once, then auto-remove
# Execution context
context: fork # Run in a subagent (isolates from conversation)
agent: Explore # Subagent type when context: fork (default: general-purpose)
effort: high # Override session effort: low | medium | high | max (max: Opus 4.6 only)
paths: "*.py,src/**/*.ts" # Glob patterns limiting auto-activation to matching files
shell: bash # Shell for bang+backtick inline-command blocks: bash (default) or powershell
# Behavior modifiers (commands)
disable-model-invocation: true # Prevent programmatic invocation (commands only)Hooks structure: Each hook event (PreToolUse, PostToolUse, Stop, SessionStart, etc.) accepts an array of entries. Each entry can have a matcher (filter by tool name) and a hooks array with handlers. Handler fields: type (command, http, prompt, agent), command, timeout (seconds), statusMessage (custom spinner text), once (skills only — run once then auto-remove). Hooks are scoped to the skill's lifetime and cleaned up when it finishes.
---
Description Patterns
For Skills (auto-triggered) — the two-layer model
Skill descriptions serve one purpose: helping the routing model decide when to trigger. They use a two-layer structure where a broad routing directive provides primary coverage and a few verbatim phrases anchor the intent.
Layer 1: Routing directive (primary coverage). A sentence at the end that tells the model to trigger broadly across an intent category. Format: "Make sure to use this skill whenever the user mentions [X, Y, Z] — even if they don't explicitly say '[skill name]'." X/Y/Z are intent categories and concept words (broad, generalizable), not verbatim query phrases. This is the main coverage mechanism — it catches the long tail of phrasings you can't anticipate.
Layer 2: Verbatim anchors (precision). 2–3 quoted phrases representing the exact words a user would type. These provide high-confidence matches for common cases. Derive them from real user language, not formalized paraphrases: "fix my skill" not "skill remediation". Cover the naive phrasing — what someone would say who has never heard of this skill.
The two layers are complementary, not interchangeable: verbatim phrases optimize for precision on known patterns; the routing directive optimizes for recall across unknown patterns.
Additional principles
- Third-person framing is a routing signal. "This skill should be used when..." reads as a condition to test. "Use this skill when..." reads as an instruction to execute. The routing model treats these differently.
- Include negative triggers for adjacent domains. Explicit exclusions sharpen the decision boundary. Add "Not for X" when the skill could plausibly false-trigger on a related domain.
- The description is always in context. Every session pays the token cost of every skill's description. Keep it dense — the routing directive eliminates the need for exhaustive trigger phrase lists.
- Keep descriptions under 150 tokens (200 absolute max). Anthropic's hard limit is 1024 characters (~250 tokens). Descriptions longer than 250 characters are truncated. The routing directive adds ~20-30 tokens — the budget accommodates it.
- Use `>` scalar, not `|`. Folded scalar (
>) collapses newlines to spaces — correct for descriptions. Literal scalar (|) preserves newlines, which can break parsing.
# Correct — verbatim anchors, broad routing directive, negative trigger
description: >
This skill should be used when the user asks to "create a hook"
or "add lifecycle automation". Make sure to use this skill whenever
the user mentions hook authoring, tool-event automation, or
validation pipelines — even if they don't explicitly say "hook".
Not for modifying existing hooks or debugging hook failures.
# Wrong — exhaustive trigger phrases, no routing directive
description: >
This skill should be used when the user asks to "create a hook",
"add validation", "implement lifecycle automation", "set up
pre-tool hooks", "add post-tool cleanup", or "write hook scripts".
# Wrong — vague, no trigger phrases, not third-person
description: Provides guidance for hooks.For Commands (user-invoked) — principles
- Verb-first, under 60 chars. The description appears as a single scannable line in the
/menu — treat it as a menu label, not a sentence. - Describe the action, not the tool. "Fix GitHub issue by number" orients by outcome. "GitHub issue fixer" orients by tool name. Users scan for what they want to accomplish.
description: Fix GitHub issue by number
description: Review code for security issues
description: Deploy to staging environment---
Essential Field Reference
- `name` — Unique identifier. Required for all skills and commands.
- `description` — How the routing model decides when to trigger this skill, or the label shown in the
/menu for commands. See Description Patterns below.
- `allowed-tools` — Restrict which tools the skill can use. Default is all tools. See Tool Selection below.
For hooks, context, effort, and paths — see Advanced Field Reference below.
- `shell` — Shell for inline !\
cmd\blocks and`!fenced blocks: bash (default) or powershell. Setting powershell runs inline shell commands via PowerShell on Windows. RequiresCLAUDE_CODE_USE_POWERSHELL_TOOL=1env var for inline bang-prefix commands to execute via PowerShell.
- `disable-model-invocation: true` — Commands only. Prevents Claude from auto-loading based on description. Has no effect on skills — use
user-invocable: falseinstead.
- `user-invocable` — Whether the skill appears in the
/command menu. Defaulttrue. Set tofalsefor background-knowledge skills that should trigger automatically but not appear as slash commands.
- `argument-hint` — Shown in autocomplete when the user types the command. Documents expected arguments.
Advanced Field Reference
These fields add execution control, lifecycle hooks, and platform-specific behavior. Most skills don't need them.
- `hooks` — Run scripts at lifecycle events, scoped to this skill's lifetime. See the Advanced Frontmatter block above for the full structure (matcher, type, timeout, statusMessage, once).
- `context: fork` — Run the skill in an isolated subagent. The skill content becomes the subagent's prompt; it won't have access to conversation history. Use for task-type skills (deploy, generate, research) where isolation prevents accidental side effects. Pair with
agentto choose the subagent type (Explore, Plan, general-purpose, or a custom agent from.claude/agents/).
- `effort` — Override session effort level for this skill. Options: low, medium, high, max (max is Opus 4.6 only). Use high/max for skills requiring deep reasoning; low for simple lookup skills.
- `paths` — Glob patterns (comma-separated or YAML list) limiting auto-activation. When set, Claude loads the skill automatically only when working with files matching the patterns. Use for language-specific or framework-specific skills.
- `shell` — Shell for inline !\
cmd\blocks and`!fenced blocks: bash (default) or powershell. Setting powershell runs inline shell commands via PowerShell on Windows. RequiresCLAUDE_CODE_USE_POWERSHELL_TOOL=1env var for inline bang-prefix commands to execute via PowerShell.
- `disable-model-invocation: true` — Commands only. Prevents Claude from auto-loading based on description. Has no effect on skills — use
user-invocable: falseinstead for skills you want to hide from auto-triggering.
---
Tool Selection
Default generous, restrict only when needed. The principle: restrict tools that have destructive or side-effect potential, not tools that are read-only or purely generative.
YAML format: allowed-tools must be a YAML list — block sequence (- Tool) or flow sequence ([Tool, Tool]). Never comma-separated on one line (allowed-tools: Read, Glob, Edit) — YAML parses that as a single string, not a list.
| Tier | Tools | Why |
|---|---|---|
| Always allow | Read, Grep, Glob | Read-only, no side effects |
| Usually allow | Edit, Write, WebSearch, WebFetch, Task | Core work tools; restrict if skill is deliberately read-only |
| Scope Bash | Bash(git:*), Bash(npm:*), Bash(pytest:*) | Bash is the highest blast-radius tool — scope to known commands |
| If interactive | AskUserQuestion | Required any time the skill needs user decisions mid-workflow |
| If delegating | Skill | Required to invoke other skills programmatically |
| If notebooks | NotebookEdit | Jupyter-specific; omit unless skill touches .ipynb files |
| If plan-gated | ExitPlanMode, EnterPlanMode | For workflows requiring explicit user approval before execution |
---
Skill Content Lifecycle
When invoked, the rendered SKILL.md enters the conversation as a single message and stays for the rest of the session. Claude Code does not re-read the file on later turns — write guidance as standing instructions, not one-time steps.
Auto-compaction carries invoked skills forward within a token budget: the first 5,000 tokens of each skill are retained after compaction, and all recently invoked skills share a combined 25,000-token budget (filled most-recent-first). Skills exceeding 5,000 tokens lose their tail after compaction. Skills invoked long ago may be dropped entirely if the budget is exhausted. If a skill seems to stop influencing behavior, re-invoke it.
Design implications: keep SKILL.md under 500 lines. Front-load critical instructions within the first ~5,000 tokens. Move detailed reference material to separate files that are loaded on demand.
---
Progressive Disclosure
For complex skills, organize into subdirectories:
skill-name/
├── SKILL.md # Core instructions (keep under 500 lines)
├── scripts/ # Executable code (Python/Bash)
├── references/ # Docs loaded into context as needed
├── examples/ # Working code examples users can copy directly
└── assets/ # Files used in output (templates, icons, fonts)scripts/ — Deterministic, token-efficient. May be executed without loading into context. Use when the same code is rewritten repeatedly or reliability is critical.
references/ — Documentation Claude reads while working. Keeps SKILL.md lean. For files >100 lines, include a table of contents. Only load when needed.
examples/ — Working code examples: complete, runnable scripts, configuration files, template files, real-world usage examples. Users can copy and adapt these directly. Distinct from references (docs) and scripts (utilities).
assets/ — Files NOT loaded into context. Used in output: templates, images, fonts, boilerplate.
Pattern 1: High-level guide with references
# PDF Processing
## Quick start
Extract text with pdfplumber:
[code example]
## Advanced features
- **Form filling**: See references/forms.md
- **API reference**: See references/api.mdClaude loads references only when needed.
Pattern 2: Domain-specific organization
bigquery-skill/
├── SKILL.md (overview and navigation)
└── references/
├── finance.md (revenue, billing)
├── sales.md (pipeline, opportunities)
└── product.md (API usage, features)When user asks about sales, Claude only reads sales.md.
Pattern 3: Variant-based organization
cloud-deploy/
├── SKILL.md (workflow + provider selection)
└── references/
├── aws.md
├── gcp.md
└── azure.mdUser chooses AWS → Claude only reads aws.md.
Generation Standards
Reference checklist and quality standards for evaluating generated skills and commands. Load during Phase 5 (Evaluate) before finalizing.
---
Degrees of Freedom
Match instruction specificity to the task's fragility and variability:
| Level | When to Use | Format |
|---|---|---|
| High freedom | Multiple valid approaches, context-dependent decisions | Text instructions, heuristics |
| Medium freedom | Preferred pattern exists, some variation acceptable | Pseudocode, scripts with parameters |
| Low freedom | Fragile operations, consistency critical, specific sequence required | Exact scripts, few parameters |
Quality Standards
Format Economy:
- Simple task → direct instruction, no sections
- Moderate task → light organization with headers
- Complex task → full semantic structure
Balance Flexibility with Precision:
- Loose enough for creative exploration
- Tight enough to prevent ambiguity
Remove ruthlessly: Filler phrases, obvious implications, redundant framing, excessive politeness
Validation Checklist
Before finalizing a skill or command:
Structure:
- [ ] SKILL.md file exists with valid YAML frontmatter
- [ ] Frontmatter has
nameanddescriptionfields - [ ] Markdown body is present and substantial
- [ ] Referenced files actually exist
Description Quality:
- [ ] Uses third person ("This skill should be used when...")
- [ ] Includes specific trigger phrases users would say
- [ ] Trigger phrases derived from user's actual words, not formalized paraphrases
- [ ] Under 100 tokens (150 absolute max)
- [ ] Negative triggers present if adjacent skills could false-trigger
- [ ] Lists concrete scenarios ("create X", "configure Y")
- [ ] Not vague or generic
Content Quality:
- [ ] Body uses imperative/infinitive form, not second person
- [ ] Body is focused and lean (1,500–2,000 words ideal, <5k max)
- [ ] Detailed content moved to references/
- [ ] Examples are complete and working
- [ ] Scripts are executable and documented
- [ ] Script opportunities identified via five signal patterns (references/script-patterns.md)
- [ ] Script references in SKILL.md include trigger condition, invocation, output handling
- [ ] Consistency-critical steps are scripted, not left to LLM re-generation
Progressive Disclosure:
- [ ] Core concepts in SKILL.md
- [ ] Detailed docs in references/
- [ ] Working code in examples/
- [ ] Utilities in scripts/
- [ ] SKILL.md references these resources
Testing:
- [ ] Skill triggers on expected user queries
- [ ] Content is helpful for intended tasks
- [ ] No duplicated information across files
- [ ] References load when needed
Error Handling
| Issue | Action |
|---|---|
| Unclear requirements | Ask clarifying questions |
| Missing context | Request examples or constraints |
| Path issues | Verify directory exists, create with confirmation |
| Type unclear | Default to skill if auto-triggering desired |
Script & CLI Patterns Reference
Intelligence for recognizing when a workflow step should be a script, how to design it as a proper CLI, and how to wire it into a skill. Load before auditing Dimension 4 or scanning for script opportunities during generation.
---
Signal Patterns: When a Step Should Be a Script
A workflow step is a CLI candidate when any of the following are true. The more signals present, the stronger the case for scripting.
Signal 1 — Repeated Generation
The step produces the same structure with different parameters across invocations. Examples: scaffolding a directory tree, generating a frontmatter block, creating a boilerplate file from a template. If Claude is re-generating the same code on every invocation, a parameterized script produces it once and runs reliably thereafter.
Test: Would two different users invoking this skill with different inputs cause Claude to write nearly identical code blocks with just the variable parts swapped? → Script it.
Signal 2 — Unclear Tool Choice
The step needs to do something but no standard Claude tool (Read, Grep, Bash, Edit, etc.) covers it cleanly without combining multiple tools in a fragile sequence. Example: "validate frontmatter YAML and report structured errors" requires reading a file, parsing YAML, and applying rules — awkward as a tool sequence, natural as a script.
Test: Does the skill body describe a multi-step procedure that would be done the same way every time, using tools as primitives? → The procedure is a script waiting to be named.
Signal 3 — Rigid Input/Output Contract
The step takes a specific input shape (a file path, a name + target directory) and produces a specific output shape (a scaffolded directory, a JSON report, a validation result). Rigid contracts are the shape of good CLIs — the interface is clear enough to parameterize immediately.
Test: Can you write the --help text for this step right now, without ambiguity? If yes, it's a CLI. If the args feel unclear, it's still agentic reasoning.
Signal 4 — Dual-Use Potential
The step would be useful to run independently, outside the skill workflow. Example: a validation script is useful during skill creation, during repair, and as a standalone pre-commit check. A scaffolding script is useful both when the skill generates a new artifact and when a user wants to scaffold manually.
Test: Would a user want to run this from the terminal directly, without triggering the full skill? → Design it as a proper CLI from the start, not an internal helper.
Signal 5 — Consistency Critical
The step must produce identical output for identical inputs — not "similar" output, but bit-for-bit reproducible results. LLM generation has variance; scripts don't. File naming conventions, path construction, structural templates — anything where variance causes downstream breakage should be scripted.
Test: Would a subtle difference in output (different field order, different whitespace, slightly different file name) break something? → Deterministic script, not LLM generation.
---
CLI Design for Skill Context
A script in a skill directory is also a CLI. Design it to be invoked both by Claude during a workflow and by users from the terminal.
Interface Design
Positional arguments — use for required, ordered inputs where the meaning is unambiguous from context. Best for 1–2 inputs: init_skill.py <name> <target-dir>.
Named flags — use for optional inputs, boolean toggles, and anything where the label clarifies meaning: --model sonnet, --dry-run, --output json.
Flag for output format — always add --output [text|json] when the script produces structured data. Claude parses JSON efficiently; humans prefer text. Defaulting to text with --output json as the machine-readable mode covers both callers.
Stdin input — use when the script is meant to be piped to: cat file | script.py. Useful for transform scripts. Use sys.stdin.read() with a flag fallback for file paths.
Explicit help text — every script needs -h/--help output. This is documentation that Claude reads when deciding how to invoke the script, and that users see when running it manually. Include: what the script does, each argument/flag with type and default, and an example invocation.
Output Conventions
Stdout for result data — the primary output goes to stdout. Claude captures stdout.
Stderr for diagnostic messages — progress notes, warnings, verbose logging go to stderr. Claude ignores stderr by default; it doesn't pollute the captured result.
Exit codes — 0 for success, 1 for usage errors (wrong args), 2 for runtime errors (file not found, parse failure). Claude checks exit codes implicitly; a non-zero exit signals failure and stops the workflow.
Structured output for multi-field results — if the script returns more than one piece of data, output JSON on stdout. A script that returns {"valid": true, "errors": []} is easier for Claude to parse than "Validation passed with 0 errors."
Script Anatomy (Python template)
#!/usr/bin/env python3
"""
One-line description of what this script does.
Usage:
script.py <required-arg> [--flag value]
Examples:
script.py input.yaml --output json
"""
import argparse
import json
import sys
def main():
parser = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("input", help="Description of required input")
parser.add_argument("--output", choices=["text", "json"], default="text",
help="Output format (default: text)")
parser.add_argument("--dry-run", action="store_true",
help="Show what would happen without making changes")
args = parser.parse_args()
# Core logic here
result = process(args.input, dry_run=args.dry_run)
if args.output == "json":
print(json.dumps(result))
else:
print(format_text(result))
sys.exit(0 if result["success"] else 1)
if __name__ == "__main__":
main()---
Common Script Archetypes
These archetypes cover most script candidates that appear in skill workflows.
Init — Scaffold a structure
Creates a directory tree or file set from a template. Takes a name and target path; produces the scaffolded output. Should be idempotent with a --force flag for overwriting, or fail fast on collision by default.
Canonical args: init.py <name> [target-dir] [--force] [--output json]
Validate — Check preconditions
Reads an artifact (file, directory, config), applies a rule set, and reports violations. Output should be structured (list of {field, message, severity} objects). Exit 0 on clean, exit 1 on violations. Never modifies anything.
Canonical args: validate.py <path> [--strict] [--output json]
Transform — Convert input to output
Takes structured input, applies a deterministic transformation, produces structured output. The purest CLI form: one input, one output, no side effects unless --write is passed. Use stdin/stdout for pipeline composability.
Canonical args: transform.py <input-path> [--output-path path] [--dry-run]
Package — Assemble an artifact
Collects files or content from multiple sources and assembles a distributable artifact (zip, tarball, manifest). Should validate inputs before assembling and report what was included. Dry-run support is valuable here.
Canonical args: package.py <source-dir> [output-dir] [--dry-run] [--output json]
Query — Read state, return structured result
Reads from a data source (DB, file, API) and returns structured data. Never writes. The primary consumer is Claude reading the output during a skill workflow, but users should be able to run it for inspection.
Canonical args: query.py [--filter key=value] [--limit N] [--output json]
---
Wiring Scripts into a Skill
A script that isn't referenced in SKILL.md is invisible to Claude.
In SKILL.md body, reference each script with: 1. When to invoke it (the trigger condition — which phase, what signals) 2. The exact invocation with relevant flags 3. How to interpret the output (what to do with exit codes, what fields matter)
Example reference pattern:
**Validate before proceeding:**~/.claude/skills/skill-name/scripts/validate.py "$PATH" --output json
Exit 1 = validation failed; parse the `errors` array and report to user before
continuing. Exit 0 = proceed to Phase 3.Avoid vague references like "run the validation script if needed" — Claude won't know which script or when "if needed" applies. State the trigger condition explicitly.
---
Delegation Pattern
When a skill workflow step is identified as a script candidate, delegate interface design to the create-cli skill rather than designing it ad-hoc. create-cli covers argument structure, help text, output formats, error messages, exit codes, and config/env precedence in depth.
Invocation pattern from within a skill workflow:
Skill: create-cli
Args: "<description of what the script does and what inputs it takes>"The generated CLI spec can then be scaffolded into scripts/ and referenced from SKILL.md. This ensures the script is designed for both Claude invocation and direct user use from the start.
#!/usr/bin/env python3
"""
Skill Initializer - Creates a new skill from template
Usage:
init_skill.py <skill-name> --path <path> [--resources scripts,references,assets] [--examples]
Examples:
init_skill.py my-new-skill --path ~/.claude/skills
init_skill.py my-new-skill --path ~/.claude/skills --resources scripts,references
init_skill.py my-api-helper --path .claude/skills --resources scripts --examples
"""
import argparse
import re
import sys
from pathlib import Path
MAX_SKILL_NAME_LENGTH = 64
ALLOWED_RESOURCES = {"scripts", "references", "assets"}
SKILL_TEMPLATE = """---
name: {skill_name}
description: >
[TODO: Write trigger-rich description. Include 3-5 varied trigger phrases.
Example: "Use when user asks to 'do X', 'handle Y', or mentions Z."]
---
# {skill_title}
[TODO: 1-2 sentences explaining what this skill enables]
## Process
[TODO: Choose structure that fits this skill's purpose:
**Workflow-Based** (sequential processes)
- Step-by-step procedures with clear ordering
- Example: ## Overview -> ## Step 1 -> ## Step 2...
**Task-Based** (tool collections)
- Different operations/capabilities grouped by function
- Example: ## Overview -> ## Task Category 1 -> ## Task Category 2...
**Reference/Guidelines** (standards or specifications)
- Brand guidelines, coding standards, requirements
- Example: ## Overview -> ## Guidelines -> ## Specifications...
Delete this section when done.]
## [First Section]
[TODO: Add content. Use imperative voice ("Analyze", "Generate").
No first-person ("I will"). Include code samples for technical skills.]
## Resources
[TODO: Delete this section if no resources needed. Otherwise, document what's in each directory:]
### scripts/
Executable code run directly to perform operations.
### references/
Documentation loaded into context as needed. Keep SKILL.md lean; put detailed info here.
### assets/
Files used in output (templates, images, fonts) - not loaded into context.
"""
EXAMPLE_SCRIPT = '''#!/usr/bin/env python3
"""
Example helper script for {skill_name}
Replace with actual implementation or delete if not needed.
"""
def main():
print("Example script for {skill_name}")
# TODO: Add actual script logic
if __name__ == "__main__":
main()
'''
EXAMPLE_REFERENCE = """# Reference Documentation for {skill_title}
Replace with actual reference content or delete if not needed.
## When Reference Docs Are Useful
- Comprehensive API documentation
- Detailed workflow guides
- Complex multi-step processes
- Information too lengthy for SKILL.md
- Content only needed for specific use cases
"""
EXAMPLE_ASSET = """# Example Asset
This placeholder represents where asset files would be stored.
Replace with actual files (templates, images, fonts) or delete if not needed.
Asset files are NOT loaded into context - they're used in output.
"""
def normalize_skill_name(skill_name):
"""Normalize a skill name to lowercase hyphen-case."""
normalized = skill_name.strip().lower()
normalized = re.sub(r"[^a-z0-9]+", "-", normalized)
normalized = normalized.strip("-")
normalized = re.sub(r"-{2,}", "-", normalized)
return normalized
def title_case_skill_name(skill_name):
"""Convert hyphenated skill name to Title Case."""
return " ".join(word.capitalize() for word in skill_name.split("-"))
def parse_resources(raw_resources):
if not raw_resources:
return []
resources = [item.strip() for item in raw_resources.split(",") if item.strip()]
invalid = sorted({item for item in resources if item not in ALLOWED_RESOURCES})
if invalid:
allowed = ", ".join(sorted(ALLOWED_RESOURCES))
print(f"[ERROR] Unknown resource type(s): {', '.join(invalid)}")
print(f" Allowed: {allowed}")
sys.exit(1)
return list(dict.fromkeys(resources)) # dedupe preserving order
def create_resource_dirs(skill_dir, skill_name, skill_title, resources, include_examples):
for resource in resources:
resource_dir = skill_dir / resource
resource_dir.mkdir(exist_ok=True)
if resource == "scripts":
if include_examples:
example_script = resource_dir / "example.py"
example_script.write_text(EXAMPLE_SCRIPT.format(skill_name=skill_name))
example_script.chmod(0o755)
print("[OK] Created scripts/example.py")
else:
print("[OK] Created scripts/")
elif resource == "references":
if include_examples:
example_ref = resource_dir / "reference.md"
example_ref.write_text(EXAMPLE_REFERENCE.format(skill_title=skill_title))
print("[OK] Created references/reference.md")
else:
print("[OK] Created references/")
elif resource == "assets":
if include_examples:
example_asset = resource_dir / "example_asset.txt"
example_asset.write_text(EXAMPLE_ASSET)
print("[OK] Created assets/example_asset.txt")
else:
print("[OK] Created assets/")
def init_skill(skill_name, path, resources, include_examples):
skill_dir = Path(path).expanduser().resolve() / skill_name
if skill_dir.exists():
print(f"[ERROR] Skill directory already exists: {skill_dir}")
return None
try:
skill_dir.mkdir(parents=True, exist_ok=False)
print(f"[OK] Created skill directory: {skill_dir}")
except Exception as e:
print(f"[ERROR] Error creating directory: {e}")
return None
skill_title = title_case_skill_name(skill_name)
skill_content = SKILL_TEMPLATE.format(skill_name=skill_name, skill_title=skill_title)
skill_md_path = skill_dir / "SKILL.md"
try:
skill_md_path.write_text(skill_content)
print("[OK] Created SKILL.md")
except Exception as e:
print(f"[ERROR] Error creating SKILL.md: {e}")
return None
if resources:
try:
create_resource_dirs(skill_dir, skill_name, skill_title, resources, include_examples)
except Exception as e:
print(f"[ERROR] Error creating resource directories: {e}")
return None
print(f"\n[OK] Skill '{skill_name}' initialized at {skill_dir}")
print("\nNext steps:")
print("1. Edit SKILL.md - complete TODOs and update description")
if resources:
print("2. Add/customize resources in scripts/, references/, assets/")
print("3. Test the skill, then package with package_skill.py")
return skill_dir
def main():
parser = argparse.ArgumentParser(description="Create a new skill directory with template SKILL.md")
parser.add_argument("skill_name", help="Skill name (normalized to hyphen-case)")
parser.add_argument("--path", required=True, help="Output directory (e.g., ~/.claude/skills)")
parser.add_argument("--resources", default="", help="Comma-separated: scripts,references,assets")
parser.add_argument("--examples", action="store_true", help="Create example files in resource dirs")
args = parser.parse_args()
skill_name = normalize_skill_name(args.skill_name)
if not skill_name:
print("[ERROR] Skill name must include at least one letter or digit.")
sys.exit(1)
if len(skill_name) > MAX_SKILL_NAME_LENGTH:
print(f"[ERROR] Skill name too long ({len(skill_name)} chars). Max: {MAX_SKILL_NAME_LENGTH}")
sys.exit(1)
if skill_name != args.skill_name:
print(f"Note: Normalized '{args.skill_name}' to '{skill_name}'")
resources = parse_resources(args.resources)
if args.examples and not resources:
print("[ERROR] --examples requires --resources")
sys.exit(1)
print(f"Initializing skill: {skill_name}")
print(f" Location: {args.path}")
if resources:
print(f" Resources: {', '.join(resources)}")
print()
result = init_skill(skill_name, args.path, resources, args.examples)
sys.exit(0 if result else 1)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Skill Packager - Creates a distributable .skill file
Usage:
package_skill.py <path/to/skill-folder> [output-directory]
Example:
package_skill.py ~/.claude/skills/my-skill
package_skill.py ~/.claude/skills/my-skill ./dist
"""
import sys
import zipfile
from pathlib import Path
# Add scripts directory to path for sibling import
sys.path.insert(0, str(Path(__file__).parent))
from validate_skill import validate_skill
def package_skill(skill_path, output_dir=None):
"""Package a skill folder into a .skill file."""
skill_path = Path(skill_path).expanduser().resolve()
if not skill_path.exists():
print(f"[ERROR] Skill folder not found: {skill_path}")
return None
if not skill_path.is_dir():
print(f"[ERROR] Path is not a directory: {skill_path}")
return None
skill_md = skill_path / "SKILL.md"
if not skill_md.exists():
print(f"[ERROR] SKILL.md not found in {skill_path}")
return None
# Validate before packaging
print("Validating skill...")
valid, message = validate_skill(skill_path)
if not valid:
print(f"[ERROR] Validation failed: {message}")
print(" Fix validation errors before packaging.")
return None
print(f"[OK] {message}\n")
# Determine output location
skill_name = skill_path.name
if output_dir:
output_path = Path(output_dir).expanduser().resolve()
output_path.mkdir(parents=True, exist_ok=True)
else:
output_path = Path.cwd()
skill_filename = output_path / f"{skill_name}.skill"
# Create the .skill file (zip format)
try:
with zipfile.ZipFile(skill_filename, "w", zipfile.ZIP_DEFLATED) as zipf:
for file_path in skill_path.rglob("*"):
if file_path.is_file():
# Skip __pycache__ and .pyc files
if "__pycache__" in str(file_path) or file_path.suffix == ".pyc":
continue
arcname = file_path.relative_to(skill_path.parent)
zipf.write(file_path, arcname)
print(f" Added: {arcname}")
print(f"\n[OK] Packaged to: {skill_filename}")
return skill_filename
except Exception as e:
print(f"[ERROR] Error creating .skill file: {e}")
return None
def main():
if len(sys.argv) < 2:
print("Usage: package_skill.py <path/to/skill-folder> [output-directory]")
print("\nExample:")
print(" package_skill.py ~/.claude/skills/my-skill")
print(" package_skill.py ~/.claude/skills/my-skill ./dist")
sys.exit(1)
skill_path = sys.argv[1]
output_dir = sys.argv[2] if len(sys.argv) > 2 else None
print(f"Packaging skill: {skill_path}")
if output_dir:
print(f" Output: {output_dir}")
print()
result = package_skill(skill_path, output_dir)
sys.exit(0 if result else 1)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Skill Validator - Validates skill structure and frontmatter for Claude Code
Usage:
validate_skill.py <skill_directory>
Example:
validate_skill.py ~/.claude/skills/my-skill
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
MAX_SKILL_NAME_LENGTH = 64
# Claude Code supported frontmatter fields
ALLOWED_FRONTMATTER = {
"name",
"description",
"allowed-tools",
"hooks",
"user-invocable",
"disable-model-invocation",
"argument-hint",
"license",
"metadata",
}
def _parse_frontmatter(text):
"""Parse simple YAML frontmatter (key: value pairs) without PyYAML.
Handles scalar values and multi-line folded scalars (>).
List-valued fields (e.g. allowed-tools) are stored as joined strings,
not Python lists — sufficient for key-presence validation.
Returns a dict, or None if parsing fails.
"""
result = {}
current_key = None
current_value_lines = []
def _flush():
if current_key is not None:
val = " ".join(current_value_lines).strip()
# Unquote if wrapped in matching quotes
if len(val) >= 2 and val[0] == val[-1] and val[0] in ('"', "'"):
val = val[1:-1]
# Convert YAML booleans
if val.lower() in ("true", "yes"):
val = True
elif val.lower() in ("false", "no"):
val = False
result[current_key] = val
for line in text.splitlines():
# Continuation line (indented, part of multi-line scalar like >)
if current_key and line and line[0] in (" ", "\t"):
current_value_lines.append(line.strip())
continue
# New key: value pair
m = re.match(r"^([a-zA-Z_][a-zA-Z0-9_-]*)\s*:\s*(.*)", line)
if m:
_flush()
current_key = m.group(1)
val = m.group(2).strip()
# Folded scalar indicator — value follows on next lines
current_value_lines = [] if val in (">", "|") else [val]
continue
# Blank or unparseable line — flush and reset
if not line.strip():
_flush()
current_key = None
current_value_lines = []
_flush()
return result if result else None
def validate_skill(skill_path):
"""Validate a skill directory for Claude Code compatibility."""
skill_path = Path(skill_path).expanduser().resolve()
skill_md = skill_path / "SKILL.md"
if not skill_md.exists():
return False, "SKILL.md not found"
content = skill_md.read_text()
if not content.startswith("---"):
return False, "No YAML frontmatter found (must start with ---)"
match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
if not match:
return False, "Invalid frontmatter format (missing closing ---)"
frontmatter_text = match.group(1)
frontmatter = _parse_frontmatter(frontmatter_text)
if frontmatter is None:
return False, "Frontmatter must be a YAML dictionary with simple key: value pairs"
# Check for unexpected keys
unexpected = set(frontmatter.keys()) - ALLOWED_FRONTMATTER
if unexpected:
return False, f"Unexpected frontmatter key(s): {', '.join(sorted(unexpected))}"
# Required fields
if "name" not in frontmatter:
return False, "Missing required 'name' field"
if "description" not in frontmatter:
return False, "Missing required 'description' field"
# Validate name
name = frontmatter.get("name", "")
if not isinstance(name, str):
return False, f"'name' must be a string, got {type(name).__name__}"
name = name.strip()
if name:
if not re.match(r"^[a-z0-9-]+$", name):
return False, f"Name '{name}' must be hyphen-case (lowercase, digits, hyphens only)"
if name.startswith("-") or name.endswith("-") or "--" in name:
return False, f"Name '{name}' cannot start/end with hyphen or have consecutive hyphens"
if len(name) > MAX_SKILL_NAME_LENGTH:
return False, f"Name too long ({len(name)} chars). Max: {MAX_SKILL_NAME_LENGTH}"
# Validate description
description = frontmatter.get("description", "")
if not isinstance(description, str):
return False, f"'description' must be a string, got {type(description).__name__}"
description = description.strip()
if description:
if len(description) > 1024:
return False, f"Description too long ({len(description)} chars). Max: 1024"
if "[TODO" in description:
return False, "Description contains TODO placeholder - please complete it"
# Check body has content
body = content[match.end():].strip()
if not body:
return False, "SKILL.md body is empty"
if "[TODO" in body:
return False, "SKILL.md contains TODO placeholders - please complete them"
return True, "Skill is valid"
def main():
if len(sys.argv) != 2:
print("Usage: validate_skill.py <skill_directory>")
print("Example: validate_skill.py ~/.claude/skills/my-skill")
sys.exit(1)
skill_path = sys.argv[1]
print(f"Validating: {skill_path}")
valid, message = validate_skill(skill_path)
if valid:
print(f"[OK] {message}")
else:
print(f"[ERROR] {message}")
sys.exit(0 if valid else 1)
if __name__ == "__main__":
main()
Related skills
FAQ
Is Create Skill safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.