Skip to main content

vtcode_core/skills/
command_skills.rs

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        "Plan Mode: read-only planning with optional prompt (usage: /plan [on|off] [task])",
389        "/plan [on|off] [task]",
390        "session"
391    ),
392    built_in_command_spec!(
393        "mode",
394        "Open the session mode picker or switch directly (usage: /mode [edit|auto|plan|cycle])",
395        "/mode [edit|auto|plan|cycle]",
396        "session"
397    ),
398    built_in_command_spec!(
399        "docs",
400        "Open vtcode documentation in web browser",
401        "/docs",
402        "support"
403    ),
404    built_in_command_spec!(
405        "help",
406        "Show slash command help",
407        "/help [command]",
408        "support"
409    ),
410    built_in_command_spec!("exit", "Exit the session", "/exit", "session"),
411    built_in_command_spec!(
412        "donate",
413        "Support the project by buying the author a coffee",
414        "/donate",
415        "support"
416    ),
417    built_in_command_spec!(
418        "terminal-setup",
419        "Configure terminal for VT Code (multiline, copy/paste, shell, themes)",
420        "/terminal-setup",
421        "terminal"
422    ),
423    built_in_command_spec!(
424        "statusline",
425        "Set up a custom status line with target selection (usage: /statusline [instructions...])",
426        "/statusline [instructions...]",
427        "terminal"
428    ),
429    built_in_command_spec!(
430        "title",
431        "Configure the terminal title items interactively",
432        "/title",
433        "terminal"
434    ),
435    built_in_command_spec!(
436        "login",
437        "Authenticate with OpenAI, OpenRouter, or GitHub Copilot (usage: /login [provider])",
438        "/login [provider]",
439        "auth"
440    ),
441    built_in_command_spec!(
442        "logout",
443        "Clear stored provider authentication (usage: /logout [provider])",
444        "/logout [provider]",
445        "auth"
446    ),
447    built_in_command_spec!(
448        "auth",
449        "Show authentication status for providers (usage: /auth [provider])",
450        "/auth [provider]",
451        "auth"
452    ),
453    built_in_command_spec!(
454        "refresh-oauth",
455        "Refresh stored provider credentials when supported (usage: /refresh-oauth [provider])",
456        "/refresh-oauth [provider]",
457        "auth"
458    ),
459];
460
461pub fn command_skill_specs() -> &'static [CommandSkillSpec] {
462    COMMAND_SKILL_SPECS
463}
464
465pub fn find_command_skill_by_slash_name(name: &str) -> Option<&'static CommandSkillSpec> {
466    COMMAND_SKILL_SPECS
467        .iter()
468        .find(|spec| spec.slash_name == name)
469}
470
471pub fn find_command_skill_by_skill_name(name: &str) -> Option<&'static CommandSkillSpec> {
472    COMMAND_SKILL_SPECS
473        .iter()
474        .find(|spec| spec.skill_name == name)
475}
476
477pub fn is_command_skill_name(name: &str) -> bool {
478    find_command_skill_by_skill_name(name).is_some()
479}
480
481pub fn is_model_catalog_eligible(skill: &SkillMetadata) -> bool {
482    if skill
483        .manifest
484        .as_ref()
485        .and_then(|manifest| manifest.disable_model_invocation)
486        .unwrap_or(false)
487    {
488        return false;
489    }
490
491    !is_command_skill_name(&skill.name)
492}
493
494pub fn built_in_command_skill_contexts() -> Vec<SkillContext> {
495    COMMAND_SKILL_SPECS
496        .iter()
497        .copied()
498        .filter(|spec| spec.is_built_in())
499        .map(|spec| {
500            SkillContext::MetadataOnly(built_in_manifest(&spec), built_in_path(spec.skill_name))
501        })
502        .collect()
503}
504
505pub fn built_in_command_skill(name: &str) -> Option<BuiltInCommandSkill> {
506    find_command_skill_by_skill_name(name)
507        .filter(|spec| spec.is_built_in())
508        .map(BuiltInCommandSkill::from_spec)
509}
510
511pub fn merge_built_in_command_skill_contexts(skills: &mut Vec<SkillContext>) {
512    skills.extend(built_in_command_skill_contexts());
513    skills.sort_by(|left, right| left.manifest().name.cmp(&right.manifest().name));
514    skills.dedup_by(|left, right| left.manifest().name == right.manifest().name);
515}
516
517pub fn merge_built_in_command_skill_metadata(skills: &mut Vec<SkillMetadata>) {
518    skills.extend(
519        built_in_command_skill_contexts()
520            .into_iter()
521            .map(|skill_ctx| SkillMetadata {
522                name: skill_ctx.manifest().name.clone(),
523                description: skill_ctx.manifest().description.clone(),
524                short_description: None,
525                path: skill_ctx.path().clone(),
526                scope: SkillScope::System,
527                manifest: Some(skill_ctx.manifest().clone().into()),
528            }),
529    );
530    skills.sort_by(|left, right| left.name.cmp(&right.name));
531    skills.dedup_by(|left, right| left.name == right.name);
532}
533
534fn built_in_manifest(spec: &CommandSkillSpec) -> SkillManifest {
535    SkillManifest {
536        name: spec.skill_name.to_string(),
537        description: spec.description.to_string(),
538        disable_model_invocation: Some(true),
539        variety: SkillVariety::BuiltIn,
540        metadata: Some(command_skill_metadata(spec, "built_in_command")),
541        ..Default::default()
542    }
543}
544
545fn command_skill_metadata(spec: &CommandSkillSpec, backend: &str) -> SkillManifestMetadata {
546    let mut metadata = HashMap::new();
547    metadata.insert(
548        "slash_alias".to_string(),
549        json!(format!("/{}", spec.slash_name)),
550    );
551    metadata.insert("usage".to_string(), json!(spec.usage));
552    metadata.insert("category".to_string(), json!(spec.category));
553    metadata.insert("backend".to_string(), json!(backend));
554    metadata
555}
556
557fn built_in_path(skill_name: &str) -> PathBuf {
558    PathBuf::from(format!("<built-in>/{}", skill_name))
559}
560
561#[cfg(test)]
562mod tests {
563    use super::*;
564
565    #[test]
566    fn traditional_and_built_in_commands_are_mapped() {
567        let review = find_command_skill_by_slash_name("review").expect("review spec");
568        assert!(review.is_traditional());
569        assert_eq!(review.skill_name, "cmd-review");
570
571        let status = find_command_skill_by_slash_name("status").expect("status spec");
572        assert!(status.is_built_in());
573        assert_eq!(status.skill_name, "cmd-status");
574    }
575
576    #[test]
577    fn built_in_contexts_are_tagged_correctly() {
578        let built_in = built_in_command_skill_contexts();
579        let status = built_in
580            .iter()
581            .find(|ctx| ctx.manifest().name == "cmd-status")
582            .expect("cmd-status context");
583        assert_eq!(status.manifest().variety, SkillVariety::BuiltIn);
584    }
585
586    #[test]
587    fn removed_generate_agent_file_command_is_not_registered() {
588        assert!(find_command_skill_by_slash_name("generate-agent-file").is_none());
589        assert!(find_command_skill_by_skill_name("cmd-generate-agent-file").is_none());
590    }
591}