|
|
@@ -0,0 +1,351 @@
|
|
|
+#!/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;
|
|
|
+}
|