diff --git a/src/create-hooks.ts b/src/create-hooks.ts
index 9972551e8..121b0f53e 100644
--- a/src/create-hooks.ts
+++ b/src/create-hooks.ts
@@ -51,6 +51,7 @@ export function createHooks(args: {
const skill = createSkillHooks({
ctx,
+ pluginConfig,
isHookEnabled,
safeHookEnabled,
mergedSkills,
diff --git a/src/hooks/auto-slash-command/constants.ts b/src/hooks/auto-slash-command/constants.ts
index de2a49a7a..a8bdac19e 100644
--- a/src/hooks/auto-slash-command/constants.ts
+++ b/src/hooks/auto-slash-command/constants.ts
@@ -3,7 +3,7 @@ export const HOOK_NAME = "auto-slash-command" as const
export const AUTO_SLASH_COMMAND_TAG_OPEN = ""
export const AUTO_SLASH_COMMAND_TAG_CLOSE = ""
-export const SLASH_COMMAND_PATTERN = /^\/([a-zA-Z][\w-]*)\s*(.*)/
+export const SLASH_COMMAND_PATTERN = /^\/([a-zA-Z@][\w:@/-]*)\s*(.*)/
export const EXCLUDED_COMMANDS = new Set([
"ralph-loop",
diff --git a/src/hooks/auto-slash-command/detector.test.ts b/src/hooks/auto-slash-command/detector.test.ts
index ce87c2d9c..36eb8bc6d 100644
--- a/src/hooks/auto-slash-command/detector.test.ts
+++ b/src/hooks/auto-slash-command/detector.test.ts
@@ -102,6 +102,19 @@ After`
expect(result?.args).toBe("project")
})
+ it("should parse namespaced marketplace commands", () => {
+ // given a namespaced command
+ const text = "/daplug:run-prompt build bridge"
+
+ // when parsing
+ const result = parseSlashCommand(text)
+
+ // then should keep full namespaced command
+ expect(result).not.toBeNull()
+ expect(result?.command).toBe("daplug:run-prompt")
+ expect(result?.args).toBe("build bridge")
+ })
+
it("should return null for non-slash text", () => {
// given text without slash
const text = "regular text"
diff --git a/src/hooks/auto-slash-command/executor.test.ts b/src/hooks/auto-slash-command/executor.test.ts
new file mode 100644
index 000000000..979215fae
--- /dev/null
+++ b/src/hooks/auto-slash-command/executor.test.ts
@@ -0,0 +1,168 @@
+import { afterEach, beforeEach, describe, expect, it } from "bun:test"
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
+import { tmpdir } from "node:os"
+import { join } from "node:path"
+import { executeSlashCommand } from "./executor"
+
+const ENV_KEYS = [
+ "CLAUDE_CONFIG_DIR",
+ "CLAUDE_PLUGINS_HOME",
+ "CLAUDE_SETTINGS_PATH",
+ "OPENCODE_CONFIG_DIR",
+] as const
+
+type EnvKey = (typeof ENV_KEYS)[number]
+type EnvSnapshot = Record
+
+function writePluginFixture(baseDir: string): void {
+ const claudeConfigDir = join(baseDir, "claude-config")
+ const pluginsHome = join(claudeConfigDir, "plugins")
+ const settingsPath = join(claudeConfigDir, "settings.json")
+ const opencodeConfigDir = join(baseDir, "opencode-config")
+ const pluginInstallPath = join(baseDir, "installed-plugins", "daplug")
+ const pluginKey = "daplug@1.0.0"
+
+ mkdirSync(join(pluginInstallPath, ".claude-plugin"), { recursive: true })
+ mkdirSync(join(pluginInstallPath, "commands"), { recursive: true })
+
+ writeFileSync(
+ join(pluginInstallPath, ".claude-plugin", "plugin.json"),
+ JSON.stringify({ name: "daplug", version: "1.0.0" }, null, 2),
+ )
+ writeFileSync(
+ join(pluginInstallPath, "commands", "run-prompt.md"),
+ `---
+description: Run prompt from daplug
+---
+Execute daplug prompt flow.
+`,
+ )
+
+ mkdirSync(pluginsHome, { recursive: true })
+ writeFileSync(
+ join(pluginsHome, "installed_plugins.json"),
+ JSON.stringify(
+ {
+ version: 2,
+ plugins: {
+ [pluginKey]: [
+ {
+ scope: "user",
+ installPath: pluginInstallPath,
+ version: "1.0.0",
+ installedAt: "2026-01-01T00:00:00.000Z",
+ lastUpdated: "2026-01-01T00:00:00.000Z",
+ },
+ ],
+ },
+ },
+ null,
+ 2,
+ ),
+ )
+
+ mkdirSync(claudeConfigDir, { recursive: true })
+ writeFileSync(
+ settingsPath,
+ JSON.stringify(
+ {
+ enabledPlugins: {
+ [pluginKey]: true,
+ },
+ },
+ null,
+ 2,
+ ),
+ )
+ mkdirSync(opencodeConfigDir, { recursive: true })
+
+ process.env.CLAUDE_CONFIG_DIR = claudeConfigDir
+ process.env.CLAUDE_PLUGINS_HOME = pluginsHome
+ process.env.CLAUDE_SETTINGS_PATH = settingsPath
+ process.env.OPENCODE_CONFIG_DIR = opencodeConfigDir
+}
+
+describe("auto-slash command executor plugin dispatch", () => {
+ let tempDir = ""
+ let envSnapshot: EnvSnapshot
+
+ beforeEach(() => {
+ tempDir = mkdtempSync(join(tmpdir(), "omo-executor-plugin-test-"))
+ envSnapshot = {
+ CLAUDE_CONFIG_DIR: process.env.CLAUDE_CONFIG_DIR,
+ CLAUDE_PLUGINS_HOME: process.env.CLAUDE_PLUGINS_HOME,
+ CLAUDE_SETTINGS_PATH: process.env.CLAUDE_SETTINGS_PATH,
+ OPENCODE_CONFIG_DIR: process.env.OPENCODE_CONFIG_DIR,
+ }
+ writePluginFixture(tempDir)
+ })
+
+ afterEach(() => {
+ for (const key of ENV_KEYS) {
+ const previousValue = envSnapshot[key]
+ if (previousValue === undefined) {
+ delete process.env[key]
+ } else {
+ process.env[key] = previousValue
+ }
+ }
+ rmSync(tempDir, { recursive: true, force: true })
+ })
+
+ it("resolves marketplace plugin commands when plugin loading is enabled", async () => {
+ const result = await executeSlashCommand(
+ {
+ command: "daplug:run-prompt",
+ args: "ship it",
+ raw: "/daplug:run-prompt ship it",
+ },
+ {
+ skills: [],
+ pluginsEnabled: true,
+ },
+ )
+
+ expect(result.success).toBe(true)
+ expect(result.replacementText).toContain("# /daplug:run-prompt Command")
+ expect(result.replacementText).toContain("**Scope**: plugin")
+ })
+
+ it("excludes marketplace commands when plugins are disabled via config toggle", async () => {
+ const result = await executeSlashCommand(
+ {
+ command: "daplug:run-prompt",
+ args: "",
+ raw: "/daplug:run-prompt",
+ },
+ {
+ skills: [],
+ pluginsEnabled: false,
+ },
+ )
+
+ expect(result.success).toBe(false)
+ expect(result.error).toBe(
+ 'Command "/daplug:run-prompt" not found. Use the skill tool to list available skills and commands.',
+ )
+ })
+
+ it("returns standard not-found for unknown namespaced commands", async () => {
+ const result = await executeSlashCommand(
+ {
+ command: "daplug:missing",
+ args: "",
+ raw: "/daplug:missing",
+ },
+ {
+ skills: [],
+ pluginsEnabled: true,
+ },
+ )
+
+ expect(result.success).toBe(false)
+ expect(result.error).toBe(
+ 'Command "/daplug:missing" not found. Use the skill tool to list available skills and commands.',
+ )
+ expect(result.error).not.toContain("Marketplace plugin commands")
+ })
+})
diff --git a/src/hooks/auto-slash-command/executor.ts b/src/hooks/auto-slash-command/executor.ts
index ffa96be8b..f7c906e20 100644
--- a/src/hooks/auto-slash-command/executor.ts
+++ b/src/hooks/auto-slash-command/executor.ts
@@ -12,10 +12,15 @@ import { loadBuiltinCommands } from "../../features/builtin-commands"
import type { CommandFrontmatter } from "../../features/claude-code-command-loader/types"
import { isMarkdownFile } from "../../shared/file-utils"
import { discoverAllSkills, type LoadedSkill, type LazyContentLoader } from "../../features/opencode-skill-loader"
+import {
+ discoverInstalledPlugins,
+ loadPluginCommands,
+ loadPluginSkillsAsCommands,
+} from "../../features/claude-code-plugin-loader"
import type { ParsedSlashCommand } from "./types"
interface CommandScope {
- type: "user" | "project" | "opencode" | "opencode-project" | "skill" | "builtin"
+ type: "user" | "project" | "opencode" | "opencode-project" | "skill" | "builtin" | "plugin"
}
interface CommandMetadata {
@@ -99,6 +104,36 @@ function skillToCommandInfo(skill: LoadedSkill): CommandInfo {
export interface ExecutorOptions {
skills?: LoadedSkill[]
+ pluginsEnabled?: boolean
+ enabledPluginsOverride?: Record
+}
+
+function discoverPluginCommands(options?: ExecutorOptions): 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",
+ }))
}
async function discoverAllCommands(options?: ExecutorOptions): Promise {
@@ -128,6 +163,7 @@ async function discoverAllCommands(options?: ExecutorOptions): Promise()
export interface AutoSlashCommandHookOptions {
skills?: LoadedSkill[]
+ pluginsEnabled?: boolean
+ enabledPluginsOverride?: Record
}
export function createAutoSlashCommandHook(options?: AutoSlashCommandHookOptions) {
const executorOptions: ExecutorOptions = {
skills: options?.skills,
+ pluginsEnabled: options?.pluginsEnabled,
+ enabledPluginsOverride: options?.enabledPluginsOverride,
}
return {
diff --git a/src/plugin/hooks/create-skill-hooks.ts b/src/plugin/hooks/create-skill-hooks.ts
index 043a0bbbb..b0514d583 100644
--- a/src/plugin/hooks/create-skill-hooks.ts
+++ b/src/plugin/hooks/create-skill-hooks.ts
@@ -1,5 +1,5 @@
import type { AvailableSkill } from "../../agents/dynamic-agent-prompt-builder"
-import type { HookName } from "../../config"
+import type { HookName, OhMyOpenCodeConfig } from "../../config"
import type { LoadedSkill } from "../../features/opencode-skill-loader/types"
import type { PluginContext } from "../types"
@@ -13,12 +13,20 @@ export type SkillHooks = {
export function createSkillHooks(args: {
ctx: PluginContext
+ pluginConfig: OhMyOpenCodeConfig
isHookEnabled: (hookName: HookName) => boolean
safeHookEnabled: boolean
mergedSkills: LoadedSkill[]
availableSkills: AvailableSkill[]
}): SkillHooks {
- const { ctx, isHookEnabled, safeHookEnabled, mergedSkills, availableSkills } = args
+ const {
+ ctx,
+ pluginConfig,
+ isHookEnabled,
+ safeHookEnabled,
+ mergedSkills,
+ availableSkills,
+ } = args
const safeHook = (hookName: HookName, factory: () => T): T | null =>
safeCreateHook(hookName, factory, { enabled: safeHookEnabled })
@@ -30,7 +38,11 @@ export function createSkillHooks(args: {
const autoSlashCommand = isHookEnabled("auto-slash-command")
? safeHook("auto-slash-command", () =>
- createAutoSlashCommandHook({ skills: mergedSkills }))
+ createAutoSlashCommandHook({
+ skills: mergedSkills,
+ pluginsEnabled: pluginConfig.claude_code?.plugins ?? true,
+ enabledPluginsOverride: pluginConfig.claude_code?.plugins_override,
+ }))
: null
return { categorySkillReminder, autoSlashCommand }
diff --git a/src/plugin/tool-registry.ts b/src/plugin/tool-registry.ts
index aaf20b6d5..3b441c197 100644
--- a/src/plugin/tool-registry.ts
+++ b/src/plugin/tool-registry.ts
@@ -95,7 +95,10 @@ export function createToolRegistry(args: {
getSessionID: getSessionIDForMcp,
})
- const commands = discoverCommandsSync(ctx.directory)
+ const commands = discoverCommandsSync(ctx.directory, {
+ pluginsEnabled: pluginConfig.claude_code?.plugins ?? true,
+ enabledPluginsOverride: pluginConfig.claude_code?.plugins_override,
+ })
const skillTool = createSkillTool({
commands,
skills: skillContext.mergedSkills,
diff --git a/src/tools/skill/tools.test.ts b/src/tools/skill/tools.test.ts
index d4f2d01f6..e64a20fb4 100644
--- a/src/tools/skill/tools.test.ts
+++ b/src/tools/skill/tools.test.ts
@@ -464,7 +464,7 @@ describe("skill tool - ordering and priority", () => {
const tool = createSkillTool({ skills, commands })
//#then: should include priority info
- expect(tool.description).toContain("Priority: project > user > opencode > builtin")
+ expect(tool.description).toContain("Priority: project > user > opencode > builtin/plugin")
expect(tool.description).toContain("Skills listed before commands")
})
diff --git a/src/tools/skill/tools.ts b/src/tools/skill/tools.ts
index 044776909..4bdfb42a0 100644
--- a/src/tools/skill/tools.ts
+++ b/src/tools/skill/tools.ts
@@ -16,6 +16,7 @@ const scopePriority: Record = {
user: 3,
opencode: 2,
"opencode-project": 2,
+ plugin: 1,
config: 1,
builtin: 1,
}
@@ -89,7 +90,7 @@ function formatCombinedDescription(skills: SkillInfo[], commands: CommandInfo[])
}
if (allItems.length > 0) {
- lines.push(`\n\nPriority: project > user > opencode > builtin | Skills listed before commands\nInvoke via: skill(name="item-name") — omit leading slash for commands.\n${allItems.join("\n")}\n`)
+ lines.push(`\n\nPriority: project > user > opencode > builtin/plugin | Skills listed before commands\nInvoke via: skill(name="item-name") — omit leading slash for commands.\n${allItems.join("\n")}\n`)
}
return TOOL_DESCRIPTION_PREFIX + lines.join("")
@@ -195,7 +196,10 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition
const getCommands = (): CommandInfo[] => {
if (cachedCommands) return cachedCommands
- cachedCommands = discoverCommandsSync()
+ cachedCommands = discoverCommandsSync(undefined, {
+ pluginsEnabled: options.pluginsEnabled,
+ enabledPluginsOverride: options.enabledPluginsOverride,
+ })
return cachedCommands
}
diff --git a/src/tools/skill/types.ts b/src/tools/skill/types.ts
index 4fd48d6c7..579eb69cc 100644
--- a/src/tools/skill/types.ts
+++ b/src/tools/skill/types.ts
@@ -33,4 +33,8 @@ export interface SkillLoadOptions {
/** Git master configuration for watermark/co-author settings */
gitMasterConfig?: GitMasterConfig
disabledSkills?: Set
+ /** Include Claude marketplace plugin commands in discovery (default: true) */
+ pluginsEnabled?: boolean
+ /** Override plugin enablement from Claude settings by plugin key */
+ enabledPluginsOverride?: Record
}
diff --git a/src/tools/slashcommand/command-discovery.test.ts b/src/tools/slashcommand/command-discovery.test.ts
new file mode 100644
index 000000000..05e49ab3d
--- /dev/null
+++ b/src/tools/slashcommand/command-discovery.test.ts
@@ -0,0 +1,160 @@
+import { afterEach, beforeEach, describe, expect, it } from "bun:test"
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
+import { tmpdir } from "node:os"
+import { join } from "node:path"
+import { discoverCommandsSync } from "./command-discovery"
+
+const ENV_KEYS = [
+ "CLAUDE_CONFIG_DIR",
+ "CLAUDE_PLUGINS_HOME",
+ "CLAUDE_SETTINGS_PATH",
+ "OPENCODE_CONFIG_DIR",
+] as const
+
+type EnvKey = (typeof ENV_KEYS)[number]
+type EnvSnapshot = Record
+
+function writePluginFixture(baseDir: string): { projectDir: string } {
+ const projectDir = join(baseDir, "project")
+ const claudeConfigDir = join(baseDir, "claude-config")
+ const pluginsHome = join(claudeConfigDir, "plugins")
+ const settingsPath = join(claudeConfigDir, "settings.json")
+ const opencodeConfigDir = join(baseDir, "opencode-config")
+ const pluginInstallPath = join(baseDir, "installed-plugins", "daplug")
+ const pluginKey = "daplug@1.0.0"
+
+ mkdirSync(projectDir, { recursive: true })
+ mkdirSync(join(pluginInstallPath, ".claude-plugin"), { recursive: true })
+ mkdirSync(join(pluginInstallPath, "commands"), { recursive: true })
+ mkdirSync(join(pluginInstallPath, "skills", "plugin-plan"), { recursive: true })
+
+ writeFileSync(
+ join(pluginInstallPath, ".claude-plugin", "plugin.json"),
+ JSON.stringify({ name: "daplug", version: "1.0.0" }, null, 2),
+ )
+ writeFileSync(
+ join(pluginInstallPath, "commands", "run-prompt.md"),
+ `---
+description: Run prompt from daplug
+---
+Execute daplug prompt flow.
+`,
+ )
+ writeFileSync(
+ join(pluginInstallPath, "skills", "plugin-plan", "SKILL.md"),
+ `---
+name: plugin-plan
+description: Plan work from daplug skill
+---
+Build a plan from plugin skill context.
+`,
+ )
+
+ mkdirSync(pluginsHome, { recursive: true })
+ writeFileSync(
+ join(pluginsHome, "installed_plugins.json"),
+ JSON.stringify(
+ {
+ version: 2,
+ plugins: {
+ [pluginKey]: [
+ {
+ scope: "user",
+ installPath: pluginInstallPath,
+ version: "1.0.0",
+ installedAt: "2026-01-01T00:00:00.000Z",
+ lastUpdated: "2026-01-01T00:00:00.000Z",
+ },
+ ],
+ },
+ },
+ null,
+ 2,
+ ),
+ )
+
+ mkdirSync(claudeConfigDir, { recursive: true })
+ writeFileSync(
+ settingsPath,
+ JSON.stringify(
+ {
+ enabledPlugins: {
+ [pluginKey]: true,
+ },
+ },
+ null,
+ 2,
+ ),
+ )
+ mkdirSync(opencodeConfigDir, { recursive: true })
+
+ process.env.CLAUDE_CONFIG_DIR = claudeConfigDir
+ process.env.CLAUDE_PLUGINS_HOME = pluginsHome
+ process.env.CLAUDE_SETTINGS_PATH = settingsPath
+ process.env.OPENCODE_CONFIG_DIR = opencodeConfigDir
+
+ return { projectDir }
+}
+
+describe("slashcommand command discovery plugin integration", () => {
+ let tempDir = ""
+ let projectDir = ""
+ let envSnapshot: EnvSnapshot
+
+ beforeEach(() => {
+ tempDir = mkdtempSync(join(tmpdir(), "omo-command-discovery-test-"))
+ envSnapshot = {
+ CLAUDE_CONFIG_DIR: process.env.CLAUDE_CONFIG_DIR,
+ CLAUDE_PLUGINS_HOME: process.env.CLAUDE_PLUGINS_HOME,
+ CLAUDE_SETTINGS_PATH: process.env.CLAUDE_SETTINGS_PATH,
+ OPENCODE_CONFIG_DIR: process.env.OPENCODE_CONFIG_DIR,
+ }
+ const setup = writePluginFixture(tempDir)
+ projectDir = setup.projectDir
+ })
+
+ afterEach(() => {
+ for (const key of ENV_KEYS) {
+ const previousValue = envSnapshot[key]
+ if (previousValue === undefined) {
+ delete process.env[key]
+ } else {
+ process.env[key] = previousValue
+ }
+ }
+ rmSync(tempDir, { recursive: true, force: true })
+ })
+
+ it("discovers marketplace plugin commands and skills as command items", () => {
+ const commands = discoverCommandsSync(projectDir, { pluginsEnabled: true })
+ const names = commands.map(command => command.name)
+
+ expect(names).toContain("daplug:run-prompt")
+ expect(names).toContain("daplug:plugin-plan")
+
+ const pluginCommand = commands.find(command => command.name === "daplug:run-prompt")
+ const pluginSkill = commands.find(command => command.name === "daplug:plugin-plan")
+
+ expect(pluginCommand?.scope).toBe("plugin")
+ expect(pluginSkill?.scope).toBe("plugin")
+ })
+
+ it("omits marketplace plugin commands when plugins are disabled", () => {
+ const commands = discoverCommandsSync(projectDir, { pluginsEnabled: false })
+ const names = commands.map(command => command.name)
+
+ expect(names).not.toContain("daplug:run-prompt")
+ expect(names).not.toContain("daplug:plugin-plan")
+ })
+
+ it("honors plugins_override by disabling overridden plugin keys", () => {
+ const commands = discoverCommandsSync(projectDir, {
+ pluginsEnabled: true,
+ enabledPluginsOverride: { "daplug@1.0.0": false },
+ })
+ const names = commands.map(command => command.name)
+
+ expect(names).not.toContain("daplug:run-prompt")
+ expect(names).not.toContain("daplug:plugin-plan")
+ })
+})
diff --git a/src/tools/slashcommand/command-discovery.ts b/src/tools/slashcommand/command-discovery.ts
index d06990036..57182f020 100644
--- a/src/tools/slashcommand/command-discovery.ts
+++ b/src/tools/slashcommand/command-discovery.ts
@@ -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
+}
+
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,
]
}
diff --git a/src/tools/slashcommand/types.ts b/src/tools/slashcommand/types.ts
index 090e12178..af3935ef3 100644
--- a/src/tools/slashcommand/types.ts
+++ b/src/tools/slashcommand/types.ts
@@ -1,6 +1,6 @@
import type { LazyContentLoader } from "../../features/opencode-skill-loader"
-export type CommandScope = "builtin" | "config" | "user" | "project" | "opencode" | "opencode-project"
+export type CommandScope = "builtin" | "config" | "user" | "project" | "opencode" | "opencode-project" | "plugin"
export interface CommandMetadata {
name: string