# Cowork Plugin Builder

> Guide for building custom Cowork plugins that leverage CC Commander skills. Covers plugin anatomy (manifest, handler, permissions), 5 example plugins, packaging CC Commander skills as plugins, local testing, and publishing to the plugin directory.

- Skill: `kevinzai/cowork-plugin-builder` (Agent Skill)
- Install (CLI): `npx skillmds@latest add kevinzai/cowork-plugin-builder`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kevinzai/cowork-plugin-builder/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: KevinZai (https://skillmd.com/u/kevinzai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/kevinzai/cowork-plugin-builder

---


# Cowork Plugin Builder

## Overview

Cowork plugins extend Claude Desktop's autonomous execution with custom capabilities. This skill teaches you how to package CC Commander skills, hooks, and workflows as installable Cowork plugins.

## Plugin Anatomy

Every Cowork plugin consists of three files:

```
my-plugin/
  manifest.json    # Plugin metadata, permissions, configuration
  handler.js       # Entry point -- receives events, returns results
  README.md        # Usage docs (optional but recommended)
```

### manifest.json

```json
{
  "name": "bible-daily-standup",
  "version": "1.0.0",
  "description": "Automated daily standup using CC Commander task-commander patterns",
  "author": "Kevin Z",
  "license": "MIT",
  "entry": "handler.js",
  "permissions": {
    "filesystem": {
      "read": ["tasks/**", "output/**", ".claude/**"],
      "write": ["output/standups/**", "tasks/todo.md"]
    },
    "terminal": {
      "commands": ["git log *", "git status", "git diff --stat *"]
    }
  },
  "config": {
    "outputDir": {
      "type": "string",
      "default": "output/standups",
      "description": "Directory for standup reports"
    },
    "includeGitLog": {
      "type": "boolean",
      "default": true,
      "description": "Include git activity in standup"
    },
    "lookbackHours": {
      "type": "number",
      "default": 24,
      "description": "Hours of activity to include"
    }
  },
  "triggers": {
    "schedule": "0 9 * * 1-5",
    "manual": true
  },
  "bible": {
    "skills": ["session-startup", "task-commander"],
    "mode": "normal"
  }
}
```

### handler.js

```javascript
#!/usr/bin/env node
'use strict';

/**
 * Plugin handler receives a context object on stdin and writes
 * results to stdout. The context includes:
 * - project: absolute path to project directory
 * - config: merged plugin config (defaults + user overrides)
 * - trigger: what triggered this run (schedule, manual, webhook)
 * - previousRun: summary of last execution (if any)
 */

const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

let data = '';
process.stdin.on('data', chunk => data += chunk);
process.stdin.on('end', () => {
  try {
    const ctx = JSON.parse(data);
    const result = run(ctx);
    console.log(JSON.stringify(result));
  } catch (e) {
    console.log(JSON.stringify({
      status: 'error',
      error: e.message
    }));
    process.exit(1);
  }
});

function run(ctx) {
  const { project, config } = ctx;

  // Read task state
  const todoPath = path.join(project, 'tasks', 'todo.md');
  const tasks = fs.existsSync(todoPath)
    ? fs.readFileSync(todoPath, 'utf8')
    : 'No tasks/todo.md found.';

  // Git activity
  let gitLog = 'Git log disabled.';
  if (config.includeGitLog) {
    try {
      gitLog = execSync(
        `git log --since="${config.lookbackHours} hours ago" --oneline`,
        { cwd: project, encoding: 'utf8', timeout: 10000 }
      ).trim() || 'No commits in the lookback window.';
    } catch {
      gitLog = 'Not a git repository or git unavailable.';
    }
  }

  // Build standup report
  const date = new Date().toISOString().split('T')[0];
  const report = [
    `# Daily Standup -- ${date}`,
    '',
    '## Task Status',
    tasks,
    '',
    '## Recent Git Activity',
    '```',
    gitLog,
    '```',
    '',
    `Generated by bible-daily-standup plugin at ${new Date().toISOString()}`
  ].join('\n');

  // Write output
  const outDir = path.join(project, config.outputDir);
  fs.mkdirSync(outDir, { recursive: true });
  const outPath = path.join(outDir, `${date}.md`);
  fs.writeFileSync(outPath, report);

  return {
    status: 'success',
    artifact: outPath,
    summary: `Standup written to ${outPath}`
  };
}
```

### Permission Model

Plugins declare permissions in `manifest.json`. Cowork enforces them at runtime:

| Permission | Scope | Example |
|---|---|---|
| `filesystem.read` | Glob patterns for read access | `["src/**", "*.md"]` |
| `filesystem.write` | Glob patterns for write access | `["output/**"]` |
| `terminal.commands` | Allowed shell command patterns | `["git *", "npm test"]` |
| `browser.urls` | Allowed URL patterns | `["https://api.github.com/*"]` |
| `network.hosts` | Allowed outbound hosts | `["localhost:3110"]` |

Users approve permissions on first install. Changes require re-approval.

## Example Plugins

### 1. Daily Standup Plugin

Generates a standup report from task state and git history.

```json
{
  "name": "bible-daily-standup",
  "version": "1.0.0",
  "description": "Morning standup from tasks/todo.md + git log",
  "entry": "handler.js",
  "permissions": {
    "filesystem": {
      "read": ["tasks/**", "output/**"],
      "write": ["output/standups/**"]
    },
    "terminal": { "commands": ["git log *", "git status"] }
  },
  "triggers": { "schedule": "0 9 * * 1-5" },
  "bible": { "skills": ["session-startup", "task-commander"] }
}
```

**Handler logic:**
1. Read `tasks/todo.md` for current task state
2. Run `git log --since="24 hours ago"` for recent activity
3. Identify blockers (items marked `[blocked]`)
4. Generate markdown standup report
5. Write to `output/standups/{date}.md`

### 2. Code Review Bot Plugin

Automatically reviews new PRs and posts feedback.

```json
{
  "name": "bible-code-review-bot",
  "version": "1.0.0",
  "description": "Auto-review PRs using CC Commander coding-standards skill",
  "entry": "handler.js",
  "permissions": {
    "filesystem": { "read": ["**"] },
    "terminal": { "commands": ["git *", "gh pr *", "npx tsc --noEmit"] },
    "network": { "hosts": ["api.github.com"] }
  },
  "config": {
    "severity": {
      "type": "string",
      "default": "medium",
      "description": "Minimum severity to report (low, medium, high, critical)"
    },
    "autoApprove": {
      "type": "boolean",
      "default": false,
      "description": "Auto-approve PRs with no issues found"
    }
  },
  "triggers": {
    "webhook": "github.pull_request.opened",
    "manual": true
  },
  "bible": { "skills": ["coding-standards", "review", "security-reviewer"] }
}
```

**Handler logic:**
1. Receive PR event (number, branch, author)
2. Checkout PR branch locally
3. Run typecheck (`npx tsc --noEmit`)
4. Analyze diff against coding-standards skill rules
5. Check for security issues per pentest-checklist skill
6. Write review to `output/reviews/pr-{number}.md`
7. Post review comment via `gh pr review`

### 3. Dependency Updater Plugin

Checks and updates dependencies with safety checks.

```json
{
  "name": "bible-dependency-updater",
  "version": "1.0.0",
  "description": "Safe dependency updates with test verification",
  "entry": "handler.js",
  "permissions": {
    "filesystem": {
      "read": ["package*.json", "pnpm-lock.yaml", "src/**", "tests/**"],
      "write": ["package*.json", "pnpm-lock.yaml", "output/deps/**"]
    },
    "terminal": {
      "commands": [
        "npm outdated *", "npm audit *", "npm update *",
        "pnpm outdated *", "pnpm update *",
        "npx tsc --noEmit", "npm test", "pnpm test",
        "git *"
      ]
    }
  },
  "config": {
    "updateType": {
      "type": "string",
      "default": "minor",
      "description": "Max update level: patch, minor, major"
    },
    "createPR": {
      "type": "boolean",
      "default": true,
      "description": "Create PR for updates"
    }
  },
  "triggers": { "schedule": "0 6 * * 1" },
  "bible": { "skills": ["verification-loop"] }
}
```

**Handler logic:**
1. Run `npm outdated` / `pnpm outdated` to list available updates
2. Filter by update type (patch/minor/major)
3. For each update: install, typecheck, run tests
4. If tests pass: keep update. If tests fail: revert and log.
5. Create branch `deps/update-{date}` with passing updates
6. Write report to `output/deps/{date}.md`
7. Optionally create PR via `gh pr create`

### 4. Test Runner Plugin

Runs test suite and generates coverage report.

```json
{
  "name": "bible-test-runner",
  "version": "1.0.0",
  "description": "Scheduled test runner with coverage tracking",
  "entry": "handler.js",
  "permissions": {
    "filesystem": {
      "read": ["src/**", "tests/**", "vitest.config.*", "package.json"],
      "write": ["output/tests/**", "coverage/**"]
    },
    "terminal": {
      "commands": ["npm test *", "pnpm test *", "npx vitest *", "npx jest *"]
    }
  },
  "config": {
    "coverageThreshold": {
      "type": "number",
      "default": 80,
      "description": "Minimum coverage percentage"
    },
    "failOnDecrease": {
      "type": "boolean",
      "default": true,
      "description": "Fail if coverage decreased since last run"
    }
  },
  "triggers": {
    "schedule": "0 */6 * * *",
    "manual": true
  },
  "bible": { "skills": ["tdd-workflow", "verification-loop"] }
}
```

**Handler logic:**
1. Run test suite with coverage enabled
2. Parse coverage report (JSON format)
3. Compare against previous run (stored in `output/tests/coverage-history.json`)
4. Flag coverage decreases if `failOnDecrease` is true
5. Write report to `output/tests/{date}.md`
6. Update coverage history

### 5. Report Generator Plugin

Batch-generates reports from templates and data sources.

```json
{
  "name": "bible-report-generator",
  "version": "1.0.0",
  "description": "Generate reports from templates using CC Commander patterns",
  "entry": "handler.js",
  "permissions": {
    "filesystem": {
      "read": ["templates/**", "data/**", "output/**"],
      "write": ["output/reports/**"]
    },
    "terminal": {
      "commands": ["git log *", "wc *"]
    }
  },
  "config": {
    "templateDir": {
      "type": "string",
      "default": "templates/reports",
      "description": "Directory containing report templates"
    },
    "dataDir": {
      "type": "string",
      "default": "data",
      "description": "Directory containing data sources"
    }
  },
  "triggers": {
    "schedule": "0 17 * * 5",
    "manual": true
  },
  "bible": { "skills": ["business-analytics"] }
}
```

**Handler logic:**
1. Scan template directory for `.md` templates
2. For each template: load data sources referenced in frontmatter
3. Fill template variables from data
4. Generate formatted report
5. Write to `output/reports/{template-name}-{date}.md`

## Packaging CC Commander Skills as Plugins

### Step-by-Step

1. **Identify the skill** -- pick a SKILL.md from `~/.claude/skills/`
2. **Extract the logic** -- determine what tools/commands the skill uses
3. **Create manifest** -- declare permissions matching the skill's requirements
4. **Write handler** -- implement the skill's workflow in JavaScript
5. **Test locally** -- verify with `cowork test` command
6. **Package** -- zip the plugin directory

### Skill-to-Plugin Mapping

```
SKILL.md section        -> manifest.json field
---
name                    -> name (prefix with "bible-")
description             -> description
triggers                -> triggers
allowed-tools           -> permissions (map tool names to plugin permissions)
```

### Converting a Skill

Example: converting `overnight-runner` skill to a plugin.

**Original skill uses:**
- Bash (terminal commands)
- Read/Write (filesystem access)
- Checkpoint files

**Plugin manifest:**
```json
{
  "name": "bible-overnight-runner",
  "version": "1.0.0",
  "entry": "handler.js",
  "permissions": {
    "filesystem": {
      "read": ["tasks/**", "output/**", "CLAUDE.md"],
      "write": ["tasks/checkpoint.json", "output/**", "logs/**"]
    },
    "terminal": {
      "commands": [
        "git *", "npm *", "pnpm *", "npx *",
        "node *", "terminal-notifier *"
      ]
    }
  },
  "config": {
    "maxRetries": { "type": "number", "default": 5 },
    "retryDelaySeconds": { "type": "number", "default": 1800 },
    "notifyOnComplete": { "type": "boolean", "default": true }
  },
  "triggers": {
    "schedule": "0 1 * * *",
    "manual": true
  }
}
```

**Handler wraps the retry/checkpoint pattern:**
```javascript
function run(ctx) {
  const checkpointPath = path.join(ctx.project, 'tasks', 'checkpoint.json');

  let checkpoint = { status: 'not-started', completedItems: 0 };
  try {
    checkpoint = JSON.parse(fs.readFileSync(checkpointPath, 'utf8'));
  } catch {
    // Fresh start
  }

  if (checkpoint.status === 'complete') {
    return { status: 'success', summary: 'Already complete.' };
  }

  // Execute batch with checkpoint updates
  // (handler delegates to Claude for actual processing)
  return {
    status: 'delegated',
    task: `Resume from checkpoint: ${JSON.stringify(checkpoint)}`,
    skills: ['overnight-runner'],
    maxTurns: 100
  };
}
```

## Testing Plugins Locally

### Manual Test

```bash
# Create test input
echo '{"project":"/path/to/project","config":{"outputDir":"output/test"},"trigger":"manual"}' | node handler.js
```

### Cowork Test Command

```bash
# Validate manifest
cowork plugin validate ./my-plugin/

# Dry run (no side effects)
cowork plugin test ./my-plugin/ --dry-run

# Full test run
cowork plugin test ./my-plugin/ --project /path/to/test-project
```

### Test Checklist

- [ ] Handler reads stdin JSON without error
- [ ] Handler writes valid JSON to stdout
- [ ] All file paths stay within declared permissions
- [ ] Terminal commands match declared patterns
- [ ] Config defaults produce valid behavior
- [ ] Error cases return `{ "status": "error", "error": "message" }`
- [ ] No hardcoded paths (use `ctx.project` as base)
- [ ] No secrets in manifest or handler

## Publishing to Plugin Directory

### Package Structure

```bash
# Create distributable
cd my-plugin/
zip -r bible-my-plugin-1.0.0.zip manifest.json handler.js README.md
```

### Plugin Directory Submission

1. **Test thoroughly** -- local testing must pass all checklist items
2. **Version correctly** -- follow semver (major.minor.patch)
3. **Write README** -- include usage examples and configuration reference
4. **Submit** -- push to plugin registry or share as a zip

### Versioning

- **Patch** (1.0.1) -- bug fixes, no config changes
- **Minor** (1.1.0) -- new features, backward-compatible config
- **Major** (2.0.0) -- breaking config changes, new permissions

## Best Practices

1. **Minimal permissions** -- request only what the plugin needs
2. **Fail gracefully** -- always return valid JSON, even on error
3. **Idempotent** -- running twice should not corrupt state
4. **Checkpoint state** -- write progress to disk for crash recovery
5. **No secrets in code** -- use environment variables or vault references
6. **Test with real projects** -- synthetic tests miss permission edge cases
7. **Document config** -- every config field needs a description
8. **Respect cost limits** -- if delegating to Claude, set maxTurns
9. **Prefix with "cc-"** -- clear naming convention for CC Commander-derived plugins
10. **Include skill references** -- the `bible.skills` field in manifest helps users understand provenance

