From de5d2255294842dcc85cccea8335ec3672e3918d Mon Sep 17 00:00:00 2001 From: cafeng Date: Wed, 17 Jun 2026 10:23:31 +0800 Subject: [PATCH] fix(opencode): rename .opencode/command/ to .opencode/commands/ to match OpenCode's discovery convention OpenCode auto-discovers commands from .opencode/commands/ (plural, per the official docs at https://opencode.ai/docs/commands), but ponytail shipped them in .opencode/command/ (singular), so the slash commands (/ponytail, /ponytail-audit, /ponytail-review, /ponytail-debt, /ponytail-help) were never auto-loaded. The plugin's system-prompt injection worked, but the README claim 'adds the /ponytail commands' was effectively false for everyone except someone running OpenCode from inside this repo's checkout with manual discovery. Changes: - git mv .opencode/command .opencode/commands (rename detected, history preserved) - tests/commands.test.js: update path assertion to .opencode/commands/ - docs/agent-portability.md: update OpenCode row to .opencode/commands/ - README.md: add global install instructions (symlink into ~/.config/opencode/commands/) for cross-project use --- .opencode/{command => commands}/ponytail-audit.md | 0 .opencode/{command => commands}/ponytail-debt.md | 0 .opencode/{command => commands}/ponytail-help.md | 0 .opencode/{command => commands}/ponytail-review.md | 0 .opencode/{command => commands}/ponytail.md | 0 README.md | 9 +++++++++ docs/agent-portability.md | 2 +- tests/commands.test.js | 8 ++++---- 8 files changed, 14 insertions(+), 5 deletions(-) rename .opencode/{command => commands}/ponytail-audit.md (100%) rename .opencode/{command => commands}/ponytail-debt.md (100%) rename .opencode/{command => commands}/ponytail-help.md (100%) rename .opencode/{command => commands}/ponytail-review.md (100%) rename .opencode/{command => commands}/ponytail.md (100%) diff --git a/.opencode/command/ponytail-audit.md b/.opencode/commands/ponytail-audit.md similarity index 100% rename from .opencode/command/ponytail-audit.md rename to .opencode/commands/ponytail-audit.md diff --git a/.opencode/command/ponytail-debt.md b/.opencode/commands/ponytail-debt.md similarity index 100% rename from .opencode/command/ponytail-debt.md rename to .opencode/commands/ponytail-debt.md diff --git a/.opencode/command/ponytail-help.md b/.opencode/commands/ponytail-help.md similarity index 100% rename from .opencode/command/ponytail-help.md rename to .opencode/commands/ponytail-help.md diff --git a/.opencode/command/ponytail-review.md b/.opencode/commands/ponytail-review.md similarity index 100% rename from .opencode/command/ponytail-review.md rename to .opencode/commands/ponytail-review.md diff --git a/.opencode/command/ponytail.md b/.opencode/commands/ponytail.md similarity index 100% rename from .opencode/command/ponytail.md rename to .opencode/commands/ponytail.md diff --git a/README.md b/README.md index d003bfa..b887f8c 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,15 @@ Injects the ruleset every turn at the active level; adds the `/ponytail` command The `./` path resolves against your project's `opencode.json`; to share one checkout across projects, point it at the absolute path of the `.mjs` instead (it finds its `hooks/` and `skills/` relative to its own file). +The slash commands ship in `.opencode/commands/` and are auto-discovered when you run OpenCode from this repo's root. To use them in every project without copying, link them into OpenCode's global commands directory: + +```bash +mkdir -p ~/.config/opencode/commands +ln -s "$PWD"/.opencode/commands/*.md ~/.config/opencode/commands/ +``` + +Run that once from this repo's root; rerun if a new command is added. Restart OpenCode to pick up the changes. + ### Gemini CLI ```bash diff --git a/docs/agent-portability.md b/docs/agent-portability.md index 40b4af8..34daa3c 100644 --- a/docs/agent-portability.md +++ b/docs/agent-portability.md @@ -10,7 +10,7 @@ to load in a given agent. |------|-------|-------| | Claude Code | `.claude-plugin/`, `commands/`, `hooks/` | Full plugin install with session activation, mode tracking, commands, and statusline support. | | Codex | `.codex-plugin/plugin.json`, `hooks/hooks.json`, `hooks/`, `skills/` | Plugin install with the same skills plus lifecycle hooks for activation and mode tracking. | -| OpenCode | `.opencode/plugins/ponytail.mjs`, `.opencode/command/`, `hooks/`, `skills/` | Server plugin injects the ruleset each turn via `experimental.chat.system.transform` and persists `/ponytail` switches; reuses the shared instruction builder. | +| OpenCode | `.opencode/plugins/ponytail.mjs`, `.opencode/commands/`, `hooks/`, `skills/` | Server plugin injects the ruleset each turn via `experimental.chat.system.transform` and persists `/ponytail` switches; reuses the shared instruction builder. | | pi | `pi-extension/`, `skills/`, `hooks/` | Package extension: injects the ruleset each turn through the shared instruction builder and registers the `/ponytail` commands. | | Gemini CLI | `gemini-extension.json`, `AGENTS.md`, `commands/`, `skills/` | Extension manifest points `contextFileName` at `AGENTS.md` for always-on rules, and reuses the existing `commands/*.toml` and `skills/`, which Gemini CLI auto-discovers. | | Cursor | `.cursor/rules/ponytail.mdc` | Always-on project rule. | diff --git a/tests/commands.test.js b/tests/commands.test.js index ef47a86..bc1dbff 100644 --- a/tests/commands.test.js +++ b/tests/commands.test.js @@ -1,7 +1,7 @@ #!/usr/bin/env node // Every ponytail command the pi extension registers must also ship as a // file-based command for the hosts that need one: Claude Code (commands/*.toml, -// which Gemini CLI reuses) and OpenCode (.opencode/command/*.md). /ponytail-help +// which Gemini CLI reuses) and OpenCode (.opencode/commands/*.md). /ponytail-help // was advertised in the README and the help card but missing both files; this // guards that drift -- a registered command with no adapter file fails here. @@ -29,11 +29,11 @@ test('every registered command ships a Claude commands/*.toml', () => { } }); -test('every registered command ships an OpenCode .opencode/command/*.md', () => { +test('every registered command ships an OpenCode .opencode/commands/*.md', () => { for (const name of commands) { assert.ok( - fs.existsSync(path.join(root, '.opencode', 'command', `${name}.md`)), - `missing .opencode/command/${name}.md`, + fs.existsSync(path.join(root, '.opencode', 'commands', `${name}.md`)), + `missing .opencode/commands/${name}.md`, ); } });