summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDax <[email protected]>2026-01-31 00:41:55 -0500
committerGitHub <[email protected]>2026-01-31 00:41:55 -0500
commit81ac41e0891cf9318af641805e7b1c5af1194be4 (patch)
treeeb27bad841764a9b5ed7f2cb6ea1ede3ff8320b2
parentc0e71c4261d0d23a9f24064c7b2094c207c25d2f (diff)
downloadopencode-81ac41e0891cf9318af641805e7b1c5af1194be4.tar.gz
opencode-81ac41e0891cf9318af641805e7b1c5af1194be4.zip
feat: make skills invokable as slash commands in the TUI (#11390)
-rw-r--r--packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx3
-rw-r--r--packages/opencode/src/command/index.ts20
-rw-r--r--packages/opencode/src/skill/skill.ts2
-rw-r--r--packages/opencode/src/tool/skill.ts3
-rw-r--r--packages/sdk/js/src/v2/gen/types.gen.ts3
5 files changed, 25 insertions, 6 deletions
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..bd000e2ab 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.source === "mcp" ? ":mcp" : serverCommand.source === "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..14dbeb679 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 = {
@@ -26,7 +27,7 @@ export namespace Command {
description: z.string().optional(),
agent: z.string().optional(),
model: z.string().optional(),
- mcp: z.boolean().optional(),
+ source: z.enum(["command", "mcp", "skill"]).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()),
@@ -94,7 +95,7 @@ export namespace Command {
for (const [name, prompt] of Object.entries(await MCP.prompts())) {
result[name] = {
name,
- mcp: true,
+ source: "mcp",
description: prompt.description,
get template() {
// since a getter can't be async we need to manually return a promise here
@@ -118,6 +119,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,
+ source: "skill",
+ get template() {
+ return skill.content
+ },
+ hints: [],
+ }
+ }
+
return result
})
diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts
index 5b300a928..6e05d013a 100644
--- a/packages/opencode/src/skill/skill.ts
+++ b/packages/opencode/src/skill/skill.ts
@@ -18,6 +18,7 @@ export namespace Skill {
name: z.string(),
description: z.string(),
location: z.string(),
+ content: z.string(),
})
export type Info = z.infer<typeof Info>
@@ -74,6 +75,7 @@ export namespace Skill {
name: parsed.data.name,
description: parsed.data.description,
location: match,
+ content: md.content,
}
}
diff --git a/packages/opencode/src/tool/skill.ts b/packages/opencode/src/tool/skill.ts
index 76d9fd535..8f285d599 100644
--- a/packages/opencode/src/tool/skill.ts
+++ b/packages/opencode/src/tool/skill.ts
@@ -2,7 +2,6 @@ import path from "path"
import z from "zod"
import { Tool } from "./tool"
import { Skill } from "../skill"
-import { ConfigMarkdown } from "../config/markdown"
import { PermissionNext } from "../permission/next"
export const SkillTool = Tool.define("skill", async (ctx) => {
@@ -62,7 +61,7 @@ export const SkillTool = Tool.define("skill", async (ctx) => {
always: [params.name],
metadata: {},
})
- const content = (await ConfigMarkdown.parse(skill.location)).content
+ const content = skill.content
const dir = path.dirname(skill.location)
// Format output similar to plugin pattern
diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts
index a8c61c4da..cb2f58677 100644
--- a/packages/sdk/js/src/v2/gen/types.gen.ts
+++ b/packages/sdk/js/src/v2/gen/types.gen.ts
@@ -2116,7 +2116,7 @@ export type Command = {
description?: string
agent?: string
model?: string
- mcp?: boolean
+ source?: "command" | "mcp" | "skill"
template: string
subtask?: boolean
hints: Array<string>
@@ -4913,6 +4913,7 @@ export type AppSkillsResponses = {
name: string
description: string
location: string
+ content: string
}>
}