diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 04a3b1e9c2..adf733e322 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -560,6 +560,11 @@ export namespace Config { }) export type Command = z.infer + export const Skills = z.object({ + paths: z.array(z.string()).optional().describe("Additional paths to skill folders"), + }) + export type Skills = z.infer + export const Agent = z .object({ model: z.string().optional(), @@ -895,6 +900,7 @@ export namespace Config { .record(z.string(), Command) .optional() .describe("Command configuration, see https://opencode.ai/docs/commands"), + skills: Skills.optional().describe("Additional skill folder paths"), watcher: z .object({ ignore: z.array(z.string()).optional(), diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts index 12fc9ee90c..5b300a9287 100644 --- a/packages/opencode/src/skill/skill.ts +++ b/packages/opencode/src/skill/skill.ts @@ -1,5 +1,6 @@ import z from "zod" import path from "path" +import os from "os" import { Config } from "../config/config" import { Instance } from "../project/instance" import { NamedError } from "@opencode-ai/util/error" @@ -40,6 +41,7 @@ export namespace Skill { const OPENCODE_SKILL_GLOB = new Bun.Glob("{skill,skills}/**/SKILL.md") const CLAUDE_SKILL_GLOB = new Bun.Glob("skills/**/SKILL.md") + const SKILL_GLOB = new Bun.Glob("**/SKILL.md") export const state = Instance.state(async () => { const skills: Record = {} @@ -122,6 +124,25 @@ export namespace Skill { } } + // Scan additional skill paths from config + const config = await Config.get() + for (const skillPath of config.skills?.paths ?? []) { + const expanded = skillPath.startsWith("~/") ? path.join(os.homedir(), skillPath.slice(2)) : skillPath + const resolved = path.isAbsolute(expanded) ? expanded : path.join(Instance.directory, expanded) + if (!(await Filesystem.isDir(resolved))) { + log.warn("skill path not found", { path: resolved }) + continue + } + for await (const match of SKILL_GLOB.scan({ + cwd: resolved, + absolute: true, + onlyFiles: true, + followSymlinks: true, + })) { + await addSkill(match) + } + } + return skills }) diff --git a/packages/opencode/src/tool/skill.ts b/packages/opencode/src/tool/skill.ts index 9536685ef9..76d9fd535e 100644 --- a/packages/opencode/src/tool/skill.ts +++ b/packages/opencode/src/tool/skill.ts @@ -62,12 +62,11 @@ export const SkillTool = Tool.define("skill", async (ctx) => { always: [params.name], metadata: {}, }) - // Load and parse skill content - const parsed = await ConfigMarkdown.parse(skill.location) + const content = (await ConfigMarkdown.parse(skill.location)).content const dir = path.dirname(skill.location) // Format output similar to plugin pattern - const output = [`## Skill: ${skill.name}`, "", `**Base directory**: ${dir}`, "", parsed.content.trim()].join("\n") + const output = [`## Skill: ${skill.name}`, "", `**Base directory**: ${dir}`, "", content.trim()].join("\n") return { title: `Loaded skill: ${skill.name}`, diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 12c7bf7dfd..ceab04c19f 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1633,6 +1633,15 @@ export type Config = { subtask?: boolean } } + /** + * Additional skill folder paths to scan + */ + skills?: { + /** + * Additional paths to skill folders to scan + */ + paths?: Array + } watcher?: { ignore?: Array }