|
1 | | -import type { SkillInfo } from "@vegamo/deepcode-core"; |
2 | | - |
3 | | -export type SlashCommandKind = |
4 | | - | "skill" |
5 | | - | "skills" |
6 | | - | "model" |
7 | | - | "permissions" |
8 | | - | "plan" |
9 | | - | "new" |
10 | | - | "init" |
11 | | - | "resume" |
12 | | - | "fork" |
13 | | - | "continue" |
14 | | - | "compact" |
15 | | - | "undo" |
16 | | - | "mcp" |
17 | | - | "raw" |
18 | | - | "exit"; |
19 | | - |
20 | | -export type SlashCommandItem = { |
21 | | - kind: SlashCommandKind; |
22 | | - name: string; |
23 | | - label: string; |
24 | | - description: string; |
25 | | - skill?: SkillInfo; |
26 | | - args?: string[]; |
27 | | -}; |
28 | | - |
29 | | -export const BUILTIN_SLASH_COMMANDS: SlashCommandItem[] = [ |
30 | | - { |
31 | | - kind: "skills", |
32 | | - name: "skills", |
33 | | - label: "/skills", |
34 | | - description: "List available skills", |
35 | | - }, |
36 | | - { |
37 | | - kind: "model", |
38 | | - name: "model", |
39 | | - label: "/model", |
40 | | - description: "Select model, thinking mode and effort control", |
41 | | - }, |
42 | | - { |
43 | | - kind: "permissions", |
44 | | - name: "permissions", |
45 | | - label: "/permissions", |
46 | | - description: "Configure tool permissions", |
47 | | - }, |
48 | | - { |
49 | | - kind: "plan", |
50 | | - name: "plan", |
51 | | - label: "/plan", |
52 | | - description: "Switch the input to Plan Mode", |
53 | | - }, |
54 | | - { |
55 | | - kind: "new", |
56 | | - name: "new", |
57 | | - label: "/new", |
58 | | - description: "Start a fresh conversation", |
59 | | - }, |
60 | | - { |
61 | | - kind: "init", |
62 | | - name: "init", |
63 | | - label: "/init", |
64 | | - description: "Initialize an AGENTS.md file with instructions for LLM", |
65 | | - }, |
66 | | - { |
67 | | - kind: "resume", |
68 | | - name: "resume", |
69 | | - label: "/resume", |
70 | | - description: "Pick a previous conversation to continue", |
71 | | - }, |
72 | | - { |
73 | | - kind: "fork", |
74 | | - name: "fork", |
75 | | - label: "/fork", |
76 | | - description: "Fork the current conversation", |
77 | | - }, |
78 | | - { |
79 | | - kind: "continue", |
80 | | - name: "continue", |
81 | | - label: "/continue", |
82 | | - description: "Continue the active conversation or pick one to resume", |
83 | | - }, |
84 | | - { |
85 | | - kind: "compact", |
86 | | - name: "compact", |
87 | | - label: "/compact", |
88 | | - description: "Compact the current conversation", |
89 | | - }, |
90 | | - { |
91 | | - kind: "undo", |
92 | | - name: "undo", |
93 | | - label: "/undo", |
94 | | - description: "Restore code and/or conversation to a previous point", |
95 | | - }, |
96 | | - { |
97 | | - kind: "mcp", |
98 | | - name: "mcp", |
99 | | - label: "/mcp", |
100 | | - description: "Show MCP server status and available tools", |
101 | | - }, |
102 | | - { |
103 | | - kind: "raw", |
104 | | - name: "raw", |
105 | | - label: "/raw", |
106 | | - args: ["lite", "normal", "raw-scrollback"], |
107 | | - description: "Toggle display mode for viewing or collapsing reasoning content", |
108 | | - }, |
109 | | - { |
110 | | - kind: "exit", |
111 | | - name: "exit", |
112 | | - label: "/exit", |
113 | | - description: "Quit Deep Code CLI", |
114 | | - }, |
115 | | -]; |
116 | | - |
117 | | -export function buildSlashCommands(skills: SkillInfo[]): SlashCommandItem[] { |
118 | | - const skillItems: SlashCommandItem[] = skills.map((skill) => ({ |
119 | | - kind: "skill", |
120 | | - name: skill.name, |
121 | | - label: `/${skill.name}`, |
122 | | - description: skill.description || "(no description)", |
123 | | - skill, |
124 | | - })); |
125 | | - return [...skillItems, ...BUILTIN_SLASH_COMMANDS]; |
126 | | -} |
127 | | - |
128 | | -export function filterSlashCommands(items: SlashCommandItem[], token: string): SlashCommandItem[] { |
129 | | - if (!token.startsWith("/")) { |
130 | | - return []; |
131 | | - } |
132 | | - const query = token.slice(1).toLowerCase(); |
133 | | - if (!query) { |
134 | | - return items; |
135 | | - } |
136 | | - const prefixMatches: SlashCommandItem[] = []; |
137 | | - const otherMatches: SlashCommandItem[] = []; |
138 | | - for (const item of items) { |
139 | | - const name = item.name.toLowerCase(); |
140 | | - if (name.startsWith(query)) { |
141 | | - prefixMatches.push(item); |
142 | | - } else if (name.includes(query)) { |
143 | | - otherMatches.push(item); |
144 | | - } |
145 | | - } |
146 | | - return [...prefixMatches, ...otherMatches]; |
147 | | -} |
148 | | - |
149 | | -export function findExactSlashCommand(items: SlashCommandItem[], token: string): SlashCommandItem | null { |
150 | | - if (!token.startsWith("/")) { |
151 | | - return null; |
152 | | - } |
153 | | - const query = token.slice(1); |
154 | | - const matches = items.filter((item) => item.name === query); |
155 | | - return matches.find((item) => item.kind !== "skill") ?? matches[0] ?? null; |
156 | | -} |
157 | | - |
158 | | -export type SlashSubmission = |
159 | | - | { kind: "command"; item: SlashCommandItem } |
160 | | - | { kind: "plan"; item: SlashCommandItem; text: string } |
161 | | - | { kind: "raw"; item: SlashCommandItem; mode: string } |
162 | | - | { kind: "argument-error"; item: SlashCommandItem; argument: string }; |
163 | | - |
164 | | -/** |
165 | | - * Classify a submitted slash input without touching UI state. A command alone |
166 | | - * runs as before; `/plan <text>` is a one-shot plan-mode prompt; `/raw <mode>` |
167 | | - * selects that mode and reports an unknown mode. Other commands keep their |
168 | | - * existing behavior and ignore trailing text. |
169 | | - */ |
170 | | -export function resolveSlashSubmission(items: SlashCommandItem[], text: string): SlashSubmission | null { |
171 | | - const trimmed = text.trim(); |
172 | | - if (!trimmed.startsWith("/")) { |
173 | | - return null; |
174 | | - } |
175 | | - const match = /^(\S+)([\s\S]*)$/.exec(trimmed); |
176 | | - if (!match) { |
177 | | - return null; |
178 | | - } |
179 | | - const item = findExactSlashCommand(items, match[1]); |
180 | | - if (!item) { |
181 | | - return null; |
182 | | - } |
183 | | - const rest = match[2].trim(); |
184 | | - if (!rest) { |
185 | | - return { kind: "command", item }; |
186 | | - } |
187 | | - if (item.kind === "plan") { |
188 | | - return { kind: "plan", item, text: rest }; |
189 | | - } |
190 | | - if (item.kind === "raw") { |
191 | | - if (item.args?.includes(rest)) { |
192 | | - return { kind: "raw", item, mode: rest }; |
193 | | - } |
194 | | - return { kind: "argument-error", item, argument: rest }; |
195 | | - } |
196 | | - // Other commands keep their existing behavior and ignore trailing text. |
197 | | - return { kind: "command", item }; |
198 | | -} |
199 | | - |
200 | | -export function formatSlashCommandDescription(description: string): string { |
201 | | - return (description || "(no description)").trim().replace(/\s+/g, " "); |
202 | | -} |
203 | | - |
204 | | -export function formatSlashCommandLabel(item: SlashCommandItem): string { |
205 | | - return item.kind === "skill" && item.skill?.isLoaded ? `${item.label} ✓` : item.label; |
206 | | -} |
| 1 | +export * from "@vegamo/deepcode-core/common/slash-commands"; |
0 commit comments