108 lines
3.8 KiB
JavaScript
108 lines
3.8 KiB
JavaScript
/**
|
|
* Superpowers plugin for OpenCode.ai
|
|
*
|
|
* Injects superpowers bootstrap context via system prompt transform.
|
|
* Auto-registers skills directory via config hook (no symlinks needed).
|
|
*/
|
|
|
|
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\` → \`todowrite\`
|
|
- \`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 {
|
|
// Inject skills path into live config so OpenCode discovers superpowers skills
|
|
// without requiring manual symlinks or config file edits.
|
|
// This works because Config.get() returns a cached singleton — modifications
|
|
// here are visible when skills are lazily discovered later.
|
|
config: async (config) => {
|
|
config.skills = config.skills || {};
|
|
config.skills.paths = config.skills.paths || [];
|
|
if (!config.skills.paths.includes(superpowersSkillsDir)) {
|
|
config.skills.paths.push(superpowersSkillsDir);
|
|
}
|
|
},
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
};
|
|
};
|