initial commit and build out

This commit is contained in:
Paul Bakaus
2025-11-16 14:54:35 -08:00
parent 9ee3afcdf9
commit 661293796c
30 changed files with 3190 additions and 134 deletions
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env node
/**
* Build System for Cross-Provider Design Skills & Commands
*
* Transforms feature-rich source files into provider-specific formats:
* - Cursor: Downgraded (no frontmatter/args)
* - Claude Code: Full featured (frontmatter + body)
* - Gemini: Full featured (TOML + modular skills)
* - Codex: Full featured (custom prompts + modular skills)
*/
import path from 'path';
import { fileURLToPath } from 'url';
import { readSourceFiles } from './lib/utils.js';
import {
transformCursor,
transformClaudeCode,
transformGemini,
transformCodex
} from './lib/transformers/index.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const ROOT_DIR = path.resolve(__dirname, '..');
const DIST_DIR = path.join(ROOT_DIR, 'dist');
/**
* Main build process
*/
function build() {
console.log('🔨 Building cross-provider design plugins...\n');
// Read source files
const { commands, skills } = readSourceFiles(ROOT_DIR);
console.log(`📖 Read ${commands.length} commands and ${skills.length} skills\n`);
// Transform for each provider
transformCursor(commands, skills, DIST_DIR);
transformClaudeCode(commands, skills, DIST_DIR);
transformGemini(commands, skills, DIST_DIR);
transformCodex(commands, skills, DIST_DIR);
console.log('\n✨ Build complete!');
}
// Run the build
build();
+48
View File
@@ -0,0 +1,48 @@
import path from 'path';
import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter } from '../utils.js';
/**
* Claude Code Transformer (Full Featured)
*
* Keeps full YAML frontmatter with args support.
* Skills stored in subdirectories with SKILL.md filename.
*/
export function transformClaudeCode(commands, skills, distDir) {
const commandsDir = path.join(distDir, 'claude-code/commands');
const skillsDir = path.join(distDir, 'claude-code/skills');
cleanDir(path.join(distDir, 'claude-code'));
ensureDir(commandsDir);
ensureDir(skillsDir);
// Commands: Keep frontmatter + body
for (const command of commands) {
const frontmatter = generateYamlFrontmatter({
name: command.name,
description: command.description,
...(command.args.length > 0 && { args: command.args })
});
const content = `${frontmatter}\n\n${command.body}`;
const outputPath = path.join(commandsDir, `${command.name}.md`);
writeFile(outputPath, content);
}
// Skills: Keep frontmatter + body in subdirectories
for (const skill of skills) {
const skillDir = path.join(skillsDir, skill.name);
const frontmatter = generateYamlFrontmatter({
name: skill.name,
description: skill.description,
...(skill.license && { license: skill.license })
});
const content = `${frontmatter}\n\n${skill.body}`;
const outputPath = path.join(skillDir, 'SKILL.md');
writeFile(outputPath, content);
}
console.log(`✓ Claude Code: ${commands.length} commands, ${skills.length} skills`);
}
+84
View File
@@ -0,0 +1,84 @@
import path from 'path';
import { cleanDir, ensureDir, writeFile } from '../utils.js';
/**
* Codex Transformer (Full Featured - Custom Prompts + Modular Skills)
*
* Commands: Uses argument-hint format with $VARIABLE placeholders
* Skills: Creates modular files with guiding AGENTS.md
*/
export function transformCodex(commands, skills, distDir) {
const codexDir = path.join(distDir, 'codex');
const promptsDir = path.join(codexDir, 'prompts');
cleanDir(codexDir);
ensureDir(promptsDir);
// Commands: Transform to Codex prompt format
for (const command of commands) {
const yamlLines = ['---'];
yamlLines.push(`description: ${command.description}`);
// Build argument-hint from args array
if (command.args && command.args.length > 0) {
const hints = command.args.map(arg => {
const hint = arg.required ? `<${arg.name}>` : `[${arg.name.toUpperCase()}=<value>]`;
return hint;
});
yamlLines.push(`argument-hint: ${hints.join(' ')}`);
}
yamlLines.push('---');
// Transform {{argname}} to $ARGNAME for Codex
let body = command.body;
body = body.replace(/\{\{([^}]+)\}\}/g, (match, argName) => {
return `$${argName.toUpperCase()}`;
});
const content = `${yamlLines.join('\n')}\n\n${body}`;
const outputPath = path.join(promptsDir, `${command.name}.md`);
writeFile(outputPath, content);
}
// Skills: Create modular files (body only)
const skillEntries = [];
for (const skill of skills) {
const outputPath = path.join(codexDir, `AGENTS.${skill.name}.md`);
writeFile(outputPath, skill.body);
skillEntries.push({
name: skill.name,
description: skill.description,
file: `AGENTS.${skill.name}.md`
});
}
// Create main AGENTS.md that guides Codex to the right skill files
const agentsMd = [
'# Codex Agent Instructions',
'',
'This repository contains specialized skills for different tasks. When the user requests work in a particular domain, read the corresponding skill file for detailed guidance.',
'',
'## Available Skills',
'',
'Each skill provides deep expertise in its domain. Use the descriptions below to decide which skill file to read:',
'',
...skillEntries.map(skill =>
`### ${skill.name}\n\n**When to use**: ${skill.description}\n\n**Read**: \`${skill.file}\` for complete instructions.\n`
),
'',
'## How to Use Skills',
'',
'1. Identify the user\'s request domain',
'2. Match it to a skill description above',
'3. Read the corresponding skill file',
'4. Follow the guidance in that file',
'',
'Multiple skills can be combined when the task requires expertise from different domains.'
].join('\n');
writeFile(path.join(codexDir, 'AGENTS.md'), agentsMd);
console.log(`✓ Codex: ${commands.length} prompts, ${skills.length} skills (modular)`);
}
+32
View File
@@ -0,0 +1,32 @@
import path from 'path';
import { cleanDir, ensureDir, writeFile } from '../utils.js';
/**
* Cursor Transformer (Downgraded - No Frontmatter/Args)
*
* Strips all frontmatter and metadata, outputs body only.
* Cursor doesn't support arguments or frontmatter.
*/
export function transformCursor(commands, skills, distDir) {
const commandsDir = path.join(distDir, 'cursor/commands');
const rulesDir = path.join(distDir, 'cursor/rules');
cleanDir(path.join(distDir, 'cursor'));
ensureDir(commandsDir);
ensureDir(rulesDir);
// Commands: Body only (no frontmatter)
for (const command of commands) {
const outputPath = path.join(commandsDir, `${command.name}.md`);
writeFile(outputPath, command.body);
}
// Skills: Body only (no frontmatter)
for (const skill of skills) {
const outputPath = path.join(rulesDir, `${skill.name}.md`);
writeFile(outputPath, skill.body);
}
console.log(`✓ Cursor: ${commands.length} commands, ${skills.length} skills (downgraded)`);
}
+65
View File
@@ -0,0 +1,65 @@
import path from 'path';
import { cleanDir, ensureDir, writeFile } from '../utils.js';
/**
* Gemini Transformer (Full Featured - TOML + Modular Skills)
*
* Commands: Converts to TOML format with {{args}} placeholders
* Skills: Creates modular files imported via @./GEMINI.{name}.md syntax
*/
export function transformGemini(commands, skills, distDir) {
const geminiDir = path.join(distDir, 'gemini');
const commandsDir = path.join(geminiDir, 'commands');
cleanDir(geminiDir);
ensureDir(commandsDir);
// Commands: Transform to TOML
for (const command of commands) {
// Replace named placeholders with {{args}}
let prompt = command.body.replace(/\{\{[^}]+\}\}/g, '{{args}}');
const toml = [
`description = "${command.description.replace(/"/g, '\\"')}"`,
`prompt = """`,
prompt,
`"""`
].join('\n');
const outputPath = path.join(commandsDir, `${command.name}.toml`);
writeFile(outputPath, toml);
}
// Skills: Create modular files
for (const skill of skills) {
const outputPath = path.join(geminiDir, `GEMINI.${skill.name}.md`);
writeFile(outputPath, skill.body);
}
// Create main GEMINI.md that imports skill files
const geminiMd = [
'# Gemini Context',
'',
'This repository contains specialized skills for different tasks. When you detect a user request in a particular domain, the corresponding skill file will be automatically loaded to provide detailed guidance.',
'',
'## Available Skills',
'',
'Each skill provides deep expertise in its domain. The skills below are automatically imported and will guide your responses:',
'',
...skills.map(skill =>
`### ${skill.name}\n\n**When to use**: ${skill.description}\n\n@./GEMINI.${skill.name}.md\n`
),
'',
'## How Skills Work',
'',
'1. Skills are automatically loaded via the import statements above',
'2. When a user request matches a skill domain, apply that skill\'s guidance',
'3. Multiple skills can be combined when the task requires expertise from different domains',
'4. Follow the detailed instructions provided in each imported skill file'
].join('\n');
writeFile(path.join(geminiDir, 'GEMINI.md'), geminiMd);
console.log(`✓ Gemini: ${commands.length} commands (TOML), ${skills.length} skills (modular)`);
}
+5
View File
@@ -0,0 +1,5 @@
export { transformCursor } from './cursor.js';
export { transformClaudeCode } from './claude-code.js';
export { transformGemini } from './gemini.js';
export { transformCodex } from './codex.js';
+198
View File
@@ -0,0 +1,198 @@
import fs from 'fs';
import path from 'path';
/**
* Parse frontmatter from markdown content
* Returns { frontmatter: object, body: string }
*/
export function parseFrontmatter(content) {
const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
const match = content.match(frontmatterRegex);
if (!match) {
return { frontmatter: {}, body: content };
}
const [, frontmatterText, body] = match;
const frontmatter = {};
// Simple YAML parser (handles basic key-value and arrays)
const lines = frontmatterText.split('\n');
let currentKey = null;
let currentArray = null;
for (const line of lines) {
if (!line.trim()) continue;
// Calculate indent level
const leadingSpaces = line.length - line.trimStart().length;
const trimmed = line.trim();
// Array item at level 2 (nested under a key)
if (trimmed.startsWith('- ') && leadingSpaces >= 2) {
if (currentArray) {
if (trimmed.startsWith('- name:')) {
// New object in array
const obj = {};
obj.name = trimmed.slice(7).trim();
currentArray.push(obj);
}
}
continue;
}
// Property of array object (indented further)
if (leadingSpaces >= 4 && currentArray && currentArray.length > 0) {
const colonIndex = trimmed.indexOf(':');
if (colonIndex > 0) {
const key = trimmed.slice(0, colonIndex).trim();
const value = trimmed.slice(colonIndex + 1).trim();
const lastObj = currentArray[currentArray.length - 1];
lastObj[key] = value === 'true' ? true : value === 'false' ? false : value;
}
continue;
}
// Top-level key-value pair
if (leadingSpaces === 0) {
const colonIndex = trimmed.indexOf(':');
if (colonIndex > 0) {
const key = trimmed.slice(0, colonIndex).trim();
const value = trimmed.slice(colonIndex + 1).trim();
if (value) {
frontmatter[key] = value;
currentKey = key;
currentArray = null;
} else {
// Start of array
currentKey = key;
currentArray = [];
frontmatter[key] = currentArray;
}
}
}
}
return { frontmatter, body: body.trim() };
}
/**
* Recursively read all .md files from a directory
*/
export function readFilesRecursive(dir, fileList = []) {
if (!fs.existsSync(dir)) {
return fileList;
}
const files = fs.readdirSync(dir);
for (const file of files) {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
readFilesRecursive(filePath, fileList);
} else if (file.endsWith('.md')) {
fileList.push(filePath);
}
}
return fileList;
}
/**
* Read and parse all source files
*/
export function readSourceFiles(rootDir) {
const commandsDir = path.join(rootDir, 'source/commands');
const skillsDir = path.join(rootDir, 'source/skills');
const commandFiles = readFilesRecursive(commandsDir);
const skillFiles = readFilesRecursive(skillsDir);
const commands = commandFiles.map(filePath => {
const content = fs.readFileSync(filePath, 'utf-8');
const { frontmatter, body } = parseFrontmatter(content);
const name = path.basename(filePath, '.md');
return {
name: frontmatter.name || name,
description: frontmatter.description || '',
args: frontmatter.args || [],
body,
filePath
};
});
const skills = skillFiles.map(filePath => {
const content = fs.readFileSync(filePath, 'utf-8');
const { frontmatter, body } = parseFrontmatter(content);
const name = path.basename(filePath, '.md');
return {
name: frontmatter.name || name,
description: frontmatter.description || '',
license: frontmatter.license || '',
body,
filePath
};
});
return { commands, skills };
}
/**
* Ensure directory exists, create if needed
*/
export function ensureDir(dirPath) {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
}
/**
* Clean directory (remove all contents)
*/
export function cleanDir(dirPath) {
if (fs.existsSync(dirPath)) {
fs.rmSync(dirPath, { recursive: true, force: true });
}
}
/**
* Write file with automatic directory creation
*/
export function writeFile(filePath, content) {
const dir = path.dirname(filePath);
ensureDir(dir);
fs.writeFileSync(filePath, content, 'utf-8');
}
/**
* Generate YAML frontmatter string
*/
export function generateYamlFrontmatter(data) {
const lines = ['---'];
for (const [key, value] of Object.entries(data)) {
if (Array.isArray(value)) {
lines.push(`${key}:`);
for (const item of value) {
if (typeof item === 'object') {
lines.push(` - name: ${item.name}`);
if (item.description) lines.push(` description: ${item.description}`);
if (item.required !== undefined) lines.push(` required: ${item.required}`);
} else {
lines.push(` - ${item}`);
}
}
} else {
lines.push(`${key}: ${value}`);
}
}
lines.push('---');
return lines.join('\n');
}