diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx
index 4b177e292cf..9a1b331cbdb 100644
--- a/packages/opencode/src/cli/cmd/tui/app.tsx
+++ b/packages/opencode/src/cli/cmd/tui/app.tsx
@@ -18,6 +18,7 @@ import { DialogHelp } from "./ui/dialog-help"
import { CommandProvider, useCommandDialog } from "@tui/component/dialog-command"
import { DialogAgent } from "@tui/component/dialog-agent"
import { DialogSessionList } from "@tui/component/dialog-session-list"
+import { DialogSkillList } from "@tui/component/dialog-skill-list"
import { KeybindProvider } from "@tui/context/keybind"
import { ThemeProvider, useTheme } from "@tui/context/theme"
import { Home } from "@tui/routes/home"
@@ -393,6 +394,17 @@ function App() {
dialog.replace(() => )
},
},
+ {
+ title: "List skills",
+ value: "skill.list",
+ category: "Agent",
+ slash: {
+ name: "skills",
+ },
+ onSelect: () => {
+ dialog.replace(() => )
+ },
+ },
{
title: "Agent cycle",
value: "agent.cycle",
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-skill-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-skill-list.tsx
new file mode 100644
index 00000000000..75672bab9d3
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-skill-list.tsx
@@ -0,0 +1,79 @@
+import { createMemo, createSignal, createResource } from "solid-js"
+import { useSDK } from "@tui/context/sdk"
+import { DialogSelect } from "@tui/ui/dialog-select"
+import { useDialog } from "@tui/ui/dialog"
+import { useLocal } from "@tui/context/local"
+import { useRoute } from "@tui/context/route"
+import { Identifier } from "@/id/id"
+import { Global } from "@/global"
+
+function getSource(location: string): "global" | "project" {
+ const home = Global.Path.home
+ if (
+ location.startsWith(`${home}/.claude/`) ||
+ location.startsWith(`${home}/.opencode/`) ||
+ location.startsWith(`${home}/.config/opencode/`)
+ ) {
+ return "global"
+ }
+ return "project"
+}
+
+export function DialogSkillList() {
+ const sdk = useSDK()
+ const dialog = useDialog()
+ const local = useLocal()
+ const route = useRoute()
+ const [query, setQuery] = createSignal("")
+
+ const [skills] = createResource(async () => {
+ const result = await sdk.client.app.skills()
+ return result.data ?? []
+ })
+
+ const options = createMemo(() => {
+ const list = skills() ?? []
+ const q = query().toLowerCase()
+
+ return list
+ .filter((skill) => !q || skill.name.toLowerCase().includes(q) || skill.description.toLowerCase().includes(q))
+ .map((skill) => ({
+ value: skill,
+ title: skill.name,
+ category: getSource(skill.location) === "global" ? "Global" : "Project",
+ onSelect: async () => {
+ dialog.clear()
+ await invoke(skill.name)
+ },
+ }))
+ })
+
+ async function invoke(name: string) {
+ const selectedModel = local.model.current()
+ if (!selectedModel) return
+
+ const sessionID =
+ route.data.type === "session" ? route.data.sessionID : await sdk.client.session.create({}).then((x) => x.data!.id)
+
+ await sdk.client.session.prompt({
+ sessionID,
+ ...selectedModel,
+ messageID: Identifier.ascending("message"),
+ agent: local.agent.current().name,
+ model: selectedModel,
+ parts: [
+ {
+ id: Identifier.ascending("part"),
+ type: "text",
+ text: `Use the skill tool to load the "${name}" skill`,
+ },
+ ],
+ })
+
+ if (route.data.type !== "session") {
+ route.navigate({ type: "session", sessionID })
+ }
+ }
+
+ return
+}
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 e27c32dfb2e..1715274fdd6 100644
--- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
@@ -310,6 +310,16 @@ export function Autocomplete(props: {
return options
})
+ const [skills] = createResource(
+ () => store.visible === "/",
+ async (active) => {
+ if (!active) return []
+ const result = await sdk.client.app.skills()
+ return result.data ?? []
+ },
+ { initialValue: [] },
+ )
+
const agents = createMemo(() => {
const agents = sync.data.agent
return agents
@@ -349,6 +359,20 @@ export function Autocomplete(props: {
})
}
+ for (const skill of skills() ?? []) {
+ results.push({
+ display: "/" + skill.name,
+ description: skill.description,
+ onSelect: () => {
+ const cursor = props.input().logicalCursor
+ props.input().deleteRange(0, 0, cursor.row, cursor.col)
+ const text = `Use the skill tool to load the "${skill.name}" skill`
+ props.input().insertText(text)
+ props.input().cursorOffset = Bun.stringWidth(text)
+ },
+ })
+ }
+
results.sort((a, b) => a.display.localeCompare(b.display))
const max = firstBy(results, [(x) => x.display.length, "desc"])?.display.length