feat: reorganize codebase with single source of truth + merge prompts into styles (#116)

BREAKING CHANGES:
- Moved canonical data/scripts to src/ui-ux-pro-max/
- Removed duplicate folders (.codex/, .gemini/, .trae/, .codebuddy/, .continue/, skills/, .qoder/)
- CLI now uses template system instead of copying pre-built folders

New features:
- Merged prompts.csv into styles.csv with 4 new columns:
  - AI Prompt Keywords
  - CSS/Technical Keywords
  - Implementation Checklist
  - Design System Variables
- All 67 styles now have complete prompt data
- Added Astro stack (53 guidelines)
- Added 10 new 2025 UI trend styles

CLI changes:
- New template rendering system (cli/src/utils/template.ts)
- Reduced cli/assets from ~34MB to ~564KB
- Assets now contain only: data/, scripts/, templates/

File structure:
- src/ui-ux-pro-max/ - Single source of truth
- .claude/skills/ - Symlinks to src/ for development
- .shared/ - Symlink to src/ui-ux-pro-max/

Bumped CLI version: 2.1.3 → 2.2.0

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Viet Tran
2026-01-22 22:05:41 +07:00
committed by GitHub
parent 89dd73abc9
commit b2a05eceeb
454 changed files with 1722 additions and 41812 deletions

View File

@@ -49,6 +49,9 @@ export function detectAIType(cwd: string = process.cwd()): DetectionResult {
if (existsSync(join(cwd, '.continue'))) {
detected.push('continue');
}
if (existsSync(join(cwd, '.codebuddy'))) {
detected.push('codebuddy');
}
// Suggest based on what's detected
let suggested: AIType | null = null;
@@ -70,7 +73,7 @@ export function getAITypeDescription(aiType: AIType): string {
case 'windsurf':
return 'Windsurf (.windsurf/workflows/ + .shared/)';
case 'antigravity':
return 'Antigravity (.agent/workflows/ + .shared/)';
return 'Antigravity (.agent/skills/)';
case 'copilot':
return 'GitHub Copilot (.github/prompts/ + .shared/)';
case 'kiro':
@@ -84,11 +87,13 @@ export function getAITypeDescription(aiType: AIType): string {
case 'gemini':
return 'Gemini CLI (.gemini/skills/ + .shared/)';
case 'trae':
return 'Trae (.trae/skills/ + .shared/)';
return 'Trae (.trae/skills/)';
case 'opencode':
return 'OpenCode (.opencode/skills/ + .shared/)';
return 'OpenCode (.opencode/skills/)';
case 'continue':
return 'Continue (.continue/skills/)';
case 'codebuddy':
return 'CodeBuddy (.codebuddy/skills/)';
case 'all':
return 'All AI assistants';
}

247
cli/src/utils/template.ts Normal file
View File

@@ -0,0 +1,247 @@
import { readFile, mkdir, writeFile, cp, access, readdir } from 'node:fs/promises';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
// After bun build: dist/index.js -> ../assets = cli/assets ✓
const ASSETS_DIR = join(__dirname, '..', 'assets');
export interface PlatformConfig {
platform: string;
displayName: string;
installType: 'full' | 'reference';
folderStructure: {
root: string;
skillPath: string;
filename: string;
};
scriptPath: string;
frontmatter: Record<string, string> | null;
sections: {
quickReference: boolean;
};
title: string;
description: string;
skillOrWorkflow: string;
}
// Map AIType to platform config file name
const AI_TO_PLATFORM: Record<string, string> = {
claude: 'claude',
cursor: 'cursor',
windsurf: 'windsurf',
antigravity: 'agent',
copilot: 'copilot',
kiro: 'kiro',
opencode: 'opencode',
roocode: 'roocode',
codex: 'codex',
qoder: 'qoder',
gemini: 'gemini',
trae: 'trae',
continue: 'continue',
codebuddy: 'codebuddy',
};
async function exists(path: string): Promise<boolean> {
try {
await access(path);
return true;
} catch {
return false;
}
}
/**
* Load platform configuration from JSON file
*/
export async function loadPlatformConfig(aiType: string): Promise<PlatformConfig> {
const platformName = AI_TO_PLATFORM[aiType];
if (!platformName) {
throw new Error(`Unknown AI type: ${aiType}`);
}
const configPath = join(ASSETS_DIR, 'templates', 'platforms', `${platformName}.json`);
const content = await readFile(configPath, 'utf-8');
return JSON.parse(content) as PlatformConfig;
}
/**
* Load all available platform configs
*/
export async function loadAllPlatformConfigs(): Promise<Map<string, PlatformConfig>> {
const configs = new Map<string, PlatformConfig>();
for (const [aiType, platformName] of Object.entries(AI_TO_PLATFORM)) {
try {
const config = await loadPlatformConfig(aiType);
configs.set(aiType, config);
} catch {
// Skip if config doesn't exist
}
}
return configs;
}
/**
* Load a template file
*/
async function loadTemplate(templateName: string): Promise<string> {
const templatePath = join(ASSETS_DIR, 'templates', templateName);
return readFile(templatePath, 'utf-8');
}
/**
* Render frontmatter section
*/
function renderFrontmatter(frontmatter: Record<string, string> | null): string {
if (!frontmatter) return '';
const lines = ['---'];
for (const [key, value] of Object.entries(frontmatter)) {
// Quote values that contain special characters
if (value.includes(':') || value.includes('"') || value.includes('\n')) {
lines.push(`${key}: "${value.replace(/"/g, '\\"')}"`);
} else {
lines.push(`${key}: ${value}`);
}
}
lines.push('---', '');
return lines.join('\n');
}
/**
* Render skill file content from template
*/
export async function renderSkillFile(config: PlatformConfig): Promise<string> {
// Load base template
let content = await loadTemplate('base/skill-content.md');
// Load quick reference if needed
let quickReferenceContent = '';
if (config.sections.quickReference) {
quickReferenceContent = await loadTemplate('base/quick-reference.md');
}
// Build the final content
const frontmatter = renderFrontmatter(config.frontmatter);
// Replace placeholders
// Add newline before quick reference content if it exists
const quickRefWithNewline = quickReferenceContent ? '\n' + quickReferenceContent : '';
content = content
.replace(/\{\{TITLE\}\}/g, config.title)
.replace(/\{\{DESCRIPTION\}\}/g, config.description)
.replace(/\{\{SCRIPT_PATH\}\}/g, config.scriptPath)
.replace(/\{\{SKILL_OR_WORKFLOW\}\}/g, config.skillOrWorkflow)
.replace(/\{\{QUICK_REFERENCE\}\}/g, quickRefWithNewline);
return frontmatter + content;
}
/**
* Copy data and scripts to target directory
*/
async function copyDataAndScripts(targetSkillDir: string): Promise<void> {
const dataSource = join(ASSETS_DIR, 'data');
const scriptsSource = join(ASSETS_DIR, 'scripts');
const dataTarget = join(targetSkillDir, 'data');
const scriptsTarget = join(targetSkillDir, 'scripts');
// Copy data
if (await exists(dataSource)) {
await mkdir(dataTarget, { recursive: true });
await cp(dataSource, dataTarget, { recursive: true });
}
// Copy scripts
if (await exists(scriptsSource)) {
await mkdir(scriptsTarget, { recursive: true });
await cp(scriptsSource, scriptsTarget, { recursive: true });
}
}
/**
* Ensure .shared folder exists with data and scripts
*/
async function ensureSharedExists(targetDir: string): Promise<boolean> {
const sharedDir = join(targetDir, '.shared', 'ui-ux-pro-max');
// Check if already exists
if (await exists(sharedDir)) {
return false; // Already exists, didn't create
}
await mkdir(sharedDir, { recursive: true });
await copyDataAndScripts(sharedDir);
return true; // Created new
}
/**
* Generate platform files for a specific AI type
*/
export async function generatePlatformFiles(
targetDir: string,
aiType: string
): Promise<string[]> {
const config = await loadPlatformConfig(aiType);
const createdFolders: string[] = [];
// Determine full skill directory path
const skillDir = join(
targetDir,
config.folderStructure.root,
config.folderStructure.skillPath
);
// Create directory structure
await mkdir(skillDir, { recursive: true });
// Render and write skill file
const skillContent = await renderSkillFile(config);
const skillFilePath = join(skillDir, config.folderStructure.filename);
await writeFile(skillFilePath, skillContent, 'utf-8');
createdFolders.push(config.folderStructure.root);
// Handle data/scripts based on install type
if (config.installType === 'full') {
// Full install: copy data and scripts into the skill directory
await copyDataAndScripts(skillDir);
} else {
// Reference install: ensure .shared exists
const createdShared = await ensureSharedExists(targetDir);
if (createdShared) {
createdFolders.push('.shared');
}
}
return createdFolders;
}
/**
* Generate files for all AI types
*/
export async function generateAllPlatformFiles(targetDir: string): Promise<string[]> {
const allFolders = new Set<string>();
for (const aiType of Object.keys(AI_TO_PLATFORM)) {
try {
const folders = await generatePlatformFiles(targetDir, aiType);
folders.forEach(f => allFolders.add(f));
} catch {
// Skip if generation fails for a platform
}
}
return Array.from(allFolders);
}
/**
* Get list of supported AI types
*/
export function getSupportedAITypes(): string[] {
return Object.keys(AI_TO_PLATFORM);
}