superpowers.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. /**
  2. * Superpowers plugin for OpenCode.ai
  3. *
  4. * Provides custom tools for loading and discovering skills,
  5. * with automatic bootstrap on session start.
  6. */
  7. const skillsCore = require('../../lib/skills-core');
  8. const path = require('path');
  9. const fs = require('fs');
  10. const os = require('os');
  11. const homeDir = os.homedir();
  12. const superpowersSkillsDir = path.join(homeDir, '.config/opencode/superpowers/skills');
  13. const personalSkillsDir = path.join(homeDir, '.config/opencode/skills');
  14. module.exports = async ({ project, client, $, directory, worktree }) => {
  15. const { z } = await import('zod');
  16. return {
  17. tools: [
  18. {
  19. name: 'use_skill',
  20. description: 'Load and read a specific skill to guide your work. Skills contain proven workflows, mandatory processes, and expert techniques.',
  21. schema: z.object({
  22. skill_name: z.string().describe('Name of the skill to load (e.g., "superpowers:brainstorming" or "my-custom-skill")')
  23. }),
  24. execute: async ({ skill_name }) => {
  25. const resolved = skillsCore.resolveSkillPath(skill_name, superpowersSkillsDir, personalSkillsDir);
  26. if (!resolved) {
  27. return `Error: Skill "${skill_name}" not found.\n\nRun find_skills to see available skills.`;
  28. }
  29. const fullContent = fs.readFileSync(resolved.skillFile, 'utf8');
  30. const { name, description } = skillsCore.extractFrontmatter(resolved.skillFile);
  31. // Extract content after frontmatter
  32. const lines = fullContent.split('\n');
  33. let inFrontmatter = false;
  34. let frontmatterEnded = false;
  35. const contentLines = [];
  36. for (const line of lines) {
  37. if (line.trim() === '---') {
  38. if (inFrontmatter) {
  39. frontmatterEnded = true;
  40. continue;
  41. }
  42. inFrontmatter = true;
  43. continue;
  44. }
  45. if (frontmatterEnded || !inFrontmatter) {
  46. contentLines.push(line);
  47. }
  48. }
  49. const content = contentLines.join('\n').trim();
  50. const skillDirectory = path.dirname(resolved.skillFile);
  51. return `# ${name || skill_name}
  52. # ${description || ''}
  53. # Supporting tools and docs are in ${skillDirectory}
  54. # ============================================
  55. ${content}`;
  56. }
  57. },
  58. {
  59. name: 'find_skills',
  60. description: 'List all available skills in the superpowers and personal skill libraries.',
  61. schema: z.object({}),
  62. execute: async () => {
  63. const superpowersSkills = skillsCore.findSkillsInDir(superpowersSkillsDir, 'superpowers', 3);
  64. const personalSkills = skillsCore.findSkillsInDir(personalSkillsDir, 'personal', 3);
  65. const allSkills = [...personalSkills, ...superpowersSkills];
  66. if (allSkills.length === 0) {
  67. return 'No skills found. Install superpowers skills to ~/.config/opencode/superpowers/skills/';
  68. }
  69. let output = 'Available skills:\n\n';
  70. for (const skill of allSkills) {
  71. const namespace = skill.sourceType === 'personal' ? '' : 'superpowers:';
  72. const skillName = skill.name || path.basename(skill.path);
  73. output += `${namespace}${skillName}\n`;
  74. if (skill.description) {
  75. output += ` ${skill.description}\n`;
  76. }
  77. output += ` Directory: ${skill.path}\n\n`;
  78. }
  79. return output;
  80. }
  81. }
  82. ],
  83. 'session.started': async () => {
  84. const usingSuperpowersPath = skillsCore.resolveSkillPath('using-superpowers', superpowersSkillsDir, personalSkillsDir);
  85. let usingSuperpowersContent = '';
  86. if (usingSuperpowersPath) {
  87. const fullContent = fs.readFileSync(usingSuperpowersPath.skillFile, 'utf8');
  88. // Strip frontmatter
  89. const lines = fullContent.split('\n');
  90. let inFrontmatter = false;
  91. let frontmatterEnded = false;
  92. const contentLines = [];
  93. for (const line of lines) {
  94. if (line.trim() === '---') {
  95. if (inFrontmatter) {
  96. frontmatterEnded = true;
  97. continue;
  98. }
  99. inFrontmatter = true;
  100. continue;
  101. }
  102. if (frontmatterEnded || !inFrontmatter) {
  103. contentLines.push(line);
  104. }
  105. }
  106. usingSuperpowersContent = contentLines.join('\n').trim();
  107. }
  108. const toolMapping = `
  109. **Tool Mapping for OpenCode:**
  110. When skills reference tools you don't have, substitute OpenCode equivalents:
  111. - \`TodoWrite\` → \`update_plan\` (your planning/task tracking tool)
  112. - \`Task\` tool with subagents → Use OpenCode's subagent system (@mention syntax or automatic dispatch)
  113. - \`Skill\` tool → \`use_skill\` custom tool (already available)
  114. - \`Read\`, \`Write\`, \`Edit\`, \`Bash\` → Use your native tools
  115. **Skill directories contain supporting files:**
  116. - Scripts you can run with bash tool
  117. - Additional documentation you can read
  118. - Utilities and helpers specific to that skill
  119. **Skills naming:**
  120. - Superpowers skills: \`superpowers:skill-name\` (from ~/.config/opencode/superpowers/skills/)
  121. - Personal skills: \`skill-name\` (from ~/.config/opencode/skills/)
  122. - Personal skills override superpowers skills when names match
  123. `;
  124. const hasUpdates = skillsCore.checkForUpdates(path.join(homeDir, '.config/opencode/superpowers'));
  125. const updateNotice = hasUpdates ?
  126. '\n\n**Updates available!** Run `cd ~/.config/opencode/superpowers && git pull` to update superpowers.' :
  127. '';
  128. return {
  129. context: `<EXTREMELY_IMPORTANT>
  130. You have superpowers.
  131. **Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, use the 'use_skill' tool:**
  132. ${usingSuperpowersContent}
  133. ${toolMapping}${updateNotice}
  134. </EXTREMELY_IMPORTANT>`
  135. };
  136. }
  137. };
  138. };