Editor
QinghongLin/data2story-skill
Read analyst.json and detective.json, make all editorial decisions — what the blog argues, which findings matter, narrative arc and section structure.
Step-by-step guide for adding support for a new AI editor format to PRPM - covers types, converters, schemas, CLI, webapp, and testing
$ npx skills add pr-pm/prpm --skill adding-new-ai-format -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install pr-pm/prpm adding-new-ai-format --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/pr-pm/prpm.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/adding-new-ai-format .claude/skills/adding-new-ai-format && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "adding-new-ai-format" agent skill from https://github.com/pr-pm/prpm/tree/main/.claude/skills/adding-new-ai-format into .claude/skills/adding-new-ai-format/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "adding-new-ai-format", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/pr-pm/prpm/tree/main/.claude/skills/adding-new-ai-formatType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add pr-pm/prpm --skill adding-new-ai-format -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install pr-pm/prpm adding-new-ai-format --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/pr-pm/prpm.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/adding-new-ai-format .agents/skills/adding-new-ai-format && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "adding-new-ai-format" agent skill from https://github.com/pr-pm/prpm/tree/main/.claude/skills/adding-new-ai-format into .agents/skills/adding-new-ai-format/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "adding-new-ai-format", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add pr-pm/prpm --skill adding-new-ai-format -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install pr-pm/prpm adding-new-ai-format --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/pr-pm/prpm.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/adding-new-ai-format .cursor/skills/adding-new-ai-format && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "adding-new-ai-format" agent skill from https://github.com/pr-pm/prpm/tree/main/.claude/skills/adding-new-ai-format into .cursor/skills/adding-new-ai-format/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "adding-new-ai-format", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/pr-pm/prpm.git --path .claude/skills/adding-new-ai-format--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add pr-pm/prpm --skill adding-new-ai-format -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install pr-pm/prpm adding-new-ai-format --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/pr-pm/prpm.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/adding-new-ai-format .gemini/skills/adding-new-ai-format && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "adding-new-ai-format" agent skill from https://github.com/pr-pm/prpm/tree/main/.claude/skills/adding-new-ai-format into .gemini/skills/adding-new-ai-format/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "adding-new-ai-format", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install pr-pm/prpm adding-new-ai-formatInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add pr-pm/prpm --skill adding-new-ai-format -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/pr-pm/prpm.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/adding-new-ai-format .github/skills/adding-new-ai-format && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "adding-new-ai-format" agent skill from https://github.com/pr-pm/prpm/tree/main/.claude/skills/adding-new-ai-format into .github/skills/adding-new-ai-format/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "adding-new-ai-format", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add pr-pm/prpm --skill adding-new-ai-format -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install pr-pm/prpm adding-new-ai-format --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/pr-pm/prpm.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/adding-new-ai-format .opencode/skills/adding-new-ai-format && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "adding-new-ai-format" agent skill from https://github.com/pr-pm/prpm/tree/main/.claude/skills/adding-new-ai-format into .opencode/skills/adding-new-ai-format/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "adding-new-ai-format", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
adding-new-ai-formatStep-by-step guide for adding support for a new AI editor format to PRPM - covers types, converters, schemas, CLI, webapp, and testing
Adding New AI Format is an agent skill from pr-pm/prpm. Step-by-step guide for adding support for a new AI editor format to PRPM - covers types, converters, schemas, CLI, webapp, and testing
Its SKILL.md is about 6.6k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
The repository describes itself as: The universal registry for AI coding tools. The licence is MIT.
12 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 5f993e6. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
npmFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
registry.prpm.devjson-schema.orgFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Adding New AI Format loads about 6.6k tokens when it runs. Until then it costs about 39 tokens; SKILL.md has 1,061 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from pr-pm/prpm at commit 5f993e6, republished under its MIT licence (© pr-pm). 1,061 words, ~6,618 tokens.
.claude/skills/adding-new-ai-format/SKILL.md (or your agent's skills folder).Complete process for adding support for a new AI editor format (like OpenCode, Cursor, Claude, etc.) to PRPM.
This skill documents the systematic process for adding a new AI format to PRPM, based on the OpenCode integration. Follow these steps in order to ensure complete integration across all packages.
packages/types/)File: src/package.ts
Add the format to the Format type and FORMATS array:
export type Format =
| 'cursor'
| 'claude'
| 'continue'
| 'windsurf'
| 'copilot'
| 'kiro'
| 'agents.md'
| 'gemini.md'
| 'claude.md'
| 'gemini'
| 'opencode' // Add new format here
| 'ruler'
| 'generic'
| 'mcp';
export const FORMATS: readonly Format[] = [
'cursor',
'claude',
// ... other formats
'opencode', // Add here too
'ruler',
'generic',
'mcp',
] as const;Build and verify:
npm run build --workspace=@pr-pm/typespackages/converters/schemas/)Create JSON schema file: {format}.schema.json
Example structure:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://registry.prpm.dev/api/v1/schemas/opencode.json",
"title": "OpenCode Agent Format",
"description": "JSON Schema for OpenCode Agents",
"type": "object",
"required": ["frontmatter", "content"],
"properties": {
"frontmatter": {
"type": "object",
"required": ["description"],
"properties": {
"description": { "type": "string" },
// Format-specific fields
},
"additionalProperties": false
},
"content": {
"type": "string",
"description": "Body content as markdown"
}
}
}CRITICAL Schema Requirements:
$id must use new URL pattern: https://registry.prpm.dev/api/v1/schemas/{format}.json for base schemashttps://registry.prpm.dev/api/v1/schemas/{format}/{subtype}.json"additionalProperties": false to frontmatter object to catch invalid fieldsname) should use pattern: "pattern": "^[a-z0-9-]+$"If the format has subtypes (like Claude with agents/skills/commands), create separate schema files:
{format}-agent.schema.json{format}-skill.schema.json{format}-slash-command.schema.jsonIMPORTANT: When creating subtype schemas, you MUST update the validation logic to map them.
packages/converters/docs/)CRITICAL: Create comprehensive format documentation file: {format}.md
This documentation serves as the source of truth for:
Required sections:
# {Format Name} Format Specification
**File Locations:**
- {Type 1}: `{path}`
- {Type 2}: `{path}`
**Format:** {Markdown/JSON/etc.} with {YAML frontmatter/etc.}
**Official Docs:** {link to official documentation}
## Overview
Brief description of the format and its purpose.
## Frontmatter Fields
### Required Fields
- **`field-name`** (type): Description
### Optional Fields
- **`field-name`** (type): Description
## Content Format
Describe the body/content structure.
## Best Practices
1. Practice 1
2. Practice 2
## Conversion Notes
### From {Format} to Canonical
How the converter parses this format.
### From Canonical to {Format}
How the converter generates this format.
## Limitations
- Limitation 1
- Limitation 2
## Examples
### Example 1
```markdown
{example content}
**Add to README.md**:
1. **Format Matrix table**: Add row(s) with subtypes, official docs, and OpenCode docs links
2. **Available Formats table**: Add row with link to your new `.md` file
3. **Schema Validation section**: Add schema filename(s) to appropriate list
4. **Frontmatter Support table**: Add row with frontmatter requirements
5. **File Organization table**: Add row with file paths and structure
See `packages/converters/docs/README.md` for examples of how other formats are documented.
## Step 4: Converters Package - Canonical Types
**File**: `packages/converters/src/types/canonical.ts`
### 3a. Add format to CanonicalPackage.format union:
```typescript
format: 'cursor' | 'claude' | ... | 'opencode' | 'ruler' | 'generic' | 'mcp';// In CanonicalPackage.metadata
metadata?: {
// ... existing configs
opencode?: {
mode?: 'subagent' | 'primary' | 'all';
model?: string;
temperature?: number;
permission?: Record<string, any>;
disable?: boolean;
};
};export interface MetadataSection {
type: 'metadata';
data: {
title: string;
description: string;
// ... existing fields
opencode?: {
// Same structure as above
};
};
}formatScores?: {
cursor?: number;
// ... others
opencode?: number;
};
sourceFormat?: 'cursor' | 'claude' | ... | 'opencode' | ... | 'generic';File: packages/converters/src/from-{format}.ts
Create converter that parses format → canonical:
import type {
CanonicalPackage,
PackageMetadata,
Section,
MetadataSection,
ToolsSection,
} from './types/canonical.js';
import { setTaxonomy } from './taxonomy-utils.js';
import yaml from 'js-yaml'; // If using YAML frontmatter
// Define format-specific interfaces
interface FormatFrontmatter {
// Format-specific frontmatter structure
}
// Parse frontmatter if needed
function parseFrontmatter(content: string): {
frontmatter: Record<string, any>;
body: string
} {
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
if (!match) {
return { frontmatter: {}, body: content };
}
const frontmatter = yaml.load(match[1]) as Record<string, any>;
const body = match[2];
return { frontmatter, body };
}
export function fromFormat(
content: string,
metadata: Partial<PackageMetadata> & Pick<PackageMetadata, 'id' | 'name' | 'version' | 'author'>
): CanonicalPackage {
const { frontmatter, body } = parseFrontmatter(content);
const fm = frontmatter as FormatFrontmatter;
const sections: Section[] = [];
// 1. Create metadata section
const metadataSection: MetadataSection = {
type: 'metadata',
data: {
title: metadata.name || metadata.id,
description: fm.description || metadata.description || '',
version: metadata.version || '1.0.0',
author: metadata.author,
},
};
// Store format-specific data for roundtrip
if (/* has format-specific fields */) {
metadataSection.data.formatName = {
// Format-specific data
};
}
sections.push(metadataSection);
// 2. Extract tools (if applicable)
if (fm.tools) {
const enabledTools = Object.entries(fm.tools)
.filter(([_, enabled]) => enabled === true)
.map(([tool, _]) => {
// Normalize tool names to canonical format
return normalizeToolName(tool);
});
if (enabledTools.length > 0) {
sections.push({
type: 'tools',
tools: enabledTools,
});
}
}
// 3. Add body as instructions
if (body.trim()) {
sections.push({
type: 'instructions',
title: 'Instructions',
content: body.trim(),
});
}
// 4. Build canonical package
const canonicalContent: CanonicalPackage['content'] = {
format: 'canonical',
version: '1.0',
sections
};
const pkg: CanonicalPackage = {
...metadata,
id: metadata.id,
name: metadata.name || metadata.id,
version: metadata.version,
author: metadata.author,
description: metadata.description || fm.description || '',
tags: metadata.tags || [],
format: 'formatname',
subtype: 'agent', // Or detect from content
content: canonicalContent,
};
setTaxonomy(pkg, 'formatname', 'agent');
return pkg;
}Key points:
format: 'canonical' and version: '1.0' in contenttitle fieldsetTaxonomy() before returningFile: packages/converters/src/to-{format}.ts
Create converter that converts canonical → format:
import type {
CanonicalPackage,
ConversionResult,
} from './types/canonical.js';
import yaml from 'js-yaml';
export function toFormat(pkg: CanonicalPackage): ConversionResult {
const warnings: string[] = [];
let qualityScore = 100;
try {
const content = convertContent(pkg, warnings);
const lossyConversion = warnings.some(w =>
w.includes('not supported') || w.includes('skipped')
);
if (lossyConversion) {
qualityScore -= 10;
}
return {
content,
format: 'formatname',
warnings: warnings.length > 0 ? warnings : undefined,
lossyConversion,
qualityScore,
};
} catch (error) {
warnings.push(`Conversion error: ${error instanceof Error ? error.message : String(error)}`);
return {
content: '',
format: 'formatname',
warnings,
lossyConversion: true,
qualityScore: 0,
};
}
}
function convertContent(pkg: CanonicalPackage, warnings: string[]): string {
const lines: string[] = [];
// Extract sections
const metadata = pkg.content.sections.find(s => s.type === 'metadata');
const tools = pkg.content.sections.find(s => s.type === 'tools');
const instructions = pkg.content.sections.find(s => s.type === 'instructions');
// Build frontmatter
const frontmatter: Record<string, any> = {};
if (metadata?.type === 'metadata') {
frontmatter.description = metadata.data.description;
}
// Restore format-specific metadata (for roundtrip)
const formatData = metadata?.type === 'metadata' ? metadata.data.formatName : undefined;
if (formatData) {
Object.assign(frontmatter, formatData);
}
// Convert tools
if (tools?.type === 'tools' && tools.tools.length > 0) {
frontmatter.tools = convertToolsToFormatStructure(tools.tools);
}
// Generate YAML frontmatter (if applicable)
lines.push('---');
lines.push(yaml.dump(frontmatter, { indent: 2, lineWidth: -1 }).trim());
lines.push('---');
lines.push('');
// Add body content
if (instructions?.type === 'instructions') {
lines.push(instructions.content);
}
return lines.join('\n').trim() + '\n';
}Section type handling:
section.data.role (NOT section.content)section.items (NOT section.rules), each item has rule.contentsection.content and section.titlesection.examples array with description and codeFile: packages/converters/src/index.ts
Add to exports:
// From converters
export { fromFormat } from './from-format.js';
// To converters
export { toFormat } from './to-format.js';File: packages/converters/src/validation.ts
export type FormatType =
| 'cursor'
| 'claude'
// ... others
| 'opencode'
| 'canonical';const schemaMap: Record<FormatType, string> = {
'cursor': 'cursor.schema.json',
// ... others
'opencode': 'opencode.schema.json',
'canonical': 'canonical.schema.json',
};const subtypeSchemaMap: Record<string, string> = {
'claude:agent': 'claude-agent.schema.json',
'claude:skill': 'claude-skill.schema.json',
'claude:slash-command': 'claude-slash-command.schema.json',
'claude:hook': 'claude-hook.schema.json',
'cursor:slash-command': 'cursor-command.schema.json',
'kiro:hook': 'kiro-hooks.schema.json',
'kiro:agent': 'kiro-agent.schema.json',
'droid:skill': 'droid-skill.schema.json',
'droid:slash-command': 'droid-slash-command.schema.json',
'droid:hook': 'droid-hook.schema.json',
'opencode:slash-command': 'opencode-slash-command.schema.json', // Add your subtypes here
};Why this matters: Without adding subtypes to subtypeSchemaMap, validation will fall back to the base format schema and won't validate subtype-specific fields. This causes validation to fail or pass incorrectly.
File: packages/converters/src/taxonomy-utils.ts
Add to Format type:
export type Format = 'cursor' | 'claude' | ... | 'opencode' | ... | 'mcp';Add to normalizeFormat:
export function normalizeFormat(sourceFormat: string): Format {
const normalized = sourceFormat.toLowerCase();
if (normalized.includes('cursor')) return 'cursor';
// ... others
if (normalized.includes('opencode')) return 'opencode';
return 'generic';
}Build converters:
npm run build --workspace=@pr-pm/convertersFile: packages/cli/src/core/filesystem.ts
export function getDestinationDir(format: Format, subtype: Subtype, name?: string): string {
const packageName = stripAuthorNamespace(name);
switch (format) {
// ... existing cases
case 'opencode':
// OpenCode supports agents, slash commands, and custom tools
// Agents: .opencode/agent/*.md
// Commands: .opencode/command/*.md
// Tools: .opencode/tool/*.ts or *.js
if (subtype === 'agent') return '.opencode/agent';
if (subtype === 'slash-command') return '.opencode/command';
if (subtype === 'tool') return '.opencode/tool';
return '.opencode/agent'; // Default
// ... rest
}
}const formatDirs: Array<{ format: Format; dir: string }> = [
{ format: 'cursor', dir: '.cursor' },
// ... others
{ format: 'opencode', dir: '.opencode' },
{ format: 'agents.md', dir: '.agents' },
];Files: packages/cli/src/commands/search.ts and packages/cli/src/commands/install.ts
Add to both files:
const formatIcons: Record<Format, string> = {
'claude': '🤖',
'cursor': '📋',
// ... others
'opencode': '⚡', // Choose appropriate emoji
'gemini.md': '✨', // Don't forget format aliases
'claude.md': '🤖',
'ruler': '📏',
'generic': '📦',
};const formatLabels: Record<Format, string> = {
'claude': 'Claude',
'cursor': 'Cursor',
// ... others
'opencode': 'OpenCode',
'gemini.md': 'Gemini', // Format aliases
'claude.md': 'Claude',
'ruler': 'Ruler',
'generic': '',
};File: packages/webapp/src/app/(app)/search/SearchClient.tsx
const FORMAT_SUBTYPES: Record<Format, Subtype[]> = {
'cursor': ['rule', 'agent', 'slash-command', 'tool'],
'claude': ['skill', 'agent', 'slash-command', 'tool', 'hook'],
'claude.md': ['agent'], // Format aliases
'gemini.md': ['slash-command'],
// ... others
'opencode': ['agent', 'slash-command', 'tool'], // List all supported subtypes
'ruler': ['rule', 'agent', 'tool'],
'generic': ['rule', 'agent', 'skill', 'slash-command', 'tool', 'chatmode', 'hook'],
};<select
value={selectedFormat}
onChange={(e) => setSelectedFormat(e.target.value as Format | '')}
className="w-full px-3 py-2 bg-prpm-dark border border-prpm-border rounded text-white focus:outline-none focus:border-prpm-accent"
>
<option value="">All Formats</option>
<option value="cursor">Cursor</option>
<option value="claude">Claude</option>
<option value="continue">Continue</option>
<option value="windsurf">Windsurf</option>
<option value="copilot">GitHub Copilot</option>
<option value="kiro">Kiro</option>
<option value="gemini">Gemini CLI</option>
<option value="droid">Droid</option>
<option value="opencode">OpenCode</option> {/* Add your format here */}
<option value="mcp">MCP</option>
<option value="agents.md">Agents.md</option>
<option value="generic">Generic</option>
</select>{selectedFormat === 'opencode' && (
<div className="mt-3 p-3 bg-gray-500/10 border border-gray-500/30 rounded-lg">
<p className="text-xs text-gray-400">
Tool-specific format for <strong>OpenCode AI</strong>
</p>
</div>
)}CRITICAL: Add the format to all Fastify route validation schemas to prevent 400 errors.
packages/registry/src/routes/download.tsAdd to format enum in schema (2-3 places):
// Download route schema (line ~46)
format: {
type: 'string',
enum: ['cursor', 'claude', 'continue', 'windsurf', 'copilot', 'kiro', 'ruler', 'agents.md', 'gemini', 'droid', 'opencode', 'generic'],
description: 'Target format for conversion (optional)',
},
// Compatibility check route schema (lines ~201, 205)
from: {
type: 'string',
enum: ['cursor', 'claude', 'continue', 'windsurf', 'copilot', 'kiro', 'ruler', 'agents.md', 'gemini', 'droid', 'opencode', 'generic'],
},
to: {
type: 'string',
enum: ['cursor', 'claude', 'continue', 'windsurf', 'copilot', 'kiro', 'ruler', 'agents.md', 'gemini', 'droid', 'opencode', 'generic'],
},packages/registry/src/routes/search.tsAdd to FORMAT_ENUM constant (line ~12):
const FORMAT_ENUM = ['cursor', 'claude', 'continue', 'windsurf', 'copilot', 'kiro', 'agents.md', 'gemini', 'ruler', 'droid', 'opencode', 'generic', 'mcp'] as const;packages/registry/src/routes/analytics.tsAdd to both Zod schema and Fastify schema:
// Zod schema (line ~15)
const TrackDownloadSchema = z.object({
packageId: z.string(),
version: z.string().optional(),
format: z.enum(['cursor', 'claude', 'continue', 'windsurf', 'copilot', 'kiro', 'agents.md', 'gemini', 'ruler', 'droid', 'opencode', 'generic', 'mcp']).optional(),
client: z.enum(['cli', 'web', 'api']).optional(),
});
// Fastify schema (line ~45)
format: {
type: 'string',
enum: ['cursor', 'claude', 'continue', 'windsurf', 'copilot', 'kiro', 'agents.md', 'gemini', 'ruler', 'droid', 'opencode', 'generic', 'mcp'],
description: 'Download format'
},Why this matters: Without these additions, the registry will reject API requests with 400 validation errors when users try to download or filter by the new format.
npm run build --workspace=@pr-pm/typesThis is critical because other packages depend on the updated Format type.
npm run build --workspace=@pr-pm/registry
npm run build --workspace=@pr-pm/webappnpm run typecheckFix any TypeScript errors:
npm run buildnpm test --workspace=@pr-pm/converters// packages/converters/src/__tests__/to-opencode.test.ts
import { describe, it, expect } from 'vitest';
import { toOpencode } from '../to-opencode.js';
import { validateMarkdown } from '../validation.js';
import type { CanonicalPackage } from '../types/canonical.js';
describe('OpenCode Format', () => {
it('should convert from OpenCode to canonical', () => {
const opencodeContent = `---
description: Test agent
mode: subagent
---
Test instructions`;
const result = fromOpencode(opencodeContent, {
id: 'test',
name: 'test',
version: '1.0.0',
author: 'test',
});
expect(result.format).toBe('opencode');
expect(result.subtype).toBe('agent');
});
it('should convert canonical to OpenCode', () => {
const canonical: CanonicalPackage = {
// ... build test package
};
const result = toOpencode(canonical);
expect(result.format).toBe('opencode');
expect(result.content).toContain('---');
});
// CRITICAL: Add schema validation tests!
describe('JSON Schema Validation', () => {
it('should generate schema-compliant agent output', () => {
const agentPackage: CanonicalPackage = {
// ... build agent test package with subtype: 'agent'
};
const result = toOpencode(agentPackage);
const validation = validateMarkdown('opencode', result.content, 'agent');
if (!validation.valid) {
console.error('Validation errors:', validation.errors);
}
expect(validation.valid).toBe(true);
expect(validation.errors).toHaveLength(0);
});
});
});Why Schema Validation Tests Matter:
mode field via validation testsBeyond the format documentation created in Step 3:
docs/development/ if there are special considerationsdocs/decisions/Formats like 'gemini.md' and 'claude.md' are aliases that MUST be included in all format mappings.
data.role, not contentitems, not rulestitle fieldcontent, not descriptionMust always include:
{
format: 'canonical',
version: '1.0',
sections: [...]
}setTaxonomy(pkg, 'formatname', 'subtype'); // Returns void
return pkg; // Return the package separatelyMap format-specific tool names to canonical:
write → Writeedit → Editbash → BashIf using YAML frontmatter:
import yaml from 'js-yaml'; // Top-level import
// NOT: const yaml = await import('js-yaml');Before submitting:
Types Package:
Converters Package:
CLI Package:
Webapp Package:
Registry Package:
Testing:
See the following files for reference:
packages/converters/src/from-opencode.tspackages/converters/src/to-opencode.tspackages/converters/schemas/opencode.schema.jsonAdding a new format requires changes across 6 packages:
Build order matters: types → converters → cli → webapp → registry
Follow the steps systematically, use existing format implementations as reference, and always run typecheck and tests before submitting.
© pr-pm, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .claude/skills/adding-new-ai-format of pr-pm/prpm.
Open the folder on GitHubat commit 5f993e6
Adding New AI Format next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Adding New AI Format this skillpr-pm/prpm | 122 | — | ~6.6k | Automated safety check: Pass | MIT | |
| EditorQinghongLin/data2story-skill | 156 | — | ~3.9k | Automated safety check: Pass | MIT | |
| EditorQinghongLin/data2story-skill | 156 | — | ~1.4k | Automated safety check: Pass | MIT | |
| Editoralecs5am/ralphy | 136 | — | ~3.6k | Automated safety check: Pass | Apache-2.0 | |
| Handsontable Cell Editor Developerhandsontable/handsontable | 22k | — | ~3.8k | Automated safety check: Pass | Custom licence | |
| Editor Test Harvesterudecode/plate | 17k | — | ~7.6k | Automated safety check: Pass | Custom licence |
QinghongLin/data2story-skill
Read analyst.json and detective.json, make all editorial decisions — what the blog argues, which findings matter, narrative arc and section structure.
QinghongLin/data2story-skill
Read analyst.json and detective.json, make all editorial decisions — what the blog argues, which findings matter, narrative arc and section structure.
alecs5am/ralphy
Composition and render craft — assembles scenario.json plus asset-manifest.json into a HyperFrames HTML composition and renders the mp4.
handsontable/handsontable
Guides building a new Handsontable cell editor through its four-state lifecycle, DOM setup, viewport-aware positioning and async validation, matching the grid's existing editor classes.
udecode/plate
Mine external editor repositories for portable editor-behavior tests with ClawSweeper-style discipline: multi-pass exhaustive inventory, confidence scoring, framework-specific skip reasons…
udecode/plate
Turn an editor-test-harvester report into one lane-specific execution plan, e.g.
pr-pm/prpm
Reference for writing Claude Code agent files: location, frontmatter fields, validation limits, tool and model choices, and the required content format.
pr-pm/prpm
Covers how to build, configure and publish Claude Code hooks: event types, exit codes, JSON I/O, and PRPM packaging.
pr-pm/prpm
Shows how to write .claude/rules/ files correctly: paths frontmatter instead of globs, quoted glob patterns, global rules and conversion of Cursor rules.
pr-pm/prpm
Reference for writing portable Agent Skills packages, covering SKILL.md frontmatter limits, name rules, directory layout and where Codex CLI, GitHub Copilot and Amp look for skills.
pr-pm/prpm
A skill your agent uses when building custom Kiro AI agents or when user asks for agent configurations - provides JSON structure, tool configuration, prompt patterns, and security best practices for…
pr-pm/prpm
A skill your agent uses when creating OpenCode agents - provides markdown format with YAML frontmatter, mode/tools/permission configuration, and best practices for specialized AI assistants
Step-by-step guide for adding support for a new AI editor format to PRPM - covers types, converters, schemas, CLI, webapp, and testing. Adding New AI Format is an agent skill from pr-pm/prpm.
Run `npx skills add pr-pm/prpm --skill adding-new-ai-format -a claude-code`. Or copy the skill folder (.claude/skills/adding-new-ai-format in pr-pm/prpm) into .claude/skills/adding-new-ai-format in your project. Claude Code loads it when a task matches its description.
Run `npx skills add pr-pm/prpm --skill adding-new-ai-format -a codex`. Or copy the skill folder (.claude/skills/adding-new-ai-format in pr-pm/prpm) into .agents/skills/adding-new-ai-format in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add pr-pm/prpm --skill adding-new-ai-format -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/adding-new-ai-format, .gemini/skills/adding-new-ai-format, .github/skills/adding-new-ai-format and .opencode/skills/adding-new-ai-format in your project.
Going by SKILL.md and its folder, Adding New AI Format needs the command-line tools its instructions call (npm).
SKILL.md names 2 domains. In commands or code: registry.prpm.dev and json-schema.org; the agent is likely to contact these when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Adding New AI Format is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.6k tokens (SKILL.md is roughly 26k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Adding New AI Format: Editor (QinghongLin/data2story-skill, 156 stars), Editor (QinghongLin/data2story-skill, 156 stars), Editor (alecs5am/ralphy, 136 stars) and Handsontable Cell Editor Developer (handsontable/handsontable, 22k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
pr-pm (a GitHub organization) maintains it in pr-pm/prpm, which has 122 GitHub stars. The repository holds 35 skills in this directory. The repository was last updated on October 6, 2026.
Source: pr-pm/prpm on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.