Skip to content
Closed
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
15 changes: 11 additions & 4 deletions .opencode/plugin/superpowers.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,19 @@
import path from 'path';
import fs from 'fs';
import os from 'os';
import { fileURLToPath } from 'url';
import { fileURLToPath, pathToFileURL } 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));

// Resolve skills-core.js path dynamically to work with both symlinks and copies
// On Mac/Linux with symlink: __dirname points to actual location
// On Windows with copy: we need to find the superpowers installation
const homeDir = os.homedir();
const superpowersRoot = path.join(homeDir, '.config/opencode/superpowers');
const skillsCorePathResolved = path.join(superpowersRoot, 'lib/skills-core.js');
const skillsCore = await import(pathToFileURL(skillsCorePathResolved).href);
Comment on lines +16 to +22
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

Missing error handling for dynamic import.

The top-level await import() will throw an unhandled error if skills-core.js doesn't exist at the resolved path (e.g., fresh install, non-standard installation). This causes the entire plugin to fail to load with an unhelpful error message.

🛠️ Suggested fix: wrap in try-catch with helpful error
 // Resolve skills-core.js path dynamically to work with both symlinks and copies
 // On Mac/Linux with symlink: __dirname points to actual location
 // On Windows with copy: we need to find the superpowers installation
 const homeDir = os.homedir();
 const superpowersRoot = path.join(homeDir, '.config/opencode/superpowers');
 const skillsCorePathResolved = path.join(superpowersRoot, 'lib/skills-core.js');
-const skillsCore = await import(pathToFileURL(skillsCorePathResolved).href);
+
+let skillsCore;
+try {
+  skillsCore = await import(pathToFileURL(skillsCorePathResolved).href);
+} catch (err) {
+  throw new Error(
+    `Failed to load skills-core.js from ${skillsCorePathResolved}. ` +
+    `Ensure superpowers is installed at ${superpowersRoot}. ` +
+    `Original error: ${err.message}`
+  );
+}
📝 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
// Resolve skills-core.js path dynamically to work with both symlinks and copies
// On Mac/Linux with symlink: __dirname points to actual location
// On Windows with copy: we need to find the superpowers installation
const homeDir = os.homedir();
const superpowersRoot = path.join(homeDir, '.config/opencode/superpowers');
const skillsCorePathResolved = path.join(superpowersRoot, 'lib/skills-core.js');
const skillsCore = await import(pathToFileURL(skillsCorePathResolved).href);
// Resolve skills-core.js path dynamically to work with both symlinks and copies
// On Mac/Linux with symlink: __dirname points to actual location
// On Windows with copy: we need to find the superpowers installation
const homeDir = os.homedir();
const superpowersRoot = path.join(homeDir, '.config/opencode/superpowers');
const skillsCorePathResolved = path.join(superpowersRoot, 'lib/skills-core.js');
let skillsCore;
try {
skillsCore = await import(pathToFileURL(skillsCorePathResolved).href);
} catch (err) {
throw new Error(
`Failed to load skills-core.js from ${skillsCorePathResolved}. ` +
`Ensure superpowers is installed at ${superpowersRoot}. ` +
`Original error: ${err.message}`
);
}
🤖 Prompt for AI Agents
In @.opencode/plugin/superpowers.js around lines 16 - 22, Wrap the dynamic
top-level import of skills-core.js in a try-catch around the
import(pathToFileURL(skillsCorePathResolved).href) call: catch the error, log a
clear, actionable message that includes skillsCorePathResolved and the caught
error (use existing logger or console.error), and either rethrow or gracefully
disable the plugin depending on desired behavior; ensure this handling is
applied where homeDir, superpowersRoot, skillsCorePathResolved and skillsCore
are defined so the plugin fails with a helpful message instead of an unhandled
exception.

Comment on lines +19 to +22
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

Inconsistency: hardcoded path ignores OPENCODE_CONFIG_DIR environment variable.

The superpowersRoot path is hardcoded to ~/.config/opencode/superpowers, but later (lines 45-46) the plugin respects OPENCODE_CONFIG_DIR for the config directory. If a user sets this env var, skills-core.js will still be loaded from the default location, causing a mismatch.

Consider using the same resolution logic:

🛠️ Suggested fix: respect OPENCODE_CONFIG_DIR
+// Helper to normalize path (needed before plugin init)
+const normalizePathEarly = (p, home) => {
+  if (!p || typeof p !== 'string') return null;
+  let normalized = p.trim();
+  if (!normalized) return null;
+  if (normalized.startsWith('~/')) {
+    normalized = path.join(home, normalized.slice(2));
+  } else if (normalized === '~') {
+    normalized = home;
+  }
+  return path.resolve(normalized);
+};
+
 const homeDir = os.homedir();
-const superpowersRoot = path.join(homeDir, '.config/opencode/superpowers');
+const envConfigDir = normalizePathEarly(process.env.OPENCODE_CONFIG_DIR, homeDir);
+const superpowersRoot = path.join(envConfigDir || path.join(homeDir, '.config/opencode'), 'superpowers');
 const skillsCorePathResolved = path.join(superpowersRoot, 'lib/skills-core.js');
📝 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
const homeDir = os.homedir();
const superpowersRoot = path.join(homeDir, '.config/opencode/superpowers');
const skillsCorePathResolved = path.join(superpowersRoot, 'lib/skills-core.js');
const skillsCore = await import(pathToFileURL(skillsCorePathResolved).href);
// Helper to normalize path (needed before plugin init)
const normalizePathEarly = (p, home) => {
if (!p || typeof p !== 'string') return null;
let normalized = p.trim();
if (!normalized) return null;
if (normalized.startsWith('~/')) {
normalized = path.join(home, normalized.slice(2));
} else if (normalized === '~') {
normalized = home;
}
return path.resolve(normalized);
};
const homeDir = os.homedir();
const envConfigDir = normalizePathEarly(process.env.OPENCODE_CONFIG_DIR, homeDir);
const superpowersRoot = path.join(envConfigDir || path.join(homeDir, '.config/opencode'), 'superpowers');
const skillsCorePathResolved = path.join(superpowersRoot, 'lib/skills-core.js');
const skillsCore = await import(pathToFileURL(skillsCorePathResolved).href);
🤖 Prompt for AI Agents
In @.opencode/plugin/superpowers.js around lines 19 - 22, The code hardcodes
superpowersRoot and loads skills-core.js from ~/.config/opencode/superpowers
which ignores OPENCODE_CONFIG_DIR; update the resolution to reuse the same
config-dir logic used later (the OPENCODE_CONFIG_DIR fallback) so that
superpowersRoot is derived from that resolved config directory, then rebuild
skillsCorePathResolved and import skillsCore from that path; ensure you
reference the same environment variable handling used elsewhere so loading
respects OPENCODE_CONFIG_DIR.


// Normalize a path: trim whitespace, expand ~, resolve to absolute
const normalizePath = (p, homeDir) => {
if (!p || typeof p !== 'string') return null;
Expand All @@ -32,8 +39,8 @@ const normalizePath = (p, homeDir) => {
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');
// Derive superpowers skills dir - use absolute path for Windows compatibility
const superpowersSkillsDir = path.join(superpowersRoot, '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');
Expand Down