| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- #!/usr/bin/env node
- const fs = require('fs');
- const path = require('path');
- const os = require('os');
- // Paths
- const homeDir = os.homedir();
- const superpowersSkillsDir = path.join(homeDir, '.codex', 'superpowers', 'skills');
- const personalSkillsDir = path.join(homeDir, '.codex', 'skills');
- const bootstrapFile = path.join(homeDir, '.codex', 'superpowers', 'codex', 'superpowers-bootstrap.md');
- // Utility functions
- function extractFrontmatter(filePath) {
- try {
- const content = fs.readFileSync(filePath, 'utf8');
- const lines = content.split('\n');
- let inFrontmatter = false;
- let name = '';
- let description = '';
- let whenToUse = '';
- for (const line of lines) {
- if (line.trim() === '---') {
- if (inFrontmatter) break;
- inFrontmatter = true;
- continue;
- }
- if (inFrontmatter) {
- const match = line.match(/^(\w+):\s*(.*)$/);
- if (match) {
- const [, key, value] = match;
- switch (key) {
- case 'name': name = value.trim(); break;
- case 'description': description = value.trim(); break;
- case 'when_to_use': whenToUse = value.trim(); break;
- }
- }
- }
- }
- return { name, description, whenToUse };
- } catch (error) {
- return { name: '', description: '', whenToUse: '' };
- }
- }
- function printSkill(skillPath, sourceType) {
- const skillFile = path.join(skillPath, 'SKILL.md');
- const relPath = sourceType === 'personal'
- ? path.relative(personalSkillsDir, skillPath)
- : path.relative(superpowersSkillsDir, skillPath);
- // Print skill name with namespace
- if (sourceType === 'personal') {
- console.log(relPath.replace(/\\/g, '/')); // Personal skills are not namespaced
- } else {
- console.log(`superpowers:${relPath.replace(/\\/g, '/')}`); // Superpowers skills get superpowers namespace
- }
- // Extract and print metadata
- const { name, description, whenToUse } = extractFrontmatter(skillFile);
- if (description) console.log(` ${description}`);
- if (whenToUse) console.log(` When to use: ${whenToUse}`);
- console.log('');
- }
- function findSkillsInDir(dir, sourceType, maxDepth = 1) {
- const skills = [];
- if (!fs.existsSync(dir)) return skills;
- function searchDir(currentDir, currentDepth) {
- if (currentDepth > maxDepth) return;
- try {
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
- for (const entry of entries) {
- if (entry.isDirectory()) {
- const skillDir = path.join(currentDir, entry.name);
- const skillFile = path.join(skillDir, 'SKILL.md');
- if (fs.existsSync(skillFile)) {
- skills.push(skillDir);
- }
- // For personal skills, search deeper (category/skill structure)
- if (sourceType === 'personal' && currentDepth < maxDepth) {
- searchDir(skillDir, currentDepth + 1);
- }
- }
- }
- } catch (error) {
- // Ignore permission errors or other issues
- }
- }
- searchDir(dir, 0);
- return skills;
- }
- // Commands
- function runFindSkills() {
- console.log('Available skills:');
- console.log('==================');
- console.log('');
- const foundSkills = new Set();
- // Find personal skills first (these take precedence)
- const personalSkills = findSkillsInDir(personalSkillsDir, 'personal', 2);
- for (const skillPath of personalSkills) {
- const relPath = path.relative(personalSkillsDir, skillPath);
- foundSkills.add(relPath);
- printSkill(skillPath, 'personal');
- }
- // Find superpowers skills (only if not already found in personal)
- const superpowersSkills = findSkillsInDir(superpowersSkillsDir, 'superpowers', 1);
- for (const skillPath of superpowersSkills) {
- const relPath = path.relative(superpowersSkillsDir, skillPath);
- if (!foundSkills.has(relPath)) {
- printSkill(skillPath, 'superpowers');
- }
- }
- console.log('Usage:');
- console.log(' superpowers-codex use-skill <skill-name> # Load a specific skill');
- console.log('');
- console.log('Skill naming:');
- console.log(' Superpowers skills: superpowers:skill-name (from ~/.codex/superpowers/skills/)');
- console.log(' Personal skills: skill-name (from ~/.codex/skills/)');
- console.log(' Personal skills override superpowers skills when names match.');
- console.log('');
- console.log('Note: All skills are disclosed at session start via bootstrap.');
- }
- function runBootstrap() {
- console.log('# Superpowers Bootstrap for Codex');
- console.log('# ================================');
- console.log('');
- // Show the bootstrap instructions
- if (fs.existsSync(bootstrapFile)) {
- console.log('## Bootstrap Instructions:');
- console.log('');
- try {
- const content = fs.readFileSync(bootstrapFile, 'utf8');
- console.log(content);
- } catch (error) {
- console.log(`Error reading bootstrap file: ${error.message}`);
- }
- console.log('');
- console.log('---');
- console.log('');
- }
- // Run find-skills to show available skills
- console.log('## Available Skills:');
- console.log('');
- runFindSkills();
- console.log('');
- console.log('---');
- console.log('');
- // Load the using-superpowers skill automatically
- console.log('## Auto-loading superpowers:using-superpowers skill:');
- console.log('');
- runUseSkill('superpowers:using-superpowers');
- console.log('');
- console.log('---');
- console.log('');
- console.log('# Bootstrap Complete!');
- console.log('# You now have access to all superpowers skills.');
- console.log('# Use "superpowers-codex use-skill <skill>" to load and apply skills.');
- console.log('# Remember: If a skill applies to your task, you MUST use it!');
- }
- function runUseSkill(skillName) {
- if (!skillName) {
- console.log('Usage: superpowers-codex use-skill <skill-name>');
- console.log('Examples:');
- console.log(' superpowers-codex use-skill superpowers:brainstorming # Load superpowers skill');
- console.log(' superpowers-codex use-skill brainstorming # Load personal skill (or superpowers if not found)');
- console.log(' superpowers-codex use-skill my-custom-skill # Load personal skill');
- return;
- }
- // Handle namespaced skill names
- let actualSkillPath;
- let forceSuperpowers = false;
- if (skillName.startsWith('superpowers:')) {
- // Remove the superpowers: namespace prefix
- actualSkillPath = skillName.substring('superpowers:'.length);
- forceSuperpowers = true;
- } else {
- actualSkillPath = skillName;
- }
- // Remove "skills/" prefix if present
- if (actualSkillPath.startsWith('skills/')) {
- actualSkillPath = actualSkillPath.substring('skills/'.length);
- }
- // Function to find skill file
- function findSkillFile(searchPath) {
- // Check for exact match with SKILL.md
- const skillMdPath = path.join(searchPath, 'SKILL.md');
- if (fs.existsSync(skillMdPath)) {
- return skillMdPath;
- }
- // Check for direct SKILL.md file
- if (searchPath.endsWith('SKILL.md') && fs.existsSync(searchPath)) {
- return searchPath;
- }
- return null;
- }
- let skillFile = null;
- // If superpowers: namespace was used, only check superpowers skills
- if (forceSuperpowers) {
- if (fs.existsSync(superpowersSkillsDir)) {
- const superpowersPath = path.join(superpowersSkillsDir, actualSkillPath);
- skillFile = findSkillFile(superpowersPath);
- }
- } else {
- // First check personal skills directory (takes precedence)
- if (fs.existsSync(personalSkillsDir)) {
- const personalPath = path.join(personalSkillsDir, actualSkillPath);
- skillFile = findSkillFile(personalPath);
- if (skillFile) {
- console.log(`# Loading personal skill: ${actualSkillPath}`);
- console.log(`# Source: ${skillFile}`);
- console.log('');
- }
- }
- // If not found in personal, check superpowers skills
- if (!skillFile && fs.existsSync(superpowersSkillsDir)) {
- const superpowersPath = path.join(superpowersSkillsDir, actualSkillPath);
- skillFile = findSkillFile(superpowersPath);
- if (skillFile) {
- console.log(`# Loading superpowers skill: superpowers:${actualSkillPath}`);
- console.log(`# Source: ${skillFile}`);
- console.log('');
- }
- }
- }
- // If still not found, error
- if (!skillFile) {
- console.log(`Error: Skill not found: ${actualSkillPath}`);
- console.log('');
- console.log('Available skills:');
- runFindSkills();
- return;
- }
- // Extract frontmatter and content
- let content, frontmatter;
- try {
- const fullContent = fs.readFileSync(skillFile, 'utf8');
- const { name, description, whenToUse } = extractFrontmatter(skillFile);
- // Extract just the content after frontmatter
- const lines = fullContent.split('\n');
- let inFrontmatter = false;
- let frontmatterEnded = false;
- const contentLines = [];
- for (const line of lines) {
- if (line.trim() === '---') {
- if (inFrontmatter) {
- frontmatterEnded = true;
- continue;
- }
- inFrontmatter = true;
- continue;
- }
- if (frontmatterEnded || !inFrontmatter) {
- contentLines.push(line);
- }
- }
- content = contentLines.join('\n').trim();
- frontmatter = { name, description, whenToUse };
- } catch (error) {
- console.log(`Error reading skill file: ${error.message}`);
- return;
- }
- // Display skill header with clean info
- const displayName = forceSuperpowers ? `superpowers:${actualSkillPath}` :
- (skillFile.includes(personalSkillsDir) ? actualSkillPath : `superpowers:${actualSkillPath}`);
- const skillDirectory = path.dirname(skillFile);
- console.log(`# ${frontmatter.name || displayName}`);
- if (frontmatter.description) {
- console.log(`# ${frontmatter.description}`);
- }
- if (frontmatter.whenToUse) {
- console.log(`# When to use: ${frontmatter.whenToUse}`);
- }
- console.log(`# Supporting tools and docs are in ${skillDirectory}`);
- console.log('# ============================================');
- console.log('');
- // Display the skill content (without frontmatter)
- console.log(content);
- }
- // Main CLI
- const command = process.argv[2];
- const arg = process.argv[3];
- switch (command) {
- case 'bootstrap':
- runBootstrap();
- break;
- case 'use-skill':
- runUseSkill(arg);
- break;
- case 'find-skills':
- runFindSkills();
- break;
- default:
- console.log('Superpowers for Codex');
- console.log('Usage:');
- console.log(' superpowers-codex bootstrap # Run complete bootstrap with all skills');
- console.log(' superpowers-codex use-skill <skill-name> # Load a specific skill');
- console.log(' superpowers-codex find-skills # List all available skills');
- console.log('');
- console.log('Examples:');
- console.log(' superpowers-codex bootstrap');
- console.log(' superpowers-codex use-skill superpowers:brainstorming');
- console.log(' superpowers-codex use-skill my-custom-skill');
- break;
- }
|