Merge pull request #1807 from code-yeongyu/fix/skills-sources-schema
fix schema generation and implement skills.sources runtime loading
This commit is contained in:
commit
b8c12495b6
File diff suppressed because it is too large
Load Diff
17
script/build-schema-document.ts
Normal file
17
script/build-schema-document.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import * as z from "zod"
|
||||||
|
import { OhMyOpenCodeConfigSchema } from "../src/config/schema"
|
||||||
|
|
||||||
|
export function createOhMyOpenCodeJsonSchema(): Record<string, unknown> {
|
||||||
|
const jsonSchema = z.toJSONSchema(OhMyOpenCodeConfigSchema, {
|
||||||
|
target: "draft-07",
|
||||||
|
unrepresentable: "any",
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
$schema: "http://json-schema.org/draft-07/schema#",
|
||||||
|
$id: "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json",
|
||||||
|
title: "Oh My OpenCode Configuration",
|
||||||
|
description: "Configuration schema for oh-my-opencode plugin",
|
||||||
|
...jsonSchema,
|
||||||
|
}
|
||||||
|
}
|
||||||
18
script/build-schema.test.ts
Normal file
18
script/build-schema.test.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import { createOhMyOpenCodeJsonSchema } from "./build-schema-document"
|
||||||
|
|
||||||
|
describe("build-schema-document", () => {
|
||||||
|
test("generates schema with skills property", () => {
|
||||||
|
// given
|
||||||
|
const expectedDraft = "http://json-schema.org/draft-07/schema#"
|
||||||
|
|
||||||
|
// when
|
||||||
|
const schema = createOhMyOpenCodeJsonSchema()
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(schema.$schema).toBe(expectedDraft)
|
||||||
|
expect(schema.title).toBe("Oh My OpenCode Configuration")
|
||||||
|
expect(schema.properties).toBeDefined()
|
||||||
|
expect(schema.properties.skills).toBeDefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -1,24 +1,12 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
import * as z from "zod"
|
import { createOhMyOpenCodeJsonSchema } from "./build-schema-document"
|
||||||
import { zodToJsonSchema } from "zod-to-json-schema"
|
|
||||||
import { OhMyOpenCodeConfigSchema } from "../src/config/schema"
|
|
||||||
|
|
||||||
const SCHEMA_OUTPUT_PATH = "assets/oh-my-opencode.schema.json"
|
const SCHEMA_OUTPUT_PATH = "assets/oh-my-opencode.schema.json"
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
console.log("Generating JSON Schema...")
|
console.log("Generating JSON Schema...")
|
||||||
|
|
||||||
const jsonSchema = zodToJsonSchema(OhMyOpenCodeConfigSchema, {
|
const finalSchema = createOhMyOpenCodeJsonSchema()
|
||||||
target: "draft7",
|
|
||||||
})
|
|
||||||
|
|
||||||
const finalSchema = {
|
|
||||||
$schema: "http://json-schema.org/draft-07/schema#",
|
|
||||||
$id: "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json",
|
|
||||||
title: "Oh My OpenCode Configuration",
|
|
||||||
description: "Configuration schema for oh-my-opencode plugin",
|
|
||||||
...jsonSchema,
|
|
||||||
}
|
|
||||||
|
|
||||||
await Bun.write(SCHEMA_OUTPUT_PATH, JSON.stringify(finalSchema, null, 2))
|
await Bun.write(SCHEMA_OUTPUT_PATH, JSON.stringify(finalSchema, null, 2))
|
||||||
|
|
||||||
|
|||||||
@ -733,3 +733,20 @@ describe("GitMasterConfigSchema", () => {
|
|||||||
expect(result.success).toBe(false)
|
expect(result.success).toBe(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("skills schema", () => {
|
||||||
|
test("accepts skills.sources configuration", () => {
|
||||||
|
//#given
|
||||||
|
const config = {
|
||||||
|
skills: {
|
||||||
|
sources: [{ path: "skill/", recursive: true }],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const result = OhMyOpenCodeConfigSchema.safeParse(config)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(result.success).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@ -28,17 +28,11 @@ export const SkillEntrySchema = z.union([z.boolean(), SkillDefinitionSchema])
|
|||||||
|
|
||||||
export const SkillsConfigSchema = z.union([
|
export const SkillsConfigSchema = z.union([
|
||||||
z.array(z.string()),
|
z.array(z.string()),
|
||||||
z
|
z.object({
|
||||||
.record(z.string(), SkillEntrySchema)
|
sources: z.array(SkillSourceSchema).optional(),
|
||||||
.and(
|
enable: z.array(z.string()).optional(),
|
||||||
z
|
disable: z.array(z.string()).optional(),
|
||||||
.object({
|
}).catchall(SkillEntrySchema),
|
||||||
sources: z.array(SkillSourceSchema).optional(),
|
|
||||||
enable: z.array(z.string()).optional(),
|
|
||||||
disable: z.array(z.string()).optional(),
|
|
||||||
})
|
|
||||||
.partial()
|
|
||||||
),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
export type SkillsConfig = z.infer<typeof SkillsConfigSchema>
|
export type SkillsConfig = z.infer<typeof SkillsConfigSchema>
|
||||||
|
|||||||
@ -0,0 +1,82 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, it } from "bun:test"
|
||||||
|
import { mkdirSync, rmSync, writeFileSync } from "fs"
|
||||||
|
import { join } from "path"
|
||||||
|
import { tmpdir } from "os"
|
||||||
|
import { SkillsConfigSchema } from "../../config/schema/skills"
|
||||||
|
import { discoverConfigSourceSkills, normalizePathForGlob } from "./config-source-discovery"
|
||||||
|
|
||||||
|
const TEST_DIR = join(tmpdir(), `config-source-discovery-test-${Date.now()}`)
|
||||||
|
|
||||||
|
function writeSkill(path: string, name: string, description: string): void {
|
||||||
|
mkdirSync(path, { recursive: true })
|
||||||
|
writeFileSync(
|
||||||
|
join(path, "SKILL.md"),
|
||||||
|
`---\nname: ${name}\ndescription: ${description}\n---\nBody\n`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("config source discovery", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mkdirSync(TEST_DIR, { recursive: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
rmSync(TEST_DIR, { recursive: true, force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
it("loads skills from local sources path", async () => {
|
||||||
|
// given
|
||||||
|
const configDir = join(TEST_DIR, "config")
|
||||||
|
const sourceDir = join(configDir, "custom-skills")
|
||||||
|
writeSkill(join(sourceDir, "local-skill"), "local-skill", "Loaded from local source")
|
||||||
|
const config = SkillsConfigSchema.parse({
|
||||||
|
sources: [{ path: "./custom-skills", recursive: true }],
|
||||||
|
})
|
||||||
|
|
||||||
|
// when
|
||||||
|
const skills = await discoverConfigSourceSkills({
|
||||||
|
config,
|
||||||
|
configDir,
|
||||||
|
})
|
||||||
|
|
||||||
|
// then
|
||||||
|
const localSkill = skills.find((skill) => skill.name === "local-skill")
|
||||||
|
expect(localSkill).toBeDefined()
|
||||||
|
expect(localSkill?.scope).toBe("config")
|
||||||
|
expect(localSkill?.definition.description).toContain("Loaded from local source")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("filters discovered skills using source glob", async () => {
|
||||||
|
// given
|
||||||
|
const configDir = join(TEST_DIR, "config")
|
||||||
|
const sourceDir = join(configDir, "custom-skills")
|
||||||
|
|
||||||
|
writeSkill(join(sourceDir, "keep", "kept"), "kept-skill", "Should be kept")
|
||||||
|
writeSkill(join(sourceDir, "skip", "skipped"), "skipped-skill", "Should be skipped")
|
||||||
|
const config = SkillsConfigSchema.parse({
|
||||||
|
sources: [{ path: "./custom-skills", recursive: true, glob: "keep/**" }],
|
||||||
|
})
|
||||||
|
|
||||||
|
// when
|
||||||
|
const skills = await discoverConfigSourceSkills({
|
||||||
|
config,
|
||||||
|
configDir,
|
||||||
|
})
|
||||||
|
|
||||||
|
// then
|
||||||
|
const names = skills.map((skill) => skill.name)
|
||||||
|
expect(names).toContain("keep/kept-skill")
|
||||||
|
expect(names).not.toContain("skip/skipped-skill")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("normalizes windows separators before glob matching", () => {
|
||||||
|
// given
|
||||||
|
const windowsPath = "keep\\nested\\SKILL.md"
|
||||||
|
|
||||||
|
// when
|
||||||
|
const normalized = normalizePathForGlob(windowsPath)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(normalized).toBe("keep/nested/SKILL.md")
|
||||||
|
})
|
||||||
|
})
|
||||||
105
src/features/opencode-skill-loader/config-source-discovery.ts
Normal file
105
src/features/opencode-skill-loader/config-source-discovery.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { promises as fs } from "fs"
|
||||||
|
import { dirname, extname, isAbsolute, join, relative } from "path"
|
||||||
|
import picomatch from "picomatch"
|
||||||
|
import type { SkillsConfig } from "../../config/schema"
|
||||||
|
import { normalizeSkillsConfig } from "./merger/skills-config-normalizer"
|
||||||
|
import { deduplicateSkillsByName } from "./skill-deduplication"
|
||||||
|
import { loadSkillsFromDir } from "./skill-directory-loader"
|
||||||
|
import { inferSkillNameFromFileName, loadSkillFromPath } from "./loaded-skill-from-path"
|
||||||
|
import type { LoadedSkill } from "./types"
|
||||||
|
|
||||||
|
const MAX_RECURSIVE_DEPTH = 10
|
||||||
|
|
||||||
|
function isHttpUrl(path: string): boolean {
|
||||||
|
return path.startsWith("http://") || path.startsWith("https://")
|
||||||
|
}
|
||||||
|
|
||||||
|
function toAbsolutePath(path: string, configDir: string): string {
|
||||||
|
if (isAbsolute(path)) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return join(configDir, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMarkdownPath(path: string): boolean {
|
||||||
|
return extname(path).toLowerCase() === ".md"
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizePathForGlob(path: string): string {
|
||||||
|
return path.split("\\").join("/")
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterByGlob(skills: LoadedSkill[], sourceBaseDir: string, globPattern?: string): LoadedSkill[] {
|
||||||
|
if (!globPattern) return skills
|
||||||
|
|
||||||
|
return skills.filter((skill) => {
|
||||||
|
if (!skill.path) return false
|
||||||
|
const rel = normalizePathForGlob(relative(sourceBaseDir, skill.path))
|
||||||
|
return picomatch.isMatch(rel, globPattern, { dot: true, bash: true })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSourcePath(options: {
|
||||||
|
sourcePath: string
|
||||||
|
recursive: boolean
|
||||||
|
globPattern?: string
|
||||||
|
configDir: string
|
||||||
|
}): Promise<LoadedSkill[]> {
|
||||||
|
if (isHttpUrl(options.sourcePath)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const absolutePath = toAbsolutePath(options.sourcePath, options.configDir)
|
||||||
|
const stat = await fs.stat(absolutePath).catch(() => null)
|
||||||
|
if (!stat) return []
|
||||||
|
|
||||||
|
if (stat.isFile()) {
|
||||||
|
if (!isMarkdownPath(absolutePath)) return []
|
||||||
|
const loaded = await loadSkillFromPath({
|
||||||
|
skillPath: absolutePath,
|
||||||
|
resolvedPath: dirname(absolutePath),
|
||||||
|
defaultName: inferSkillNameFromFileName(absolutePath),
|
||||||
|
scope: "config",
|
||||||
|
})
|
||||||
|
if (!loaded) return []
|
||||||
|
return filterByGlob([loaded], dirname(absolutePath), options.globPattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stat.isDirectory()) return []
|
||||||
|
|
||||||
|
const directorySkills = await loadSkillsFromDir({
|
||||||
|
skillsDir: absolutePath,
|
||||||
|
scope: "config",
|
||||||
|
maxDepth: options.recursive ? MAX_RECURSIVE_DEPTH : 0,
|
||||||
|
})
|
||||||
|
return filterByGlob(directorySkills, absolutePath, options.globPattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function discoverConfigSourceSkills(options: {
|
||||||
|
config: SkillsConfig | undefined
|
||||||
|
configDir: string
|
||||||
|
}): Promise<LoadedSkill[]> {
|
||||||
|
const normalized = normalizeSkillsConfig(options.config)
|
||||||
|
if (normalized.sources.length === 0) return []
|
||||||
|
|
||||||
|
const loadedBySource = await Promise.all(
|
||||||
|
normalized.sources.map((source) => {
|
||||||
|
if (typeof source === "string") {
|
||||||
|
return loadSourcePath({
|
||||||
|
sourcePath: source,
|
||||||
|
recursive: false,
|
||||||
|
configDir: options.configDir,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadSourcePath({
|
||||||
|
sourcePath: source.path,
|
||||||
|
recursive: source.recursive ?? false,
|
||||||
|
globPattern: source.glob,
|
||||||
|
configDir: options.configDir,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
return deduplicateSkillsByName(loadedBySource.flat())
|
||||||
|
}
|
||||||
@ -14,3 +14,4 @@ export * from "./skill-discovery"
|
|||||||
export * from "./skill-resolution-options"
|
export * from "./skill-resolution-options"
|
||||||
export * from "./loaded-skill-template-extractor"
|
export * from "./loaded-skill-template-extractor"
|
||||||
export * from "./skill-template-resolver"
|
export * from "./skill-template-resolver"
|
||||||
|
export * from "./config-source-discovery"
|
||||||
|
|||||||
55
src/features/opencode-skill-loader/merger.test.ts
Normal file
55
src/features/opencode-skill-loader/merger.test.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { describe, expect, it } from "bun:test"
|
||||||
|
import type { BuiltinSkill } from "../builtin-skills/types"
|
||||||
|
import type { CommandDefinition } from "../claude-code-command-loader/types"
|
||||||
|
import { mergeSkills } from "./merger"
|
||||||
|
import type { LoadedSkill, SkillScope } from "./types"
|
||||||
|
|
||||||
|
function createLoadedSkill(scope: SkillScope, name: string, description: string): LoadedSkill {
|
||||||
|
const definition: CommandDefinition = {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
template: "template",
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
definition,
|
||||||
|
scope,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("mergeSkills", () => {
|
||||||
|
it("gives higher scopes priority over config source skills", () => {
|
||||||
|
// given
|
||||||
|
const builtinSkills: BuiltinSkill[] = [
|
||||||
|
{
|
||||||
|
name: "priority-skill",
|
||||||
|
description: "builtin",
|
||||||
|
template: "builtin-template",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const configSourceSkills: LoadedSkill[] = [
|
||||||
|
createLoadedSkill("config", "priority-skill", "config source"),
|
||||||
|
]
|
||||||
|
const userSkills: LoadedSkill[] = [
|
||||||
|
createLoadedSkill("user", "priority-skill", "user skill"),
|
||||||
|
]
|
||||||
|
|
||||||
|
// when
|
||||||
|
const merged = mergeSkills(
|
||||||
|
builtinSkills,
|
||||||
|
undefined,
|
||||||
|
configSourceSkills,
|
||||||
|
userSkills,
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(merged).toHaveLength(1)
|
||||||
|
expect(merged[0]?.scope).toBe("user")
|
||||||
|
expect(merged[0]?.definition.description).toBe("user skill")
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -14,6 +14,7 @@ export interface MergeSkillsOptions {
|
|||||||
export function mergeSkills(
|
export function mergeSkills(
|
||||||
builtinSkills: BuiltinSkill[],
|
builtinSkills: BuiltinSkill[],
|
||||||
config: SkillsConfig | undefined,
|
config: SkillsConfig | undefined,
|
||||||
|
configSourceSkills: LoadedSkill[],
|
||||||
userClaudeSkills: LoadedSkill[],
|
userClaudeSkills: LoadedSkill[],
|
||||||
userOpencodeSkills: LoadedSkill[],
|
userOpencodeSkills: LoadedSkill[],
|
||||||
projectClaudeSkills: LoadedSkill[],
|
projectClaudeSkills: LoadedSkill[],
|
||||||
@ -47,6 +48,7 @@ export function mergeSkills(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fileSystemSkills = [
|
const fileSystemSkills = [
|
||||||
|
...configSourceSkills,
|
||||||
...userClaudeSkills,
|
...userClaudeSkills,
|
||||||
...userOpencodeSkills,
|
...userOpencodeSkills,
|
||||||
...projectClaudeSkills,
|
...projectClaudeSkills,
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import type { OhMyOpenCodeConfig } from "../config";
|
|||||||
import { log, migrateAgentConfig } from "../shared";
|
import { log, migrateAgentConfig } from "../shared";
|
||||||
import { AGENT_NAME_MAP } from "../shared/migration";
|
import { AGENT_NAME_MAP } from "../shared/migration";
|
||||||
import {
|
import {
|
||||||
|
discoverConfigSourceSkills,
|
||||||
discoverOpencodeGlobalSkills,
|
discoverOpencodeGlobalSkills,
|
||||||
discoverOpencodeProjectSkills,
|
discoverOpencodeProjectSkills,
|
||||||
discoverProjectClaudeSkills,
|
discoverProjectClaudeSkills,
|
||||||
@ -34,11 +35,16 @@ export async function applyAgentConfig(params: {
|
|||||||
|
|
||||||
const includeClaudeSkillsForAwareness = params.pluginConfig.claude_code?.skills ?? true;
|
const includeClaudeSkillsForAwareness = params.pluginConfig.claude_code?.skills ?? true;
|
||||||
const [
|
const [
|
||||||
|
discoveredConfigSourceSkills,
|
||||||
discoveredUserSkills,
|
discoveredUserSkills,
|
||||||
discoveredProjectSkills,
|
discoveredProjectSkills,
|
||||||
discoveredOpencodeGlobalSkills,
|
discoveredOpencodeGlobalSkills,
|
||||||
discoveredOpencodeProjectSkills,
|
discoveredOpencodeProjectSkills,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
|
discoverConfigSourceSkills({
|
||||||
|
config: params.pluginConfig.skills,
|
||||||
|
configDir: params.ctx.directory,
|
||||||
|
}),
|
||||||
includeClaudeSkillsForAwareness ? discoverUserClaudeSkills() : Promise.resolve([]),
|
includeClaudeSkillsForAwareness ? discoverUserClaudeSkills() : Promise.resolve([]),
|
||||||
includeClaudeSkillsForAwareness
|
includeClaudeSkillsForAwareness
|
||||||
? discoverProjectClaudeSkills()
|
? discoverProjectClaudeSkills()
|
||||||
@ -48,6 +54,7 @@ export async function applyAgentConfig(params: {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const allDiscoveredSkills = [
|
const allDiscoveredSkills = [
|
||||||
|
...discoveredConfigSourceSkills,
|
||||||
...discoveredOpencodeProjectSkills,
|
...discoveredOpencodeProjectSkills,
|
||||||
...discoveredProjectSkills,
|
...discoveredProjectSkills,
|
||||||
...discoveredOpencodeGlobalSkills,
|
...discoveredOpencodeGlobalSkills,
|
||||||
|
|||||||
@ -7,16 +7,19 @@ import {
|
|||||||
} from "../features/claude-code-command-loader";
|
} from "../features/claude-code-command-loader";
|
||||||
import { loadBuiltinCommands } from "../features/builtin-commands";
|
import { loadBuiltinCommands } from "../features/builtin-commands";
|
||||||
import {
|
import {
|
||||||
|
discoverConfigSourceSkills,
|
||||||
loadUserSkills,
|
loadUserSkills,
|
||||||
loadProjectSkills,
|
loadProjectSkills,
|
||||||
loadOpencodeGlobalSkills,
|
loadOpencodeGlobalSkills,
|
||||||
loadOpencodeProjectSkills,
|
loadOpencodeProjectSkills,
|
||||||
|
skillsToCommandDefinitionRecord,
|
||||||
} from "../features/opencode-skill-loader";
|
} from "../features/opencode-skill-loader";
|
||||||
import type { PluginComponents } from "./plugin-components-loader";
|
import type { PluginComponents } from "./plugin-components-loader";
|
||||||
|
|
||||||
export async function applyCommandConfig(params: {
|
export async function applyCommandConfig(params: {
|
||||||
config: Record<string, unknown>;
|
config: Record<string, unknown>;
|
||||||
pluginConfig: OhMyOpenCodeConfig;
|
pluginConfig: OhMyOpenCodeConfig;
|
||||||
|
ctx: { directory: string };
|
||||||
pluginComponents: PluginComponents;
|
pluginComponents: PluginComponents;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const builtinCommands = loadBuiltinCommands(params.pluginConfig.disabled_commands);
|
const builtinCommands = loadBuiltinCommands(params.pluginConfig.disabled_commands);
|
||||||
@ -26,6 +29,7 @@ export async function applyCommandConfig(params: {
|
|||||||
const includeClaudeSkills = params.pluginConfig.claude_code?.skills ?? true;
|
const includeClaudeSkills = params.pluginConfig.claude_code?.skills ?? true;
|
||||||
|
|
||||||
const [
|
const [
|
||||||
|
configSourceSkills,
|
||||||
userCommands,
|
userCommands,
|
||||||
projectCommands,
|
projectCommands,
|
||||||
opencodeGlobalCommands,
|
opencodeGlobalCommands,
|
||||||
@ -35,6 +39,10 @@ export async function applyCommandConfig(params: {
|
|||||||
opencodeGlobalSkills,
|
opencodeGlobalSkills,
|
||||||
opencodeProjectSkills,
|
opencodeProjectSkills,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
|
discoverConfigSourceSkills({
|
||||||
|
config: params.pluginConfig.skills,
|
||||||
|
configDir: params.ctx.directory,
|
||||||
|
}),
|
||||||
includeClaudeCommands ? loadUserCommands() : Promise.resolve({}),
|
includeClaudeCommands ? loadUserCommands() : Promise.resolve({}),
|
||||||
includeClaudeCommands ? loadProjectCommands() : Promise.resolve({}),
|
includeClaudeCommands ? loadProjectCommands() : Promise.resolve({}),
|
||||||
loadOpencodeGlobalCommands(),
|
loadOpencodeGlobalCommands(),
|
||||||
@ -47,6 +55,7 @@ export async function applyCommandConfig(params: {
|
|||||||
|
|
||||||
params.config.command = {
|
params.config.command = {
|
||||||
...builtinCommands,
|
...builtinCommands,
|
||||||
|
...skillsToCommandDefinitionRecord(configSourceSkills),
|
||||||
...userCommands,
|
...userCommands,
|
||||||
...userSkills,
|
...userSkills,
|
||||||
...opencodeGlobalCommands,
|
...opencodeGlobalCommands,
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export function createConfigHandler(deps: ConfigHandlerDeps) {
|
|||||||
|
|
||||||
applyToolConfig({ config, pluginConfig, agentResult });
|
applyToolConfig({ config, pluginConfig, agentResult });
|
||||||
await applyMcpConfig({ config, pluginConfig, pluginComponents });
|
await applyMcpConfig({ config, pluginConfig, pluginComponents });
|
||||||
await applyCommandConfig({ config, pluginConfig, pluginComponents });
|
await applyCommandConfig({ config, pluginConfig, ctx, pluginComponents });
|
||||||
|
|
||||||
log("[config-handler] config handler applied", {
|
log("[config-handler] config handler applied", {
|
||||||
agentCount: Object.keys(agentResult).length,
|
agentCount: Object.keys(agentResult).length,
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import type {
|
|||||||
} from "../features/opencode-skill-loader/types"
|
} from "../features/opencode-skill-loader/types"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
discoverConfigSourceSkills,
|
||||||
discoverUserClaudeSkills,
|
discoverUserClaudeSkills,
|
||||||
discoverProjectClaudeSkills,
|
discoverProjectClaudeSkills,
|
||||||
discoverOpencodeGlobalSkills,
|
discoverOpencodeGlobalSkills,
|
||||||
@ -54,8 +55,12 @@ export async function createSkillContext(args: {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const includeClaudeSkills = pluginConfig.claude_code?.skills !== false
|
const includeClaudeSkills = pluginConfig.claude_code?.skills !== false
|
||||||
const [userSkills, globalSkills, projectSkills, opencodeProjectSkills] =
|
const [configSourceSkills, userSkills, globalSkills, projectSkills, opencodeProjectSkills] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
discoverConfigSourceSkills({
|
||||||
|
config: pluginConfig.skills,
|
||||||
|
configDir: directory,
|
||||||
|
}),
|
||||||
includeClaudeSkills ? discoverUserClaudeSkills() : Promise.resolve([]),
|
includeClaudeSkills ? discoverUserClaudeSkills() : Promise.resolve([]),
|
||||||
discoverOpencodeGlobalSkills(),
|
discoverOpencodeGlobalSkills(),
|
||||||
includeClaudeSkills ? discoverProjectClaudeSkills() : Promise.resolve([]),
|
includeClaudeSkills ? discoverProjectClaudeSkills() : Promise.resolve([]),
|
||||||
@ -65,6 +70,7 @@ export async function createSkillContext(args: {
|
|||||||
const mergedSkills = mergeSkills(
|
const mergedSkills = mergeSkills(
|
||||||
builtinSkills,
|
builtinSkills,
|
||||||
pluginConfig.skills,
|
pluginConfig.skills,
|
||||||
|
configSourceSkills,
|
||||||
userSkills,
|
userSkills,
|
||||||
globalSkills,
|
globalSkills,
|
||||||
projectSkills,
|
projectSkills,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user