feat(app): add skill slash commands (#11369)

This commit is contained in:
Ryan Vogel
2026-01-31 09:59:28 -05:00
committed by GitHub
parent f73f88fb56
commit 786ae0a584
5 changed files with 59 additions and 2 deletions

View File

@@ -111,7 +111,7 @@ interface SlashCommand {
title: string
description?: string
keybind?: string
type: "builtin" | "custom"
type: "builtin" | "custom" | "skill"
}
export const PromptInput: Component<PromptInputProps> = (props) => {
@@ -519,7 +519,15 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
type: "custom" as const,
}))
return [...custom, ...builtin]
const skills = sync.data.skill.map((skill) => ({
id: `skill.${skill.name}`,
trigger: `skill:${skill.name}`,
title: skill.name,
description: skill.description,
type: "skill" as const,
}))
return [...skills, ...custom, ...builtin]
})
const handleSlashSelect = (cmd: SlashCommand | undefined) => {
@@ -543,6 +551,25 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
return
}
if (cmd.type === "skill") {
// Extract skill name from the id (skill.{name})
const skillName = cmd.id.replace("skill.", "")
const text = `Load the "${skillName}" skill and follow its instructions.`
editorRef.innerHTML = ""
editorRef.textContent = text
prompt.set([{ type: "text", content: text, start: 0, end: text.length }], text.length)
requestAnimationFrame(() => {
editorRef.focus()
const range = document.createRange()
const sel = window.getSelection()
range.selectNodeContents(editorRef)
range.collapse(false)
sel?.removeAllRanges()
sel?.addRange(range)
})
return
}
editorRef.innerHTML = ""
prompt.set([{ type: "text", content: "", start: 0, end: 0 }], 0)
command.trigger(cmd.id, "slash")
@@ -1706,6 +1733,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
{language.t("prompt.slash.badge.custom")}
</span>
</Show>
<Show when={cmd.type === "skill"}>
<span class="text-11-regular text-text-subtle px-1.5 py-0.5 bg-surface-base rounded">
{language.t("prompt.slash.badge.skill")}
</span>
</Show>
<Show when={command.keybind(cmd.id)}>
<span class="text-12-regular text-text-subtle">{command.keybind(cmd.id)}</span>
</Show>

View File

@@ -17,6 +17,7 @@ import {
type VcsInfo,
type PermissionRequest,
type QuestionRequest,
type AppSkillsResponse,
createOpencodeClient,
} from "@opencode-ai/sdk/v2/client"
import { createStore, produce, reconcile, type SetStoreFunction, type Store } from "solid-js/store"
@@ -56,10 +57,13 @@ type ProjectMeta = {
}
}
export type Skill = AppSkillsResponse[number]
type State = {
status: "loading" | "partial" | "complete"
agent: Agent[]
command: Command[]
skill: Skill[]
project: string
projectMeta: ProjectMeta | undefined
icon: string | undefined
@@ -388,6 +392,7 @@ function createGlobalSync() {
status: "loading" as const,
agent: [],
command: [],
skill: [],
session: [],
sessionTotal: 0,
session_status: {},
@@ -528,6 +533,7 @@ function createGlobalSync() {
Promise.all([
sdk.path.get().then((x) => setStore("path", x.data!)),
sdk.command.list().then((x) => setStore("command", x.data ?? [])),
sdk.app.skills().then((x) => setStore("skill", x.data ?? [])),
sdk.session.status().then((x) => setStore("session_status", x.data!)),
loadSessions(directory),
sdk.mcp.status().then((x) => setStore("mcp", x.data!)),

View File

@@ -216,6 +216,7 @@ export const dict = {
"prompt.popover.emptyCommands": "No matching commands",
"prompt.dropzone.label": "Drop images or PDFs here",
"prompt.slash.badge.custom": "custom",
"prompt.slash.badge.skill": "skill",
"prompt.context.active": "active",
"prompt.context.includeActiveFile": "Include active file",
"prompt.context.removeActiveFile": "Remove active file from context",