/** * Superpowers plugin for OpenCode.ai * * Injects superpowers bootstrap context via system prompt transform. * Skills are discovered via OpenCode's native skill tool from symlinked directory. */ import path from 'path'; import fs from 'fs'; import os from 'os'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Simple frontmatter extraction (avoid dependency on skills-core for bootstrap) const extractAndStripFrontmatter = (content) => { const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); if (!match) return { frontmatter: {}, content }; const frontmatterStr = match[1]; const body = match[2]; const frontmatter = {}; for (const line of frontmatterStr.split('\n')) { const colonIdx = line.indexOf(':'); if (colonIdx > 0) { const key = line.slice(0, colonIdx).trim(); const value = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, ''); frontmatter[key] = value; } } return { frontmatter, content: body }; }; // Normalize a path: trim whitespace, expand ~, resolve to absolute const normalizePath = (p, homeDir) => { if (!p || typeof p !== 'string') return null; let normalized = p.trim(); if (!normalized) return null; if (normalized.startsWith('~/')) { normalized = path.join(homeDir, normalized.slice(2)); } else if (normalized === '~') { normalized = homeDir; } return path.resolve(normalized); }; export const SuperpowersPlugin = async ({ client, directory }) => { const homeDir = os.homedir(); const superpowersSkillsDir = path.resolve(__dirname, '../../skills'); const envConfigDir = normalizePath(process.env.OPENCODE_CONFIG_DIR, homeDir); const configDir = envConfigDir || path.join(homeDir, '.config/opencode'); // Helper to generate bootstrap content const getBootstrapContent = () => { // Try to load using-superpowers skill const skillPath = path.join(superpowersSkillsDir, 'using-superpowers', 'SKILL.md'); if (!fs.existsSync(skillPath)) return null; const fullContent = fs.readFileSync(skillPath, 'utf8'); const { content } = extractAndStripFrontmatter(fullContent); const toolMapping = `**Tool Mapping for OpenCode:** When skills reference tools you don't have, substitute OpenCode equivalents: - \`TodoWrite\` → \`update_plan\` - \`Task\` tool with subagents → Use OpenCode's subagent system (@mention) - \`Skill\` tool → OpenCode's native \`skill\` tool - \`Read\`, \`Write\`, \`Edit\`, \`Bash\` → Your native tools **Skills location:** Superpowers skills are in \`${configDir}/skills/superpowers/\` Use OpenCode's native \`skill\` tool to list and load skills.`; return ` You have superpowers. **IMPORTANT: The using-superpowers skill content is included below. It is ALREADY LOADED - you are currently following it. Do NOT use the skill tool to load "using-superpowers" again - that would be redundant.** ${content} ${toolMapping} `; }; return { // Use system prompt transform to inject bootstrap (fixes #226 agent reset bug) 'experimental.chat.system.transform': async (_input, output) => { const bootstrap = getBootstrapContent(); if (bootstrap) { (output.system ||= []).push(bootstrap); } } }; };