Files
oh-my-opencode/src/features/claude-code-plugin-loader/command-loader.ts
T
Biem a26117d944 fix: resolve ${CLAUDE_PLUGIN_ROOT} in plugin commands, agents, and skills
When loading Claude Code plugins, the hooks JSON config already had
${CLAUDE_PLUGIN_ROOT} replaced via resolvePluginPath. However, command
markdown bodies, agent prompts, and skill content were passed through
as-is, leaving the variable unresolved. This caused plugin commands
(e.g. codex:review) to fail when executed in OpenCode because the shell
had no CLAUDE_PLUGIN_ROOT environment variable set.

Apply resolvePluginPath to:
- command-loader.ts: command body before wrapping in template
- agent-loader.ts: agent prompt body
- skill-loader.ts: skill body after @path resolution
2026-05-04 19:47:08 +08:00

56 lines
2.2 KiB
TypeScript

import { existsSync, readdirSync, readFileSync } from "fs"
import { basename, join } from "path"
import { parseFrontmatter } from "../../shared/frontmatter"
import { isMarkdownFile } from "../../shared/file-utils"
import { sanitizeModelField } from "../../shared/model-sanitizer"
import { resolvePluginPath } from "./plugin-path-resolver"
import { log } from "../../shared/logger"
import type { CommandDefinition, CommandFrontmatter } from "../claude-code-command-loader/types"
import type { LoadedPlugin } from "./types"
export function loadPluginCommands(plugins: LoadedPlugin[]): Record<string, CommandDefinition> {
const commands: Record<string, CommandDefinition> = {}
for (const plugin of plugins) {
if (!plugin.commandsDir || !existsSync(plugin.commandsDir)) continue
const entries = readdirSync(plugin.commandsDir, { withFileTypes: true })
for (const entry of entries) {
if (!isMarkdownFile(entry)) continue
const commandPath = join(plugin.commandsDir, entry.name)
const commandName = basename(entry.name, ".md")
const namespacedName = `${plugin.name}:${commandName}`
try {
const content = readFileSync(commandPath, "utf-8")
const { data, body } = parseFrontmatter<CommandFrontmatter>(content)
const resolvedBody = resolvePluginPath(body, plugin.installPath)
const wrappedTemplate = `<command-instruction>\n${resolvedBody.trim()}\n</command-instruction>\n\n<user-request>\n$ARGUMENTS\n</user-request>`
const formattedDescription = `(plugin: ${plugin.name}) ${data.description || ""}`
const definition = {
name: namespacedName,
description: formattedDescription,
template: wrappedTemplate,
agent: data.agent,
model: sanitizeModelField(data.model, "claude-code"),
subtask: data.subtask,
argumentHint: data["argument-hint"],
}
const { name: _name, argumentHint: _argumentHint, ...openCodeCompatible } = definition
commands[namespacedName] = openCodeCompatible as CommandDefinition
log(`Loaded plugin command: ${namespacedName}`, { path: commandPath })
} catch (error) {
log(`Failed to load plugin command: ${commandPath}`, error)
}
}
}
return commands
}