a26117d944
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
56 lines
2.2 KiB
TypeScript
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
|
|
}
|