opencodetoolingautomation

2026-09-03 12:20 UTC

Rolling out ponytail + superpowers across the lab

Problem

The laptop had ponytail (a lazy-coding ruleset) and superpowers (a skill library) installed as opencode plugins. Forge and Curly — the two remote lab nodes — had bare opencode installs with no plugins or skills. Every machine should run the same tooling so the workflow is consistent across the lab.

Investigation

The laptop's setup turned out to be a chain of symlinks:

  • opencode.json references ./plugins/superpowers.js and ./plugins/ponytail.mjs
  • plugins/superpowers.js is a symlink to superpowers/.opencode/plugins/superpowers.js (a git clone of the superpowers repo)
  • plugins/ponytail.mjs is a symlink to node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail.mjs (the npm package)

The symlinks matter: each plugin resolves its skills directory relative to its real file location. The superpowers plugin computes ../../skills from superpowers/.opencode/plugins/, landing on superpowers/skills. The ponytail plugin resolves ../../hooks from the npm package location. Copying the plugin files as plain files would break these relative paths.

Fix

On each machine (Forge, then Curly):

1. Copy the superpowers repo to ~/.config/opencode/superpowers/ (excluding .git).

2. Install the ponytail npm package: npm install @dietrichgebert/[email protected].

3. Create the plugin symlinks in ~/.config/opencode/plugins/ pointing at the real plugin files.

4. Write opencode.jsonc with the two plugin entries.

Verification

  • opencode debug config shows both skills paths registered:
  • node_modules/@dietrichgebert/ponytail/skills
  • superpowers/skills
  • opencode debug skill lists all 20 skills (6 ponytail + 14 superpowers).
  • opencode run loads the plugins with no errors.
  • Ponytail defaults to full mode (no state file needed).

Lesson

When replicating a plugin setup across machines, copy the whole directory structure including symlinks — don't flatten the files. The relative-path resolution inside each plugin depends on where the real file lives, not where the symlink points.