feat: make skills invokable as slash commands in the TUI (#11390)
This commit is contained in:
@@ -345,8 +345,9 @@ export function Autocomplete(props: {
|
|||||||
const results: AutocompleteOption[] = [...command.slashes()]
|
const results: AutocompleteOption[] = [...command.slashes()]
|
||||||
|
|
||||||
for (const serverCommand of sync.data.command) {
|
for (const serverCommand of sync.data.command) {
|
||||||
|
const label = serverCommand.source === "mcp" ? ":mcp" : serverCommand.source === "skill" ? ":skill" : ""
|
||||||
results.push({
|
results.push({
|
||||||
display: "/" + serverCommand.name + (serverCommand.mcp ? " (MCP)" : ""),
|
display: "/" + serverCommand.name + label,
|
||||||
description: serverCommand.description,
|
description: serverCommand.description,
|
||||||
onSelect: () => {
|
onSelect: () => {
|
||||||
const newText = "/" + serverCommand.name + " "
|
const newText = "/" + serverCommand.name + " "
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Identifier } from "../id/id"
|
|||||||
import PROMPT_INITIALIZE from "./template/initialize.txt"
|
import PROMPT_INITIALIZE from "./template/initialize.txt"
|
||||||
import PROMPT_REVIEW from "./template/review.txt"
|
import PROMPT_REVIEW from "./template/review.txt"
|
||||||
import { MCP } from "../mcp"
|
import { MCP } from "../mcp"
|
||||||
|
import { Skill } from "../skill"
|
||||||
|
|
||||||
export namespace Command {
|
export namespace Command {
|
||||||
export const Event = {
|
export const Event = {
|
||||||
@@ -26,7 +27,7 @@ export namespace Command {
|
|||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
agent: z.string().optional(),
|
agent: z.string().optional(),
|
||||||
model: 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
|
// workaround for zod not supporting async functions natively so we use getters
|
||||||
// https://zod.dev/v4/changelog?id=zfunction
|
// https://zod.dev/v4/changelog?id=zfunction
|
||||||
template: z.promise(z.string()).or(z.string()),
|
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())) {
|
for (const [name, prompt] of Object.entries(await MCP.prompts())) {
|
||||||
result[name] = {
|
result[name] = {
|
||||||
name,
|
name,
|
||||||
mcp: true,
|
source: "mcp",
|
||||||
description: prompt.description,
|
description: prompt.description,
|
||||||
get template() {
|
get template() {
|
||||||
// since a getter can't be async we need to manually return a promise here
|
// 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
|
return result
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export namespace Skill {
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
location: z.string(),
|
location: z.string(),
|
||||||
|
content: z.string(),
|
||||||
})
|
})
|
||||||
export type Info = z.infer<typeof Info>
|
export type Info = z.infer<typeof Info>
|
||||||
|
|
||||||
@@ -74,6 +75,7 @@ export namespace Skill {
|
|||||||
name: parsed.data.name,
|
name: parsed.data.name,
|
||||||
description: parsed.data.description,
|
description: parsed.data.description,
|
||||||
location: match,
|
location: match,
|
||||||
|
content: md.content,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import path from "path"
|
|||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import { Skill } from "../skill"
|
import { Skill } from "../skill"
|
||||||
import { ConfigMarkdown } from "../config/markdown"
|
|
||||||
import { PermissionNext } from "../permission/next"
|
import { PermissionNext } from "../permission/next"
|
||||||
|
|
||||||
export const SkillTool = Tool.define("skill", async (ctx) => {
|
export const SkillTool = Tool.define("skill", async (ctx) => {
|
||||||
@@ -62,7 +61,7 @@ export const SkillTool = Tool.define("skill", async (ctx) => {
|
|||||||
always: [params.name],
|
always: [params.name],
|
||||||
metadata: {},
|
metadata: {},
|
||||||
})
|
})
|
||||||
const content = (await ConfigMarkdown.parse(skill.location)).content
|
const content = skill.content
|
||||||
const dir = path.dirname(skill.location)
|
const dir = path.dirname(skill.location)
|
||||||
|
|
||||||
// Format output similar to plugin pattern
|
// Format output similar to plugin pattern
|
||||||
|
|||||||
@@ -2116,7 +2116,7 @@ export type Command = {
|
|||||||
description?: string
|
description?: string
|
||||||
agent?: string
|
agent?: string
|
||||||
model?: string
|
model?: string
|
||||||
mcp?: boolean
|
source?: "command" | "mcp" | "skill"
|
||||||
template: string
|
template: string
|
||||||
subtask?: boolean
|
subtask?: boolean
|
||||||
hints: Array<string>
|
hints: Array<string>
|
||||||
@@ -4913,6 +4913,7 @@ export type AppSkillsResponses = {
|
|||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
location: string
|
location: string
|
||||||
|
content: string
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user