vx_cli/commands/
shell.rs

1// Shell integration commands
2
3use anyhow::Result;
4use std::env;
5
6pub async fn handle_shell_init(shell: Option<String>) -> Result<()> {
7    let shell_type = shell.unwrap_or_else(detect_shell);
8
9    match shell_type.as_str() {
10        "bash" => print_bash_init(),
11        "zsh" => print_zsh_init(),
12        "fish" => print_fish_init(),
13        "powershell" | "pwsh" => print_powershell_init(),
14        "cmd" => print_cmd_init(),
15        _ => {
16            return Err(anyhow::anyhow!("Unsupported shell: {}", shell_type));
17        }
18    }
19
20    Ok(())
21}
22
23pub async fn handle_completion(shell: String) -> Result<()> {
24    match shell.as_str() {
25        "bash" => print_bash_completion(),
26        "zsh" => print_zsh_completion(),
27        "fish" => print_fish_completion(),
28        "powershell" | "pwsh" => print_powershell_completion(),
29        _ => {
30            return Err(anyhow::anyhow!("Unsupported shell: {}", shell));
31        }
32    }
33
34    Ok(())
35}
36
37fn detect_shell() -> String {
38    // Try to detect shell from environment variables
39    if let Ok(shell) = env::var("SHELL") {
40        if shell.contains("bash") {
41            return "bash".to_string();
42        } else if shell.contains("zsh") {
43            return "zsh".to_string();
44        } else if shell.contains("fish") {
45            return "fish".to_string();
46        }
47    }
48
49    // Check for PowerShell
50    if env::var("PSModulePath").is_ok() {
51        return "powershell".to_string();
52    }
53
54    // Default to bash on Unix-like systems, cmd on Windows
55    if cfg!(windows) {
56        "cmd".to_string()
57    } else {
58        "bash".to_string()
59    }
60}
61
62fn print_bash_init() {
63    let vx_home = dirs::home_dir()
64        .map(|p| p.join(".vx").display().to_string())
65        .unwrap_or_else(|| "$HOME/.vx".to_string());
66
67    println!(
68        r#"# VX Shell Integration for Bash
69# Add this to your ~/.bashrc or source it directly
70
71# Set VX environment variables
72export VX_HOME="{vx_home}"
73export VX_SHELL="bash"
74
75# Add VX bin directory to PATH if not already present
76if [[ ":$PATH:" != *":$VX_HOME/bin:"* ]]; then
77    export PATH="$VX_HOME/bin:$PATH"
78fi
79
80# VX project detection function
81__vx_detect_project() {{
82    local dir="$PWD"
83    while [[ "$dir" != "/" ]]; do
84        if [[ -f "$dir/.vx.toml" ]]; then
85            export VX_PROJECT_ROOT="$dir"
86            return 0
87        fi
88        dir="$(dirname "$dir")"
89    done
90    unset VX_PROJECT_ROOT
91    return 1
92}}
93
94# Auto-sync on directory change
95__vx_auto_sync() {{
96    if __vx_detect_project && [[ -f "$VX_PROJECT_ROOT/.vx.toml" ]]; then
97        if command -v vx >/dev/null 2>&1; then
98            vx sync --check --quiet 2>/dev/null || true
99        fi
100    fi
101}}
102
103# Hook into cd command
104__vx_original_cd=$(declare -f cd)
105cd() {{
106    builtin cd "$@"
107    __vx_auto_sync
108}}
109
110# Initialize on shell startup
111__vx_auto_sync
112
113# VX prompt integration (optional)
114__vx_prompt() {{
115    if [[ -n "$VX_PROJECT_ROOT" ]]; then
116        echo "[vx]"
117    fi
118}}
119
120# Uncomment to add VX info to prompt
121# PS1="$(__vx_prompt)$PS1"
122"#,
123        vx_home = vx_home
124    );
125}
126
127fn print_zsh_init() {
128    let vx_home = dirs::home_dir()
129        .map(|p| p.join(".vx").display().to_string())
130        .unwrap_or_else(|| "$HOME/.vx".to_string());
131
132    println!(
133        r#"# VX Shell Integration for Zsh
134# Add this to your ~/.zshrc or source it directly
135
136# Set VX environment variables
137export VX_HOME="{vx_home}"
138export VX_SHELL="zsh"
139
140# Add VX bin directory to PATH if not already present
141if [[ ":$PATH:" != *":$VX_HOME/bin:"* ]]; then
142    export PATH="$VX_HOME/bin:$PATH"
143fi
144
145# VX project detection function
146__vx_detect_project() {{
147    local dir="$PWD"
148    while [[ "$dir" != "/" ]]; do
149        if [[ -f "$dir/.vx.toml" ]]; then
150            export VX_PROJECT_ROOT="$dir"
151            return 0
152        fi
153        dir="${{dir:h}}"
154    done
155    unset VX_PROJECT_ROOT
156    return 1
157}}
158
159# Auto-sync on directory change
160__vx_auto_sync() {{
161    if __vx_detect_project && [[ -f "$VX_PROJECT_ROOT/.vx.toml" ]]; then
162        if command -v vx >/dev/null 2>&1; then
163            vx sync --check --quiet 2>/dev/null || true
164        fi
165    fi
166}}
167
168# Hook into chpwd
169autoload -U add-zsh-hook
170add-zsh-hook chpwd __vx_auto_sync
171
172# Initialize on shell startup
173__vx_auto_sync
174
175# VX prompt integration (optional)
176__vx_prompt() {{
177    if [[ -n "$VX_PROJECT_ROOT" ]]; then
178        echo "[vx]"
179    fi
180}}
181
182# Uncomment to add VX info to prompt
183# PROMPT="$(__vx_prompt)$PROMPT"
184"#,
185        vx_home = vx_home
186    );
187}
188
189fn print_fish_init() {
190    let vx_home = dirs::home_dir()
191        .map(|p| p.join(".vx").display().to_string())
192        .unwrap_or_else(|| "$HOME/.vx".to_string());
193
194    println!(
195        r#"# VX Shell Integration for Fish
196# Add this to your ~/.config/fish/config.fish or source it directly
197
198# Set VX environment variables
199set -gx VX_HOME "{vx_home}"
200set -gx VX_SHELL "fish"
201
202# Add VX bin directory to PATH if not already present
203if not contains "$VX_HOME/bin" $PATH
204    set -gx PATH "$VX_HOME/bin" $PATH
205end
206
207# VX project detection function
208function __vx_detect_project
209    set dir (pwd)
210    while test "$dir" != "/"
211        if test -f "$dir/.vx.toml"
212            set -gx VX_PROJECT_ROOT "$dir"
213            return 0
214        end
215        set dir (dirname "$dir")
216    end
217    set -e VX_PROJECT_ROOT
218    return 1
219end
220
221# Auto-sync on directory change
222function __vx_auto_sync
223    if __vx_detect_project; and test -f "$VX_PROJECT_ROOT/.vx.toml"
224        if command -v vx >/dev/null 2>&1
225            vx sync --check --quiet 2>/dev/null; or true
226        end
227    end
228end
229
230# Hook into directory change
231function __vx_pwd_handler --on-variable PWD
232    __vx_auto_sync
233end
234
235# Initialize on shell startup
236__vx_auto_sync
237
238# VX prompt integration (optional)
239function __vx_prompt
240    if set -q VX_PROJECT_ROOT
241        echo "[vx]"
242    end
243end
244
245# Uncomment to add VX info to prompt
246# function fish_prompt
247#     echo (__vx_prompt)(fish_prompt)
248# end
249"#,
250        vx_home = vx_home
251    );
252}
253
254fn print_powershell_init() {
255    let vx_home = dirs::home_dir()
256        .map(|p| p.join(".vx").display().to_string())
257        .unwrap_or_else(|| "$env:USERPROFILE\\.vx".to_string());
258
259    println!(
260        r#"# VX Shell Integration for PowerShell
261# Add this to your $PROFILE or dot-source it directly
262
263# Set VX environment variables
264$env:VX_HOME = "{vx_home}"
265$env:VX_SHELL = "powershell"
266
267# Add VX bin directory to PATH if not already present
268$vxBinPath = Join-Path $env:VX_HOME "bin"
269if ($env:PATH -notlike "*$vxBinPath*") {{
270    $env:PATH = "$vxBinPath;$env:PATH"
271}}
272
273# VX project detection function
274function Find-VxProject {{
275    $dir = Get-Location
276    while ($dir.Path -ne $dir.Root.Name) {{
277        $vxConfig = Join-Path $dir.Path ".vx.toml"
278        if (Test-Path $vxConfig) {{
279            $env:VX_PROJECT_ROOT = $dir.Path
280            return $true
281        }}
282        $dir = $dir.Parent
283    }}
284    Remove-Item Env:VX_PROJECT_ROOT -ErrorAction SilentlyContinue
285    return $false
286}}
287
288# Auto-sync on directory change
289function Invoke-VxAutoSync {{
290    if (Find-VxProject -and (Test-Path "$env:VX_PROJECT_ROOT\.vx.toml")) {{
291        if (Get-Command vx -ErrorAction SilentlyContinue) {{
292            try {{
293                vx sync --check --quiet 2>$null
294            }} catch {{
295                # Ignore errors
296            }}
297        }}
298    }}
299}}
300
301# Hook into location change
302$ExecutionContext.SessionState.InvokeCommand.LocationChangedAction = {{
303    Invoke-VxAutoSync
304}}
305
306# Initialize on shell startup
307Invoke-VxAutoSync
308
309# VX prompt integration (optional)
310function Get-VxPrompt {{
311    if ($env:VX_PROJECT_ROOT) {{
312        return "[vx]"
313    }}
314    return ""
315}}
316
317# Uncomment to add VX info to prompt
318# function prompt {{
319#     "$(Get-VxPrompt)PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "
320# }}
321"#,
322        vx_home = vx_home
323    );
324}
325
326fn print_cmd_init() {
327    let vx_home = "%USERPROFILE%\\.vx".to_string();
328
329    println!(
330        r#"@echo off
331REM VX Shell Integration for CMD
332REM Add this to your startup script or run it manually
333
334REM Set VX environment variables
335set VX_HOME={vx_home}
336set VX_SHELL=cmd
337
338REM Add VX bin directory to PATH if not already present
339echo %PATH% | find /i "%VX_HOME%\bin" >nul
340if errorlevel 1 (
341    set PATH=%VX_HOME%\bin;%PATH%
342)
343
344echo VX Shell integration initialized for CMD
345echo Use 'vx --help' to get started
346"#,
347        vx_home = vx_home
348    );
349}
350
351fn print_bash_completion() {
352    println!(
353        r#"# VX Bash Completion
354# Source this file or add it to /etc/bash_completion.d/
355
356_vx_completion() {{
357    local cur prev words cword
358    _init_completion || return
359
360    case $prev in
361        install|remove|switch|fetch)
362            # Complete with available tools
363            COMPREPLY=($(compgen -W "node npm npx go cargo uv uvx python" -- "$cur"))
364            return
365            ;;
366        --template)
367            # Complete with available templates
368            COMPREPLY=($(compgen -W "node python rust go fullstack minimal" -- "$cur"))
369            return
370            ;;
371        --format)
372            # Complete with output formats
373            COMPREPLY=($(compgen -W "table json yaml" -- "$cur"))
374            return
375            ;;
376        --category)
377            # Complete with categories
378            COMPREPLY=($(compgen -W "javascript python rust go utility" -- "$cur"))
379            return
380            ;;
381    esac
382
383    case ${{words[1]}} in
384        venv)
385            case ${{words[2]}} in
386                activate|remove|use)
387                    # Complete with venv names
388                    local venvs=$(vx venv list --names-only 2>/dev/null || echo "")
389                    COMPREPLY=($(compgen -W "$venvs" -- "$cur"))
390                    return
391                    ;;
392                *)
393                    COMPREPLY=($(compgen -W "create list activate remove current" -- "$cur"))
394                    return
395                    ;;
396            esac
397            ;;
398        config)
399            COMPREPLY=($(compgen -W "show set get reset edit" -- "$cur"))
400            return
401            ;;
402        *)
403            # Complete with main commands
404            COMPREPLY=($(compgen -W "install remove list update search sync init cleanup stats venv config global plugin shell-init completion version help" -- "$cur"))
405            return
406            ;;
407    esac
408}}
409
410complete -F _vx_completion vx
411"#
412    );
413}
414
415fn print_zsh_completion() {
416    println!(
417        r#"#compdef vx
418
419# VX Zsh Completion
420
421_vx() {{
422    local context state line
423    typeset -A opt_args
424
425    _arguments -C \
426        '1: :_vx_commands' \
427        '*::arg:->args'
428
429    case $state in
430        args)
431            case $words[1] in
432                install|remove|switch|fetch)
433                    _arguments \
434                        '*:tools:(node npm npx go cargo uv uvx python)'
435                    ;;
436                venv)
437                    case $words[2] in
438                        activate|remove|use)
439                            _arguments \
440                                '*:venv:_vx_venvs'
441                            ;;
442                        *)
443                            _arguments \
444                                '1:subcommand:(create list activate remove current)'
445                            ;;
446                    esac
447                    ;;
448                config)
449                    _arguments \
450                        '1:subcommand:(show set get reset edit)'
451                    ;;
452            esac
453            ;;
454    esac
455}}
456
457_vx_commands() {{
458    local commands
459    commands=(
460        'install:Install a tool'
461        'remove:Remove a tool'
462        'list:List installed tools'
463        'update:Update tools'
464        'search:Search available tools'
465        'sync:Sync project tools'
466        'init:Initialize project'
467        'cleanup:Clean up cache and orphaned files'
468        'stats:Show statistics'
469        'venv:Virtual environment management'
470        'config:Configuration management'
471        'global:Global tool management'
472        'plugin:Plugin management'
473        'shell-init:Generate shell initialization script'
474        'completion:Generate shell completion script'
475        'version:Show version information'
476        'help:Show help'
477    )
478    _describe 'commands' commands
479}}
480
481_vx_venvs() {{
482    local venvs
483    venvs=($(vx venv list --names-only 2>/dev/null || echo ""))
484    _describe 'virtual environments' venvs
485}}
486
487_vx "$@"
488"#
489    );
490}
491
492fn print_fish_completion() {
493    println!(
494        r#"# VX Fish Completion
495
496# Main commands
497complete -c vx -f -n '__fish_use_subcommand' -a 'install' -d 'Install a tool'
498complete -c vx -f -n '__fish_use_subcommand' -a 'remove' -d 'Remove a tool'
499complete -c vx -f -n '__fish_use_subcommand' -a 'list' -d 'List installed tools'
500complete -c vx -f -n '__fish_use_subcommand' -a 'update' -d 'Update tools'
501complete -c vx -f -n '__fish_use_subcommand' -a 'search' -d 'Search available tools'
502complete -c vx -f -n '__fish_use_subcommand' -a 'sync' -d 'Sync project tools'
503complete -c vx -f -n '__fish_use_subcommand' -a 'init' -d 'Initialize project'
504complete -c vx -f -n '__fish_use_subcommand' -a 'cleanup' -d 'Clean up cache and orphaned files'
505complete -c vx -f -n '__fish_use_subcommand' -a 'stats' -d 'Show statistics'
506complete -c vx -f -n '__fish_use_subcommand' -a 'venv' -d 'Virtual environment management'
507complete -c vx -f -n '__fish_use_subcommand' -a 'config' -d 'Configuration management'
508complete -c vx -f -n '__fish_use_subcommand' -a 'global' -d 'Global tool management'
509complete -c vx -f -n '__fish_use_subcommand' -a 'plugin' -d 'Plugin management'
510complete -c vx -f -n '__fish_use_subcommand' -a 'shell-init' -d 'Generate shell initialization script'
511complete -c vx -f -n '__fish_use_subcommand' -a 'completion' -d 'Generate shell completion script'
512complete -c vx -f -n '__fish_use_subcommand' -a 'version' -d 'Show version information'
513complete -c vx -f -n '__fish_use_subcommand' -a 'help' -d 'Show help'
514
515# Tool names for install/remove/switch/fetch
516complete -c vx -f -n '__fish_seen_subcommand_from install remove switch fetch' -a 'node npm npx go cargo uv uvx python'
517
518# Venv subcommands
519complete -c vx -f -n '__fish_seen_subcommand_from venv; and not __fish_seen_subcommand_from create list activate remove current' -a 'create' -d 'Create virtual environment'
520complete -c vx -f -n '__fish_seen_subcommand_from venv; and not __fish_seen_subcommand_from create list activate remove current' -a 'list' -d 'List virtual environments'
521complete -c vx -f -n '__fish_seen_subcommand_from venv; and not __fish_seen_subcommand_from create list activate remove current' -a 'activate' -d 'Activate virtual environment'
522complete -c vx -f -n '__fish_seen_subcommand_from venv; and not __fish_seen_subcommand_from create list activate remove current' -a 'remove' -d 'Remove virtual environment'
523complete -c vx -f -n '__fish_seen_subcommand_from venv; and not __fish_seen_subcommand_from create list activate remove current' -a 'current' -d 'Show current virtual environment'
524
525# Config subcommands
526complete -c vx -f -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from show set get reset edit' -a 'show' -d 'Show configuration'
527complete -c vx -f -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from show set get reset edit' -a 'set' -d 'Set configuration value'
528complete -c vx -f -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from show set get reset edit' -a 'get' -d 'Get configuration value'
529complete -c vx -f -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from show set get reset edit' -a 'reset' -d 'Reset configuration'
530complete -c vx -f -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from show set get reset edit' -a 'edit' -d 'Edit configuration'
531
532# Shell types for completion and shell-init
533complete -c vx -f -n '__fish_seen_subcommand_from completion shell-init' -a 'bash zsh fish powershell'
534
535# Common options
536complete -c vx -l help -d 'Show help'
537complete -c vx -l version -d 'Show version'
538complete -c vx -l verbose -d 'Verbose output'
539complete -c vx -l dry-run -d 'Preview operations'
540complete -c vx -l force -d 'Force operation'
541"#
542    );
543}
544
545fn print_powershell_completion() {
546    println!(
547        r#"# VX PowerShell Completion
548
549Register-ArgumentCompleter -Native -CommandName vx -ScriptBlock {{
550    param($wordToComplete, $commandAst, $cursorPosition)
551
552    $commands = @(
553        'install', 'remove', 'list', 'update', 'search', 'sync', 'init', 'cleanup', 'stats',
554        'venv', 'config', 'global', 'plugin', 'shell-init', 'completion', 'version', 'help'
555    )
556
557    $tools = @('node', 'npm', 'npx', 'go', 'cargo', 'uv', 'uvx', 'python')
558    $shells = @('bash', 'zsh', 'fish', 'powershell')
559    $formats = @('table', 'json', 'yaml')
560    $templates = @('node', 'python', 'rust', 'go', 'fullstack', 'minimal')
561
562    $tokens = $commandAst.CommandElements
563    $command = $tokens[1].Value
564
565    switch ($command) {{
566        {{ $_ -in @('install', 'remove', 'switch', 'fetch') }} {{
567            $tools | Where-Object {{ $_ -like "$wordToComplete*" }}
568        }}
569        'venv' {{
570            if ($tokens.Count -eq 2) {{
571                @('create', 'list', 'activate', 'remove', 'current') | Where-Object {{ $_ -like "$wordToComplete*" }}
572            }}
573        }}
574        'config' {{
575            if ($tokens.Count -eq 2) {{
576                @('show', 'set', 'get', 'reset', 'edit') | Where-Object {{ $_ -like "$wordToComplete*" }}
577            }}
578        }}
579        {{ $_ -in @('completion', 'shell-init') }} {{
580            $shells | Where-Object {{ $_ -like "$wordToComplete*" }}
581        }}
582        default {{
583            if ($tokens.Count -eq 1) {{
584                $commands | Where-Object {{ $_ -like "$wordToComplete*" }}
585            }}
586        }}
587    }}
588}}
589"#
590    );
591}