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
{
"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
#!/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.
{
"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:
- Read
tasks/todo.mdfor current task state - Run
git log --since="24 hours ago"for recent activity - Identify blockers (items marked
[blocked]) - Generate markdown standup report
- Write to
output/standups/{date}.md
2. Code Review Bot Plugin
Automatically reviews new PRs and posts feedback.
{
"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:
- Receive PR event (number, branch, author)
- Checkout PR branch locally
- Run typecheck (
npx tsc --noEmit) - Analyze diff against coding-standards skill rules
- Check for security issues per pentest-checklist skill
- Write review to
output/reviews/pr-{number}.md - Post review comment via
gh pr review
3. Dependency Updater Plugin
Checks and updates dependencies with safety checks.
{
"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:
- Run
npm outdated/pnpm outdatedto list available updates - Filter by update type (patch/minor/major)
- For each update: install, typecheck, run tests
- If tests pass: keep update. If tests fail: revert and log.
- Create branch
deps/update-{date}with passing updates - Write report to
output/deps/{date}.md - Optionally create PR via
gh pr create
4. Test Runner Plugin
Runs test suite and generates coverage report.
{
"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:
- Run test suite with coverage enabled
- Parse coverage report (JSON format)
- Compare against previous run (stored in
output/tests/coverage-history.json) - Flag coverage decreases if
failOnDecreaseis true - Write report to
output/tests/{date}.md - Update coverage history
5. Report Generator Plugin
Batch-generates reports from templates and data sources.
{
"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:
- Scan template directory for
.mdtemplates - For each template: load data sources referenced in frontmatter
- Fill template variables from data
- Generate formatted report
- Write to
output/reports/{template-name}-{date}.md
Packaging CC Commander Skills as Plugins
Step-by-Step
- Identify the skill -- pick a SKILL.md from
~/.claude/skills/ - Extract the logic -- determine what tools/commands the skill uses
- Create manifest -- declare permissions matching the skill's requirements
- Write handler -- implement the skill's workflow in JavaScript
- Test locally -- verify with
cowork testcommand - 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:
{
"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:
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
# Create test input
echo '{"project":"/path/to/project","config":{"outputDir":"output/test"},"trigger":"manual"}' | node handler.js
Cowork Test Command
# 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.projectas base) - No secrets in manifest or handler
Publishing to Plugin Directory
Package Structure
# Create distributable
cd my-plugin/
zip -r bible-my-plugin-1.0.0.zip manifest.json handler.js README.md
Plugin Directory Submission
- Test thoroughly -- local testing must pass all checklist items
- Version correctly -- follow semver (major.minor.patch)
- Write README -- include usage examples and configuration reference
- 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
- Minimal permissions -- request only what the plugin needs
- Fail gracefully -- always return valid JSON, even on error
- Idempotent -- running twice should not corrupt state
- Checkpoint state -- write progress to disk for crash recovery
- No secrets in code -- use environment variables or vault references
- Test with real projects -- synthetic tests miss permission edge cases
- Document config -- every config field needs a description
- Respect cost limits -- if delegating to Claude, set maxTurns
- Prefix with "cc-" -- clear naming convention for CC Commander-derived plugins
- Include skill references -- the
bible.skillsfield in manifest helps users understand provenance