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        "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}