# Skills Guide

Technical guide to creating and using Skills in TeamDay — reusable capabilities for AI agents.

# 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:

```markdown
---
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):
```typescript
#!/usr/bin/env bun
// Script logic here
```

**Shell scripts**:
```bash
#!/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:

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

```markdown
---
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**

```markdown
---
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:

```typescript
// 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:

```yaml
---
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:

```bash
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:

```bash
teamday skills list
```

Or in chat via the TeamdayAdmin tool:
```json
{"action": "browseSkillsRegistry"}
```

### Git Repositories

Clone repositories containing `.claude/skills/`:

```bash
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:

```bash
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:**
```bash
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:**
```bash
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:**
```bash
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

```bash
mkdir -p .claude/skills/my-skill/scripts
```

### 3. Write SKILL.md

```markdown
---
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

```bash
bun .claude/skills/my-skill/scripts/run.ts test-input
```

### 6. Version Control

```bash
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:

```markdown
## 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

```markdown
## 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

- [Spaces & Workspaces](https://docs.teamday.ai/guides/spaces) — Where skills live
- [Agent Configuration](https://docs.teamday.ai/guides/agent-configuration) — Attaching skills to agents
- [MCP Servers](https://docs.teamday.ai/guides/mcp-servers/what-are-mcp-servers) — When to use MCPs instead
- [Platform Tools](https://docs.teamday.ai/guides/platform-tools/teamday-admin) — TeamdayAdmin tool for skill management
