Skills
Skills are executable expertise for AI agents - combining instructions with optional scripts to teach agents how to perform specialized tasks.
Structure
A skill consists of two parts:
.claude/skills/
βββ skill-name/
βββ SKILL.md # Instructions (required)
βββ scripts/ # Executable code (optional)
βββ script.ts
βββ helper.sh
SKILL.md Format
Skills use YAML frontmatter followed by markdown instructions:
---
name: skill-name
description: Brief description shown in discovery
allowed-tools: Bash, Read, Write
---
# Skill Name
Instructions for the agent...
## When to Use
- Use case 1
- Use case 2
## Usage
Detailed steps...
Frontmatter fields:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Display name (defaults to directory name) |
description | string | Yes | Brief description for skill discovery |
allowed-tools | string/array | No | Tools the skill can use (e.g., "Bash, Read, Write") |
version | string | No | Semantic version for tracking changes |
Scripts Directory
Scripts contain executable code called by the agent. Common patterns:
TypeScript/JavaScript (with Bun runtime):
#!/usr/bin/env bun
// Script logic here
Shell scripts:
#!/bin/bash
# Script logic here
Scripts accept command-line arguments and communicate via stdout/stderr.
Progressive Discovery
Unlike MCPs (loaded at startup), skills are discovered progressively:
- Agent receives task
- Scans
.claude/skills/for relevant skills - Reads SKILL.md files that might help
- Executes skill if applicable
This eliminates startup overhead and memory waste from unused skills.
Skill Patterns
Pattern 1: Simple Scripts
Single executable with clear purpose.
Example: Screenshot Capture
---
description: Take screenshots of web pages for documentation
allowed-tools: Bash
---
# Screenshot
Usage:
```bash
bun .claude/skills/screenshot/screenshot.ts <url> <filename> [options]
Options:
--width=1200- Viewport width--dark- Enable dark mode
**Implementation** (`scripts/screenshot.ts`):
```typescript
#!/usr/bin/env bun
import { chromium } from 'playwright';
const url = process.argv[2];
const filename = process.argv[3];
const outputPath = resolve('packages/marketing/public/images', filename);
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(url);
await page.screenshot({ path: outputPath });
await browser.close();
Pattern 2: Multiple Providers
Multiple scripts for different backends/providers.
Example: Blog Image Generation
---
name: blog-image-generation
description: Generate AI images using FAL AI, OpenAI, or Gemini
allowed-tools: Bash, Read, Write
---
# Blog Image Generation
Generate blog cover images using multiple AI providers.
## Usage
```bash
# FAL AI (recommended)
bun .claude/skills/blog-image-generation/scripts/generate-image.ts "prompt" output.webp
# OpenAI GPT Image 1.5
bun .claude/skills/blog-image-generation/scripts/generate-image-openai.ts "prompt" output.webp
# Gemini (fast)
bun .claude/skills/blog-image-generation/scripts/generate-image-gemini.ts "prompt" output.webp
Each provider script handles API-specific details (authentication, request format, response parsing).
### Pattern 3: API Integration with Credentials
External API calls requiring authentication.
**Example: Google Analytics**
```markdown
---
name: google-analytics
description: Query GA4 data for traffic analysis
allowed-tools: Bash, Read, Write
---
# Google Analytics
Query GA4 via Analytics Data API.
## Configuration
Uses Application Default Credentials from gcloud:
- Property: 478766521 (www.teamday.ai)
- Scope: analytics.readonly
## Commands
```bash
# Summary
bun .claude/skills/google-analytics/scripts/ga-report.ts summary [days]
# Top pages
bun .claude/skills/google-analytics/scripts/ga-report.ts pages [days] [limit]
**Implementation** (`scripts/ga-report.ts`):
```typescript
#!/usr/bin/env bun
import { GoogleAuth } from 'google-auth-library';
const PROPERTY_ID = process.env.GA4_PROPERTY_ID || '478766521';
async function getAccessToken(): Promise<string> {
const auth = new GoogleAuth({
scopes: ['https://www.googleapis.com/auth/analytics.readonly']
});
const client = await auth.getClient();
const token = await client.getAccessToken();
return token.token!;
}
async function runReport(request: ReportRequest) {
const token = await getAccessToken();
const response = await fetch(
`https://analyticsdata.googleapis.com/v1beta/properties/${PROPERTY_ID}:runReport`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(request)
}
);
return response.json();
}
Security Model
Credentials Scoping
Credentials are scoped to spaces, not globally:
- Each space has isolated credential storage
- Credentials encrypted at rest
- Only space members can access
- Never shared across spaces
Environment Variables
Scripts access credentials via environment variables:
// Script reads from process.env
const apiKey = process.env.OPENAI_API_KEY;
const projectId = process.env.GOOGLE_PROJECT_ID;
if (!apiKey) {
throw new Error('Missing required credential: OPENAI_API_KEY');
}
TeamDay injects these variables when running skills within a space context.
Declaring Required Credentials
Use the env field in SKILL.md frontmatter to declare which environment variables a skill needs:
---
name: my-skill
description: Does something useful
allowed-tools: Bash, Read, Write
env: OPENAI_API_KEY, GOOGLE_PROJECT_ID
---
Patterns:
API_KEYβ requiredA | B | Cβ any one of (OR)A, Bβ all required (AND)KEY?β optional
Credentials are stored as Space secrets:
teamday spaces set-secret <space-id> OPENAI_API_KEY=sk-abc123
The agent's sandbox receives these as environment variables at runtime.
Skill Sources
1. Git Repositories
Clone repositories containing .claude/skills/:
cd ~/your-space
git clone https://github.com/your-org/custom-skills
Skills are discovered automatically:
your-space/
βββ custom-skills/
β βββ .claude/skills/
β βββ compliance-check/
βββ .claude/skills/
βββ screenshot/
Both directories are scanned. Use Git for:
- Version control
- Team collaboration
- Pull updates (
git pull)
2. Skills Registry
Browse available skills via CLI or the TeamdayAdmin tool:
teamday skills list
Or in chat:
{"action": "browseSkillsRegistry"}
Core skills (prefixed with core:) are auto-installed in new Spaces.
3. Agent-Created
Agents can create skills during work:
User: "Create a skill for generating weekly reports"
Agent: [Creates .claude/skills/weekly-reports/SKILL.md]
These persist in your space and improve over time.
4. User-Created
Write skills manually:
mkdir -p .claude/skills/my-skill
cat > .claude/skills/my-skill/SKILL.md << 'EOF'
---
description: My custom automation skill
---
# My Skill
Instructions...
EOF
Skills vs MCPs
| Aspect | Skills | MCPs |
|---|---|---|
| Discovery | Progressive (on-demand) | Upfront (at startup) |
| Performance | Fast (local execution) | Slower (network calls) |
| Reliability | High (self-contained) | Variable (service dependency) |
| Use case | Workflows, automation, domain expertise | External API integration |
When to use skills:
- Orchestrating multiple tools
- Repeatable workflows
- Domain-specific expertise
- Performance-critical operations
When to use MCPs:
- External service integration (GitHub, Slack)
- Database access
- Real-time data feeds
- Shared infrastructure
Best practice: Default to skills. Use MCPs only when external service integration is required.
Real-World Examples
Blog Image Generation
Files:
.claude/skills/blog-image-generation/SKILL.md.claude/skills/blog-image-generation/scripts/generate-image.ts.claude/skills/blog-image-generation/scripts/generate-image-openai.ts.claude/skills/blog-image-generation/scripts/generate-image-gemini.ts
Pattern: Multiple provider scripts, unified interface
Usage:
bun .claude/skills/blog-image-generation/scripts/generate-image.ts \
"Modern neural network illustration, glowing blue connections" \
cover.webp
Output saved to packages/marketing/public/images/cover.webp
Screenshot Capture
Files:
.claude/skills/screenshot/SKILL.md.claude/skills/screenshot/screenshot.ts
Pattern: Single-purpose script with options
Usage:
bun .claude/skills/screenshot/screenshot.ts \
http://localhost:3002/ai \
screenshot.webp \
--dark --width=1400
Uses Playwright to capture UI, outputs WebP.
Google Analytics
Files:
.claude/skills/google-analytics/skill.md.claude/skills/google-analytics/scripts/ga-report.ts
Pattern: API integration with credentials
Usage:
bun .claude/skills/google-analytics/scripts/ga-report.ts summary 28
bun .claude/skills/google-analytics/scripts/ga-report.ts pages 7 10
Queries GA4 API using Application Default Credentials.
Creating Skills
1. Define Purpose
Pick a specific, focused task:
- β "Generate blog cover images"
- β "Capture UI screenshots"
- β "General purpose helper"
2. Create Directory Structure
mkdir -p .claude/skills/my-skill/scripts
3. Write SKILL.md
---
description: Brief description for discovery
allowed-tools: Bash, Read, Write
---
# My Skill
Clear instructions for the agent.
## When to Use
- Scenario 1
- Scenario 2
## Usage
```bash
bun .claude/skills/my-skill/scripts/run.ts <args>
Options
| Option | Description |
|---|---|
--flag | Does something |
### 4. Implement Scripts
```typescript
#!/usr/bin/env bun
// Parse arguments
const input = process.argv[2];
// Validate inputs
if (!input) {
console.error('Usage: bun run.ts <input>');
process.exit(1);
}
// Execute logic
try {
const result = await processInput(input);
console.log('β
Success:', result);
} catch (error) {
console.error('β Error:', error.message);
process.exit(1);
}
5. Test
bun .claude/skills/my-skill/scripts/run.ts test-input
6. Version Control
git add .claude/skills/my-skill
git commit -m "Add my-skill v1.0.0"
Best Practices
Keep Skills Focused
One skill = one job:
- β
screenshot- Captures screenshots - β
blog-image-generation- Generates AI images - β
content-helper- Does everything
Use Clear Instructions
Agent instructions should be precise:
## Usage
Generate a blog cover image:
```bash
bun .claude/skills/blog-image-generation/scripts/generate-image.ts "prompt" output.webp
Where:
prompt- Detailed image descriptionoutput.webp- Filename (saved to packages/marketing/public/images/)
### Handle Errors Gracefully
```typescript
try {
await executeTask();
} catch (error) {
if (error.code === 'ENOENT') {
console.error('β File not found. Check path.');
} else if (error.code === 'UNAUTHORIZED') {
console.error('β Invalid credentials. Run: gcloud auth login');
} else {
console.error('β Error:', error.message);
}
process.exit(1);
}
Document Dependencies
## Requirements
**Runtime:**
- Bun 1.0+
- Playwright (for browser automation)
**Installation:**
```bash
npx playwright install chromium
Environment:
OPENAI_API_KEY- Required for image generation
### Use Semantic Versioning
```yaml
---
version: "1.2.0"
---
# Changelog
## 1.2.0 - 2025-01-15
- Added dark mode support
- Fixed timeout handling
## 1.1.0 - 2025-01-10
- Added custom viewport sizes
## 1.0.0 - 2025-01-01
- Initial release
Troubleshooting
Skill Not Discovered
Check:
- File located at
.claude/skills/<name>/SKILL.md - Valid YAML frontmatter with
descriptionfield - Proper markdown syntax
Script Execution Fails
Common issues:
- Missing shebang:
#!/usr/bin/env bun - File not executable:
chmod +x script.ts - Wrong working directory (use absolute paths)
- Missing dependencies (check package.json)
Credentials Not Available
For scripts requiring auth:
- Verify credentials configured in space settings
- Check environment variable names match
- Test credential access:
echo $OPENAI_API_KEY
Next Steps
- Spaces & Workspaces β Where skills live
- Agent Configuration β Attaching skills to agents
- MCP Servers β When to use MCPs instead
- Platform Tools β TeamdayAdmin tool for skill management