这是indexloc提供的服务,不要输入任何密码
Skip to content

Conversation

@crdant
Copy link
Contributor

@crdant crdant commented Nov 5, 2025

What problem(s) was I solving?

Problem: CodeLayer users cannot see or use slash commands from Claude Code marketplace plugins in the CodeLayer UI.

Claude Code marketplace plugins install their commands to a different location (~/.config/claude-code/plugins/{plugin-name}/commands/), and their metadata is stored in separate JSON files (installed_plugins.json and settings.json). The daemon had no logic to discover these plugin commands.

Impact: Users installing plugins like "Superpowers" or other marketplace plugins would not see those commands available in CodeLayer, creating a disconnected experience between Claude Code and CodeLayer.

What user-facing changes did I ship?

CodeLayer will now display slash commands from installed Claude Code plugins:

  • Plugin commands appear in the slash command list with source: "plugin"
  • Commands are namespaced as /plugin-name:command-name (e.g., /superpowers:brainstorm)
  • Only enabled plugins' commands appear (respects user's Claude Code settings)
  • Nested plugin commands use colon separators (e.g., /plugin-name:config:show)
  • Fuzzy search works across plugin commands
  • Commands seamlessly integrate with existing local/global commands

No breaking changes - existing local and global commands work exactly as before.

How I implemented it

Architecture Decision: Isolated Plugin Discovery

I chose to encapsulate all plugin-specific logic in isolated functions to make the plugin system maintainable and adaptable to future changes by Anthropic:

New File: hld/api/handlers/plugins.go (122 lines)

  • discoverPluginCommands(configDir string) - Main discovery orchestration
  • scanPluginCommandsDir(dir, pluginName string) - Scans individual plugin commands
  • Type definitions for plugin metadata structures

Implementation Details

1. OpenAPI Schema Extension (hld/api/openapi.yaml)

  • Added "plugin" to the SlashCommand.source enum
  • Regenerated Go types → SlashCommandSourcePlugin constant

2. Plugin Discovery Logic (hld/api/handlers/plugins.go)

Discovery Flow:
1. Read $CLAUDE_CONFIG_DIR/plugins/installed_plugins.json
   - Contains plugin metadata: version, install path, git commit
2. Read $CLAUDE_CONFIG_DIR/settings.json
   - Contains enabledPlugins map (plugin-id → true/false)
3. For each enabled plugin:
   - Extract plugin name from "plugin-name@marketplace" ID
   - Scan {installPath}/commands/ for .md files
   - Create namespaced command: /plugin-name:command-name
4. Return list of plugin commands with source="plugin"

if CLAUDE_CONFIG_DIR is discovery will use ~/.claude

3. Error Handling (Graceful Degradation)

  • Missing installed_plugins.json → return empty list (no plugins installed)
  • Missing settings.json → treat all plugins as enabled (default behavior)
  • Malformed JSON → log warning, return empty list
  • Missing plugin commands directory → skip that plugin
  • All errors logged but don't break the entire API request

4. Integration (hld/api/handlers/sessions.go)

  • Added plugin discovery after global command discovery (line 1699-1721)
  • Plugin commands merged into same deduplication map as local/global
  • Maintains existing precedence: local → global → plugin (no conflicts due to namespacing)

5. Comprehensive Testing

Unit Tests (hld/api/handlers/plugins_test.go - 12 tests):

  • No installed plugins file
  • Malformed installed_plugins.json
  • No settings file (default enabled behavior)
  • Disabled plugin filtering
  • Enabled plugin discovery
  • Multiple plugins
  • Non-existent directories
  • Empty directories
  • Single command
  • Nested commands (colon separator)
  • Non-markdown file filtering
  • Multiple commands per plugin

Integration Tests (hld/daemon/daemon_slash_commands_integration_test.go):

  • TestPluginCommandsIntegration with 3 subtests:
    • Enabled/disabled plugin filtering via settings.json
    • Default behavior without settings.json (all enabled)
    • Graceful handling of missing installed_plugins.json

Key Design Decisions

1. Namespace Format: /plugin-name:command

  • Plugin name is in the command name itself
  • Source field remains simple enum: "plugin" (not "plugin:name")
  • Easy to parse: command.name.split(':')[0].slice(1) gives plugin name

2. Config Directory Detection

  • Uses existing CLAUDE_CONFIG_DIR environment variable logic
  • Falls back to ~/.claude (macOS/Linux standard)
  • Leverages existing expandTilde() helper function

3. Isolation Pattern

  • All plugin logic in separate plugins.go file
  • Can be updated independently if Anthropic changes plugin format
  • No pollution of main session handler logic

How to verify it

  • I have ensured make check test passes

Verification Results:

✓ Format check passed
✓ Vet check passed
✓ Lint check passed
✓ Unit tests passed (424 tests)
✓ Unit tests with race detection passed (424 tests)
✓ Integration tests passed (50 tests)
✓ Integration tests with race detection passed (50 tests)

Test Coverage:

  • 12 new unit tests for plugin discovery (all passing)
  • 3 new integration test scenarios (all passing)
  • 0 failing tests
  • 0 lint issues

Manual Testing (optional but recommended):

With real Claude Code plugins installed:

# Start daemon
cd hld && make build && ./hld

# Query plugin commands
curl "http://127.0.0.1:5188/api/v1/slash-commands?working_dir=$(pwd)" | \
  jq '.data[] | select(.source == "plugin")'

# Test fuzzy search
curl "http://127.0.0.1:5188/api/v1/slash-commands?working_dir=$(pwd)&query=brain" | \
  jq '.data[] | select(.source == "plugin")'

Description for the changelog

Added: Claude Code marketplace plugin commands now appear in CodeLayer's slash command list. Plugin commands are discovered from ~/.config/claude-code/plugins/ and respect enabled/disabled state from Claude Code settings. Commands are namespaced as /plugin-name:command-name.

A picture of a cute animal (not mandatory but encouraged)

9A5FAC61-39FA-43EF-A96D-328D009BCA86_1_102_o

Technical Notes

Files Modified:

  • hld/api/openapi.yaml - Added "plugin" to source enum
  • hld/api/server.gen.go - Auto-generated constant
  • hld/api/handlers/sessions.go - Added plugin discovery call (24 lines)
  • hld/daemon/daemon_slash_commands_integration_test.go - Added integration test (274 lines)
  • hld/go.mod / hld/go.sum - Added test mock dependencies

Files Created:

  • hld/api/handlers/plugins.go - Plugin discovery implementation (122 lines)
  • hld/api/handlers/plugins_test.go - Comprehensive unit tests (250+ lines)

Implementation Plan: thoughts/shared/plans/2025-01-08-plugin-command-discovery.md
Research Document: docs/research/2025-11-04-codelayer-slash-command-loading.md

Total Changes: +635 lines, -156 lines across 6 modified + 2 new files


Important

Adds discovery and display of Claude Code marketplace plugin commands in CodeLayer UI, with new logic in plugins.go and comprehensive testing.

  • Behavior:
    • Adds discovery of slash commands from Claude Code marketplace plugins in hld/api/handlers/plugins.go.
    • Commands are namespaced as /plugin-name:command-name and appear with source: "plugin".
    • Only enabled plugins' commands are shown, respecting settings.json.
    • Supports nested commands with colon separators.
    • Integrates with existing local/global commands without breaking changes.
  • Implementation:
    • New file plugins.go for plugin command discovery logic.
    • Updates openapi.yaml to add "plugin" to SlashCommand.source enum.
    • Modifies sessions.go to include plugin command discovery.
  • Testing:
    • Adds unit tests in plugins_test.go for various scenarios including missing files and malformed JSON.
    • Adds integration tests in daemon_slash_commands_integration_test.go for plugin command discovery.
  • Misc:
    • Updates go.mod and go.sum for new dependencies.

This description was created by Ellipsis for c354296. You can customize this summary. It will automatically update as commits are pushed.

Add "plugin" as a third source type alongside "local" and "global"
in the SlashCommand schema. This enables the API to distinguish
commands from Claude Code marketplace plugins.
Add discovery of slash commands from Claude Code marketplace plugins.
Reads installed_plugins.json and settings.json to find enabled plugins
and scans their commands directories. Plugin commands are namespaced
as /plugin-name:command-name and respect enabled/disabled state.

Gracefully handles missing or malformed plugin metadata files.
Add 12 unit tests covering plugin discovery edge cases:
- Missing/malformed metadata files
- Enabled/disabled plugin filtering
- Nested commands and namespacing
- Multiple plugins

Add integration test with 3 scenarios validating end-to-end behavior.
@crdant crdant changed the title feat: Add Claude Code plugin command discovery Add Claude Code plugin command discovery Nov 5, 2025
Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Looks good to me! 👍

Reviewed everything up to c354296 in 1 minute and 46 seconds. Click for details.
  • Reviewed 1274 lines of code in 8 files
  • Skipped 0 files when reviewing.
  • Skipped posting 5 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. hld/api/server.gen.go:1
  • Draft comment:
    This file is auto-generated by oapi-codegen (v2.5.1); please avoid manual modifications. Ensure that the new constant for SlashCommandSourcePlugin is correctly integrated with the rest of the system.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
2. hld/daemon/daemon_slash_commands_integration_test.go:195
  • Draft comment:
    Prefer using the api.SlashCommandSourceLocal constant (and similarly for 'global') instead of literal strings ('local', 'global') in assertions for better maintainability and consistency.
  • Reason this comment was not posted:
    Comment was not on a location in the diff, so it can't be submitted as a review comment.
3. hld/daemon/daemon_slash_commands_integration_test.go:630
  • Draft comment:
    When removing the installed_plugins.json file (using os.Remove), consider checking and ignoring errors if the file does not exist. This can improve robustness in test cleanup.
  • Reason this comment was not posted:
    Comment looked like it was already resolved.
4. hld/go.mod:5
  • Draft comment:
    The go.mod file updates (including replace directives and dependency versions) appear intentional. Please double‐check that these dependency version bumps (e.g., for github.com/golang/mock) are expected.
  • Reason this comment was not posted:
    Comment was not on a location in the diff, so it can't be submitted as a review comment.
5. hld/go.sum:1
  • Draft comment:
    The go.sum file reflects updated dependency checksums. They seem consistent with the go.mod changes; no issues noted.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None

Workflow ID: wflow_btR2tdcTJ52P8wnY

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

@K-Mistele
Copy link
Contributor

Thanks for the PR @crdant !

Plugins are still considered to be in beta by anthropic right now so I am hesitant to merge something that depends on what the Claude code team considers an unstable API

Will discuss internally and let you know where we wind up on this

@crdant
Copy link
Contributor Author

crdant commented Nov 5, 2025

Plugins are still considered to be in beta by anthropic right now so I am hesitant to merge something that depends on what the Claude code team considers an unstable API

Makes a lot of sense.

This PR and my last one have a common theme. hld is in the loop for autocompleting slash commands, so as soon as I type / and don't see a command I expect I'm back to the terminal until I have time to create a PR, get it approved, and install a new release.

This whack-a-mole game isn't the best thing for me as a user or for your team as creators. Perhaps there's a better approach I can't see to stay aligned so the / makes the same recommendations either in or out of CodeLayer.

@dexhorthy
Copy link
Contributor

Appreciate both sides of this convo, and thanks chuck for the PR!

@K-Mistele is this something we could drop in an opt-in “experimental features” thing?

I would guess Claude will continue to launch beta features that ppl wanna use and we should weigh the tradeoffs of not supporting them

@crdant
Copy link
Contributor Author

crdant commented Nov 11, 2025

@K-Mistele @dexhorthy if the experimental feature model makes sense, I can imagine I wouldn't be too hard for me to wrap it and contribute the whole thing.

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.

3 participants