diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index 718929d44..53ff79539 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -345,8 +345,9 @@ export function Autocomplete(props: { const results: AutocompleteOption[] = [...command.slashes()] for (const serverCommand of sync.data.command) { + const label = serverCommand.mcp ? " (MCP)" : serverCommand.skill ? " (Skill)" : "" results.push({ - display: "/" + serverCommand.name + (serverCommand.mcp ? " (MCP)" : ""), + display: "/" + serverCommand.name + label, description: serverCommand.description, onSelect: () => { const newText = "/" + serverCommand.name + " " diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index 976f1cd51..96276800c 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -6,6 +6,7 @@ import { Identifier } from "../id/id" import PROMPT_INITIALIZE from "./template/initialize.txt" import PROMPT_REVIEW from "./template/review.txt" import { MCP } from "../mcp" +import { Skill } from "../skill" export namespace Command { export const Event = { @@ -27,6 +28,7 @@ export namespace Command { agent: z.string().optional(), model: z.string().optional(), mcp: z.boolean().optional(), + skill: z.boolean().optional(), // workaround for zod not supporting async functions natively so we use getters // https://zod.dev/v4/changelog?id=zfunction template: z.promise(z.string()).or(z.string()), @@ -118,6 +120,21 @@ export namespace Command { } } + // Add skills as invokable commands + for (const skill of await Skill.all()) { + // Skip if a command with this name already exists + if (result[skill.name]) continue + result[skill.name] = { + name: skill.name, + description: skill.description, + skill: true, + get template() { + return Skill.content(skill.name).then((content) => content ?? "") + }, + hints: [], + } + } + return result }) diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts index 5b300a928..d5da85cfb 100644 --- a/packages/opencode/src/skill/skill.ts +++ b/packages/opencode/src/skill/skill.ts @@ -153,4 +153,11 @@ export namespace Skill { export async function all() { return state().then((x) => Object.values(x)) } + + export async function content(name: string) { + const info = await get(name) + if (!info) return undefined + const md = await ConfigMarkdown.parse(info.location) + return md.content + } } diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index a8c61c4da..ad8b18dfa 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -2117,6 +2117,7 @@ export type Command = { agent?: string model?: string mcp?: boolean + skill?: boolean template: string subtask?: boolean hints: Array