Skip to content

Show plugin-provided skills in the slash picker and Skills panel - #228

Open
vladkopzon wants to merge 1 commit into
andrepimenta:mainfrom
vladkopzon:show-plugin-provided-skills
Open

vladkopzon wants to merge 1 commit into
andrepimenta:mainfrom
vladkopzon:show-plugin-provided-skills

Conversation

@vladkopzon

Copy link
Copy Markdown

Problem

Skills and commands contributed by plugins never appear in the UI — not in the slash picker, not in the Skills panel — even though they are loaded and callable by the model.

This is most visible with plugins supplied on the command line via --plugin-dir (wrappers that inject a toolkit this way lose their entire command set), but it applies to any plugin-provided skill, since those live under the plugin's own directory rather than ~/.claude/skills.

Root cause

The CLI already tells us everything we need. Its system/init message carries:

{
  "type": "system", "subtype": "init",
  "slash_commands": ["hsd:hsd", "myplugin:setup", "dataviz", ...],
  "skills":         ["hsd:hsd", "myplugin:setup", ...],
  "plugins":        [{ "name": "myplugin", "path": "/path/to/plugin", "source": "..." }],
  ...
}

_processJsonStreamData parses that message but forwards only session_id, tools, and mcp_servers into sessionInfo. The remaining fields are dropped.

The UI is then left with _loadSkills, which only scans ~/.claude/skills and <workspace>/.claude/skills. Plugin skills are in neither, so they are invisible.

Changes

  • Capture slash_commands, skills, and plugins from system/init, cache them in globalState, and include them in the sessionInfo message.
  • Replay the cache in _sendCustomSnippets, which the webview calls on load. Without this the picker stays empty until the first message of a session, since system/init hasn't arrived yet.
  • Merge plugin skills into _loadSkills, reading each SKILL.md from its plugin directory for the description and body.
  • Render plugin commands in the picker, reusing the existing .slash-command-item markup so filterSlashCommands() works unchanged.
  • Hide "Delete" on plugin skills — they aren't managed by this extension, and deleteSkill would target the wrong directory.

Skill ids without a plugin:skill prefix are treated as built-ins and skipped, so nothing already listed is duplicated. Everything is defensive: if the fields are absent, behaviour is exactly as before.

There is a second sessionInfo emitter in case 'result'. It carries no slash_commands, and the webview hook is guarded by if (message.data.slashCommands), so it skips rather than clearing the rendered list.

Testing

  • tsc -p ./ — clean, 0 errors
  • eslint src — 25 warnings, byte-identical to the pre-change baseline; this PR adds none
  • Generated webview script parses under vm.Script (the injected code lives inside the getScript template literal, so it deliberately avoids backticks and ${})
  • Renderer unit-tested against a DOM stub: given ['dataviz','myplugin:setup','hsd:hsd','loop','eqvcheck:eqvcheck',null,42] it emits exactly the three plugin:skill entries, sorted and HTML-escaped, with built-ins and non-strings filtered out
  • Verified end to end against a real install with six plugins injected via --plugin-dir: all six appear in the picker and the Skills panel, click inserts /plugin:skill into the input, and search filters them correctly

The CLI's system/init message already reports every loaded plugin in
`slash_commands`, `skills`, and `plugins`, including plugins supplied on
the command line with `--plugin-dir`. `_processJsonStreamData` parsed
that message but forwarded only `session_id`, `tools`, and `mcp_servers`
to the webview, so the rest was dropped.

As a result the slash picker and the Skills panel showed only what
`_loadSkills` could find by scanning `~/.claude/skills` and
`<workspace>/.claude/skills`. Plugin skills live under their own plugin
directory and were therefore invisible in the UI, even though they were
loaded and callable by the model. Wrappers that inject plugins via
`--plugin-dir` lost their whole command set this way.

Changes:

- Capture `slash_commands`, `skills`, and `plugins` from system/init,
  cache them in globalState, and include them in the sessionInfo message.
- Replay the cache in `_sendCustomSnippets`, which the webview calls on
  load, so the picker is populated before the first message of a session.
- Merge plugin-provided skills into `_loadSkills`, reading each SKILL.md
  from its plugin directory for the description and body.
- Render plugin commands in the picker reusing the existing
  `.slash-command-item` markup, so the search filter works unchanged.
- Hide "Delete" on plugin skills; they are not managed by this extension
  and deleting them would target the wrong directory.

Skill ids without a `plugin:skill` prefix are treated as built-ins and
skipped, so nothing already listed is duplicated.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant