1use crate::skills::model::{SkillMetadata, SkillScope};
2use crate::skills::types::{SkillContext, SkillManifest, SkillManifestMetadata, SkillVariety};
3use hashbrown::HashMap;
4use serde_json::json;
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum BuiltInCommandExecutor {
9 SlashAlias,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum CommandSkillBackend {
14 TraditionalSkill {
15 skill_name: &'static str,
16 skill_path: &'static str,
17 },
18 BuiltInCommand {
19 executor: BuiltInCommandExecutor,
20 },
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct CommandSkillSpec {
25 pub slash_name: &'static str,
26 pub skill_name: &'static str,
27 pub description: &'static str,
28 pub usage: &'static str,
29 pub category: &'static str,
30 pub backend: CommandSkillBackend,
31}
32
33impl CommandSkillSpec {
34 pub const fn is_traditional(self) -> bool {
35 matches!(self.backend, CommandSkillBackend::TraditionalSkill { .. })
36 }
37
38 pub const fn is_built_in(self) -> bool {
39 matches!(self.backend, CommandSkillBackend::BuiltInCommand { .. })
40 }
41}
42
43#[derive(Debug, Clone)]
44pub struct BuiltInCommandSkill {
45 spec: &'static CommandSkillSpec,
46 manifest: SkillManifest,
47 path: PathBuf,
48}
49
50impl BuiltInCommandSkill {
51 pub fn from_spec(spec: &'static CommandSkillSpec) -> Self {
52 Self {
53 spec,
54 manifest: built_in_manifest(spec),
55 path: built_in_path(spec.skill_name),
56 }
57 }
58
59 pub fn spec(&self) -> &'static CommandSkillSpec {
60 self.spec
61 }
62
63 pub fn manifest(&self) -> &SkillManifest {
64 &self.manifest
65 }
66
67 pub fn name(&self) -> &str {
68 &self.manifest.name
69 }
70
71 pub fn description(&self) -> &str {
72 &self.manifest.description
73 }
74
75 pub fn usage(&self) -> &'static str {
76 self.spec.usage
77 }
78
79 pub fn category(&self) -> &'static str {
80 self.spec.category
81 }
82
83 pub fn slash_name(&self) -> &'static str {
84 self.spec.slash_name
85 }
86
87 pub fn path(&self) -> &PathBuf {
88 &self.path
89 }
90
91 pub fn scope(&self) -> SkillScope {
92 SkillScope::System
93 }
94
95 pub fn instructions(&self) -> String {
96 format!(
97 "# {}\n\nThis built-in command skill executes the existing `/{}` slash command backend.\n\n- Slash alias: `/{}`
98\n- Usage: `{}`
99\n- Category: `{}`
100\n- Backend: `built_in`\n",
101 self.name(),
102 self.slash_name(),
103 self.slash_name(),
104 self.usage(),
105 self.category()
106 )
107 }
108}
109
110macro_rules! built_in_command_spec {
111 ($slash:literal, $description:literal, $usage:literal, $category:literal) => {
112 CommandSkillSpec {
113 slash_name: $slash,
114 skill_name: concat!("cmd-", $slash),
115 description: $description,
116 usage: $usage,
117 category: $category,
118 backend: CommandSkillBackend::BuiltInCommand {
119 executor: BuiltInCommandExecutor::SlashAlias,
120 },
121 }
122 };
123}
124
125macro_rules! traditional_command_spec {
126 ($slash:literal, $description:literal, $usage:literal, $category:literal, $skill_path:literal) => {
127 CommandSkillSpec {
128 slash_name: $slash,
129 skill_name: concat!("cmd-", $slash),
130 description: $description,
131 usage: $usage,
132 category: $category,
133 backend: CommandSkillBackend::TraditionalSkill {
134 skill_name: concat!("cmd-", $slash),
135 skill_path: $skill_path,
136 },
137 }
138 };
139}
140
141pub const COMMAND_SKILL_SPECS: &[CommandSkillSpec] = &[
142 built_in_command_spec!(
143 "init",
144 "Guided workspace setup for vtcode.toml, AGENTS.md, and indexing (usage: /init [--force])",
145 "/init [--force]",
146 "workspace"
147 ),
148 built_in_command_spec!(
149 "loop",
150 "Run a session-scoped scheduled prompt repeatedly (usage: /loop [interval] <prompt>)",
151 "/loop [interval] <prompt>",
152 "automation"
153 ),
154 built_in_command_spec!(
155 "schedule",
156 "Manage durable scheduled tasks interactively (usage: /schedule)",
157 "/schedule [list|create|delete ...]",
158 "automation"
159 ),
160 built_in_command_spec!(
161 "config",
162 "Browse settings sections or focused memory controls (usage: /config [path|memory])",
163 "/config [path|memory]",
164 "configuration"
165 ),
166 built_in_command_spec!(
167 "hooks",
168 "Browse resolved lifecycle hooks for this session and workspace",
169 "/hooks",
170 "configuration"
171 ),
172 built_in_command_spec!(
173 "permissions",
174 "Open the permissions settings section and effective summary",
175 "/permissions",
176 "configuration"
177 ),
178 built_in_command_spec!(
179 "memory",
180 "Show memory status, loaded AGENTS/rules, and quick memory actions",
181 "/memory",
182 "configuration"
183 ),
184 built_in_command_spec!(
185 "model",
186 "Launch the interactive model picker",
187 "/model",
188 "configuration"
189 ),
190 built_in_command_spec!(
191 "effort",
192 "Set model effort level for this conversation (usage: /effort [--persist] [level])",
193 "/effort [--persist] [none|minimal|low|medium|high|xhigh|max]",
194 "configuration"
195 ),
196 built_in_command_spec!(
197 "ide",
198 "Toggle IDE context for this session",
199 "/ide",
200 "configuration"
201 ),
202 built_in_command_spec!(
203 "theme",
204 "Switch UI theme (usage: /theme <theme-id>)",
205 "/theme [theme-id]",
206 "configuration"
207 ),
208 traditional_command_spec!(
209 "command",
210 "Run a terminal command (usage: /command <program> [args...])",
211 "/command <program> [args...]",
212 "tools",
213 ".system/cmd-command"
214 ),
215 built_in_command_spec!(
216 "edit",
217 "Open file in external editor (tools.editor config, then VISUAL/EDITOR) (usage: /edit [file])",
218 "/edit [file]",
219 "tools"
220 ),
221 built_in_command_spec!(
222 "git",
223 "Launch git interface (lazygit or interactive git)",
224 "/git",
225 "tools"
226 ),
227 traditional_command_spec!(
228 "analyze",
229 "Perform comprehensive codebase analysis and generate reports (usage: /analyze [full|security|performance])",
230 "/analyze [full|security|performance]",
231 "tools",
232 ".system/cmd-analyze"
233 ),
234 traditional_command_spec!(
235 "review",
236 "Review the current diff or selected files (usage: /review [--last-diff|--target <expr>|--file <path>|files...] [--style <style>])",
237 "/review [--last-diff|--target <expr>|--file <path>|files...] [--style <style>]",
238 "tools",
239 ".system/cmd-review"
240 ),
241 built_in_command_spec!(
242 "files",
243 "Browse and select files from workspace (usage: /files [filter])",
244 "/files [filter]",
245 "tools"
246 ),
247 built_in_command_spec!(
248 "copy",
249 "Copy the latest complete assistant reply to clipboard",
250 "/copy",
251 "tools"
252 ),
253 built_in_command_spec!(
254 "suggest",
255 "Suggest follow-up prompts from the current session context",
256 "/suggest",
257 "tools"
258 ),
259 built_in_command_spec!(
260 "tasks",
261 "Toggle the dedicated TODO panel fed by task_tracker output",
262 "/tasks",
263 "tools"
264 ),
265 built_in_command_spec!(
266 "jobs",
267 "Inspect active/background command sessions",
268 "/jobs",
269 "tools"
270 ),
271 built_in_command_spec!(
272 "skills",
273 "Open interactive skills manager (usage: /skills, /skills manager)",
274 "/skills [manager|list|search|create|load|unload|info|use|validate|package|regenerate-index|help]",
275 "tools"
276 ),
277 built_in_command_spec!(
278 "agents",
279 "Manage subagents and delegated child threads (usage: /agents [list|create [project|user] [name]|edit [name]|delete <name>|threads])",
280 "/agents [list|threads|inspect <id>|close <id>|create [project|user] [name]|edit [name]|delete <name>]",
281 "tools"
282 ),
283 built_in_command_spec!(
284 "agent",
285 "Show delegated child threads for the current session",
286 "/agent [threads|inspect <id>|close <id>]",
287 "tools"
288 ),
289 built_in_command_spec!(
290 "subprocess",
291 "Open local agents or manage background subprocesses (usage: /subprocess[es] [list|toggle|refresh|inspect <id>|stop <id>|cancel <id>])",
292 "/subprocess[es] [list|toggle|refresh|inspect <id>|stop <id>|cancel <id>]",
293 "tools"
294 ),
295 built_in_command_spec!(
296 "status",
297 "Show model, provider, workspace, and tool status",
298 "/status",
299 "status"
300 ),
301 built_in_command_spec!(
302 "notify",
303 "Send a VT Code notification immediately (usage: /notify [message])",
304 "/notify [message]",
305 "status"
306 ),
307 built_in_command_spec!(
308 "stop",
309 "Stop the active turn immediately",
310 "/stop",
311 "status"
312 ),
313 built_in_command_spec!(
314 "pause",
315 "Pause the active turn at the next safe boundary",
316 "/pause",
317 "status"
318 ),
319 built_in_command_spec!(
320 "doctor",
321 "Run installation and configuration diagnostics (interactive in inline UI; usage: /doctor [--quick|--full])",
322 "/doctor [--quick|--full]",
323 "status"
324 ),
325 built_in_command_spec!(
326 "update",
327 "Check for new VT Code releases and install updates (usage: /update [check|install] [--force])",
328 "/update [check|install] [--force]",
329 "status"
330 ),
331 built_in_command_spec!(
332 "mcp",
333 "Open interactive MCP manager (usage: /mcp, optional subcommands still supported)",
334 "/mcp [status|list|tools|refresh|config|config edit|repair|diagnose|login <name>|logout <name>]",
335 "integration"
336 ),
337 built_in_command_spec!(
338 "local",
339 "Manage local inference servers: Ollama, LM Studio, llama.cpp (usage: /local [action] [provider])",
340 "/local [status|start|stop|configure|troubleshoot] [ollama|lmstudio|llamacpp]",
341 "integration"
342 ),
343 built_in_command_spec!(
344 "resume",
345 "List archived sessions when idle; resume the active turn while it is paused",
346 "/resume [limit|--all]",
347 "session"
348 ),
349 built_in_command_spec!(
350 "fork",
351 "Fork an archived session into a new thread (usage: /fork [limit] [--all])",
352 "/fork [limit] [--all]",
353 "session"
354 ),
355 built_in_command_spec!(
356 "history",
357 "Open command history picker (usage: /history, same as Ctrl+R)",
358 "/history",
359 "session"
360 ),
361 built_in_command_spec!(
362 "clear",
363 "Clear visible screen (usage: /clear [new])",
364 "/clear [new]",
365 "session"
366 ),
367 built_in_command_spec!(
368 "compact",
369 "Compact the current conversation immediately or manage the saved manual compaction prompt",
370 "/compact [--instructions <text>] [--max-output-tokens <n>] [--reasoning-effort <none|minimal|low|medium|high|xhigh>] [--verbosity <low|medium|high>] [--include <selector> ...] [--store|--no-store] [--service-tier <flex|priority>] [--prompt-cache-key <key>] | /compact edit-prompt | /compact reset-prompt",
371 "session"
372 ),
373 built_in_command_spec!("new", "Start a new session", "/new", "session"),
374 built_in_command_spec!(
375 "share",
376 "Export the current session as JSON, Markdown, or self-contained HTML timeline (usage: /share [json|markdown|html])",
377 "/share [json|markdown|html]",
378 "session"
379 ),
380 built_in_command_spec!(
381 "rewind",
382 "Open the rewind picker or restore a specific checkpoint (usage: /rewind [turn] [conversation|code|both])",
383 "/rewind [turn] [conversation|code|both]",
384 "session"
385 ),
386 built_in_command_spec!(
387 "plan",
388 "Start or continue the planning workflow with an optional task prompt (usage: /plan [task])",
389 "/plan [task]",
390 "session"
391 ),
392 built_in_command_spec!(
393 "docs",
394 "Open vtcode documentation in web browser",
395 "/docs",
396 "support"
397 ),
398 built_in_command_spec!(
399 "help",
400 "Show slash command help",
401 "/help [command]",
402 "support"
403 ),
404 built_in_command_spec!("exit", "Exit the session", "/exit", "session"),
405 built_in_command_spec!(
406 "donate",
407 "Support the project by buying the author a coffee",
408 "/donate",
409 "support"
410 ),
411 built_in_command_spec!(
412 "terminal-setup",
413 "Configure terminal for VT Code (multiline, copy/paste, shell, themes)",
414 "/terminal-setup",
415 "terminal"
416 ),
417 built_in_command_spec!(
418 "statusline",
419 "Set up a custom status line with target selection (usage: /statusline [instructions...])",
420 "/statusline [instructions...]",
421 "terminal"
422 ),
423 built_in_command_spec!(
424 "title",
425 "Configure the terminal title items interactively",
426 "/title",
427 "terminal"
428 ),
429 built_in_command_spec!(
430 "login",
431 "Authenticate with OpenAI, OpenRouter, or GitHub Copilot (usage: /login [provider])",
432 "/login [provider]",
433 "auth"
434 ),
435 built_in_command_spec!(
436 "logout",
437 "Clear stored provider authentication (usage: /logout [provider])",
438 "/logout [provider]",
439 "auth"
440 ),
441 built_in_command_spec!(
442 "auth",
443 "Show authentication status for providers (usage: /auth [provider])",
444 "/auth [provider]",
445 "auth"
446 ),
447 built_in_command_spec!(
448 "refresh-oauth",
449 "Refresh stored provider credentials when supported (usage: /refresh-oauth [provider])",
450 "/refresh-oauth [provider]",
451 "auth"
452 ),
453];
454
455pub fn command_skill_specs() -> &'static [CommandSkillSpec] {
456 COMMAND_SKILL_SPECS
457}
458
459pub fn find_command_skill_by_slash_name(name: &str) -> Option<&'static CommandSkillSpec> {
460 COMMAND_SKILL_SPECS
461 .iter()
462 .find(|spec| spec.slash_name == name)
463}
464
465pub fn find_command_skill_by_skill_name(name: &str) -> Option<&'static CommandSkillSpec> {
466 COMMAND_SKILL_SPECS
467 .iter()
468 .find(|spec| spec.skill_name == name)
469}
470
471pub fn is_command_skill_name(name: &str) -> bool {
472 find_command_skill_by_skill_name(name).is_some()
473}
474
475pub fn is_model_catalog_eligible(skill: &SkillMetadata) -> bool {
476 if skill
477 .manifest
478 .as_ref()
479 .and_then(|manifest| manifest.disable_model_invocation)
480 .unwrap_or(false)
481 {
482 return false;
483 }
484
485 !is_command_skill_name(&skill.name)
486}
487
488pub fn built_in_command_skill_contexts() -> Vec<SkillContext> {
489 COMMAND_SKILL_SPECS
490 .iter()
491 .copied()
492 .filter(|spec| spec.is_built_in())
493 .map(|spec| {
494 SkillContext::MetadataOnly(built_in_manifest(&spec), built_in_path(spec.skill_name))
495 })
496 .collect()
497}
498
499pub fn built_in_command_skill(name: &str) -> Option<BuiltInCommandSkill> {
500 find_command_skill_by_skill_name(name)
501 .filter(|spec| spec.is_built_in())
502 .map(BuiltInCommandSkill::from_spec)
503}
504
505pub fn merge_built_in_command_skill_contexts(skills: &mut Vec<SkillContext>) {
506 skills.extend(built_in_command_skill_contexts());
507 skills.sort_by(|left, right| left.manifest().name.cmp(&right.manifest().name));
508 skills.dedup_by(|left, right| left.manifest().name == right.manifest().name);
509}
510
511pub fn merge_built_in_command_skill_metadata(skills: &mut Vec<SkillMetadata>) {
512 skills.extend(
513 built_in_command_skill_contexts()
514 .into_iter()
515 .map(|skill_ctx| SkillMetadata {
516 name: skill_ctx.manifest().name.clone(),
517 description: skill_ctx.manifest().description.clone(),
518 short_description: None,
519 path: skill_ctx.path().clone(),
520 scope: SkillScope::System,
521 manifest: Some(skill_ctx.manifest().clone().into()),
522 }),
523 );
524 skills.sort_by(|left, right| left.name.cmp(&right.name));
525 skills.dedup_by(|left, right| left.name == right.name);
526}
527
528fn built_in_manifest(spec: &CommandSkillSpec) -> SkillManifest {
529 SkillManifest {
530 name: spec.skill_name.to_string(),
531 description: spec.description.to_string(),
532 disable_model_invocation: Some(true),
533 variety: SkillVariety::BuiltIn,
534 metadata: Some(command_skill_metadata(spec, "built_in_command")),
535 ..Default::default()
536 }
537}
538
539fn command_skill_metadata(spec: &CommandSkillSpec, backend: &str) -> SkillManifestMetadata {
540 let mut metadata = HashMap::new();
541 metadata.insert(
542 "slash_alias".to_string(),
543 json!(format!("/{}", spec.slash_name)),
544 );
545 metadata.insert("usage".to_string(), json!(spec.usage));
546 metadata.insert("category".to_string(), json!(spec.category));
547 metadata.insert("backend".to_string(), json!(backend));
548 metadata
549}
550
551fn built_in_path(skill_name: &str) -> PathBuf {
552 PathBuf::from(format!("<built-in>/{}", skill_name))
553}
554
555#[cfg(test)]
556mod tests {
557 use super::*;
558
559 #[test]
560 fn traditional_and_built_in_commands_are_mapped() {
561 let review = find_command_skill_by_slash_name("review").expect("review spec");
562 assert!(review.is_traditional());
563 assert_eq!(review.skill_name, "cmd-review");
564
565 let status = find_command_skill_by_slash_name("status").expect("status spec");
566 assert!(status.is_built_in());
567 assert_eq!(status.skill_name, "cmd-status");
568 }
569
570 #[test]
571 fn built_in_contexts_are_tagged_correctly() {
572 let built_in = built_in_command_skill_contexts();
573 let status = built_in
574 .iter()
575 .find(|ctx| ctx.manifest().name == "cmd-status")
576 .expect("cmd-status context");
577 assert_eq!(status.manifest().variety, SkillVariety::BuiltIn);
578 }
579
580 #[test]
581 fn removed_generate_agent_file_command_is_not_registered() {
582 assert!(find_command_skill_by_slash_name("generate-agent-file").is_none());
583 assert!(find_command_skill_by_skill_name("cmd-generate-agent-file").is_none());
584 }
585
586 #[test]
587 fn removed_mode_command_is_not_registered() {
588 assert!(find_command_skill_by_slash_name("mode").is_none());
589 assert!(find_command_skill_by_skill_name("cmd-mode").is_none());
590 }
591
592 #[test]
593 fn plan_command_skill_uses_workflow_copy() {
594 let plan = find_command_skill_by_slash_name("plan").expect("plan spec");
595 assert!(plan.description.contains("planning workflow"));
596 assert!(!plan.description.contains("mode"));
597 assert_eq!(plan.usage, "/plan [task]");
598 }
599}