1
0

superpowers-codex 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const path = require('path');
  4. const os = require('os');
  5. // Paths
  6. const homeDir = os.homedir();
  7. const superpowersSkillsDir = path.join(homeDir, '.codex', 'superpowers', 'skills');
  8. const personalSkillsDir = path.join(homeDir, '.codex', 'skills');
  9. const bootstrapFile = path.join(homeDir, '.codex', 'superpowers', 'codex', 'superpowers-bootstrap.md');
  10. // Utility functions
  11. function extractFrontmatter(filePath) {
  12. try {
  13. const content = fs.readFileSync(filePath, 'utf8');
  14. const lines = content.split('\n');
  15. let inFrontmatter = false;
  16. let name = '';
  17. let description = '';
  18. let whenToUse = '';
  19. for (const line of lines) {
  20. if (line.trim() === '---') {
  21. if (inFrontmatter) break;
  22. inFrontmatter = true;
  23. continue;
  24. }
  25. if (inFrontmatter) {
  26. const match = line.match(/^(\w+):\s*(.*)$/);
  27. if (match) {
  28. const [, key, value] = match;
  29. switch (key) {
  30. case 'name': name = value.trim(); break;
  31. case 'description': description = value.trim(); break;
  32. case 'when_to_use': whenToUse = value.trim(); break;
  33. }
  34. }
  35. }
  36. }
  37. return { name, description, whenToUse };
  38. } catch (error) {
  39. return { name: '', description: '', whenToUse: '' };
  40. }
  41. }
  42. function printSkill(skillPath, sourceType) {
  43. const skillFile = path.join(skillPath, 'SKILL.md');
  44. const relPath = sourceType === 'personal'
  45. ? path.relative(personalSkillsDir, skillPath)
  46. : path.relative(superpowersSkillsDir, skillPath);
  47. // Print skill name with namespace
  48. if (sourceType === 'personal') {
  49. console.log(relPath.replace(/\\/g, '/')); // Personal skills are not namespaced
  50. } else {
  51. console.log(`superpowers:${relPath.replace(/\\/g, '/')}`); // Superpowers skills get superpowers namespace
  52. }
  53. // Extract and print metadata
  54. const { name, description, whenToUse } = extractFrontmatter(skillFile);
  55. if (description) console.log(` ${description}`);
  56. if (whenToUse) console.log(` When to use: ${whenToUse}`);
  57. console.log('');
  58. }
  59. function findSkillsInDir(dir, sourceType, maxDepth = 1) {
  60. const skills = [];
  61. if (!fs.existsSync(dir)) return skills;
  62. function searchDir(currentDir, currentDepth) {
  63. if (currentDepth > maxDepth) return;
  64. try {
  65. const entries = fs.readdirSync(currentDir, { withFileTypes: true });
  66. for (const entry of entries) {
  67. if (entry.isDirectory()) {
  68. const skillDir = path.join(currentDir, entry.name);
  69. const skillFile = path.join(skillDir, 'SKILL.md');
  70. if (fs.existsSync(skillFile)) {
  71. skills.push(skillDir);
  72. }
  73. // For personal skills, search deeper (category/skill structure)
  74. if (sourceType === 'personal' && currentDepth < maxDepth) {
  75. searchDir(skillDir, currentDepth + 1);
  76. }
  77. }
  78. }
  79. } catch (error) {
  80. // Ignore permission errors or other issues
  81. }
  82. }
  83. searchDir(dir, 0);
  84. return skills;
  85. }
  86. // Commands
  87. function runFindSkills() {
  88. console.log('Available skills:');
  89. console.log('==================');
  90. console.log('');
  91. const foundSkills = new Set();
  92. // Find personal skills first (these take precedence)
  93. const personalSkills = findSkillsInDir(personalSkillsDir, 'personal', 2);
  94. for (const skillPath of personalSkills) {
  95. const relPath = path.relative(personalSkillsDir, skillPath);
  96. foundSkills.add(relPath);
  97. printSkill(skillPath, 'personal');
  98. }
  99. // Find superpowers skills (only if not already found in personal)
  100. const superpowersSkills = findSkillsInDir(superpowersSkillsDir, 'superpowers', 1);
  101. for (const skillPath of superpowersSkills) {
  102. const relPath = path.relative(superpowersSkillsDir, skillPath);
  103. if (!foundSkills.has(relPath)) {
  104. printSkill(skillPath, 'superpowers');
  105. }
  106. }
  107. console.log('Usage:');
  108. console.log(' superpowers-codex use-skill <skill-name> # Load a specific skill');
  109. console.log('');
  110. console.log('Skill naming:');
  111. console.log(' Superpowers skills: superpowers:skill-name (from ~/.codex/superpowers/skills/)');
  112. console.log(' Personal skills: skill-name (from ~/.codex/skills/)');
  113. console.log(' Personal skills override superpowers skills when names match.');
  114. console.log('');
  115. console.log('Note: All skills are disclosed at session start via bootstrap.');
  116. }
  117. function runBootstrap() {
  118. console.log('# Superpowers Bootstrap for Codex');
  119. console.log('# ================================');
  120. console.log('');
  121. // Show the bootstrap instructions
  122. if (fs.existsSync(bootstrapFile)) {
  123. console.log('## Bootstrap Instructions:');
  124. console.log('');
  125. try {
  126. const content = fs.readFileSync(bootstrapFile, 'utf8');
  127. console.log(content);
  128. } catch (error) {
  129. console.log(`Error reading bootstrap file: ${error.message}`);
  130. }
  131. console.log('');
  132. console.log('---');
  133. console.log('');
  134. }
  135. // Run find-skills to show available skills
  136. console.log('## Available Skills:');
  137. console.log('');
  138. runFindSkills();
  139. console.log('');
  140. console.log('---');
  141. console.log('');
  142. // Load the using-superpowers skill automatically
  143. console.log('## Auto-loading superpowers:using-superpowers skill:');
  144. console.log('');
  145. runUseSkill('superpowers:using-superpowers');
  146. console.log('');
  147. console.log('---');
  148. console.log('');
  149. console.log('# Bootstrap Complete!');
  150. console.log('# You now have access to all superpowers skills.');
  151. console.log('# Use "superpowers-codex use-skill <skill>" to load and apply skills.');
  152. console.log('# Remember: If a skill applies to your task, you MUST use it!');
  153. }
  154. function runUseSkill(skillName) {
  155. if (!skillName) {
  156. console.log('Usage: superpowers-codex use-skill <skill-name>');
  157. console.log('Examples:');
  158. console.log(' superpowers-codex use-skill superpowers:brainstorming # Load superpowers skill');
  159. console.log(' superpowers-codex use-skill brainstorming # Load personal skill (or superpowers if not found)');
  160. console.log(' superpowers-codex use-skill my-custom-skill # Load personal skill');
  161. return;
  162. }
  163. // Handle namespaced skill names
  164. let actualSkillPath;
  165. let forceSuperpowers = false;
  166. if (skillName.startsWith('superpowers:')) {
  167. // Remove the superpowers: namespace prefix
  168. actualSkillPath = skillName.substring('superpowers:'.length);
  169. forceSuperpowers = true;
  170. } else {
  171. actualSkillPath = skillName;
  172. }
  173. // Remove "skills/" prefix if present
  174. if (actualSkillPath.startsWith('skills/')) {
  175. actualSkillPath = actualSkillPath.substring('skills/'.length);
  176. }
  177. // Function to find skill file
  178. function findSkillFile(searchPath) {
  179. // Check for exact match with SKILL.md
  180. const skillMdPath = path.join(searchPath, 'SKILL.md');
  181. if (fs.existsSync(skillMdPath)) {
  182. return skillMdPath;
  183. }
  184. // Check for direct SKILL.md file
  185. if (searchPath.endsWith('SKILL.md') && fs.existsSync(searchPath)) {
  186. return searchPath;
  187. }
  188. return null;
  189. }
  190. let skillFile = null;
  191. // If superpowers: namespace was used, only check superpowers skills
  192. if (forceSuperpowers) {
  193. if (fs.existsSync(superpowersSkillsDir)) {
  194. const superpowersPath = path.join(superpowersSkillsDir, actualSkillPath);
  195. skillFile = findSkillFile(superpowersPath);
  196. }
  197. } else {
  198. // First check personal skills directory (takes precedence)
  199. if (fs.existsSync(personalSkillsDir)) {
  200. const personalPath = path.join(personalSkillsDir, actualSkillPath);
  201. skillFile = findSkillFile(personalPath);
  202. if (skillFile) {
  203. console.log(`# Loading personal skill: ${actualSkillPath}`);
  204. console.log(`# Source: ${skillFile}`);
  205. console.log('');
  206. }
  207. }
  208. // If not found in personal, check superpowers skills
  209. if (!skillFile && fs.existsSync(superpowersSkillsDir)) {
  210. const superpowersPath = path.join(superpowersSkillsDir, actualSkillPath);
  211. skillFile = findSkillFile(superpowersPath);
  212. if (skillFile) {
  213. console.log(`# Loading superpowers skill: superpowers:${actualSkillPath}`);
  214. console.log(`# Source: ${skillFile}`);
  215. console.log('');
  216. }
  217. }
  218. }
  219. // If still not found, error
  220. if (!skillFile) {
  221. console.log(`Error: Skill not found: ${actualSkillPath}`);
  222. console.log('');
  223. console.log('Available skills:');
  224. runFindSkills();
  225. return;
  226. }
  227. // Extract frontmatter and content
  228. let content, frontmatter;
  229. try {
  230. const fullContent = fs.readFileSync(skillFile, 'utf8');
  231. const { name, description, whenToUse } = extractFrontmatter(skillFile);
  232. // Extract just the content after frontmatter
  233. const lines = fullContent.split('\n');
  234. let inFrontmatter = false;
  235. let frontmatterEnded = false;
  236. const contentLines = [];
  237. for (const line of lines) {
  238. if (line.trim() === '---') {
  239. if (inFrontmatter) {
  240. frontmatterEnded = true;
  241. continue;
  242. }
  243. inFrontmatter = true;
  244. continue;
  245. }
  246. if (frontmatterEnded || !inFrontmatter) {
  247. contentLines.push(line);
  248. }
  249. }
  250. content = contentLines.join('\n').trim();
  251. frontmatter = { name, description, whenToUse };
  252. } catch (error) {
  253. console.log(`Error reading skill file: ${error.message}`);
  254. return;
  255. }
  256. // Display skill header with clean info
  257. const displayName = forceSuperpowers ? `superpowers:${actualSkillPath}` :
  258. (skillFile.includes(personalSkillsDir) ? actualSkillPath : `superpowers:${actualSkillPath}`);
  259. const skillDirectory = path.dirname(skillFile);
  260. console.log(`# ${frontmatter.name || displayName}`);
  261. if (frontmatter.description) {
  262. console.log(`# ${frontmatter.description}`);
  263. }
  264. if (frontmatter.whenToUse) {
  265. console.log(`# When to use: ${frontmatter.whenToUse}`);
  266. }
  267. console.log(`# Supporting tools and docs are in ${skillDirectory}`);
  268. console.log('# ============================================');
  269. console.log('');
  270. // Display the skill content (without frontmatter)
  271. console.log(content);
  272. }
  273. // Main CLI
  274. const command = process.argv[2];
  275. const arg = process.argv[3];
  276. switch (command) {
  277. case 'bootstrap':
  278. runBootstrap();
  279. break;
  280. case 'use-skill':
  281. runUseSkill(arg);
  282. break;
  283. case 'find-skills':
  284. runFindSkills();
  285. break;
  286. default:
  287. console.log('Superpowers for Codex');
  288. console.log('Usage:');
  289. console.log(' superpowers-codex bootstrap # Run complete bootstrap with all skills');
  290. console.log(' superpowers-codex use-skill <skill-name> # Load a specific skill');
  291. console.log(' superpowers-codex find-skills # List all available skills');
  292. console.log('');
  293. console.log('Examples:');
  294. console.log(' superpowers-codex bootstrap');
  295. console.log(' superpowers-codex use-skill superpowers:brainstorming');
  296. console.log(' superpowers-codex use-skill my-custom-skill');
  297. break;
  298. }