From dc6082d7ff9dcfd15d16c71a883ae6cf51fb51cf Mon Sep 17 00:00:00 2001 From: Drew Ritter Date: Tue, 28 Apr 2026 16:56:39 -0700 Subject: [PATCH] Add native OpenClaw plugin support --- .openclaw/index.js | 23 +++++ .version-bump.json | 1 + README.md | 27 ++++++ openclaw.plugin.json | 13 +++ package.json | 7 +- scripts/sync-to-codex-plugin.sh | 2 + tests/openclaw/test-native-plugin.sh | 135 +++++++++++++++++++++++++++ 7 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 .openclaw/index.js create mode 100644 openclaw.plugin.json create mode 100755 tests/openclaw/test-native-plugin.sh diff --git a/.openclaw/index.js b/.openclaw/index.js new file mode 100644 index 00000000..0956b3db --- /dev/null +++ b/.openclaw/index.js @@ -0,0 +1,23 @@ +const SUPERPOWERS_GUIDANCE = `## Superpowers + +You have access to the Superpowers skill framework through OpenClaw's native skill system. +Before responding to software development work, check whether one of those skills applies. +If a Superpowers skill fits the task, invoke it before proceeding. + +Start with \`using-superpowers\` when you need the overall workflow. Common follow-on +skills include \`brainstorming\`, \`writing-plans\`, \`test-driven-development\`, +\`systematic-debugging\`, \`dispatching-parallel-agents\`, and +\`verification-before-completion\`. + +When Superpowers instructions mention generic tools, use the closest native OpenClaw +tool or workflow.`; + +export default { + id: "superpowers-openclaw", + name: "Superpowers for OpenClaw", + description: "Expose the Superpowers skill pack through OpenClaw's native plugin skill discovery.", + + register(api) { + api.on("before_prompt_build", async () => ({ prependSystemContext: SUPERPOWERS_GUIDANCE })); + }, +}; diff --git a/.version-bump.json b/.version-bump.json index 323acb3f..938291de 100644 --- a/.version-bump.json +++ b/.version-bump.json @@ -4,6 +4,7 @@ { "path": ".claude-plugin/plugin.json", "field": "version" }, { "path": ".cursor-plugin/plugin.json", "field": "version" }, { "path": ".codex-plugin/plugin.json", "field": "version" }, + { "path": "openclaw.plugin.json", "field": "version" }, { "path": ".claude-plugin/marketplace.json", "field": "plugins.0.version" }, { "path": "gemini-extension.json", "field": "version" } ], diff --git a/README.md b/README.md index ea17e30e..cedf2d96 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,33 @@ already use it in another harness. - Detailed docs: [docs/README.opencode.md](docs/README.opencode.md) +### OpenClaw + +OpenClaw uses its native plugin system. Install Superpowers separately even if +you already use it in another harness. + +- Clone and link the plugin: + + ```bash + git clone https://github.com/obra/superpowers.git ~/.openclaw/vendor/superpowers + openclaw plugins install --link ~/.openclaw/vendor/superpowers --dangerously-force-unsafe-install + openclaw plugins enable superpowers-openclaw + openclaw gateway restart + ``` + + The override is required because OpenClaw scans the linked plugin directory + during native plugin install. It flags `skills/writing-skills/render-graphs.js`, + an existing Superpowers skill-authoring helper, because that script shells out + to Graphviz. The helper is not part of the OpenClaw runtime hook; review the + source before using the override. + +- Verify the plugin and skills are available: + + ```bash + openclaw plugins info superpowers-openclaw --json + openclaw skills info using-superpowers --json + ``` + ### Cursor - In Cursor Agent chat, install from marketplace: diff --git a/openclaw.plugin.json b/openclaw.plugin.json new file mode 100644 index 00000000..925a2e86 --- /dev/null +++ b/openclaw.plugin.json @@ -0,0 +1,13 @@ +{ + "id": "superpowers-openclaw", + "name": "Superpowers for OpenClaw", + "version": "5.0.7", + "description": "Expose the Superpowers skill pack through OpenClaw's native plugin skill discovery.", + "skills": [ + "./skills" + ], + "configSchema": { + "type": "object", + "additionalProperties": false + } +} diff --git a/package.json b/package.json index 04f22b42..87cb289d 100644 --- a/package.json +++ b/package.json @@ -2,5 +2,10 @@ "name": "superpowers", "version": "5.0.7", "type": "module", - "main": ".opencode/plugins/superpowers.js" + "main": ".opencode/plugins/superpowers.js", + "openclaw": { + "extensions": [ + "./.openclaw/index.js" + ] + } } diff --git a/scripts/sync-to-codex-plugin.sh b/scripts/sync-to-codex-plugin.sh index fc0a8e85..e44baf0c 100755 --- a/scripts/sync-to-codex-plugin.sh +++ b/scripts/sync-to-codex-plugin.sh @@ -52,6 +52,7 @@ EXCLUDES=( "/.gitattributes" "/.github/" "/.gitignore" + "/.openclaw/" "/.opencode/" "/.version-bump.json" "/.worktrees/" @@ -64,6 +65,7 @@ EXCLUDES=( "/GEMINI.md" "/RELEASE-NOTES.md" "/gemini-extension.json" + "/openclaw.plugin.json" "/package.json" # Directories not shipped by canonical Codex plugins diff --git a/tests/openclaw/test-native-plugin.sh b/tests/openclaw/test-native-plugin.sh new file mode 100755 index 00000000..27b239a6 --- /dev/null +++ b/tests/openclaw/test-native-plugin.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash +# Test: OpenClaw native plugin package +# Verifies the Superpowers repo exposes a native OpenClaw plugin contract. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +cd "$REPO_ROOT" + +echo "=== Test: OpenClaw native plugin package ===" + +echo "Test 1: Checking OpenClaw manifest..." +node <<'NODE' +import fs from "node:fs"; + +const manifestPath = "openclaw.plugin.json"; +if (!fs.existsSync(manifestPath)) { + throw new Error(`${manifestPath} is missing`); +} + +const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")); +const pkg = JSON.parse(fs.readFileSync("package.json", "utf8")); +if (manifest.id !== "superpowers-openclaw") { + throw new Error(`unexpected manifest id: ${manifest.id}`); +} +if (manifest.version !== pkg.version) { + throw new Error(`manifest version ${manifest.version} must match package version ${pkg.version}`); +} +if (!Array.isArray(manifest.skills) || manifest.skills.length !== 1 || manifest.skills[0] !== "./skills") { + throw new Error(`unexpected skills declaration: ${JSON.stringify(manifest.skills)}`); +} +if ( + !manifest.configSchema || + manifest.configSchema.type !== "object" || + manifest.configSchema.additionalProperties !== false || + manifest.configSchema.properties !== undefined +) { + throw new Error(`manifest must declare an empty object configSchema: ${JSON.stringify(manifest.configSchema)}`); +} +if ("entrypoint" in manifest) { + throw new Error("OpenClaw entrypoints belong in package.json openclaw.extensions, not openclaw.plugin.json"); +} +if ("hooks" in manifest) { + throw new Error("OpenClaw hook registration belongs in the runtime entrypoint, not openclaw.plugin.json"); +} +NODE +echo " [PASS] Manifest declares plugin skills correctly" + +echo "Test 2: Checking package OpenClaw extension metadata..." +node <<'NODE' +import fs from "node:fs"; + +const pkg = JSON.parse(fs.readFileSync("package.json", "utf8")); +if (pkg.name !== "superpowers") { + throw new Error(`package name changed unexpectedly: ${pkg.name}`); +} +if (pkg.type !== "module") { + throw new Error("OpenClaw runtime should preserve the repo's ESM package mode"); +} +const extensions = pkg.openclaw?.extensions; +if (!Array.isArray(extensions) || extensions.length !== 1 || extensions[0] !== "./.openclaw/index.js") { + throw new Error(`unexpected openclaw.extensions: ${JSON.stringify(extensions)}`); +} +NODE +echo " [PASS] package.json points OpenClaw at the runtime entrypoint" + +echo "Test 3: Checking runtime entrypoint syntax and hook behavior..." +node --check .openclaw/index.js +node <<'NODE' +const module = await import(new URL("./.openclaw/index.js", import.meta.url)); +const plugin = module.default; + +if (!plugin || plugin.id !== "superpowers-openclaw" || typeof plugin.register !== "function") { + throw new Error("OpenClaw runtime must default-export a plugin with register(api)"); +} + +const hooks = []; +const api = { + pluginConfig: {}, + rootDir: process.cwd(), + logger: { warn(message) { throw new Error(`unexpected warning: ${message}`); } }, + on(name, handler) { + hooks.push({ name, handler }); + }, +}; + +plugin.register(api); +if (hooks.length !== 1 || hooks[0].name !== "before_prompt_build") { + throw new Error(`unexpected hooks: ${JSON.stringify(hooks.map((hook) => hook.name))}`); +} + +const result = await hooks[0].handler(); +if (!result?.prependSystemContext?.includes("Superpowers")) { + throw new Error("before_prompt_build hook must inject Superpowers guidance"); +} +if (result.prependSystemContext.includes("~/.openclaw/skills")) { + throw new Error("native plugin guidance must not advertise managed skill symlinks"); +} +NODE +echo " [PASS] Runtime entrypoint registers the prompt hook" + +echo "Test 4: Checking README install flow..." +if ! grep -q 'openclaw plugins install --link ~/.openclaw/vendor/superpowers' README.md; then + echo " [FAIL] README must document linked OpenClaw plugin install" + exit 1 +fi +if ! grep -q -- '--dangerously-force-unsafe-install' README.md; then + echo " [FAIL] README must document OpenClaw scanner override required by existing helper scripts" + exit 1 +fi +if ! grep -q 'skills/writing-skills/render-graphs.js' README.md; then + echo " [FAIL] README must explain the exact Superpowers helper that triggers OpenClaw's scanner" + exit 1 +fi +if ! grep -q 'not part of the OpenClaw runtime hook' README.md; then + echo " [FAIL] README must explain that the flagged helper is not part of the OpenClaw runtime hook" + exit 1 +fi +if ! grep -q 'openclaw plugins enable superpowers-openclaw' README.md; then + echo " [FAIL] README must document enabling the OpenClaw plugin" + exit 1 +fi +if ! grep -q 'openclaw gateway restart' README.md; then + echo " [FAIL] README must document restarting the gateway" + exit 1 +fi +if grep -q 'OPENCLAW_SKILLS_DIR\|ln -s\|AGENTS-snippet\|.openclaw/INSTALL.md' README.md; then + echo " [FAIL] README must not use the legacy symlink/snippet wrapper or extra install doc" + exit 1 +fi +echo " [PASS] README documents native plugin commands" + +echo "" +echo "=== OpenClaw native plugin tests passed ==="