mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-15 12:13:59 +00:00
feat(cli): refresh help and shell completion UX
- add a shared command catalog for root help and completion routing - replace shell-local completion logic with a hidden __complete backend - add topic help and parity tests for router, help, and completion coverage
This commit is contained in:
1 parent
3eb55d0e33
commit
24ba2abe10
15 files changed
+1227
-2118
No files matched your search
+42
-432
@@ -1,446 +1,56 @@
|
||||
# PowerShell completion for CCS (Claude Code Switch)
|
||||
# Compatible with PowerShell 5.1+
|
||||
#
|
||||
# Installation:
|
||||
# Add to your PowerShell profile ($PROFILE):
|
||||
# . /path/to/ccs/scripts/completion/ccs.ps1
|
||||
#
|
||||
# Or install for current user:
|
||||
# Copy-Item scripts/completion/ccs.ps1 ~\Documents\PowerShell\Scripts\
|
||||
# Add to profile: . ~\Documents\PowerShell\Scripts\ccs.ps1
|
||||
|
||||
function Invoke-CcsCompletionBackend {
|
||||
param(
|
||||
[string]$CurrentWord,
|
||||
[string[]]$TokensBeforeCurrent
|
||||
)
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
||||
$repoCli = Join-Path $repoRoot 'dist\ccs.js'
|
||||
if (-not (Test-Path $repoCli)) {
|
||||
$repoCli = Join-Path $repoRoot 'bin\ccs.js'
|
||||
}
|
||||
if (Test-Path $repoCli) {
|
||||
& node $repoCli __complete --shell powershell --current $CurrentWord -- @TokensBeforeCurrent 2>$null
|
||||
return
|
||||
}
|
||||
|
||||
if (Get-Command ccs -ErrorAction SilentlyContinue) {
|
||||
& ccs __complete --shell powershell --current $CurrentWord -- @TokensBeforeCurrent 2>$null
|
||||
}
|
||||
}
|
||||
|
||||
Register-ArgumentCompleter -CommandName ccs -ScriptBlock {
|
||||
param($commandName, $wordToComplete, $commandAst, $fakeBoundParameters)
|
||||
|
||||
$commands = @('auth', 'api', 'cliproxy', 'doctor', 'env', 'sync', 'update', '--help', '--version', '--shell-completion', '-h', '-v', '-sc')
|
||||
$cliproxyProfiles = @('gemini', 'codex', 'agy', 'qwen', 'iflow', 'kiro', 'ghcp', 'claude')
|
||||
$authCommands = @('create', 'list', 'show', 'remove', 'default', '--help', '-h')
|
||||
$apiCommands = @('create', 'list', 'remove', '--help', '-h')
|
||||
$cliproxyCommands = @('create', 'list', 'remove', '--install', '--latest', '--help', '-h')
|
||||
$apiCreateFlags = @('--base-url', '--api-key', '--model', '--force', '--yes', '-y')
|
||||
$cliproxyCreateFlags = @('--provider', '--model', '--force', '--yes', '-y')
|
||||
$providerFlags = @('--auth', '--config', '--logout', '--headless', '--help', '-h')
|
||||
$updateFlags = @('--force', '--beta', '--dev', '--help', '-h')
|
||||
$envFlags = @('--format', '--shell', '--help', '-h')
|
||||
$envFormats = @('openai', 'anthropic', 'raw')
|
||||
$envShells = @('auto', 'bash', 'zsh', 'fish', 'powershell')
|
||||
$shellCompletionFlags = @('--bash', '--zsh', '--fish', '--powershell')
|
||||
$listFlags = @('--verbose', '--json')
|
||||
$removeFlags = @('--yes', '-y')
|
||||
$showFlags = @('--json')
|
||||
$providers = @('gemini', 'codex', 'agy', 'qwen')
|
||||
|
||||
# Get current position in command
|
||||
$words = $commandAst.ToString() -split '\s+' | Where-Object { $_ -ne '' }
|
||||
$position = $words.Count
|
||||
|
||||
# Helper function to get profiles
|
||||
function Get-CcsProfiles {
|
||||
param([string]$Type = 'all')
|
||||
|
||||
$profiles = @()
|
||||
|
||||
# Settings-based profiles
|
||||
if ($Type -in @('all', 'settings')) {
|
||||
$configPath = "$env:USERPROFILE\.ccs\config.json"
|
||||
if (Test-Path $configPath) {
|
||||
try {
|
||||
$config = Get-Content $configPath -Raw | ConvertFrom-Json
|
||||
$profiles += $config.profiles.PSObject.Properties.Name
|
||||
} catch {}
|
||||
$commandElements = @($commandAst.CommandElements | ForEach-Object { $_.Extent.Text })
|
||||
$tokensBeforeCurrent = @()
|
||||
if ($commandElements.Count -gt 1) {
|
||||
$tokensBeforeCurrent = $commandElements[1..($commandElements.Count - 1)]
|
||||
if ($tokensBeforeCurrent.Count -gt 0 -and $tokensBeforeCurrent[-1] -eq $wordToComplete) {
|
||||
$tokensBeforeCurrent = if ($tokensBeforeCurrent.Count -gt 1) {
|
||||
$tokensBeforeCurrent[0..($tokensBeforeCurrent.Count - 2)]
|
||||
} else {
|
||||
@()
|
||||
}
|
||||
}
|
||||
|
||||
# Account-based profiles
|
||||
if ($Type -in @('all', 'account')) {
|
||||
$profilesPath = "$env:USERPROFILE\.ccs\profiles.json"
|
||||
if (Test-Path $profilesPath) {
|
||||
try {
|
||||
$data = Get-Content $profilesPath -Raw | ConvertFrom-Json
|
||||
$profiles += $data.profiles.PSObject.Properties.Name
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
# CLIProxy variants
|
||||
if ($Type -in @('all', 'cliproxy')) {
|
||||
$configPath = "$env:USERPROFILE\.ccs\config.json"
|
||||
if (Test-Path $configPath) {
|
||||
try {
|
||||
$config = Get-Content $configPath -Raw | ConvertFrom-Json
|
||||
if ($config.cliproxy) {
|
||||
$profiles += $config.cliproxy.PSObject.Properties.Name
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
return $profiles | Sort-Object -Unique
|
||||
}
|
||||
|
||||
# Top-level completion
|
||||
if ($position -eq 2) {
|
||||
$allOptions = $commands + $cliproxyProfiles + (Get-CcsProfiles)
|
||||
$allOptions | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
foreach ($line in Invoke-CcsCompletionBackend -CurrentWord $wordToComplete -TokensBeforeCurrent $tokensBeforeCurrent) {
|
||||
if ([string]::IsNullOrWhiteSpace($line)) {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
# shell-completion flag completion
|
||||
if ($words[1] -eq '--shell-completion' -or $words[1] -eq '-sc') {
|
||||
if ($position -eq 3) {
|
||||
$shellCompletionFlags | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
$parts = $line -split "`t", 2
|
||||
$value = $parts[0]
|
||||
$description = if ($parts.Count -gt 1) { $parts[1] } else { $parts[0] }
|
||||
|
||||
# CLIProxy provider flags (gemini, codex, agy, qwen)
|
||||
if ($words[1] -in $cliproxyProfiles) {
|
||||
$providerFlags | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
# update command completion
|
||||
if ($words[1] -eq 'update') {
|
||||
$updateFlags | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
# env command completion
|
||||
if ($words[1] -eq 'env') {
|
||||
if ($position -eq 3) {
|
||||
$options = $cliproxyProfiles + (Get-CcsProfiles -Type settings) + $envFlags
|
||||
$options | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
} elseif ($position -ge 4) {
|
||||
switch ($words[$position - 2]) {
|
||||
'--format' {
|
||||
$envFormats | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
'--shell' {
|
||||
$envShells | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
default {
|
||||
$envFlags | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
# auth subcommand completion
|
||||
if ($words[1] -eq 'auth') {
|
||||
if ($position -eq 3) {
|
||||
$authCommands | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
} elseif ($position -eq 4) {
|
||||
switch ($words[2]) {
|
||||
'show' {
|
||||
$options = (Get-CcsProfiles -Type account) + $showFlags
|
||||
$options | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
'remove' {
|
||||
$options = (Get-CcsProfiles -Type account) + $removeFlags
|
||||
$options | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
'default' {
|
||||
Get-CcsProfiles -Type account | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
'list' {
|
||||
$listFlags | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
'create' {
|
||||
@('--force') | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($position -eq 5) {
|
||||
switch ($words[2]) {
|
||||
'show' {
|
||||
$showFlags | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
'remove' {
|
||||
$removeFlags | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
# api subcommand completion
|
||||
if ($words[1] -eq 'api') {
|
||||
if ($position -eq 3) {
|
||||
$apiCommands | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
} elseif ($position -eq 4) {
|
||||
switch ($words[2]) {
|
||||
'remove' {
|
||||
$options = (Get-CcsProfiles -Type settings) + $removeFlags
|
||||
$options | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
'create' {
|
||||
$apiCreateFlags | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($position -eq 5) {
|
||||
switch ($words[2]) {
|
||||
'remove' {
|
||||
$removeFlags | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
# cliproxy subcommand completion
|
||||
if ($words[1] -eq 'cliproxy') {
|
||||
if ($position -eq 3) {
|
||||
$cliproxyCommands | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
} elseif ($position -eq 4) {
|
||||
switch ($words[2]) {
|
||||
'remove' {
|
||||
$options = (Get-CcsProfiles -Type cliproxy) + $removeFlags
|
||||
$options | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
'create' {
|
||||
$cliproxyCreateFlags | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($position -eq 5) {
|
||||
switch ($words[2]) {
|
||||
'remove' {
|
||||
$removeFlags | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
'create' {
|
||||
# After --provider, complete with provider names
|
||||
if ($words[3] -eq '--provider') {
|
||||
$providers | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
# profile subcommand completion (legacy)
|
||||
if ($words[1] -eq 'profile') {
|
||||
if ($position -eq 3) {
|
||||
@('create', 'list', 'remove', '--help', '-h') | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
} elseif ($position -eq 4) {
|
||||
switch ($words[2]) {
|
||||
'remove' {
|
||||
$options = (Get-CcsProfiles -Type settings) + $removeFlags
|
||||
$options | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
'create' {
|
||||
$apiCreateFlags | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($position -eq 5) {
|
||||
switch ($words[2]) {
|
||||
'remove' {
|
||||
$removeFlags | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$_,
|
||||
$_,
|
||||
'ParameterValue',
|
||||
$_
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
[System.Management.Automation.CompletionResult]::new(
|
||||
$value,
|
||||
$value,
|
||||
'ParameterValue',
|
||||
$description
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user