Browse Source

Add native OpenClaw plugin support

Drew Ritter 4 months ago
parent
commit
dc6082d7ff
7 changed files with 207 additions and 1 deletions
  1. 23 0
      .openclaw/index.js
  2. 1 0
      .version-bump.json
  3. 27 0
      README.md
  4. 13 0
      openclaw.plugin.json
  5. 6 1
      package.json
  6. 2 0
      scripts/sync-to-codex-plugin.sh
  7. 135 0
      tests/openclaw/test-native-plugin.sh

+ 23 - 0
.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 }));
+  },
+};

+ 1 - 0
.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" }
   ],

+ 27 - 0
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:

+ 13 - 0
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
+  }
+}

+ 6 - 1
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"
+    ]
+  }
 }

+ 2 - 0
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

+ 135 - 0
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 ==="