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}