| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495 |
- /**
- * 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 `<EXTREMELY_IMPORTANT>
- 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}
- </EXTREMELY_IMPORTANT>`;
- };
- 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);
- }
- }
- };
- };
|