Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .trae/INSTALL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Installing Superpowers for Trae

Quick setup to enable superpowers skills in Trae.

## Installation

1. **Clone superpowers repository**:
```bash
mkdir -p ~/.trae/superpowers
cd ~/.trae/superpowers
git clone https://github.com/ice-zjchen/superpowers.git .
```
Comment on lines +7 to +12
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Repository URL points to fork instead of upstream.

The clone URL https://github.com/ice-zjchen/superpowers.git references the PR author's fork. Before merging, this should be updated to the upstream repository URL https://github.com/obra/superpowers.git to ensure users clone from the canonical source.

🔧 Suggested fix
 1. **Clone superpowers repository**:
    ```bash
    mkdir -p ~/.trae/superpowers
    cd ~/.trae/superpowers
-   git clone https://github.com/ice-zjchen/superpowers.git .
+   git clone https://github.com/obra/superpowers.git .
    ```
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
1. **Clone superpowers repository**:
```bash
mkdir -p ~/.trae/superpowers
cd ~/.trae/superpowers
git clone https://github.com/ice-zjchen/superpowers.git .
```
1. **Clone superpowers repository**:
🤖 Prompt for AI Agents
In @.trae/INSTALL.md around lines 7 - 12, Replace the forked clone URL in the
installation instructions: locate the git clone command that uses
"https://github.com/ice-zjchen/superpowers.git" in .trae/INSTALL.md and update
it to the upstream canonical URL "https://github.com/obra/superpowers.git" so
the installation block (the git clone line) points to the upstream repository.


2. **Create personal skills directory**:
```bash
mkdir -p ~/.trae/skills
```

3. **Create `.trae/rules/superpowers.md` and Update it** to include this superpowers section:
```markdown
## Superpowers System

<EXTREMELY_IMPORTANT>
You have superpowers. Superpowers teach you new skills and capabilities. RIGHT NOW run: `~/.trae/superpowers/.trae/superpowers-trae bootstrap` and follow the instructions it returns.
</EXTREMELY_IMPORTANT>
```

## Verification

Test the installation:
```bash
~/.trae/superpowers/.trae/superpowers-trae bootstrap
```

You should see skill listings and bootstrap instructions. The system is now ready for use.
33 changes: 33 additions & 0 deletions .trae/superpowers-bootstrap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Superpowers Bootstrap for Trae

<EXTREMELY_IMPORTANT>
You have superpowers.

**Tool for running skills:**
- `~/.trae/superpowers/.trae/superpowers-trae use-skill <skill-name>`

**Tool Mapping for Trae:**
When skills reference tools you don't have, substitute your equivalent tools:
- `TodoWrite` → `update_plan` (your planning/task tracking tool)
- `Task` tool with subagents → Tell the user that subagents aren't available in Trae yet and you'll do the work the subagent would do
- `Skill` tool → `~/.trae/superpowers/.trae/superpowers-trae use-skill` command (already available)
- `Read`, `Write`, `Edit`, `Bash` → Use your native tools with similar functions

**Skills naming:**
- Superpowers skills: `superpowers:skill-name` (from ~/.trae/superpowers/skills/)
- Personal skills: `skill-name` (from ~/.trae/skills/)
- Personal skills override superpowers skills when names match

**Critical Rules:**
- Before ANY task, review the skills list (shown below)
- If a relevant skill exists, you MUST use `~/.trae/superpowers/.trae/superpowers-trae use-skill` to load it
- Announce: "I've read the [Skill Name] skill and I'm using it to [purpose]"
- Skills with checklists require `update_plan` todos for each item
- NEVER skip mandatory workflows (brainstorming before coding, TDD, systematic debugging)

**Skills location:**
- Superpowers skills: ~/.trae/superpowers/skills/
- Personal skills: ~/.trae/skills/ (override superpowers when names match)

IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
</EXTREMELY_IMPORTANT>
267 changes: 267 additions & 0 deletions .trae/superpowers-trae
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
#!/usr/bin/env node

const fs = require('fs');
const path = require('path');
const os = require('os');
const skillsCore = require('../lib/skills-core');

// Paths
const homeDir = os.homedir();
const superpowersSkillsDir = path.join(homeDir, '.trae', 'superpowers', 'skills');
const personalSkillsDir = path.join(homeDir, '.trae', 'skills');
const bootstrapFile = path.join(homeDir, '.trae', 'superpowers', '.trae', 'superpowers-bootstrap.md');
const superpowersRepoDir = path.join(homeDir, '.trae', 'superpowers');

// Utility functions
function printSkill(skillPath, sourceType) {
const skillFile = path.join(skillPath, 'SKILL.md');
const relPath = sourceType === 'personal'
? path.relative(personalSkillsDir, skillPath)
: path.relative(superpowersSkillsDir, skillPath);

// Print skill name with namespace
if (sourceType === 'personal') {
console.log(relPath.replace(/\\/g, '/')); // Personal skills are not namespaced
} else {
console.log(`superpowers:${relPath.replace(/\\/g, '/')}`); // Superpowers skills get superpowers namespace
}

// Extract and print metadata
const { name, description } = skillsCore.extractFrontmatter(skillFile);

if (description) console.log(` ${description}`);
console.log('');
}

// Commands
function runFindSkills() {
console.log('Available skills:');
console.log('==================');
console.log('');

const foundSkills = new Set();

// Find personal skills first (these take precedence)
const personalSkills = skillsCore.findSkillsInDir(personalSkillsDir, 'personal', 2);
for (const skill of personalSkills) {
const relPath = path.relative(personalSkillsDir, skill.path);
foundSkills.add(relPath);
printSkill(skill.path, 'personal');
}

// Find superpowers skills (only if not already found in personal)
const superpowersSkills = skillsCore.findSkillsInDir(superpowersSkillsDir, 'superpowers', 1);
for (const skill of superpowersSkills) {
const relPath = path.relative(superpowersSkillsDir, skill.path);
if (!foundSkills.has(relPath)) {
printSkill(skill.path, 'superpowers');
}
}

console.log('Usage:');
console.log(' superpowers-trae use-skill <skill-name> # Load a specific skill');
console.log('');
console.log('Skill naming:');
console.log(' Superpowers skills: superpowers:skill-name (from ~/.trae/superpowers/skills/)');
console.log(' Personal skills: skill-name (from ~/.trae/skills/)');
console.log(' Personal skills override superpowers skills when names match.');
console.log('');
console.log('Note: All skills are disclosed at session start via bootstrap.');
}

function runBootstrap() {
console.log('# Superpowers Bootstrap for Trae');
console.log('# ===============================');
console.log('');

// Check for updates (with timeout protection)
if (skillsCore.checkForUpdates(superpowersRepoDir)) {
console.log('## Update Available');
console.log('');
console.log('⚠️ Your superpowers installation is behind the latest version.');
console.log('To update, run: `cd ~/.trae/superpowers && git pull`');
console.log('');
console.log('---');
console.log('');
}

// Show the bootstrap instructions
if (fs.existsSync(bootstrapFile)) {
console.log('## Bootstrap Instructions:');
console.log('');
try {
const content = fs.readFileSync(bootstrapFile, 'utf8');
console.log(content);
} catch (error) {
console.log(`Error reading bootstrap file: ${error.message}`);
}
console.log('');
console.log('---');
console.log('');
}

// Run find-skills to show available skills
console.log('## Available Skills:');
console.log('');
runFindSkills();

console.log('');
console.log('---');
console.log('');

// Load the using-superpowers skill automatically
console.log('## Auto-loading superpowers:using-superpowers skill:');
console.log('');
runUseSkill('superpowers:using-superpowers');

console.log('');
console.log('---');
console.log('');
console.log('# Bootstrap Complete!');
console.log('# You now have access to all superpowers skills.');
console.log('# Use "superpowers-trae use-skill <skill>" to load and apply skills.');
console.log('# Remember: If a skill applies to your task, you MUST use it!');
}

function runUseSkill(skillName) {
if (!skillName) {
console.log('Usage: superpowers-trae use-skill <skill-name>');
console.log('Examples:');
console.log(' superpowers-trae use-skill superpowers:brainstorming # Load superpowers skill');
console.log(' superpowers-trae use-skill brainstorming # Load personal skill (or superpowers if not found)');
console.log(' superpowers-trae use-skill my-custom-skill # Load personal skill');
return;
}

// Handle namespaced skill names
let actualSkillPath;
let forceSuperpowers = false;

if (skillName.startsWith('superpowers:')) {
// Remove the superpowers: namespace prefix
actualSkillPath = skillName.substring('superpowers:'.length);
forceSuperpowers = true;
} else {
actualSkillPath = skillName;
}

// Remove "skills/" prefix if present
if (actualSkillPath.startsWith('skills/')) {
actualSkillPath = actualSkillPath.substring('skills/'.length);
}
Comment on lines +140 to +151
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential path traversal vulnerability.

The actualSkillPath is derived from user input (skillName) with only namespace and skills/ prefix stripping. If a user passes a skill name containing .. sequences (e.g., superpowers:../../etc/passwd), the path.join on lines 174, 180, and 191 could resolve to paths outside the intended skill directories, potentially allowing arbitrary file reads.

🔒 Suggested fix: Validate the resolved path stays within allowed directories
+    // Sanitize skill path to prevent directory traversal
+    function isPathWithinDir(filePath, dirPath) {
+        const resolvedFile = path.resolve(filePath);
+        const resolvedDir = path.resolve(dirPath);
+        return resolvedFile.startsWith(resolvedDir + path.sep);
+    }
+
     // If superpowers: namespace was used, only check superpowers skills
     if (forceSuperpowers) {
         if (fs.existsSync(superpowersSkillsDir)) {
             const superpowersPath = path.join(superpowersSkillsDir, actualSkillPath);
+            if (!isPathWithinDir(superpowersPath, superpowersSkillsDir)) {
+                console.log(`Error: Invalid skill path: ${actualSkillPath}`);
+                return;
+            }
             skillFile = findSkillFile(superpowersPath);
         }

Apply similar validation for the personal skills path (line 180) and the fallback superpowers path (line 191).

🤖 Prompt for AI Agents
In @.trae/superpowers-trae around lines 140 - 151, The code strips prefixes from
skillName into actualSkillPath but does not prevent path traversal (e.g., ".."),
so the later path.join calls can escape intended directories; to fix, after
computing actualSkillPath call path.resolve against each base directory used
(the superpowers base, personal skills base, and fallback superpowers base used
in the path.join operations) and verify the resolved path starts with the
corresponding base directory (or is equal) before using it; if the check fails,
reject the request or throw an error. Ensure you apply this validation for the
paths constructed for superpowers (where forceSuperpowers is true), the personal
skills path branch, and the fallback superpowers path so that actualSkillPath
cannot traverse outside the allowed directories.


// Function to find skill file
function findSkillFile(searchPath) {
// Check for exact match with SKILL.md
const skillMdPath = path.join(searchPath, 'SKILL.md');
if (fs.existsSync(skillMdPath)) {
return skillMdPath;
}

// Check for direct SKILL.md file
if (searchPath.endsWith('SKILL.md') && fs.existsSync(searchPath)) {
return searchPath;
}

return null;
}

let skillFile = null;

// If superpowers: namespace was used, only check superpowers skills
if (forceSuperpowers) {
if (fs.existsSync(superpowersSkillsDir)) {
const superpowersPath = path.join(superpowersSkillsDir, actualSkillPath);
skillFile = findSkillFile(superpowersPath);
}
} else {
// First check personal skills directory (takes precedence)
if (fs.existsSync(personalSkillsDir)) {
const personalPath = path.join(personalSkillsDir, actualSkillPath);
skillFile = findSkillFile(personalPath);
if (skillFile) {
console.log(`# Loading personal skill: ${actualSkillPath}`);
console.log(`# Source: ${skillFile}`);
console.log('');
}
}

// If not found in personal, check superpowers skills
if (!skillFile && fs.existsSync(superpowersSkillsDir)) {
const superpowersPath = path.join(superpowersSkillsDir, actualSkillPath);
skillFile = findSkillFile(superpowersPath);
if (skillFile) {
console.log(`# Loading superpowers skill: superpowers:${actualSkillPath}`);
console.log(`# Source: ${skillFile}`);
console.log('');
}
}
}

// If still not found, error
if (!skillFile) {
console.log(`Error: Skill not found: ${actualSkillPath}`);
console.log('');
console.log('Available skills:');
runFindSkills();
return;
}

// Extract frontmatter and content using shared core functions
let content, frontmatter;
try {
const fullContent = fs.readFileSync(skillFile, 'utf8');
const { name, description } = skillsCore.extractFrontmatter(skillFile);
content = skillsCore.stripFrontmatter(fullContent);
frontmatter = { name, description };
} catch (error) {
console.log(`Error reading skill file: ${error.message}`);
return;
}

// Display skill header with clean info
const displayName = forceSuperpowers ? `superpowers:${actualSkillPath}` :
(skillFile.includes(personalSkillsDir) ? actualSkillPath : `superpowers:${actualSkillPath}`);

const skillDirectory = path.dirname(skillFile);

console.log(`# ${frontmatter.name || displayName}`);
if (frontmatter.description) {
console.log(`# ${frontmatter.description}`);
}
console.log(`# Skill-specific tools and reference files live in ${skillDirectory}`);
console.log('# ============================================');
console.log('');

// Display the skill content (without frontmatter)
console.log(content);

}

// Main CLI
const command = process.argv[2];
const arg = process.argv[3];

switch (command) {
case 'bootstrap':
runBootstrap();
break;
case 'use-skill':
runUseSkill(arg);
break;
case 'find-skills':
runFindSkills();
break;
default:
console.log('Superpowers for Trae');
console.log('Usage:');
console.log(' superpowers-trae bootstrap # Run complete bootstrap with all skills');
console.log(' superpowers-trae use-skill <skill-name> # Load a specific skill');
console.log(' superpowers-trae find-skills # List all available skills');
console.log('');
console.log('Examples:');
console.log(' superpowers-trae bootstrap');
console.log(' superpowers-trae use-skill superpowers:brainstorming');
console.log(' superpowers-trae use-skill my-custom-skill');
break;
}
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ Fetch and follow instructions from https://raw.githubusercontent.com/obra/superp

**Detailed docs:** [docs/README.opencode.md](docs/README.opencode.md)

### Trae

Tell Trae:

```
Fetch and follow instructions from https://raw.githubusercontent.com/obra/superpowers/refs/heads/main/.trae/INSTALL.md
```

**Detailed docs:** [docs/README.trae.md](docs/README.trae.md)
Comment on lines +80 to +88
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Repository URL points to fork instead of upstream.

The Trae installation URL references the PR author's fork. Update to the upstream repository URL for consistency with other documentation.

🔧 Suggested fix
 ### Trae

 Tell Trae:

-Fetch and follow instructions from https://raw.githubusercontent.com/ice-zjchen/superpowers/refs/heads/main/.trae/INSTALL.md
+Fetch and follow instructions from https://raw.githubusercontent.com/obra/superpowers/refs/heads/main/.trae/INSTALL.md


**Detailed docs:** [docs/README.trae.md](docs/README.trae.md)

The section structure and placement are consistent with the existing Codex and OpenCode sections. 👍

🤖 Prompt for AI Agents
In `@README.md` around lines 80 - 88, The README's Trae section contains a URL
pointing to the contributor's fork; update the URL string literal
"https://raw.githubusercontent.com/ice-zjchen/superpowers/refs/heads/main/.trae/INSTALL.md"
to the upstream repository
"https://raw.githubusercontent.com/obra/superpowers/refs/heads/main/.trae/INSTALL.md"
so the Trae install instruction references the canonical source; edit the
README.md Trae block where that URL appears.


## The Basic Workflow

1. **brainstorming** - Activates before writing code. Refines rough ideas through questions, explores alternatives, presents design in sections for validation. Saves design document.
Expand Down
Loading