fix(auto-slash-command): load skill content via lazyContentLoader and include builtin skills

This commit is contained in:
YeonGyu-Kim
2026-01-07 02:55:32 +09:00
parent 980b685393
commit d4347e829d
3 changed files with 35 additions and 15 deletions

View File

@@ -10,7 +10,7 @@ import {
} from "../../shared" } from "../../shared"
import type { CommandFrontmatter } from "../../features/claude-code-command-loader/types" import type { CommandFrontmatter } from "../../features/claude-code-command-loader/types"
import { isMarkdownFile } from "../../shared/file-utils" import { isMarkdownFile } from "../../shared/file-utils"
import { discoverAllSkills, type LoadedSkill } from "../../features/opencode-skill-loader" import { discoverAllSkills, type LoadedSkill, type LazyContentLoader } from "../../features/opencode-skill-loader"
import type { ParsedSlashCommand } from "./types" import type { ParsedSlashCommand } from "./types"
interface CommandScope { interface CommandScope {
@@ -32,6 +32,7 @@ interface CommandInfo {
metadata: CommandMetadata metadata: CommandMetadata
content?: string content?: string
scope: CommandScope["type"] scope: CommandScope["type"]
lazyContentLoader?: LazyContentLoader
} }
function discoverCommandsFromDir(commandsDir: string, scope: CommandScope["type"]): CommandInfo[] { function discoverCommandsFromDir(commandsDir: string, scope: CommandScope["type"]): CommandInfo[] {
@@ -91,10 +92,15 @@ function skillToCommandInfo(skill: LoadedSkill): CommandInfo {
}, },
content: skill.definition.template, content: skill.definition.template,
scope: "skill", scope: "skill",
lazyContentLoader: skill.lazyContent,
} }
} }
async function discoverAllCommands(): Promise<CommandInfo[]> { export interface ExecutorOptions {
skills?: LoadedSkill[]
}
async function discoverAllCommands(options?: ExecutorOptions): Promise<CommandInfo[]> {
const userCommandsDir = join(getClaudeConfigDir(), "commands") const userCommandsDir = join(getClaudeConfigDir(), "commands")
const projectCommandsDir = join(process.cwd(), ".claude", "commands") const projectCommandsDir = join(process.cwd(), ".claude", "commands")
const opencodeGlobalDir = join(homedir(), ".config", "opencode", "command") const opencodeGlobalDir = join(homedir(), ".config", "opencode", "command")
@@ -105,7 +111,7 @@ async function discoverAllCommands(): Promise<CommandInfo[]> {
const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project") const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project")
const opencodeProjectCommands = discoverCommandsFromDir(opencodeProjectDir, "opencode-project") const opencodeProjectCommands = discoverCommandsFromDir(opencodeProjectDir, "opencode-project")
const skills = await discoverAllSkills() const skills = options?.skills ?? await discoverAllSkills()
const skillCommands = skills.map(skillToCommandInfo) const skillCommands = skills.map(skillToCommandInfo)
return [ return [
@@ -117,8 +123,8 @@ async function discoverAllCommands(): Promise<CommandInfo[]> {
] ]
} }
async function findCommand(commandName: string): Promise<CommandInfo | null> { async function findCommand(commandName: string, options?: ExecutorOptions): Promise<CommandInfo | null> {
const allCommands = await discoverAllCommands() const allCommands = await discoverAllCommands(options)
return allCommands.find( return allCommands.find(
(cmd) => cmd.name.toLowerCase() === commandName.toLowerCase() (cmd) => cmd.name.toLowerCase() === commandName.toLowerCase()
) ?? null ) ?? null
@@ -149,8 +155,13 @@ async function formatCommandTemplate(cmd: CommandInfo, args: string): Promise<st
sections.push("---\n") sections.push("---\n")
sections.push("## Command Instructions\n") sections.push("## Command Instructions\n")
let content = cmd.content || ""
if (!content && cmd.lazyContentLoader) {
content = await cmd.lazyContentLoader.load()
}
const commandDir = cmd.path ? dirname(cmd.path) : process.cwd() const commandDir = cmd.path ? dirname(cmd.path) : process.cwd()
const withFileRefs = await resolveFileReferencesInText(cmd.content || "", commandDir) const withFileRefs = await resolveFileReferencesInText(content, commandDir)
const resolvedContent = await resolveCommandsInText(withFileRefs) const resolvedContent = await resolveCommandsInText(withFileRefs)
sections.push(resolvedContent.trim()) sections.push(resolvedContent.trim())
@@ -169,8 +180,8 @@ export interface ExecuteResult {
error?: string error?: string
} }
export async function executeSlashCommand(parsed: ParsedSlashCommand): Promise<ExecuteResult> { export async function executeSlashCommand(parsed: ParsedSlashCommand, options?: ExecutorOptions): Promise<ExecuteResult> {
const command = await findCommand(parsed.command) const command = await findCommand(parsed.command, options)
if (!command) { if (!command) {
return { return {

View File

@@ -2,7 +2,7 @@ import {
detectSlashCommand, detectSlashCommand,
extractPromptText, extractPromptText,
} from "./detector" } from "./detector"
import { executeSlashCommand } from "./executor" import { executeSlashCommand, type ExecutorOptions } from "./executor"
import { log } from "../../shared" import { log } from "../../shared"
import { import {
AUTO_SLASH_COMMAND_TAG_OPEN, AUTO_SLASH_COMMAND_TAG_OPEN,
@@ -12,6 +12,7 @@ import type {
AutoSlashCommandHookInput, AutoSlashCommandHookInput,
AutoSlashCommandHookOutput, AutoSlashCommandHookOutput,
} from "./types" } from "./types"
import type { LoadedSkill } from "../../features/opencode-skill-loader"
export * from "./detector" export * from "./detector"
export * from "./executor" export * from "./executor"
@@ -20,7 +21,15 @@ export * from "./types"
const sessionProcessedCommands = new Set<string>() const sessionProcessedCommands = new Set<string>()
export function createAutoSlashCommandHook() { export interface AutoSlashCommandHookOptions {
skills?: LoadedSkill[]
}
export function createAutoSlashCommandHook(options?: AutoSlashCommandHookOptions) {
const executorOptions: ExecutorOptions = {
skills: options?.skills,
}
return { return {
"chat.message": async ( "chat.message": async (
input: AutoSlashCommandHookInput, input: AutoSlashCommandHookInput,
@@ -52,7 +61,7 @@ export function createAutoSlashCommandHook() {
args: parsed.args, args: parsed.args,
}) })
const result = await executeSlashCommand(parsed) const result = await executeSlashCommand(parsed, executorOptions)
const idx = output.parts.findIndex((p) => p.type === "text" && p.text) const idx = output.parts.findIndex((p) => p.type === "text" && p.text)
if (idx < 0) { if (idx < 0) {

View File

@@ -166,10 +166,6 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
}) })
: null; : null;
const autoSlashCommand = isHookEnabled("auto-slash-command")
? createAutoSlashCommandHook()
: null;
const editErrorRecovery = isHookEnabled("edit-error-recovery") const editErrorRecovery = isHookEnabled("edit-error-recovery")
? createEditErrorRecoveryHook(ctx) ? createEditErrorRecoveryHook(ctx)
: null; : null;
@@ -239,6 +235,10 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
skills: mergedSkills, skills: mergedSkills,
}); });
const autoSlashCommand = isHookEnabled("auto-slash-command")
? createAutoSlashCommandHook({ skills: mergedSkills })
: null;
const googleAuthHooks = pluginConfig.google_auth !== false const googleAuthHooks = pluginConfig.google_auth !== false
? await createGoogleAntigravityAuthPlugin(ctx) ? await createGoogleAntigravityAuthPlugin(ctx)
: null; : null;