use std::path::Path;
use anyhow::Result;
use vtcode_commons::terminal_detection::TerminalType;
use vtcode_core::prompts::find_prompt_template;
use vtcode_core::skills::find_command_skill_by_slash_name;
use vtcode_core::ui::slash::{SlashCommandInfo, find_command, visible_commands};
use vtcode_core::ui::theme;
use vtcode_core::utils::ansi::{AnsiRenderer, MessageStyle};
pub(super) fn render_mcp_usage(renderer: &mut AnsiRenderer) -> Result<()> {
renderer.line(
MessageStyle::Info,
"Usage: /mcp [status|list|tools|refresh|config|config edit|repair|diagnose|login <name>|logout <name>]",
)?;
renderer.line(MessageStyle::Info, " status – Show overall MCP connection health")?;
renderer.line(MessageStyle::Info, " list – List configured providers from vtcode.toml")?;
renderer.line(MessageStyle::Info, " tools – Show tools exposed by active providers")?;
renderer.line(MessageStyle::Info, " refresh – Reindex MCP tools without restarting")?;
renderer.line(MessageStyle::Info, " config – Summarize MCP settings from vtcode.toml")?;
renderer.line(MessageStyle::Info, " config edit – Show the config file path and editing guidance")?;
renderer.line(MessageStyle::Info, " repair – Restart MCP connections and refresh tool indices")?;
renderer.line(MessageStyle::Info, " diagnose – Validate config and run MCP health checks")?;
renderer.line(MessageStyle::Info, " login/logout <name> – Manage provider authentication (if supported)")?;
renderer.line(MessageStyle::Info, "Examples: /mcp list, /mcp tools, /mcp login github")?;
Ok(())
}
pub(super) fn render_secret_usage(renderer: &mut AnsiRenderer) -> Result<()> {
renderer
.line(MessageStyle::Info, "Usage: /secret [list|status [provider]|add <provider>|delete <provider>|help]")?;
renderer.line(MessageStyle::Info, " (no args) – Show every provider and where its key comes from")?;
renderer.line(MessageStyle::Info, " list – Same as above (status table of all providers)")?;
renderer.line(MessageStyle::Info, " status [provider]– Detailed status for one provider, or all")?;
renderer
.line(MessageStyle::Info, " add <provider> – Paste a key into the secure prompt (stored in OS keyring)")?;
renderer.line(MessageStyle::Info, " delete <provider>– Clear the keyring entry for a provider")?;
renderer.line(MessageStyle::Info, " help – Show this help")?;
renderer.line(MessageStyle::Info, "")?;
renderer.line(
MessageStyle::Info,
"Keys are stored in your OS keyring (with encrypted-file fallback) — never in workspace .env.",
)?;
renderer.line(
MessageStyle::Info,
"Environment variables (e.g. OPENROUTER_API_KEY in ~/.zshrc) are read-only here; manage them in your shell.",
)?;
renderer.line(
MessageStyle::Info,
"Examples: /secret, /secret status openrouter, /secret add anthropic, /secret delete openai",
)?;
Ok(())
}
pub(super) fn render_local_usage(renderer: &mut AnsiRenderer) -> Result<()> {
renderer.line(MessageStyle::Info, "Usage: /local (open interactive manager)")?;
renderer.line(MessageStyle::Info, " /local status (check all local servers)")?;
renderer.line(MessageStyle::Info, " /local status <provider> (check specific server)")?;
renderer.line(MessageStyle::Info, " /local start <provider> (start a server)")?;
renderer.line(MessageStyle::Info, " /local stop <provider> (stop a server)")?;
renderer.line(MessageStyle::Info, " /local configure <provider> (show config and env vars)")?;
renderer.line(MessageStyle::Info, " /local troubleshoot <provider> (diagnose and fix issues)")?;
renderer.line(MessageStyle::Info, " /local <provider> (open provider actions)")?;
renderer.line(MessageStyle::Info, "")?;
renderer.line(MessageStyle::Info, "Providers: ollama, lmstudio, llama.cpp")?;
renderer.line(MessageStyle::Info, "Aliases: lm-studio -> lmstudio, llama.cpp -> llamacpp")?;
renderer.line(
MessageStyle::Info,
"Examples: /local, /local ollama, /local start lmstudio, /local troubleshoot llamacpp",
)?;
Ok(())
}
pub(super) fn render_theme_list(renderer: &mut AnsiRenderer) -> Result<()> {
let available_themes = theme::available_themes();
renderer.line(MessageStyle::Info, "Available themes:")?;
for theme_id in available_themes {
if let Some(label) = theme::theme_label(theme_id) {
renderer.line(MessageStyle::Info, &format!(" /theme {theme_id} – {label}"))?;
} else {
renderer.line(MessageStyle::Info, &format!(" /theme {theme_id} – {theme_id}"))?;
}
}
renderer.line(MessageStyle::Info, "")?;
renderer.line(MessageStyle::Info, &format!("Current theme: {}", theme::active_theme_label()))?;
Ok(())
}
pub(super) async fn render_help(
renderer: &mut AnsiRenderer,
specific_command: Option<&str>,
workspace: &Path,
) -> Result<()> {
if let Some(cmd_name) = specific_command {
if let Some(cmd) = resolve_help_command(cmd_name) {
renderer.line(MessageStyle::Info, &format!("Help for /{}:", cmd.name))?;
renderer.line(MessageStyle::Info, &format!(" Description: {}", cmd.description))?;
if let Some(spec) = find_command_skill_by_slash_name(cmd.name) {
renderer.line(MessageStyle::Info, &format!(" Usage: {}", spec.usage))?;
}
} else if let Some(template) = find_prompt_template(workspace, cmd_name).await {
renderer.line(MessageStyle::Info, &format!("Help for /{}:", template.name))?;
renderer.line(MessageStyle::Info, &format!(" Description: {}", template.description))?;
renderer
.line(MessageStyle::Info, " Type `/name [args...]` to expand this prompt template into the editor.")?;
} else {
renderer.line(
MessageStyle::Error,
&format!("Unknown command '{cmd_name}'. Use /help without arguments to see all commands."),
)?;
}
} else {
renderer.line(MessageStyle::Info, "Available slash commands:")?;
for cmd in visible_commands() {
renderer.line(MessageStyle::Info, &format!(" /{} – {}", cmd.name, cmd.description))?;
}
let prompt_templates = vtcode_core::prompts::discover_prompt_templates(workspace)
.await
.into_iter()
.filter(|template| find_command(&template.name).is_none())
.collect::<Vec<_>>();
if !prompt_templates.is_empty() {
renderer.line(MessageStyle::Info, "")?;
renderer.line(MessageStyle::Info, "Prompt templates:")?;
for template in prompt_templates {
renderer.line(MessageStyle::Info, &format!(" /{} – {}", template.name, template.description))?;
}
}
renderer.line(MessageStyle::Info, "")?;
renderer.line(MessageStyle::Info, "Interactive mode features:")?;
renderer.line(MessageStyle::Info, " Ctrl+C – Cancel current input or generation")?;
renderer.line(MessageStyle::Info, " Ctrl+D – Exit VT Code session")?;
renderer.line(MessageStyle::Info, " Ctrl+L – Clear screen (keep context)")?;
renderer.line(MessageStyle::Info, " Ctrl+R – Reverse search command history")?;
renderer.line(MessageStyle::Info, " /history – Open command history picker")?;
renderer.line(
MessageStyle::Info,
" Ctrl+V or Alt+V: App-level image paste (image-enabled sessions only; no text fallback)",
)?;
renderer.line(MessageStyle::Info, " Up/Down arrows – Navigate command history")?;
renderer.line(MessageStyle::Info, " Enter – Submit now (or queue if a turn is active)")?;
renderer
.line(MessageStyle::Info, " Tab – Accept suggestion, cycle primary agent when empty, or queue input")?;
renderer.line(MessageStyle::Info, " Ctrl+Enter – Run now / steer the active turn")?;
renderer.line(MessageStyle::Info, " Shift+Tab – Cycle primary agent")?;
renderer.line(MessageStyle::Info, "")?;
renderer.line(MessageStyle::Info, "Multiline input:")?;
renderer.line(MessageStyle::Info, " \\ + Enter – Quick escape (insert newline without submitting)")?;
renderer.line(MessageStyle::Info, " Shift+Enter – Multiline input (if configured)")?;
match TerminalType::detect().unwrap_or(TerminalType::Unknown) {
TerminalType::Ghostty
| TerminalType::Kitty
| TerminalType::WezTerm
| TerminalType::ITerm2
| TerminalType::Warp => {
renderer.line(
MessageStyle::Info,
" Native support – Shift+Enter works in this terminal without /terminal-setup",
)?;
}
term if term.should_offer_terminal_setup() => {
renderer.line(
MessageStyle::Info,
" /terminal-setup – Install VT Code multiline bindings for this terminal",
)?;
}
_ => {
renderer.line(
MessageStyle::Info,
" Terminal-specific setup – Use Option+Enter on macOS or configure your terminal manually",
)?;
}
}
renderer.line(MessageStyle::Info, " Ctrl+J – Line feed character for multiline")?;
renderer.line(MessageStyle::Info, "")?;
renderer.line(MessageStyle::Info, "Shell mode:")?;
renderer.line(MessageStyle::Info, " !command – Run shell commands directly (e.g., !ls -la)")?;
}
Ok(())
}
fn resolve_help_command(name: &str) -> Option<&'static SlashCommandInfo> {
let trimmed = name.trim();
find_command(trimmed).or_else(|| {
let lower = trimmed.to_ascii_lowercase();
let canonical = super::normalize_command_key(&lower);
find_command(canonical)
})
}
#[cfg(test)]
mod tests {
use super::resolve_help_command;
#[test]
fn help_resolution_supports_aliases() {
assert_eq!(resolve_help_command("settings").map(|command| command.name), Some("config"));
assert_eq!(resolve_help_command("comman").map(|command| command.name), Some("command"));
}
#[test]
fn help_resolution_does_not_keep_removed_share_log_alias() {
assert!(resolve_help_command("share-log").is_none());
assert!(resolve_help_command("sharelog").is_none());
assert!(resolve_help_command("export-log").is_none());
}
#[test]
fn help_resolution_is_case_insensitive() {
assert_eq!(resolve_help_command("Review").map(|command| command.name), Some("review"));
}
}