diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 8c65726e236..03629a10d77 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -1030,6 +1030,7 @@ export namespace Config { }, ), instructions: z.array(z.string()).optional().describe("Additional instruction files or patterns to include"), + skills: z.array(z.string()).optional().describe("Additional skill directories to scan for SKILL.md files"), layout: Layout.optional().describe("@deprecated Always uses stretch layout."), permission: Permission.optional(), tools: z.record(z.string(), z.boolean()).optional(), diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts index 12fc9ee90c7..dad756e969d 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" @@ -122,6 +123,36 @@ export namespace Skill { } } + // Scan custom skill directories from config + const config = await Config.get() + if (config.skills) { + for (const skillPath of config.skills) { + const resolvedPath = (() => { + if (skillPath.startsWith("~/")) { + return path.join(os.homedir(), skillPath.slice(2)) + } + if (path.isAbsolute(skillPath)) { + return skillPath + } + return path.resolve(Instance.directory, skillPath) + })() + + if (!(await Filesystem.isDir(resolvedPath))) { + log.warn("custom skill directory not found", { path: resolvedPath }) + continue + } + + for await (const match of new Bun.Glob("**/SKILL.md").scan({ + cwd: resolvedPath, + absolute: true, + onlyFiles: true, + followSymlinks: true, + })) { + await addSkill(match) + } + } + } + return skills })