feat(dispatch): wire marketplace plugin commands into slash command dispatch

Connect the existing plugin loader infrastructure to both slash command
dispatch paths (executor and slashcommand tool), enabling namespaced
commands like /daplug:run-prompt to resolve and execute.

- Add plugin discovery to executor.ts discoverAllCommands()
- Add plugin discovery to command-discovery.ts discoverCommandsSync()
- Add "plugin" to CommandScope type
- Remove blanket colon-rejection error (replaced with standard not-found)
- Update slash command regex to accept namespaced commands
- Thread claude_code.plugins config toggle through dispatch chain
- Add unit tests for plugin command discovery and dispatch

Closes #2019

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
Gershom Rogers
2026-02-21 10:05:50 -05:00
parent 1c7eb55f9c
commit 0dee4377b8
14 changed files with 461 additions and 14 deletions
+44 -1
View File
@@ -5,8 +5,18 @@ import type { CommandFrontmatter } from "../../features/claude-code-command-load
import { isMarkdownFile } from "../../shared/file-utils"
import { getClaudeConfigDir } from "../../shared"
import { loadBuiltinCommands } from "../../features/builtin-commands"
import {
discoverInstalledPlugins,
loadPluginCommands,
loadPluginSkillsAsCommands,
} from "../../features/claude-code-plugin-loader"
import type { CommandInfo, CommandMetadata, CommandScope } from "./types"
export interface CommandDiscoveryOptions {
pluginsEnabled?: boolean
enabledPluginsOverride?: Record<string, boolean>
}
function discoverCommandsFromDir(commandsDir: string, scope: CommandScope): CommandInfo[] {
if (!existsSync(commandsDir)) return []
@@ -48,7 +58,38 @@ function discoverCommandsFromDir(commandsDir: string, scope: CommandScope): Comm
return commands
}
export function discoverCommandsSync(directory?: string): CommandInfo[] {
function discoverPluginCommands(options?: CommandDiscoveryOptions): CommandInfo[] {
if (options?.pluginsEnabled === false) {
return []
}
const { plugins } = discoverInstalledPlugins({
enabledPluginsOverride: options?.enabledPluginsOverride,
})
const pluginDefinitions = {
...loadPluginCommands(plugins),
...loadPluginSkillsAsCommands(plugins),
}
return Object.entries(pluginDefinitions).map(([name, definition]) => ({
name,
metadata: {
name,
description: definition.description || "",
model: definition.model,
agent: definition.agent,
subtask: definition.subtask,
},
content: definition.template,
scope: "plugin",
}))
}
export function discoverCommandsSync(
directory?: string,
options?: CommandDiscoveryOptions,
): CommandInfo[] {
const configDir = getOpenCodeConfigDir({ binary: "opencode" })
const userCommandsDir = join(getClaudeConfigDir(), "commands")
const projectCommandsDir = join(directory ?? process.cwd(), ".claude", "commands")
@@ -59,6 +100,7 @@ export function discoverCommandsSync(directory?: string): CommandInfo[] {
const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode")
const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project")
const opencodeProjectCommands = discoverCommandsFromDir(opencodeProjectDir, "opencode-project")
const pluginCommands = discoverPluginCommands(options)
const builtinCommandsMap = loadBuiltinCommands()
const builtinCommands: CommandInfo[] = Object.values(builtinCommandsMap).map((command) => ({
@@ -81,5 +123,6 @@ export function discoverCommandsSync(directory?: string): CommandInfo[] {
...opencodeProjectCommands,
...opencodeGlobalCommands,
...builtinCommands,
...pluginCommands,
]
}