completions/wtw.auto-completion.ps1
|
# Tab completion for wtw CLI Register-ArgumentCompleter -Native -CommandName wtw -ScriptBlock { param($wordToComplete, $commandAst, $cursorPosition) function Get-WtwRepoAliasesJson { param([psobject] $Repo) if ($Repo.PSObject.Properties.Name -contains 'aliases' -and $Repo.aliases) { return @($Repo.aliases) } if ($Repo.PSObject.Properties.Name -contains 'alias' -and $Repo.alias) { return @($Repo.alias) } return @() } function Get-WtwWorktreePath { param([psobject] $WorktreeEntry, [string] $TaskFallback) if ($WorktreeEntry -and $WorktreeEntry.PSObject.Properties.Name -contains 'path' -and $WorktreeEntry.path) { return [string]$WorktreeEntry.path } return $TaskFallback } function Build-WtwAllTargets { param([psobject] $Registry) $list = [System.Collections.Generic.List[object]]::new() foreach ($repoName in $Registry.repos.PSObject.Properties.Name) { $repo = $Registry.repos.$repoName $aliases = @(Get-WtwRepoAliasesJson $repo) $mainTip = if ($repo.mainPath) { "$repoName → $([string]$repo.mainPath)" } else { "$repoName (main)" } $list.Add([ordered]@{ Name = $repoName; Tip = $mainTip }) foreach ($a in $aliases) { if ($a) { $list.Add([ordered]@{ Name = [string]$a; Tip = $mainTip }) } } if ($repo.worktrees) { foreach ($task in $repo.worktrees.PSObject.Properties.Name) { $wt = $repo.worktrees.$task $pathTip = Get-WtwWorktreePath -WorktreeEntry $wt -TaskFallback $task $wtTip = "$repoName → $pathTip" $list.Add([ordered]@{ Name = $task; Tip = $wtTip }) foreach ($a in $aliases) { if ($a) { $list.Add([ordered]@{ Name = "$a-$task"; Tip = $wtTip }) } } } } } $seen = @{} foreach ($item in $list) { if (-not $seen.ContainsKey($item.Name)) { $seen[$item.Name] = $true $item } } } function Build-WtwRepoFilterList { param([psobject] $Registry) $byName = @{} foreach ($repoName in $Registry.repos.PSObject.Properties.Name) { $repo = $Registry.repos.$repoName if (-not $byName.ContainsKey($repoName)) { $byName[$repoName] = @{ Name = $repoName; Tip = "repo $repoName" } } foreach ($a in (Get-WtwRepoAliasesJson $repo)) { if ($a -and -not $byName.ContainsKey([string]$a)) { $byName[[string]$a] = @{ Name = [string]$a; Tip = "alias → $repoName" } } } } return @($byName.Values) } function Filter-WtwMatches { param( [object[]] $Items, [string] $Word ) if ([string]::IsNullOrWhiteSpace($Word)) { return $Items | Sort-Object -Property Name } $esc = [WildcardPattern]::Escape($Word) $hit = $Items | Where-Object { $_.Name -like "$esc*" -or $_.Name -like "*$esc*" } $prefix = @($hit | Where-Object { $_.Name -like "$esc*" } | Sort-Object -Property Name) $rest = @($hit | Where-Object { $_.Name -notlike "$esc*" } | Sort-Object -Property Name) return @($prefix + $rest) } $elems = @($commandAst.CommandElements) $registryPath = Join-Path $HOME '.wtw' 'registry.json' $knownSubcommands = @( 'init', 'add', 'create', 'list', 'ls', 'go', 'open', 'remove', 'rm', 'unregister', 'unreg', 'workspace', 'ws', 'copy', 'sync', 'color', 'clean', 'install', 'update', 'skill', 'help', '__resolve', '__aliases', 'cursor', 'cur', 'code', 'co', 'antigravity', 'anti', 'ag', 'windsurf', 'wind', 'codium', 'vscodium', 'sourcegit', 'sgit', 'sg' ) $targetSubcommands = @( 'go', 'open', 'remove', 'rm', 'unregister', 'unreg', 'sync', 'color', 'cursor', 'cur', 'code', 'co', 'antigravity', 'anti', 'ag', 'windsurf', 'wind', 'codium', 'vscodium', 'sourcegit', 'sgit', 'sg', 'workspace', 'ws' ) # First token after wtw: subcommand (or refine partial) if ($elems.Count -lt 2) { $subcommands = @( @{ Name = 'init'; Tip = 'Register current repo in wtw' } @{ Name = 'add'; Tip = 'Add existing repo/worktree to registry' } @{ Name = 'create'; Tip = 'Create worktree + workspace' } @{ Name = 'list'; Tip = 'List registered worktrees' } @{ Name = 'go'; Tip = 'Switch to worktree' } @{ Name = 'open'; Tip = 'Open workspace in editor' } @{ Name = 'remove'; Tip = 'Remove worktree + workspace' } @{ Name = 'unregister'; Tip = 'Drop repo/worktree from wtw registry only' } @{ Name = 'unreg'; Tip = 'Alias for unregister' } @{ Name = 'clean'; Tip = 'Clean stale AI worktrees' } @{ Name = 'skill'; Tip = 'Install AI skill into current repo' } @{ Name = 'help'; Tip = 'Show help' } ) $prefix = $wordToComplete $subcommands | Where-Object { $_.Name -like "$prefix*" } | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, 'ParameterValue', $_.Tip) } return } $firstArg = $elems[1].Extent.Text if ($firstArg -notin $knownSubcommands) { $subMatches = @($knownSubcommands | Where-Object { $_ -like "$firstArg*" } | Select-Object -Unique) if ($subMatches.Count -gt 0) { $subMatches | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', "wtw $_") } return } if (-not (Test-Path $registryPath)) { return } $registry = Get-Content $registryPath -Raw | ConvertFrom-Json $targets = @(Build-WtwAllTargets $registry) $needle = if (-not [string]::IsNullOrWhiteSpace($wordToComplete)) { $wordToComplete } else { $firstArg } Filter-WtwMatches $targets $needle | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, 'ParameterValue', $_.Tip) } return } $subCommand = $firstArg if (-not (Test-Path $registryPath)) { return } $registry = Get-Content $registryPath -Raw | ConvertFrom-Json # --repo value (previous token is --repo) if ($elems.Count -ge 3 -and $wordToComplete -notmatch '^-') { $prev = $elems[$elems.Count - 2].Extent.Text if ($prev -ieq '--repo' -and $subCommand -in @( 'create', 'remove', 'rm', 'open', 'unregister', 'unreg', 'sync', 'list', 'ls' )) { $repos = Build-WtwRepoFilterList $registry Filter-WtwMatches $repos $wordToComplete | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, 'ParameterValue', $_.Tip) } return } } # wtw list [repoFilter] if ($subCommand -in @('list', 'ls')) { if ($elems.Count -ge 3 -and $elems[2].Extent.Text -notlike '-*') { $repos = Build-WtwRepoFilterList $registry Filter-WtwMatches $repos $wordToComplete | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, 'ParameterValue', $_.Tip) } return } if ($elems.Count -eq 2 -and [string]::IsNullOrWhiteSpace($wordToComplete)) { $repos = Build-WtwRepoFilterList $registry Filter-WtwMatches $repos '' | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, 'ParameterValue', $_.Tip) } return } } # Positional target if ($subCommand -in $targetSubcommands) { if ($elems.Count -ge 3 -and $elems[2].Extent.Text -notlike '-*') { $targets = @(Build-WtwAllTargets $registry) Filter-WtwMatches $targets $wordToComplete | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, 'ParameterValue', $_.Tip) } return } if ($elems.Count -eq 2 -and [string]::IsNullOrWhiteSpace($wordToComplete)) { $targets = @(Build-WtwAllTargets $registry) Filter-WtwMatches $targets '' | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, 'ParameterValue', $_.Tip) } return } } if ($wordToComplete -like '-*' -or $wordToComplete -like '--*') { $flags = switch ($subCommand) { 'init' { @('--template', '--startup-script', '--startup-script-zsh', '--startup-script-bash', '--workspaces-dir', '--name') } 'skill' { @('--agent') } 'add' { @('--repo', '--task', '--branch') } 'create' { @('--branch', '--repo', '--open', '--no-branch') } 'clean' { @('--dry-run', '--force') } 'remove' { @('--repo', '--force') } 'rm' { @('--repo', '--force') } 'unregister' { @('--repo', '--force') } 'unreg' { @('--repo', '--force') } 'open' { @('--repo', '--editor') } 'list' { @('--repo', '--detailed', '-d') } 'ls' { @('--repo', '--detailed', '-d') } 'sync' { @('--all', '--repo', '--template', '--dry-run', '--color-source') } 'color' { @('--no-sync') } default { @() } } $flags | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterName', $_) } } } |