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.
Visibility vs Attachment
Skills have two independent dimensions:
Visibility
Controls who can discover the skill:
| Level | Description |
|---|---|
private | Only the creator can see |
organization | All org members can see and add |
public | Listed in the public library |
Attachment
Controls where the skill is active (actually used by agents at runtime):
| Attached To | Effect |
|---|---|
Agent (via skillIds) | Available whenever this agent chats, in any context |
Space (via skillRefs) | Available to any agent chatting in this space |
A skill with organization visibility is shared with your team — they can discover and add it. But it is not automatically active anywhere. Someone must attach it to an agent or a space for it to be used.
Browsing Skills
When browsing skills in the picker UI, you see three sections:
| Section | What it shows |
|---|---|
| Agent (or Space) | Skills attached here — these will be used at runtime |
| Organization | Shared pool of org skills — available to add, not auto-included |
| Library | Public catalog — available to add |
Scope at Runtime
| Context | Skills Available |
|---|---|
| Org-level chat | Agent’s own skills only (from skillIds) |
| Space chat | Agent’s skills union space’s skills (from skillRefs) |
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
Core Skills
Skills prefixed with core: are auto-included in new spaces. These provide foundational capabilities that most agents benefit from.
Organization Skills
Skills with organization visibility are shared across your team. Any org member can browse them and attach them to their agents or spaces. They are a shared pool — available to add, but not automatically active anywhere.
Library Skills
Public skills from the TeamDay library. Browse them via CLI, the API, or the in-app skill picker:
teamday skills list
Or in chat via the TeamdayAdmin tool:
{"action": "browseSkillsRegistry"}
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)
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.
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”
- Not “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 screenshotsblog-image-generation- Generates AI images- Not
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