fix(opencode): generate slash-command stubs for each skill#776
fix(opencode): generate slash-command stubs for each skill#776lucabattistini wants to merge 7 commits intoEveryInc:mainfrom
Conversation
Each skill installed to OpenCode now also writes a corresponding slash-command file under `~/.config/opencode/commands/<name>.md`. This lets users invoke /ce-work, /ce-plan, etc. directly in OpenCode, mirroring the experience Claude Code users have via native skill loading. Skills with `disable-model-invocation: true` are excluded, and platform-filtered skills (e.g. claude-only) never reach the converter. The `argument-hint` frontmatter field is forwarded from the skill when present.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 74a613f92a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
… skill stubs If a plugin ships both a commands/ entry and a same-named skill, the explicit command body previously could be silently overwritten by the generic skill stub. Now the skill stub is filtered out whenever an explicit command with the same name already exists. Addresses review feedback from PR EveryInc#776.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 785ffb210b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…form collisions Deduplication was comparing raw name strings, so an explicit command 'foo:bar' would not block a skill stub named 'foo/bar' — both resolve to commands/foo/bar.md on disk, causing the stub to silently overwrite the explicit command. Fix: extract commandNameToRelativePath() into src/utils/files.ts and use it in both the converter's dedup set and the writer's path resolution, so the same normalized key is used everywhere. Addresses second review comment on PR EveryInc#776.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6bba41d382
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…pending Two skills whose names normalize to the same command path (e.g. 'foo:bar' and 'foo/bar') would both write to commands/foo/bar.md, silently overwriting the first. Deduplicate convertSkillsToCommands output by commandNameToRelativePath before appending, so only the first occurrence is kept.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8bb5330972
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…mmand stubs Skills marked user-invocable: false (e.g. ce-session-inventory, ce-session-extract) are agent-only primitives and must not be exposed as user-facing slash commands. Parse the user-invocable frontmatter field into ClaudeSkill and filter these skills out in convertSkillsToCommands alongside the existing disable-model-invocation check.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 65e623ec27
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…der from-commands applyPermissions only scanned plugin.commands for allowedTools, so a skill-only plugin (or any plugin whose commands don't list 'skill') would produce permission.skill = "deny", immediately breaking every generated stub. When hasSkillStubs is true, unconditionally add 'skill' to the enabled set in from-commands mode.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 031b2ad735
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…r from-commands
When a command uses skill(foo-*), patterns["skill"] is populated and the
permission build emits { "*": "deny", "foo-*": "allow" }, blocking all
stubs for skills outside the pattern even though enabled.add("skill") was
set. Fix: delete patterns["skill"] when hasSkillStubs is true so the build
emits a flat "allow" for the skill tool, allowing all generated stubs to
execute. Also fix stray leading space on ClaudeSkill type declaration.
|
Does this handle the cleanup properly when skills change (renamed or deleted)? |
|
@tmchow Yes — cleanup rides the existing managed-install-manifest plumbing.
So:
Test coverage for this contract already exists at One small caveat (pre-existing, not introduced here): |
Problem
When installing the compound-engineering plugin to OpenCode, zero slash commands were written to
~/.config/opencode/commands/. TheconvertClaudeToOpenCodefunction only consumedplugin.commands(acommands/directory), but the CE plugin ships only skills — so users had no/ce-work,/ce-plan, etc. available in OpenCode, even though all 54 skills were correctly installed.Solution
Added
convertSkillsToCommands(skills: ClaudeSkill[]): OpenCodeCommandFile[]that generates one slash-command stub per skill. Each stub:descriptionas frontmatterargument-hintwhen present$ARGUMENTSpassed throughSkills with
disable-model-invocation: trueare excluded. Platform-filtered skills (e.g.ce_platforms: [claude]) never reach the function.The generated stubs are merged with any
convertCommands()output inconvertClaudeToOpenCode, so plugins that have both acommands/directory and skills get both.Tests
commandFiles.length > 0and≤ skillDirs.lengthskill-onegenerates a command with correct frontmatter, and thatdisabled-skill/claude-only-skillare absent fromcommandFiles