vx_cli/commands/
shell.rs

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