|
@@ -6,6 +6,7 @@ import { Instance } from "../project/instance"
|
|
|
import { Identifier } from "../id/id"
|
|
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"
|
|
|
|
|
|
|
|
export namespace Command {
|
|
export namespace Command {
|
|
|
export const Event = {
|
|
export const Event = {
|
|
@@ -26,13 +27,28 @@ 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(),
|
|
|
- template: z.string(),
|
|
|
|
|
|
|
+ // 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()),
|
|
|
subtask: z.boolean().optional(),
|
|
subtask: z.boolean().optional(),
|
|
|
|
|
+ hints: z.array(z.string()),
|
|
|
})
|
|
})
|
|
|
.meta({
|
|
.meta({
|
|
|
ref: "Command",
|
|
ref: "Command",
|
|
|
})
|
|
})
|
|
|
- export type Info = z.infer<typeof Info>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // for some reason zod is inferring `string` for z.promise(z.string()).or(z.string()) so we have to manually override it
|
|
|
|
|
+ export type Info = Omit<z.infer<typeof Info>, "template"> & { template: Promise<string> | string }
|
|
|
|
|
+
|
|
|
|
|
+ export function hints(template: string): string[] {
|
|
|
|
|
+ const result: string[] = []
|
|
|
|
|
+ const numbered = template.match(/\$\d+/g)
|
|
|
|
|
+ if (numbered) {
|
|
|
|
|
+ for (const match of [...new Set(numbered)].sort()) result.push(match)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (template.includes("$ARGUMENTS")) result.push("$ARGUMENTS")
|
|
|
|
|
+ return result
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
export const Default = {
|
|
export const Default = {
|
|
|
INIT: "init",
|
|
INIT: "init",
|
|
@@ -46,13 +62,19 @@ export namespace Command {
|
|
|
[Default.INIT]: {
|
|
[Default.INIT]: {
|
|
|
name: Default.INIT,
|
|
name: Default.INIT,
|
|
|
description: "create/update AGENTS.md",
|
|
description: "create/update AGENTS.md",
|
|
|
- template: PROMPT_INITIALIZE.replace("${path}", Instance.worktree),
|
|
|
|
|
|
|
+ get template() {
|
|
|
|
|
+ return PROMPT_INITIALIZE.replace("${path}", Instance.worktree)
|
|
|
|
|
+ },
|
|
|
|
|
+ hints: hints(PROMPT_INITIALIZE),
|
|
|
},
|
|
},
|
|
|
[Default.REVIEW]: {
|
|
[Default.REVIEW]: {
|
|
|
name: Default.REVIEW,
|
|
name: Default.REVIEW,
|
|
|
description: "review changes [commit|branch|pr], defaults to uncommitted",
|
|
description: "review changes [commit|branch|pr], defaults to uncommitted",
|
|
|
- template: PROMPT_REVIEW.replace("${path}", Instance.worktree),
|
|
|
|
|
|
|
+ get template() {
|
|
|
|
|
+ return PROMPT_REVIEW.replace("${path}", Instance.worktree)
|
|
|
|
|
+ },
|
|
|
subtask: true,
|
|
subtask: true,
|
|
|
|
|
+ hints: hints(PROMPT_REVIEW),
|
|
|
},
|
|
},
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -62,8 +84,36 @@ export namespace Command {
|
|
|
agent: command.agent,
|
|
agent: command.agent,
|
|
|
model: command.model,
|
|
model: command.model,
|
|
|
description: command.description,
|
|
description: command.description,
|
|
|
- template: command.template,
|
|
|
|
|
|
|
+ get template() {
|
|
|
|
|
+ return command.template
|
|
|
|
|
+ },
|
|
|
subtask: command.subtask,
|
|
subtask: command.subtask,
|
|
|
|
|
+ hints: hints(command.template),
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ for (const [name, prompt] of Object.entries(await MCP.prompts())) {
|
|
|
|
|
+ result[name] = {
|
|
|
|
|
+ name,
|
|
|
|
|
+ description: prompt.description,
|
|
|
|
|
+ get template() {
|
|
|
|
|
+ // since a getter can't be async we need to manually return a promise here
|
|
|
|
|
+ return new Promise<string>(async (resolve, reject) => {
|
|
|
|
|
+ const template = await MCP.getPrompt(
|
|
|
|
|
+ prompt.client,
|
|
|
|
|
+ prompt.name,
|
|
|
|
|
+ prompt.arguments
|
|
|
|
|
+ ? // substitute each argument with $1, $2, etc.
|
|
|
|
|
+ Object.fromEntries(prompt.arguments?.map((argument, i) => [argument.name, `$${i + 1}`]))
|
|
|
|
|
+ : {},
|
|
|
|
|
+ ).catch(reject)
|
|
|
|
|
+ resolve(
|
|
|
|
|
+ template?.messages
|
|
|
|
|
+ .map((message) => (message.content.type === "text" ? message.content.text : ""))
|
|
|
|
|
+ .join("\n") || "",
|
|
|
|
|
+ )
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ hints: prompt.arguments?.map((_, i) => `$${i + 1}`) ?? [],
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|