📦 deps(superpowers): vendor snapshot
Trigger Superpowers Sync / Run main sync script (push) Successful in 1m0s

This commit is contained in:
playbook-bot
2026-02-28 03:19:04 +00:00
parent 80f0d1c3cf
commit 5ab3a66d52
50 changed files with 794 additions and 840 deletions
+36 -52
View File
@@ -3,15 +3,13 @@
## Prerequisites
- [OpenCode.ai](https://opencode.ai) installed
- Node.js installed
- Git installed
## Installation Steps
### 1. Install Superpowers
### 1. Clone Superpowers
```bash
mkdir -p ~/.config/opencode/superpowers
git clone https://github.com/obra/superpowers.git ~/.config/opencode/superpowers
```
@@ -20,32 +18,43 @@ git clone https://github.com/obra/superpowers.git ~/.config/opencode/superpowers
Create a symlink so OpenCode discovers the plugin:
```bash
mkdir -p ~/.config/opencode/plugin
ln -sf ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js ~/.config/opencode/plugin/superpowers.js
mkdir -p ~/.config/opencode/plugins
rm -f ~/.config/opencode/plugins/superpowers.js
ln -s ~/.config/opencode/superpowers/.opencode/plugins/superpowers.js ~/.config/opencode/plugins/superpowers.js
```
### 3. Restart OpenCode
### 3. Symlink Skills
Restart OpenCode. The plugin will automatically inject superpowers context via the chat.message hook.
Create a symlink so OpenCode's native skill tool discovers superpowers skills:
You should see superpowers is active when you ask "do you have superpowers?"
```bash
mkdir -p ~/.config/opencode/skills
rm -rf ~/.config/opencode/skills/superpowers
ln -s ~/.config/opencode/superpowers/skills ~/.config/opencode/skills/superpowers
```
### 4. Restart OpenCode
Restart OpenCode. The plugin will automatically inject superpowers context.
Verify by asking: "do you have superpowers?"
## Usage
### Finding Skills
Use the `find_skills` tool to list all available skills:
Use OpenCode's native `skill` tool to list available skills:
```
use find_skills tool
use skill tool to list skills
```
### Loading a Skill
Use the `use_skill` tool to load a specific skill:
Use OpenCode's native `skill` tool to load a specific skill:
```
use use_skill tool with skill_name: "superpowers:brainstorming"
use skill tool to load superpowers/brainstorming
```
### Personal Skills
@@ -69,36 +78,11 @@ description: Use when [condition] - [what it does]
[Your skill content here]
```
Personal skills override superpowers skills with the same name.
### Project Skills
Create project-specific skills in your OpenCode project:
Create project-specific skills in `.opencode/skills/` within your project.
```bash
# In your OpenCode project
mkdir -p .opencode/skills/my-project-skill
```
Create `.opencode/skills/my-project-skill/SKILL.md`:
```markdown
---
name: my-project-skill
description: Use when [condition] - [what it does]
---
# My Project Skill
[Your skill content here]
```
**Skill Priority:** Project skills override personal skills, which override superpowers skills.
**Skill Naming:**
- `project:skill-name` - Force project skill lookup
- `skill-name` - Searches project → personal → superpowers
- `superpowers:skill-name` - Force superpowers skill lookup
**Skill Priority:** Project skills > Personal skills > Superpowers skills
## Updating
@@ -111,25 +95,25 @@ git pull
### Plugin not loading
1. Check plugin file exists: `ls ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js`
2. Check OpenCode logs for errors
3. Verify Node.js is installed: `node --version`
1. Check plugin symlink: `ls -l ~/.config/opencode/plugins/superpowers.js`
2. Check source exists: `ls ~/.config/opencode/superpowers/.opencode/plugins/superpowers.js`
3. Check OpenCode logs for errors
### Skills not found
1. Verify skills directory exists: `ls ~/.config/opencode/superpowers/skills`
2. Use `find_skills` tool to see what's discovered
3. Check file structure: each skill should have a `SKILL.md` file
1. Check skills symlink: `ls -l ~/.config/opencode/skills/superpowers`
2. Verify it points to: `~/.config/opencode/superpowers/skills`
3. Use `skill` tool to list what's discovered
### Tool mapping issues
### Tool mapping
When a skill references a Claude Code tool you don't have:
- `TodoWrite` use `update_plan`
- `Task` with subagents → use `@mention` syntax to invoke OpenCode subagents
- `Skill` → use `use_skill` tool
- File operations → use your native tools
When skills reference Claude Code tools:
- `TodoWrite``update_plan`
- `Task` with subagents → `@mention` syntax
- `Skill` tool → OpenCode's native `skill` tool
- File operations → your native tools
## Getting Help
- Report issues: https://github.com/obra/superpowers/issues
- Documentation: https://github.com/obra/superpowers
- Full documentation: https://github.com/obra/superpowers/blob/main/docs/README.opencode.md
-234
View File
@@ -1,234 +0,0 @@
/**
* Superpowers plugin for OpenCode.ai
*
* Provides custom tools for loading and discovering skills,
* with prompt generation for agent configuration.
*/
import path from 'path';
import fs from 'fs';
import os from 'os';
import { fileURLToPath } from 'url';
import { tool } from '@opencode-ai/plugin/tool';
import * as skillsCore from '../../lib/skills-core.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// 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;
// Expand ~ to home directory
if (normalized.startsWith('~/')) {
normalized = path.join(homeDir, normalized.slice(2));
} else if (normalized === '~') {
normalized = homeDir;
}
// Resolve to absolute path
return path.resolve(normalized);
};
export const SuperpowersPlugin = async ({ client, directory }) => {
const homeDir = os.homedir();
const projectSkillsDir = path.join(directory, '.opencode/skills');
// Derive superpowers skills dir from plugin location (works for both symlinked and local installs)
const superpowersSkillsDir = path.resolve(__dirname, '../../skills');
// Respect OPENCODE_CONFIG_DIR if set, otherwise fall back to default
const envConfigDir = normalizePath(process.env.OPENCODE_CONFIG_DIR, homeDir);
const configDir = envConfigDir || path.join(homeDir, '.config/opencode');
const personalSkillsDir = path.join(configDir, 'skills');
// Helper to generate bootstrap content
const getBootstrapContent = (compact = false) => {
const usingSuperpowersPath = skillsCore.resolveSkillPath('using-superpowers', superpowersSkillsDir, personalSkillsDir);
if (!usingSuperpowersPath) return null;
const fullContent = fs.readFileSync(usingSuperpowersPath.skillFile, 'utf8');
const content = skillsCore.stripFrontmatter(fullContent);
const toolMapping = compact
? `**Tool Mapping:** TodoWrite->update_plan, Task->@mention, Skill->use_skill
**Skills naming (priority order):** project: > personal > superpowers:`
: `**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 → \`use_skill\` custom tool
- \`Read\`, \`Write\`, \`Edit\`, \`Bash\` → Your native tools
**Skills naming (priority order):**
- Project skills: \`project:skill-name\` (in .opencode/skills/)
- Personal skills: \`skill-name\` (in ${configDir}/skills/)
- Superpowers skills: \`superpowers:skill-name\`
- Project skills override personal, which override superpowers when names match`;
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 use_skill tool to load "using-superpowers" - that would be redundant. Use use_skill only for OTHER skills.**
${content}
${toolMapping}
</EXTREMELY_IMPORTANT>`;
};
// Helper to inject bootstrap via session.prompt
const injectBootstrap = async (sessionID, compact = false) => {
const bootstrapContent = getBootstrapContent(compact);
if (!bootstrapContent) return false;
try {
await client.session.prompt({
path: { id: sessionID },
body: {
noReply: true,
parts: [{ type: "text", text: bootstrapContent, synthetic: true }]
}
});
return true;
} catch (err) {
return false;
}
};
return {
tool: {
use_skill: tool({
description: 'Load and read a specific skill to guide your work. Skills contain proven workflows, mandatory processes, and expert techniques.',
args: {
skill_name: tool.schema.string().describe('Name of the skill to load (e.g., "superpowers:brainstorming", "my-custom-skill", or "project:my-skill")')
},
execute: async (args, context) => {
const { skill_name } = args;
// Resolve with priority: project > personal > superpowers
// Check for project: prefix first
const forceProject = skill_name.startsWith('project:');
const actualSkillName = forceProject ? skill_name.replace(/^project:/, '') : skill_name;
let resolved = null;
// Try project skills first (if project: prefix or no prefix)
if (forceProject || !skill_name.startsWith('superpowers:')) {
const projectPath = path.join(projectSkillsDir, actualSkillName);
const projectSkillFile = path.join(projectPath, 'SKILL.md');
if (fs.existsSync(projectSkillFile)) {
resolved = {
skillFile: projectSkillFile,
sourceType: 'project',
skillPath: actualSkillName
};
}
}
// Fall back to personal/superpowers resolution
if (!resolved && !forceProject) {
resolved = skillsCore.resolveSkillPath(skill_name, superpowersSkillsDir, personalSkillsDir);
}
if (!resolved) {
return `Error: Skill "${skill_name}" not found.\n\nRun find_skills to see available skills.`;
}
const fullContent = fs.readFileSync(resolved.skillFile, 'utf8');
const { name, description } = skillsCore.extractFrontmatter(resolved.skillFile);
const content = skillsCore.stripFrontmatter(fullContent);
const skillDirectory = path.dirname(resolved.skillFile);
const skillHeader = `# ${name || skill_name}
# ${description || ''}
# Supporting tools and docs are in ${skillDirectory}
# ============================================`;
// Insert as user message with noReply for persistence across compaction
try {
await client.session.prompt({
path: { id: context.sessionID },
body: {
agent: context.agent,
noReply: true,
parts: [
{ type: "text", text: `Loading skill: ${name || skill_name}`, synthetic: true },
{ type: "text", text: `${skillHeader}\n\n${content}`, synthetic: true }
]
}
});
} catch (err) {
// Fallback: return content directly if message insertion fails
return `${skillHeader}\n\n${content}`;
}
return `Launching skill: ${name || skill_name}`;
}
}),
find_skills: tool({
description: 'List all available skills in the project, personal, and superpowers skill libraries.',
args: {},
execute: async (args, context) => {
const projectSkills = skillsCore.findSkillsInDir(projectSkillsDir, 'project', 3);
const personalSkills = skillsCore.findSkillsInDir(personalSkillsDir, 'personal', 3);
const superpowersSkills = skillsCore.findSkillsInDir(superpowersSkillsDir, 'superpowers', 3);
// Priority: project > personal > superpowers
const allSkills = [...projectSkills, ...personalSkills, ...superpowersSkills];
if (allSkills.length === 0) {
return `No skills found. Install superpowers skills to ${superpowersSkillsDir}/ or add personal skills to ${personalSkillsDir}/`;
}
let output = 'Available skills:\n\n';
for (const skill of allSkills) {
let namespace;
switch (skill.sourceType) {
case 'project':
namespace = 'project:';
break;
case 'personal':
namespace = '';
break;
default:
namespace = 'superpowers:';
}
const skillName = skill.name || path.basename(skill.path);
output += `${namespace}${skillName}\n`;
if (skill.description) {
output += ` ${skill.description}\n`;
}
output += ` Directory: ${skill.path}\n\n`;
}
return output;
}
})
},
event: async ({ event }) => {
// Extract sessionID from various event structures
const getSessionID = () => {
return event.properties?.info?.id ||
event.properties?.sessionID ||
event.session?.id;
};
// Inject bootstrap at session creation (before first user message)
if (event.type === 'session.created') {
const sessionID = getSessionID();
if (sessionID) {
await injectBootstrap(sessionID, false);
}
}
// Re-inject bootstrap after context compaction (compact version to save tokens)
if (event.type === 'session.compacted') {
const sessionID = getSessionID();
if (sessionID) {
await injectBootstrap(sessionID, true);
}
}
}
};
};
@@ -0,0 +1,95 @@
/**
* 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);
}
}
};
};