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:

FieldTypeRequiredDescription
namestringNoDisplay name (defaults to directory name)
descriptionstringYesBrief description for skill discovery
allowed-toolsstring/arrayNoTools the skill can use (e.g., "Bash, Read, Write")
versionstringNoSemantic 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:

  1. Agent receives task
  2. Scans .claude/skills/ for relevant skills
  3. Reads SKILL.md files that might help
  4. 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 β€” required
  • A | 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

AspectSkillsMCPs
DiscoveryProgressive (on-demand)Upfront (at startup)
PerformanceFast (local execution)Slower (network calls)
ReliabilityHigh (self-contained)Variable (service dependency)
Use caseWorkflows, automation, domain expertiseExternal 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

OptionDescription
--flagDoes 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 description
  • output.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:

  1. File located at .claude/skills/<name>/SKILL.md
  2. Valid YAML frontmatter with description field
  3. 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:

  1. Verify credentials configured in space settings
  2. Check environment variable names match
  3. Test credential access: echo $OPENAI_API_KEY

Next Steps