feat: support config skill registration (#9640)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
This commit is contained in:
@@ -560,6 +560,11 @@ export namespace Config {
|
|||||||
})
|
})
|
||||||
export type Command = z.infer<typeof Command>
|
export type Command = z.infer<typeof Command>
|
||||||
|
|
||||||
|
export const Skills = z.object({
|
||||||
|
paths: z.array(z.string()).optional().describe("Additional paths to skill folders"),
|
||||||
|
})
|
||||||
|
export type Skills = z.infer<typeof Skills>
|
||||||
|
|
||||||
export const Agent = z
|
export const Agent = z
|
||||||
.object({
|
.object({
|
||||||
model: z.string().optional(),
|
model: z.string().optional(),
|
||||||
@@ -895,6 +900,7 @@ export namespace Config {
|
|||||||
.record(z.string(), Command)
|
.record(z.string(), Command)
|
||||||
.optional()
|
.optional()
|
||||||
.describe("Command configuration, see https://opencode.ai/docs/commands"),
|
.describe("Command configuration, see https://opencode.ai/docs/commands"),
|
||||||
|
skills: Skills.optional().describe("Additional skill folder paths"),
|
||||||
watcher: z
|
watcher: z
|
||||||
.object({
|
.object({
|
||||||
ignore: z.array(z.string()).optional(),
|
ignore: z.array(z.string()).optional(),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import z from "zod"
|
import z from "zod"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
import os from "os"
|
||||||
import { Config } from "../config/config"
|
import { Config } from "../config/config"
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
import { NamedError } from "@opencode-ai/util/error"
|
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 OPENCODE_SKILL_GLOB = new Bun.Glob("{skill,skills}/**/SKILL.md")
|
||||||
const CLAUDE_SKILL_GLOB = new Bun.Glob("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 () => {
|
export const state = Instance.state(async () => {
|
||||||
const skills: Record<string, Info> = {}
|
const skills: Record<string, Info> = {}
|
||||||
@@ -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
|
return skills
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -62,12 +62,11 @@ export const SkillTool = Tool.define("skill", async (ctx) => {
|
|||||||
always: [params.name],
|
always: [params.name],
|
||||||
metadata: {},
|
metadata: {},
|
||||||
})
|
})
|
||||||
// Load and parse skill content
|
const content = (await ConfigMarkdown.parse(skill.location)).content
|
||||||
const parsed = await ConfigMarkdown.parse(skill.location)
|
|
||||||
const dir = path.dirname(skill.location)
|
const dir = path.dirname(skill.location)
|
||||||
|
|
||||||
// Format output similar to plugin pattern
|
// 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 {
|
return {
|
||||||
title: `Loaded skill: ${skill.name}`,
|
title: `Loaded skill: ${skill.name}`,
|
||||||
|
|||||||
@@ -1633,6 +1633,15 @@ export type Config = {
|
|||||||
subtask?: boolean
|
subtask?: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Additional skill folder paths to scan
|
||||||
|
*/
|
||||||
|
skills?: {
|
||||||
|
/**
|
||||||
|
* Additional paths to skill folders to scan
|
||||||
|
*/
|
||||||
|
paths?: Array<string>
|
||||||
|
}
|
||||||
watcher?: {
|
watcher?: {
|
||||||
ignore?: Array<string>
|
ignore?: Array<string>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user