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.

Visibility vs Attachment

Skills have two independent dimensions:

Visibility

Controls who can discover the skill:

LevelDescription
privateOnly the creator can see
organizationAll org members can see and add
publicListed in the public library

Attachment

Controls where the skill is active (actually used by agents at runtime):

Attached ToEffect
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:

SectionWhat it shows
Agent (or Space)Skills attached here — these will be used at runtime
OrganizationShared pool of org skills — available to add, not auto-included
LibraryPublic catalog — available to add

Scope at Runtime

ContextSkills Available
Org-level chatAgent’s own skills only (from skillIds)
Space chatAgent’s skills union space’s skills (from skillRefs)

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

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

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”
  • 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

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
  • 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 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