Skip to content
79 changes: 70 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ The recommended way to create new provider skills is using our AI-powered genera

- **Node.js 18+**
- **Python 3.9+** (for FastAPI examples)
- **[Claude CLI](https://docs.anthropic.com/en/docs/claude-cli)** — Install and authenticate with `claude login`
- **AI CLI Tool** — One of the following:
- [Claude CLI](https://docs.anthropic.com/en/docs/claude-cli) — Install and authenticate with `claude login` (default)
- [Copilot CLI](https://docs.github.com/en/copilot/how-tos/use-copilot-for-common-tasks/use-copilot-in-the-cli) — GitHub Copilot command-line tool
- **GITHUB_TOKEN** — For PR creation (optional but recommended)

```bash
Expand Down Expand Up @@ -182,13 +184,20 @@ providers:

By default, all providers run in parallel. Use `--parallel <n>` to limit concurrency.

### 4. Preview What Would Happen (Dry Run)
### 4. Use a Different CLI Tool

```bash
# Use Copilot instead of Claude
./scripts/generate-skills.sh generate "twilio" --cli copilot --create-pr
```

### 5. Preview What Would Happen (Dry Run)

```bash
./scripts/generate-skills.sh generate "linear" --dry-run
```

### 5. Resume After a Failed Generation
### 6. Resume After a Failed Generation

If generation fails (tests won't pass, API timeout, etc.), the worktree is preserved:

Expand All @@ -208,7 +217,7 @@ The review command will:
3. AI reviews and fixes any issues
4. If within thresholds, pushes and creates PR

### 6. Update an Existing Skill
### 7. Update an Existing Skill

Use the review command to improve skills already in the repository:

Expand Down Expand Up @@ -236,7 +245,7 @@ providers:
Verify signature verification is current with latest SDK.
```

### 7. Manual Review and PR Creation
### 8. Manual Review and PR Creation

```bash
# Generate without PR - inspect results first
Expand All @@ -256,7 +265,7 @@ gh pr create --title "feat: add clerk-webhooks skill"

When creating or updating a PR, use the title and description format in the next section.

### 8. Pull Request Title and Description
### 9. Pull Request Title and Description

Use these conventions so PRs are consistent and easy to review.

Expand Down Expand Up @@ -284,7 +293,7 @@ Use these conventions so PRs are consistent and easy to review.

For generator-created PRs, the body may also include **Generation details** (provider, tests passed, review passed, iterations, issues found/fixed). When editing an existing PR (e.g. after review), update the title and description to match these conventions if they don't already.

### 9. Clean Up Worktrees
### 10. Clean Up Worktrees

```bash
# List all worktrees
Expand Down Expand Up @@ -316,11 +325,12 @@ rm -rf .worktrees && git worktree prune

| Option | Description | Default |
|--------|-------------|---------|
| `--cli <tool>` | CLI tool to use (`claude`, `copilot`) | claude |
| `--working-dir <path>` | Generate in specific directory (skip worktree) | Creates worktree |
| `--no-worktree` | Generate in current directory (shorthand for `--working-dir .`) | Creates worktree |
| `--create-pr [type]` | Push and create PR (`true` or `draft`) | No PR |
| `--parallel <n>` | Max concurrent generations | All providers |
| `--model <model>` | Claude model | claude-opus-4-20250514 |
| `--model <model>` | Model to use | claude-opus-4-20250514 |
| `--max-iterations <n>` | Max test/fix cycles | 3 |
| `--base-branch <branch>` | Branch to create from | main |
| `--skip-tests` | Skip test execution | false |
Expand All @@ -338,18 +348,69 @@ rm -rf .worktrees && git worktree prune

| Option | Description | Default |
|--------|-------------|---------|
| `--cli <tool>` | CLI tool to use (`claude`, `copilot`) | claude |
| `--working-dir <path>` | Review in specific directory (skip worktree) | Creates worktree |
| `--no-worktree` | Review in current directory (shorthand for `--working-dir .`) | Creates worktree |
| `--create-pr [type]` | Create PR with fixes | No PR |
| `--max-iterations <n>` | Max fix cycles | 3 |
| `--parallel <n>` | Max concurrent reviews | All providers |
| `--model <model>` | Claude model | claude-opus-4-20250514 |
| `--model <model>` | Model to use | claude-opus-4-20250514 |
| `--branch-prefix <prefix>` | Branch name prefix | improve |
| `--config <file>` | YAML config file | — |
| `--dry-run` | Preview without executing | false |

---

## Adding New CLI Tools

The generator supports a pluggable CLI adapter system. To add support for a new AI CLI tool:

1. **Create an adapter file** at `scripts/skill-generator/lib/cli-adapters/<tool>.ts`:

```typescript
import type { CliAdapter, CliAdapterOptions, CliCommandConfig } from './types';

const DEFAULT_MODEL = 'your-model-name-here';
export const myToolAdapter: CliAdapter = {
name: 'mytool',

buildCommand(options: CliAdapterOptions): CliCommandConfig {
const model = options.model ?? DEFAULT_MODEL;

return {
command: 'mytool', // The CLI command to execute
args: [
// Arguments specific to this CLI tool
'--model', model,
'--some-flag',
],
};
},
};
```

2. **Register the adapter** in `scripts/skill-generator/lib/cli-adapters/index.ts`:

```typescript
import { myToolAdapter } from './mytool';

const adapters: Map<string, CliAdapter> = new Map([
['claude', claudeAdapter],
['copilot', copilotAdapter],
['mytool', myToolAdapter], // Add your adapter here
]);
```

3. **Use it** with the `--cli` flag:

```bash
./scripts/generate-skills.sh generate stripe --cli mytool
```

The adapter interface is simple — it just needs to return the command name and arguments. The generator handles stdin prompt passing, timeout, progress display, and output capture.

---

## Manual Contribution

Prefer to write code yourself? Follow these steps.
Expand Down
20 changes: 15 additions & 5 deletions scripts/skill-generator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import { config } from 'dotenv';
import { Command } from 'commander';
import { Command, Option } from 'commander';
import chalk from 'chalk';
import pLimit from 'p-limit';
import { mkdirSync, writeFileSync, appendFileSync, existsSync } from 'fs';
Expand All @@ -25,7 +25,8 @@ import type {
import { mergeProviderConfigs, skillExists } from './lib/config';
import { generateSkill } from './lib/generator';
import { reviewExistingSkill } from './lib/reviewer';
import { DEFAULT_MODEL, setCachedVersions } from './lib/claude';
import { setCachedVersions } from './lib/cli';
import { DEFAULT_CLI_TOOL, AVAILABLE_CLI_TOOLS } from './lib/cli-adapters';
import { getLatestVersions } from './lib/versions';
import {
createWorktree,
Expand Down Expand Up @@ -143,6 +144,7 @@ async function handleGenerate(
maxIterations: string;
createPr: boolean | string;
model: string;
cli: string;
workingDir?: string; // Generate in this directory (skip worktree creation)
worktree?: boolean; // Set to false by --no-worktree flag
}
Expand Down Expand Up @@ -186,6 +188,7 @@ async function handleGenerate(
: providerConfigs.length;

console.log(chalk.blue(`Providers (${providerConfigs.length}): ${providerConfigs.map(p => p.name).join(', ')}`));
console.log(chalk.blue(`CLI tool: ${options.cli}`));
console.log(chalk.blue(`Model: ${options.model}`));
if (!useProvidedDir && providerConfigs.length > 1) {
console.log(chalk.blue(`Parallel: ${parallelCount}`));
Expand All @@ -206,6 +209,7 @@ async function handleGenerate(
maxIterations: parseInt(options.maxIterations, 10),
createPr: normalizeCreatePr(options.createPr),
model: options.model,
cliTool: options.cli,
};

const { dir: resultsDir } = createResultsDir();
Expand Down Expand Up @@ -398,6 +402,7 @@ async function handleReview(
createPr: boolean | string;
branchPrefix: string;
model: string;
cli: string;
workingDir?: string; // Review in this directory (any git checkout, worktree, or local path)
worktree?: boolean; // Set to false by --no-worktree flag
}
Expand Down Expand Up @@ -473,6 +478,7 @@ async function handleReview(
: existingProviders.length;

console.log(chalk.blue(`Providers: ${existingProviders.map(p => p.name).join(', ')}`));
console.log(chalk.blue(`CLI tool: ${options.cli}`));
console.log(chalk.blue(`Model: ${options.model}`));
if (!useProvidedDir && existingProviders.length > 1) {
console.log(chalk.blue(`Parallel: ${parallelCount}`));
Expand All @@ -488,6 +494,7 @@ async function handleReview(
createPr: normalizeCreatePr(options.createPr),
branchPrefix: options.branchPrefix,
model: options.model,
cliTool: options.cli,
};

const { dir: resultsDir } = createResultsDir();
Expand Down Expand Up @@ -664,6 +671,7 @@ async function handleReview(
dryRun: reviewOptions.dryRun,
maxIterations: reviewOptions.maxIterations,
model: reviewOptions.model,
cliTool: reviewOptions.cliTool,
parallel: existingProviders.length > 1,
});

Expand Down Expand Up @@ -825,15 +833,16 @@ const program = new Command();

program
.name('skill-generator')
.description('Generate and review webhook skills using Claude CLI')
.description('Generate and review webhook skills using AI CLI tools')
.version('1.0.0');

program
.command('generate')
.description('Generate new webhook skills')
.argument('[providers...]', 'Provider names, or provider=url, or provider=url|notes (e.g. elevenlabs=https://github.com/elevenlabs/elevenlabs-js|Official SDK supports webhook verification)')
.option('--config <file>', 'Load provider configs from YAML file')
.option('--model <model>', 'Claude model to use', DEFAULT_MODEL)
.addOption(new Option('--cli <tool>', 'CLI tool to use').choices(AVAILABLE_CLI_TOOLS).default(DEFAULT_CLI_TOOL))
.option('--model <model>', "Model to use (defaults to CLI tool's default model)", undefined)
.option('--parallel <n>', 'Max concurrent agents (default: all providers)')
.option('--dry-run', 'Show what would be done without executing', false)
.option('--base-branch <branch>', 'Branch to create from', 'main')
Expand All @@ -850,7 +859,8 @@ program
.description('Review and improve existing webhook skills')
.argument('[providers...]', 'Provider names, or provider=url, or provider=url|notes (e.g. elevenlabs=https://.../elevenlabs-js|Prefer SDK verification in skill)')
.option('--config <file>', 'Load provider configs from YAML file')
.option('--model <model>', 'Claude model to use', DEFAULT_MODEL)
.addOption(new Option('--cli <tool>', 'CLI tool to use').choices(AVAILABLE_CLI_TOOLS).default(DEFAULT_CLI_TOOL))
.option('--model <model>', "Model to use (defaults to CLI tool's default model)", undefined)
.option('--parallel <n>', 'Max concurrent agents (default: all providers)')
.option('--dry-run', 'Show what would be done without executing', false)
.option('--max-iterations <n>', 'Max review/fix cycles', '3')
Expand Down
27 changes: 27 additions & 0 deletions scripts/skill-generator/lib/cli-adapters/claude.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Claude CLI adapter
*
* Supports the Anthropic Claude CLI tool
* https://github.com/anthropics/claude-cli
*/

import type { CliAdapter, CliAdapterOptions, CliCommandConfig } from './types';

const DEFAULT_MODEL = 'claude-opus-4-20250514';

export const claudeAdapter: CliAdapter = {
name: 'claude',

buildCommand(options: CliAdapterOptions): CliCommandConfig {
const model = options.model ?? DEFAULT_MODEL;

return {
command: 'claude',
args: [
'-p',
'--model', model,
'--dangerously-skip-permissions',
],
};
},
};
33 changes: 33 additions & 0 deletions scripts/skill-generator/lib/cli-adapters/copilot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copilot CLI adapter
*
* Supports the GitHub Copilot CLI tool
*/

import type { CliAdapter, CliAdapterOptions, CliCommandConfig } from './types';

// Default model for the GitHub Copilot CLI; can be overridden via options.model
// Valid choices: claude-sonnet-4.5, claude-haiku-4.5, claude-opus-4.5, claude-sonnet-4,
// gemini-3-pro-preview, gpt-5.2-codex, gpt-5.2, gpt-5.1-codex-max, gpt-5.1-codex,
// gpt-5.1, gpt-5, gpt-5.1-codex-mini, gpt-5-mini, gpt-4.1
// Note: gpt-5 has known issues with web fetching causing "invalid_request_body" errors
const DEFAULT_MODEL = 'claude-sonnet-4';

export const copilotAdapter: CliAdapter = {
name: 'copilot',

buildCommand(options: CliAdapterOptions): CliCommandConfig {
const model = options.model ?? DEFAULT_MODEL;

return {
command: 'copilot',
args: [
// Copilot CLI reads prompts from stdin (no -p flag needed like Claude)
'--model', model,
// Use --allow-all to enable tools, paths, AND URLs without interactive prompts
// (--allow-all-tools alone still blocks URL fetches in non-interactive mode)
'--allow-all',
],
};
},
};
48 changes: 48 additions & 0 deletions scripts/skill-generator/lib/cli-adapters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* CLI Adapter factory
*
* Registry and factory for CLI adapters
*/

import type { CliAdapter } from './types';
import { claudeAdapter } from './claude';
import { copilotAdapter } from './copilot';

// Re-export types
export type { CliAdapter, CliAdapterOptions, CliCommandConfig } from './types';

/**
* Registry of available CLI adapters
*/
const adapters: Map<string, CliAdapter> = new Map([
['claude', claudeAdapter],
['copilot', copilotAdapter],
]);

/**
* List of available CLI tool names (for help text and validation)
*/
export const AVAILABLE_CLI_TOOLS = Array.from(adapters.keys());

/**
* Default CLI tool to use
*/
export const DEFAULT_CLI_TOOL = 'claude';

/**
* Get a CLI adapter by name
*
* @param name - The name of the CLI tool (e.g., 'claude', 'copilot')
* @returns The CLI adapter
* @throws Error if the adapter is not found
*/
export function getCliAdapter(name: string): CliAdapter {
const adapter = adapters.get(name);

if (!adapter) {
const available = AVAILABLE_CLI_TOOLS.join(', ');
throw new Error(`Unknown CLI tool: '${name}'. Available tools: ${available}`);
}

return adapter;
}
29 changes: 29 additions & 0 deletions scripts/skill-generator/lib/cli-adapters/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* CLI Adapter types for supporting multiple AI CLI tools
*/

/**
* Options passed to the adapter when building the command
*/
export interface CliAdapterOptions {
model?: string;
}

/**
* The command configuration returned by an adapter
*/
export interface CliCommandConfig {
command: string;
args: string[];
}

/**
* Interface that all CLI adapters must implement
*/
export interface CliAdapter {
/** Unique identifier for this CLI tool */
name: string;

/** Build the command and arguments for executing the CLI */
buildCommand(options: CliAdapterOptions): CliCommandConfig;
}
Loading