From 12a431843924a7135f4809ff71a367c375d6b713 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Thu, 26 Mar 2026 12:15:47 +0900 Subject: [PATCH] fix(commands): load .agents skills into command config Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/features/opencode-skill-loader/loader.ts | 14 +++ .../command-config-handler.test.ts | 98 +++++++++++++++++++ src/plugin-handlers/command-config-handler.ts | 8 ++ 3 files changed, 120 insertions(+) create mode 100644 src/plugin-handlers/command-config-handler.test.ts diff --git a/src/features/opencode-skill-loader/loader.ts b/src/features/opencode-skill-loader/loader.ts index e577809fe..6f0c44c3b 100644 --- a/src/features/opencode-skill-loader/loader.ts +++ b/src/features/opencode-skill-loader/loader.ts @@ -48,6 +48,20 @@ export async function loadOpencodeProjectSkills(directory?: string): Promise> { + const agentsProjectSkillDirs = findProjectAgentsSkillDirs(directory ?? process.cwd()) + const allSkills = await Promise.all( + agentsProjectSkillDirs.map((skillsDir) => loadSkillsFromDir({ skillsDir, scope: "project" })), + ) + return skillsToCommandDefinitionRecord(deduplicateSkillsByName(allSkills.flat())) +} + +export async function loadGlobalAgentsSkills(): Promise> { + const agentsGlobalDir = join(homedir(), ".agents", "skills") + const skills = await loadSkillsFromDir({ skillsDir: agentsGlobalDir, scope: "user" }) + return skillsToCommandDefinitionRecord(skills) +} + export interface DiscoverSkillsOptions { includeClaudeCodePaths?: boolean directory?: string diff --git a/src/plugin-handlers/command-config-handler.test.ts b/src/plugin-handlers/command-config-handler.test.ts new file mode 100644 index 000000000..7767c6639 --- /dev/null +++ b/src/plugin-handlers/command-config-handler.test.ts @@ -0,0 +1,98 @@ +import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test"; +import * as builtinCommands from "../features/builtin-commands"; +import * as commandLoader from "../features/claude-code-command-loader"; +import * as skillLoader from "../features/opencode-skill-loader"; +import type { OhMyOpenCodeConfig } from "../config"; +import type { PluginComponents } from "./plugin-components-loader"; +import { applyCommandConfig } from "./command-config-handler"; + +function createPluginComponents(): PluginComponents { + return { + commands: {}, + skills: {}, + agents: {}, + mcpServers: {}, + hooksConfigs: [], + plugins: [], + errors: [], + }; +} + +function createPluginConfig(): OhMyOpenCodeConfig { + return {}; +} + +describe("applyCommandConfig", () => { + let loadBuiltinCommandsSpy: ReturnType; + let loadUserCommandsSpy: ReturnType; + let loadProjectCommandsSpy: ReturnType; + let loadOpencodeGlobalCommandsSpy: ReturnType; + let loadOpencodeProjectCommandsSpy: ReturnType; + let discoverConfigSourceSkillsSpy: ReturnType; + let loadUserSkillsSpy: ReturnType; + let loadProjectSkillsSpy: ReturnType; + let loadOpencodeGlobalSkillsSpy: ReturnType; + let loadOpencodeProjectSkillsSpy: ReturnType; + let loadProjectAgentsSkillsSpy: ReturnType; + let loadGlobalAgentsSkillsSpy: ReturnType; + + beforeEach(() => { + loadBuiltinCommandsSpy = spyOn(builtinCommands, "loadBuiltinCommands").mockReturnValue({}); + loadUserCommandsSpy = spyOn(commandLoader, "loadUserCommands").mockResolvedValue({}); + loadProjectCommandsSpy = spyOn(commandLoader, "loadProjectCommands").mockResolvedValue({}); + loadOpencodeGlobalCommandsSpy = spyOn(commandLoader, "loadOpencodeGlobalCommands").mockResolvedValue({}); + loadOpencodeProjectCommandsSpy = spyOn(commandLoader, "loadOpencodeProjectCommands").mockResolvedValue({}); + discoverConfigSourceSkillsSpy = spyOn(skillLoader, "discoverConfigSourceSkills").mockResolvedValue([]); + loadUserSkillsSpy = spyOn(skillLoader, "loadUserSkills").mockResolvedValue({}); + loadProjectSkillsSpy = spyOn(skillLoader, "loadProjectSkills").mockResolvedValue({}); + loadOpencodeGlobalSkillsSpy = spyOn(skillLoader, "loadOpencodeGlobalSkills").mockResolvedValue({}); + loadOpencodeProjectSkillsSpy = spyOn(skillLoader, "loadOpencodeProjectSkills").mockResolvedValue({}); + loadProjectAgentsSkillsSpy = spyOn(skillLoader, "loadProjectAgentsSkills").mockResolvedValue({}); + loadGlobalAgentsSkillsSpy = spyOn(skillLoader, "loadGlobalAgentsSkills").mockResolvedValue({}); + }); + + afterEach(() => { + loadBuiltinCommandsSpy.mockRestore(); + loadUserCommandsSpy.mockRestore(); + loadProjectCommandsSpy.mockRestore(); + loadOpencodeGlobalCommandsSpy.mockRestore(); + loadOpencodeProjectCommandsSpy.mockRestore(); + discoverConfigSourceSkillsSpy.mockRestore(); + loadUserSkillsSpy.mockRestore(); + loadProjectSkillsSpy.mockRestore(); + loadOpencodeGlobalSkillsSpy.mockRestore(); + loadOpencodeProjectSkillsSpy.mockRestore(); + loadProjectAgentsSkillsSpy.mockRestore(); + loadGlobalAgentsSkillsSpy.mockRestore(); + }); + + test("includes .agents skills in command config", async () => { + // given + loadProjectAgentsSkillsSpy.mockResolvedValue({ + "agents-project-skill": { + description: "(project - Skill) Agents project skill", + template: "template", + }, + }); + loadGlobalAgentsSkillsSpy.mockResolvedValue({ + "agents-global-skill": { + description: "(user - Skill) Agents global skill", + template: "template", + }, + }); + const config: Record = { command: {} }; + + // when + await applyCommandConfig({ + config, + pluginConfig: createPluginConfig(), + ctx: { directory: "/tmp" }, + pluginComponents: createPluginComponents(), + }); + + // then + const commandConfig = config.command as Record; + expect(commandConfig["agents-project-skill"]?.description).toContain("Agents project skill"); + expect(commandConfig["agents-global-skill"]?.description).toContain("Agents global skill"); + }); +}); diff --git a/src/plugin-handlers/command-config-handler.ts b/src/plugin-handlers/command-config-handler.ts index a5cb0e946..7afd1e416 100644 --- a/src/plugin-handlers/command-config-handler.ts +++ b/src/plugin-handlers/command-config-handler.ts @@ -9,6 +9,8 @@ import { import { loadBuiltinCommands } from "../features/builtin-commands"; import { discoverConfigSourceSkills, + loadGlobalAgentsSkills, + loadProjectAgentsSkills, loadUserSkills, loadProjectSkills, loadOpencodeGlobalSkills, @@ -36,7 +38,9 @@ export async function applyCommandConfig(params: { opencodeGlobalCommands, opencodeProjectCommands, userSkills, + globalAgentsSkills, projectSkills, + projectAgentsSkills, opencodeGlobalSkills, opencodeProjectSkills, ] = await Promise.all([ @@ -49,7 +53,9 @@ export async function applyCommandConfig(params: { loadOpencodeGlobalCommands(), loadOpencodeProjectCommands(params.ctx.directory), includeClaudeSkills ? loadUserSkills() : Promise.resolve({}), + includeClaudeSkills ? loadGlobalAgentsSkills() : Promise.resolve({}), includeClaudeSkills ? loadProjectSkills(params.ctx.directory) : Promise.resolve({}), + includeClaudeSkills ? loadProjectAgentsSkills(params.ctx.directory) : Promise.resolve({}), loadOpencodeGlobalSkills(), loadOpencodeProjectSkills(params.ctx.directory), ]); @@ -59,11 +65,13 @@ export async function applyCommandConfig(params: { ...skillsToCommandDefinitionRecord(configSourceSkills), ...userCommands, ...userSkills, + ...globalAgentsSkills, ...opencodeGlobalCommands, ...opencodeGlobalSkills, ...systemCommands, ...projectCommands, ...projectSkills, + ...projectAgentsSkills, ...opencodeProjectCommands, ...opencodeProjectSkills, ...params.pluginComponents.commands,