Public/Dispatcher.ps1

# PSSnips — Invoke-SnipCLI dispatcher and snip alias.
# The 'snip' function is the primary CLI entry point. It routes sub-commands
# using a regex switch, mapping short aliases (ls, rm, cp, etc.) to the full
# public functions above. Named switches (-Language, -Tags, etc.) are forwarded
# to the appropriate function.

function Invoke-SnipCLI {
    <#
    .SYNOPSIS
        PSSnips main entry point (Invoke-SnipCLI, alias: snip) — dispatches sub-commands or launches the interactive TUI.
 
    .DESCRIPTION
        Invoke-SnipCLI (invoked via the 'snip' alias) is the primary command-line interface
        for PSSnips. When called with no arguments it launches the full-screen interactive TUI.
        With a sub-command it routes to the appropriate PSSnips function.
 
        Sub-commands:
          (none) / ui / tui Open the interactive TUI (Start-SnipManager)
          list [filter] List snippets (Get-Snip)
          new <name> Create a snippet and open in editor (New-Snip)
          add <name> Add from -Path file or pipe (Add-Snip)
          show <name> Display snippet content (Show-Snip)
          edit <name> Open snippet in editor (Edit-Snip)
          run <name> Execute snippet (Invoke-Snip)
          rm <name> Delete snippet (Remove-Snip)
          copy <name> Copy to clipboard (Copy-Snip)
          tag <name> Manage tags (Set-SnipTag)
          search <query> Search by name/description/tag (Get-Snip -Filter)
          config View or update configuration (Get/Set-SnipConfig)
          gist list List GitHub Gists (Get-GistList)
          gist show <id> Display a Gist (Get-Gist)
          gist import <id> Import a Gist locally (Import-Gist)
          gist push <name> Export snippet to GitHub Gist (Export-Gist)
          gist run <id> Run a Gist without saving (Invoke-Gist)
          gist sync <name> Sync snippet with its Gist (Sync-Gist)
          help Display this help
 
    .PARAMETER Command
        The sub-command to execute. Omit to launch the interactive TUI.
        Accepts short aliases: ls, n, a, s, e, r, rm/del, cp/yank, f.
 
    .PARAMETER Arg1
        First positional argument for the sub-command, typically the snippet name
        or the Gist sub-command (list, show, import, push, run, sync).
 
    .PARAMETER Arg2
        Second positional argument, typically a Gist ID or file name.
 
    .PARAMETER Language
        Language/extension override (ps1, py, js, bat, sh, rb, go, ...).
        Forwarded to New-Snip, Add-Snip, or Set-SnipConfig as appropriate.
 
    .PARAMETER Description
        Short description for new or exported snippets.
 
    .PARAMETER Tags
        Array of tag strings for new snippets or tag operations.
 
    .PARAMETER Content
        Snippet content string forwarded to New-Snip (bypasses editor).
 
    .PARAMETER Path
        Source file path forwarded to Add-Snip -Path.
 
    .PARAMETER Editor
        Editor command override forwarded to New-Snip or Edit-Snip.
 
    .PARAMETER Token
        GitHub personal access token forwarded to Set-SnipConfig -GitHubToken.
 
    .PARAMETER Username
        GitHub username forwarded to Set-SnipConfig -GitHubUsername.
 
    .PARAMETER Public
        Creates a public GitHub Gist (forwarded to Export-Gist).
 
    .PARAMETER Force
        Skips confirmation prompts (forwarded to Remove-Snip or Add-Snip).
 
    .PARAMETER Push
        With 'gist sync': pushes local content to GitHub instead of pulling.
 
    .PARAMETER Clip
        With 'add': reads content from the Windows clipboard.
 
    .PARAMETER All
        With 'gist import': imports all files from the Gist.
 
    .EXAMPLE
        snip
 
        Launches the interactive full-screen TUI snippet manager.
 
    .EXAMPLE
        snip new deploy -Language ps1 -Description 'Deploy to Azure'
 
        Creates a new PowerShell snippet named 'deploy' and opens the editor.
 
    .EXAMPLE
        snip add loader -Path .\loader.py
 
        Imports loader.py from disk as a snippet named 'loader'.
 
    .EXAMPLE
        snip gist import abc123def456abc123def456abc1234567 -Name handy-script
 
        Downloads a GitHub Gist and saves it as local snippet 'handy-script'.
 
    .EXAMPLE
        snip config -Token ghp_abc123
 
        Saves a GitHub PAT to the configuration for Gist operations.
 
    .INPUTS
        None. This function does not accept pipeline input.
 
    .OUTPUTS
        Variable. Depends on the sub-command. Most commands write to the host;
        list and search commands also return PSCustomObject arrays.
 
    .NOTES
        Calling 'snip <name>' where <name> matches an existing snippet calls
        Show-Snip directly, making snippet names first-class sub-commands.
        Short aliases are resolved via regex matching in the internal switch statement.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Position=0)][string]   $Command     = '',
        [Parameter(Position=1)][string]   $Arg1        = '',
        [Parameter(Position=2)][string]   $Arg2        = '',
        [Parameter(ValueFromRemainingArguments)][string[]]$Rest = @(),
        [string]  $Language    = '',
        [string]  $Description = '',
        [string[]]$Tags        = @(),
        [string]  $Content     = '',
        [string]  $Path        = '',
        [string]  $Editor      = '',
        [string]  $Token       = '',
        [string]  $Username    = '',
        [switch]  $Public,
        [switch]  $Force,
        [switch]  $Push,
        [switch]  $Clip,
        [switch]  $All,
        [switch]  $Shared,
        [switch]  $IgnoreDuplicate,
        [string]  $Visibility    = '',
        [string]  $Scope         = ''
    )

    switch -Regex ($Command.ToLower()) {

        '^(|ui|tui)$' { Start-SnipManager }

        '^(list|ls|l)$' {
            $f = if ($Arg1) { $Arg1 } else { '' }
            Get-Snip -Filter $f -Shared:$Shared | Out-Null
        }

        '^(show|cat|view|s)$' {
            $n = if ($Arg1) { $Arg1 } else { script:Out-Err "Usage: snip show <name>"; return }
            Show-Snip -Name $n
        }

        '^(new|create|n)$' {
            $n = if ($Arg1) { $Arg1 } else { Read-Host " Snippet name" }
            $l = if ($Language) { $Language } elseif ($Arg2) { $Arg2 } else { '' }
            New-Snip -Name $n -Language $l -Description $Description -Tags $Tags -Content $Content -Editor $Editor -IgnoreDuplicate:$IgnoreDuplicate
        }

        '^(add|a)$' {
            $n = if ($Arg1) { $Arg1 } else { Read-Host " Snippet name" }
            $p = if ($Path) { $Path } elseif ($Arg2) { $Arg2 } else { '' }
            if ($Clip) {
                Add-Snip -Name $n -FromClipboard -Language $Language -Description $Description -Tags $Tags -Force:$Force -IgnoreDuplicate:$IgnoreDuplicate
            } elseif ($p) {
                Add-Snip -Name $n -Path $p -Language $Language -Description $Description -Tags $Tags -Force:$Force -IgnoreDuplicate:$IgnoreDuplicate
            } else {
                script:Out-Err "Specify -Path <file> or -Clip for clipboard."
            }
        }

        '^(edit|e)$' {
            $n = if ($Arg1) { $Arg1 } else { script:Out-Err "Usage: snip edit <name>"; return }
            Edit-Snip -Name $n -Editor $Editor
        }

        '^(run|exec|r)$' {
            $n = if ($Arg1) { $Arg1 } else { script:Out-Err "Usage: snip run <name>"; return }
            Invoke-Snip -Name $n -ArgumentList $Rest
        }

        '^(remove|delete|rm|del)$' {
            $n = if ($Arg1) { $Arg1 } else { script:Out-Err "Usage: snip rm <name>"; return }
            Remove-Snip -Name $n -Force:$Force
        }

        '^(copy|cp|yank)$' {
            $n = if ($Arg1) { $Arg1 } else { script:Out-Err "Usage: snip copy <name>"; return }
            Copy-Snip -Name $n
        }

        '^tag$' {
            $n = if ($Arg1) { $Arg1 } else { script:Out-Err "Usage: snip tag <name> -Tags t1,t2"; return }
            Set-SnipTag -Name $n -Tags $Tags -Add @() -Remove @()
        }

        '^(search|find|f)$' {
            $q = if ($Arg1) { $Arg1 } else { script:Out-Err "Usage: snip search <query>"; return }
            Get-Snip -Filter $q | Out-Null
        }

        '^config$' {
            if     ($Token)    { Set-SnipConfig -GitHubToken    $Token    }
            elseif ($Username) { Set-SnipConfig -GitHubUsername $Username }
            elseif ($Editor)   { Set-SnipConfig -Editor         $Editor   }
            elseif ($Language) { Set-SnipConfig -DefaultLanguage $Language }
            else               { Get-SnipConfig }
        }

        '^gist$' {
            $sub = $Arg1.ToLower()
            switch -Regex ($sub) {
                '^(list|ls|)$' {
                    $f = if ($Arg2) { $Arg2 } else { '' }
                    Get-GistList -Filter $f | Out-Null
                }
                '^(show|get|view)$' {
                    $id = $Arg2; if (-not $id) { script:Out-Err "Usage: snip gist show <id>"; return }
                    Get-Gist -GistId $id
                }
                '^(import|pull|clone)$' {
                    $id = $Arg2; if (-not $id) { script:Out-Err "Usage: snip gist import <id>"; return }
                    $n  = if ($Rest) { $Rest[0] } else { '' }
                    if ($n) { Import-Gist -GistId $id -Name $n -All:$All }
                    else    { Import-Gist -GistId $id           -All:$All }
                }
                '^(push|export)$' {
                    $n = $Arg2; if (-not $n) { script:Out-Err "Usage: snip gist push <name>"; return }
                    Export-Gist -Name $n -Description $Description -Public:$Public
                }
                '^(run|exec)$' {
                    $id = $Arg2; if (-not $id) { script:Out-Err "Usage: snip gist run <id>"; return }
                    Invoke-Gist -GistId $id -ArgumentList $Rest
                }
                '^sync$' {
                    $n = $Arg2; if (-not $n) { script:Out-Err "Usage: snip gist sync <name>"; return }
                    Sync-Gist -Name $n -Push:$Push
                }
                default {
                    # bare gist id?
                    if ($Arg1 -match '^[a-f0-9]{20,}$') { Get-Gist -GistId $Arg1 }
                    else { Write-Host "`n Gist sub-commands: list, show, import, push, run, sync`n" -ForegroundColor DarkCyan }
                }
            }
        }

        '^(pipeline|chain)$' {
            if (-not $Arg1) { script:Out-Err "Usage: snip pipeline <name1,name2,...>"; return }
            $names = ($Arg1 -split ',') + @($Rest | Where-Object { $_ })
            $names = @($names | ForEach-Object { $_.Trim() } | Where-Object { $_ })
            $ceFlag = $Force  # reuse -Force switch for -ContinueOnError in pipeline context
            Invoke-Snip -Pipeline $names -ContinueOnError:$ceFlag
        }

        '^gitlab$' {
            $sub = $Arg1.ToLower()
            switch -Regex ($sub) {
                '^(list|ls|)$'    { Get-GitLabSnipList -Filter (if ($Arg2) { $Arg2 } else { '' }) | Out-Null }
                '^(get|show)$'    { $id = $Arg2; if (-not $id) { script:Out-Err "Usage: snip gitlab get <id>"; return }; Get-GitLabSnip -SnipId $id }
                '^(import|pull)$' { $id = $Arg2; if (-not $id) { script:Out-Err "Usage: snip gitlab import <id>"; return }; Import-GitLabSnip -SnipId $id -Force:$Force }
                '^(export|push)$' { $n = $Arg2; if (-not $n) { script:Out-Err "Usage: snip gitlab export <name>"; return }; $v = if ($Visibility) { $Visibility } else { 'private' }; Export-GitLabSnip -Name $n -Description $Description -Visibility $v }
                default           { Write-Host "`n GitLab sub-commands: list, get, import, export`n" -ForegroundColor DarkCyan }
            }
        }

        '^gitlab-list$'   { Get-GitLabSnipList -Filter (if ($Arg1) { $Arg1 } else { '' }) | Out-Null }
        '^gitlab-get$'    { $id = $Arg1; if (-not $id) { script:Out-Err "Usage: snip gitlab-get <id>"; return }; Get-GitLabSnip -SnipId $id }
        '^gitlab-import$' { $id = $Arg1; if (-not $id) { script:Out-Err "Usage: snip gitlab-import <id>"; return }; Import-GitLabSnip -SnipId $id -Force:$Force }
        '^gitlab-export$' { $n = $Arg1; if (-not $n) { script:Out-Err "Usage: snip gitlab-export <name>"; return }; $v = if ($Visibility) { $Visibility } else { 'private' }; Export-GitLabSnip -Name $n -Description $Description -Visibility $v }

        '^publish$' {
            $n = if ($Arg1) { $Arg1 } else { script:Out-Err "Usage: snip publish <name>"; return }
            Publish-Snip -Name $n -Force:$Force
        }

        '^sync-shared$' { Sync-SharedSnips -Force:$Force }

        '^install$'   { $s = if ($Scope) { $Scope } else { 'CurrentUserCurrentHost' }; Install-PSSnips -Scope $s -Force:$Force }
        '^uninstall$' { $s = if ($Scope) { $Scope } else { 'CurrentUserCurrentHost' }; Uninstall-PSSnips -Scope $s }

        '^(help|h|\?)$' {
            script:Out-Banner
            Get-Help snip -Full
        }

        default {
            # If command matches a known snippet name, show it
            $idx = script:LoadIdx
            if ($idx.snippets.ContainsKey($Command)) { Show-Snip -Name $Command }
            else { script:Out-Err "Unknown command '$Command'. Run 'snip help' for usage." }
        }
    }
}
Set-Alias -Name snip -Value Invoke-SnipCLI -Scope Global -Description 'PSSnips dispatcher alias'