mirror of
https://github.com/tiennm99/ccs.git
synced 2026-08-18 06:25:36 +00:00
feat(shell-completion): add --force flag and fix zsh profile coloring
This commit is contained in:
@@ -18,7 +18,7 @@ _ccs_completion() {
|
|||||||
|
|
||||||
# Top-level completion (first argument)
|
# Top-level completion (first argument)
|
||||||
if [[ ${COMP_CWORD} -eq 1 ]]; then
|
if [[ ${COMP_CWORD} -eq 1 ]]; then
|
||||||
local commands="auth doctor sync update"
|
local commands="auth profile doctor sync update"
|
||||||
local flags="--help --version --shell-completion -h -v -sc"
|
local flags="--help --version --shell-completion -h -v -sc"
|
||||||
local profiles=""
|
local profiles=""
|
||||||
|
|
||||||
@@ -45,6 +45,36 @@ _ccs_completion() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# profile subcommands
|
||||||
|
if [[ ${prev} == "profile" ]]; then
|
||||||
|
local profile_commands="create list remove --help -h"
|
||||||
|
COMPREPLY=( $(compgen -W "${profile_commands}" -- ${cur}) )
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Completion for profile subcommands
|
||||||
|
if [[ ${COMP_WORDS[1]} == "profile" ]]; then
|
||||||
|
case "${prev}" in
|
||||||
|
remove|delete|rm)
|
||||||
|
# Complete with settings profile names
|
||||||
|
if [[ -f ~/.ccs/config.json ]]; then
|
||||||
|
local profiles=$(jq -r '.profiles | keys[]' ~/.ccs/config.json 2>/dev/null || true)
|
||||||
|
COMPREPLY=( $(compgen -W "${profiles}" -- ${cur}) )
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
create)
|
||||||
|
# Complete with create flags
|
||||||
|
COMPREPLY=( $(compgen -W "--base-url --api-key --model --force --yes -y" -- ${cur}) )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
list)
|
||||||
|
# No flags for list
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
# Completion for auth subcommands that need profile names
|
# Completion for auth subcommands that need profile names
|
||||||
if [[ ${COMP_WORDS[1]} == "auth" ]]; then
|
if [[ ${COMP_WORDS[1]} == "auth" ]]; then
|
||||||
case "${prev}" in
|
case "${prev}" in
|
||||||
|
|||||||
+43
-10
@@ -78,22 +78,23 @@ complete -c ccs -s v -l version -d 'Show version information'
|
|||||||
complete -c ccs -s sc -l shell-completion -d 'Install shell completion'
|
complete -c ccs -s sc -l shell-completion -d 'Install shell completion'
|
||||||
|
|
||||||
# Commands - grouped with [cmd] prefix for visual distinction
|
# Commands - grouped with [cmd] prefix for visual distinction
|
||||||
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a 'auth' -d '[cmd] Manage multiple Claude accounts'
|
complete -c ccs -n 'not __fish_seen_subcommand_from auth profile doctor sync update' -a 'auth' -d '[cmd] Manage multiple Claude accounts'
|
||||||
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a 'doctor' -d '[cmd] Run health check and diagnostics'
|
complete -c ccs -n 'not __fish_seen_subcommand_from auth profile doctor sync update' -a 'profile' -d '[cmd] Manage API profiles (create/remove)'
|
||||||
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a 'sync' -d '[cmd] Sync delegation commands and skills'
|
complete -c ccs -n 'not __fish_seen_subcommand_from auth profile doctor sync update' -a 'doctor' -d '[cmd] Run health check and diagnostics'
|
||||||
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a 'update' -d '[cmd] Update CCS to latest version'
|
complete -c ccs -n 'not __fish_seen_subcommand_from auth profile doctor sync update' -a 'sync' -d '[cmd] Sync delegation commands and skills'
|
||||||
|
complete -c ccs -n 'not __fish_seen_subcommand_from auth profile doctor sync update' -a 'update' -d '[cmd] Update CCS to latest version'
|
||||||
|
|
||||||
# Model profiles - grouped with [model] prefix for visual distinction
|
# Model profiles - grouped with [model] prefix for visual distinction
|
||||||
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a 'default' -d '[model] Default Claude Sonnet 4.5'
|
complete -c ccs -n 'not __fish_seen_subcommand_from auth profile doctor sync update' -a 'default' -d '[model] Default Claude Sonnet 4.5'
|
||||||
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a 'glm' -d '[model] GLM-4.6 (cost-optimized)'
|
complete -c ccs -n 'not __fish_seen_subcommand_from auth profile doctor sync update' -a 'glm' -d '[model] GLM-4.6 (cost-optimized)'
|
||||||
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a 'glmt' -d '[model] GLM-4.6 with thinking mode'
|
complete -c ccs -n 'not __fish_seen_subcommand_from auth profile doctor sync update' -a 'glmt' -d '[model] GLM-4.6 with thinking mode'
|
||||||
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a 'kimi' -d '[model] Kimi for Coding (long-context)'
|
complete -c ccs -n 'not __fish_seen_subcommand_from auth profile doctor sync update' -a 'kimi' -d '[model] Kimi for Coding (long-context)'
|
||||||
|
|
||||||
# Custom model profiles - dynamic with [model] prefix
|
# Custom model profiles - dynamic with [model] prefix
|
||||||
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a '(__fish_ccs_get_custom_settings_profiles)' -d '[model] Settings-based profile'
|
complete -c ccs -n 'not __fish_seen_subcommand_from auth profile doctor sync update' -a '(__fish_ccs_get_custom_settings_profiles)' -d '[model] Settings-based profile'
|
||||||
|
|
||||||
# Account profiles - dynamic with [account] prefix
|
# Account profiles - dynamic with [account] prefix
|
||||||
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a '(__fish_ccs_get_account_profiles)' -d '[account] Account-based profile'
|
complete -c ccs -n 'not __fish_seen_subcommand_from auth profile doctor sync update' -a '(__fish_ccs_get_account_profiles)' -d '[account] Account-based profile'
|
||||||
|
|
||||||
# shell-completion subflags
|
# shell-completion subflags
|
||||||
complete -c ccs -n '__fish_seen_argument -l shell-completion; or __fish_seen_argument -s sc' -l bash -d 'Install for bash'
|
complete -c ccs -n '__fish_seen_argument -l shell-completion; or __fish_seen_argument -s sc' -l bash -d 'Install for bash'
|
||||||
@@ -132,3 +133,35 @@ complete -c ccs -n '__fish_ccs_using_auth_subcommand default' -a '(__fish_ccs_ge
|
|||||||
|
|
||||||
# doctor command flags
|
# doctor command flags
|
||||||
complete -c ccs -n '__fish_seen_subcommand_from doctor' -s h -l help -d 'Show help for doctor command'
|
complete -c ccs -n '__fish_seen_subcommand_from doctor' -s h -l help -d 'Show help for doctor command'
|
||||||
|
|
||||||
|
# Helper function to check if we're in profile context
|
||||||
|
function __fish_ccs_using_profile
|
||||||
|
__fish_seen_subcommand_from profile
|
||||||
|
end
|
||||||
|
|
||||||
|
# Helper function to check specific profile subcommand
|
||||||
|
function __fish_ccs_using_profile_subcommand
|
||||||
|
set -l subcommand $argv[1]
|
||||||
|
__fish_ccs_using_profile; and __fish_seen_subcommand_from $subcommand
|
||||||
|
end
|
||||||
|
|
||||||
|
# profile subcommands
|
||||||
|
complete -c ccs -n '__fish_ccs_using_profile; and not __fish_seen_subcommand_from create list remove' -a 'create' -d 'Create new API profile'
|
||||||
|
complete -c ccs -n '__fish_ccs_using_profile; and not __fish_seen_subcommand_from create list remove' -a 'list' -d 'List all profiles'
|
||||||
|
complete -c ccs -n '__fish_ccs_using_profile; and not __fish_seen_subcommand_from create list remove' -a 'remove' -d 'Remove a profile'
|
||||||
|
|
||||||
|
# profile command flags
|
||||||
|
complete -c ccs -n '__fish_ccs_using_profile' -s h -l help -d 'Show help for profile commands'
|
||||||
|
|
||||||
|
# profile create flags
|
||||||
|
complete -c ccs -n '__fish_ccs_using_profile_subcommand create' -l base-url -d 'API base URL'
|
||||||
|
complete -c ccs -n '__fish_ccs_using_profile_subcommand create' -l api-key -d 'API key'
|
||||||
|
complete -c ccs -n '__fish_ccs_using_profile_subcommand create' -l model -d 'Default model'
|
||||||
|
complete -c ccs -n '__fish_ccs_using_profile_subcommand create' -l force -d 'Overwrite existing profile'
|
||||||
|
complete -c ccs -n '__fish_ccs_using_profile_subcommand create' -l yes -d 'Skip prompts'
|
||||||
|
complete -c ccs -n '__fish_ccs_using_profile_subcommand create' -s y -d 'Skip prompts'
|
||||||
|
|
||||||
|
# profile remove - profile names and flags
|
||||||
|
complete -c ccs -n '__fish_ccs_using_profile_subcommand remove' -a '(__fish_ccs_get_settings_profiles)' -d 'Settings profile'
|
||||||
|
complete -c ccs -n '__fish_ccs_using_profile_subcommand remove' -l yes -d 'Skip confirmation'
|
||||||
|
complete -c ccs -n '__fish_ccs_using_profile_subcommand remove' -s y -d 'Skip confirmation'
|
||||||
|
|||||||
@@ -12,8 +12,10 @@
|
|||||||
Register-ArgumentCompleter -CommandName ccs -ScriptBlock {
|
Register-ArgumentCompleter -CommandName ccs -ScriptBlock {
|
||||||
param($commandName, $wordToComplete, $commandAst, $fakeBoundParameters)
|
param($commandName, $wordToComplete, $commandAst, $fakeBoundParameters)
|
||||||
|
|
||||||
$commands = @('auth', 'doctor', 'sync', 'update', '--help', '--version', '--shell-completion', '-h', '-v', '-sc')
|
$commands = @('auth', 'profile', 'doctor', 'sync', 'update', '--help', '--version', '--shell-completion', '-h', '-v', '-sc')
|
||||||
$authCommands = @('create', 'list', 'show', 'remove', 'default', '--help', '-h')
|
$authCommands = @('create', 'list', 'show', 'remove', 'default', '--help', '-h')
|
||||||
|
$profileCommands = @('create', 'list', 'remove', '--help', '-h')
|
||||||
|
$profileCreateFlags = @('--base-url', '--api-key', '--model', '--force', '--yes', '-y')
|
||||||
$shellCompletionFlags = @('--bash', '--zsh', '--fish', '--powershell')
|
$shellCompletionFlags = @('--bash', '--zsh', '--fish', '--powershell')
|
||||||
$listFlags = @('--verbose', '--json')
|
$listFlags = @('--verbose', '--json')
|
||||||
$removeFlags = @('--yes', '-y')
|
$removeFlags = @('--yes', '-y')
|
||||||
@@ -170,4 +172,58 @@ Register-ArgumentCompleter -CommandName ccs -ScriptBlock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# profile subcommand completion
|
||||||
|
if ($words[1] -eq 'profile') {
|
||||||
|
if ($position -eq 3) {
|
||||||
|
# profile subcommands
|
||||||
|
$profileCommands | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||||
|
[System.Management.Automation.CompletionResult]::new(
|
||||||
|
$_,
|
||||||
|
$_,
|
||||||
|
'ParameterValue',
|
||||||
|
$_
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} elseif ($position -eq 4) {
|
||||||
|
# Profile names or flags for profile subcommands
|
||||||
|
switch ($words[2]) {
|
||||||
|
'remove' {
|
||||||
|
$options = (Get-CcsProfiles -Type settings) + $removeFlags
|
||||||
|
$options | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||||
|
[System.Management.Automation.CompletionResult]::new(
|
||||||
|
$_,
|
||||||
|
$_,
|
||||||
|
'ParameterValue',
|
||||||
|
$_
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'create' {
|
||||||
|
$profileCreateFlags | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||||
|
[System.Management.Automation.CompletionResult]::new(
|
||||||
|
$_,
|
||||||
|
$_,
|
||||||
|
'ParameterValue',
|
||||||
|
$_
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif ($position -eq 5) {
|
||||||
|
# Flags after profile name
|
||||||
|
switch ($words[2]) {
|
||||||
|
'remove' {
|
||||||
|
$removeFlags | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
||||||
|
[System.Management.Automation.CompletionResult]::new(
|
||||||
|
$_,
|
||||||
|
$_,
|
||||||
|
'ParameterValue',
|
||||||
|
$_
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
# Color codes: 0;34=blue, 0;32=green, 0;33=yellow, 2;37=dim white
|
# Color codes: 0;34=blue, 0;32=green, 0;33=yellow, 2;37=dim white
|
||||||
# Pattern format: =(#b)(group1)(group2)==color_for_group1=color_for_group2
|
# Pattern format: =(#b)(group1)(group2)==color_for_group1=color_for_group2
|
||||||
# The leading '=' means no color for whole match, then each '=' assigns to each group
|
# The leading '=' means no color for whole match, then each '=' assigns to each group
|
||||||
zstyle ':completion:*:*:ccs:*:commands' list-colors '=(#b)(auth|doctor|sync|update)([[:space:]]#--[[:space:]]#*)==0\;34=2\;37'
|
zstyle ':completion:*:*:ccs:*:commands' list-colors '=(#b)(auth|profile|doctor|sync|update)([[:space:]]#--[[:space:]]#*)==0\;34=2\;37'
|
||||||
zstyle ':completion:*:*:ccs:*:model-profiles' list-colors '=(#b)(default|glm|glmt|kimi|[^[:space:]]##)([[:space:]]#--[[:space:]]#*)==0\;32=2\;37'
|
zstyle ':completion:*:*:ccs:*:model-profiles' list-colors '=(#b)(default|glm|glmt|kimi|[^[:space:]]##)([[:space:]]#--[[:space:]]#*)==0\;32=2\;37'
|
||||||
zstyle ':completion:*:*:ccs:*:account-profiles' list-colors '=(#b)([^[:space:]]##)([[:space:]]#--[[:space:]]#*)==0\;33=2\;37'
|
zstyle ':completion:*:*:ccs:*:account-profiles' list-colors '=(#b)([^[:space:]]##)([[:space:]]#--[[:space:]]#*)==0\;33=2\;37'
|
||||||
zstyle ':completion:*:*:ccs:*' group-name ''
|
zstyle ':completion:*:*:ccs:*' group-name ''
|
||||||
@@ -33,6 +33,7 @@ _ccs() {
|
|||||||
# Define top-level commands (padded for alignment)
|
# Define top-level commands (padded for alignment)
|
||||||
commands=(
|
commands=(
|
||||||
'auth:Manage multiple Claude accounts'
|
'auth:Manage multiple Claude accounts'
|
||||||
|
'profile:Manage API profiles (create/remove)'
|
||||||
'doctor:Run health check and diagnostics'
|
'doctor:Run health check and diagnostics'
|
||||||
'sync:Sync delegation commands and skills'
|
'sync:Sync delegation commands and skills'
|
||||||
'update:Update CCS to latest version'
|
'update:Update CCS to latest version'
|
||||||
@@ -90,6 +91,9 @@ _ccs() {
|
|||||||
auth)
|
auth)
|
||||||
_ccs_auth
|
_ccs_auth
|
||||||
;;
|
;;
|
||||||
|
profile)
|
||||||
|
_ccs_profile
|
||||||
|
;;
|
||||||
doctor)
|
doctor)
|
||||||
_arguments \
|
_arguments \
|
||||||
'(- *)'{-h,--help}'[Show help for doctor command]'
|
'(- *)'{-h,--help}'[Show help for doctor command]'
|
||||||
@@ -110,6 +114,58 @@ _ccs() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ccs_profile() {
|
||||||
|
local curcontext="$curcontext" state line
|
||||||
|
typeset -A opt_args
|
||||||
|
|
||||||
|
local -a profile_commands settings_profiles
|
||||||
|
|
||||||
|
# Define profile subcommands
|
||||||
|
profile_commands=(
|
||||||
|
'create:Create new API profile (interactive)'
|
||||||
|
'list:List all profiles'
|
||||||
|
'remove:Remove a profile'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load settings profiles for remove command
|
||||||
|
if [[ -f ~/.ccs/config.json ]]; then
|
||||||
|
settings_profiles=(${(f)"$(jq -r '.profiles | keys[]' ~/.ccs/config.json 2>/dev/null)"})
|
||||||
|
fi
|
||||||
|
|
||||||
|
_arguments -C \
|
||||||
|
'(- *)'{-h,--help}'[Show help for profile commands]' \
|
||||||
|
'1: :->subcommand' \
|
||||||
|
'*:: :->subargs'
|
||||||
|
|
||||||
|
case $state in
|
||||||
|
subcommand)
|
||||||
|
_describe -t profile-commands 'profile commands' profile_commands
|
||||||
|
;;
|
||||||
|
|
||||||
|
subargs)
|
||||||
|
case $words[1] in
|
||||||
|
create)
|
||||||
|
_arguments \
|
||||||
|
'1:profile name:' \
|
||||||
|
'--base-url[API base URL]:url:' \
|
||||||
|
'--api-key[API key]:key:' \
|
||||||
|
'--model[Default model]:model:' \
|
||||||
|
'--force[Overwrite existing profile]' \
|
||||||
|
{--yes,-y}'[Skip prompts]'
|
||||||
|
;;
|
||||||
|
list)
|
||||||
|
# No arguments
|
||||||
|
;;
|
||||||
|
remove|delete|rm)
|
||||||
|
_arguments \
|
||||||
|
'1:profile:($settings_profiles)' \
|
||||||
|
{--yes,-y}'[Skip confirmation]'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
_ccs_auth() {
|
_ccs_auth() {
|
||||||
local curcontext="$curcontext" state line
|
local curcontext="$curcontext" state line
|
||||||
typeset -A opt_args
|
typeset -A opt_args
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export async function handleShellCompletionCommand(args: string[]): Promise<void
|
|||||||
|
|
||||||
// Parse flags
|
// Parse flags
|
||||||
let targetShell: string | null = null;
|
let targetShell: string | null = null;
|
||||||
|
const force = args.includes('--force') || args.includes('-f');
|
||||||
if (args.includes('--bash')) targetShell = 'bash';
|
if (args.includes('--bash')) targetShell = 'bash';
|
||||||
else if (args.includes('--zsh')) targetShell = 'zsh';
|
else if (args.includes('--zsh')) targetShell = 'zsh';
|
||||||
else if (args.includes('--fish')) targetShell = 'fish';
|
else if (args.includes('--fish')) targetShell = 'fish';
|
||||||
@@ -24,10 +25,13 @@ export async function handleShellCompletionCommand(args: string[]): Promise<void
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const installer = new ShellCompletionInstaller();
|
const installer = new ShellCompletionInstaller();
|
||||||
const result = installer.install(targetShell as 'bash' | 'zsh' | 'fish' | 'powershell' | null);
|
const result = installer.install(targetShell as 'bash' | 'zsh' | 'fish' | 'powershell' | null, {
|
||||||
|
force,
|
||||||
|
});
|
||||||
|
|
||||||
if (result.alreadyInstalled) {
|
if (result.alreadyInstalled && !force) {
|
||||||
console.log(colored('[OK] Shell completion already installed', 'green'));
|
console.log(colored('[OK] Shell completion already installed', 'green'));
|
||||||
|
console.log(` Use ${colored('--force', 'yellow')} to reinstall`);
|
||||||
console.log('');
|
console.log('');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export class ShellCompletionInstaller {
|
|||||||
/**
|
/**
|
||||||
* Install bash completion
|
* Install bash completion
|
||||||
*/
|
*/
|
||||||
private installBash(): InstallResult {
|
private installBash(force = false): InstallResult {
|
||||||
const rcFile = path.join(this.homeDir, '.bashrc');
|
const rcFile = path.join(this.homeDir, '.bashrc');
|
||||||
const completionPath = path.join(this.completionDir, 'ccs.bash');
|
const completionPath = path.join(this.completionDir, 'ccs.bash');
|
||||||
|
|
||||||
@@ -108,7 +108,15 @@ export class ShellCompletionInstaller {
|
|||||||
if (fs.existsSync(rcFile)) {
|
if (fs.existsSync(rcFile)) {
|
||||||
const content = fs.readFileSync(rcFile, 'utf8');
|
const content = fs.readFileSync(rcFile, 'utf8');
|
||||||
if (content.includes(marker)) {
|
if (content.includes(marker)) {
|
||||||
return { success: true, alreadyInstalled: true };
|
if (!force) {
|
||||||
|
return { success: true, alreadyInstalled: true };
|
||||||
|
}
|
||||||
|
// Force: completion files already updated by ensureCompletionFiles()
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `Updated completion files in ${this.completionDir}`,
|
||||||
|
reload: 'source ~/.bashrc',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +133,7 @@ export class ShellCompletionInstaller {
|
|||||||
/**
|
/**
|
||||||
* Install zsh completion
|
* Install zsh completion
|
||||||
*/
|
*/
|
||||||
private installZsh(): InstallResult {
|
private installZsh(force = false): InstallResult {
|
||||||
const rcFile = path.join(this.homeDir, '.zshrc');
|
const rcFile = path.join(this.homeDir, '.zshrc');
|
||||||
const completionPath = path.join(this.completionDir, 'ccs.zsh');
|
const completionPath = path.join(this.completionDir, 'ccs.zsh');
|
||||||
const zshCompDir = path.join(this.homeDir, '.zsh', 'completion');
|
const zshCompDir = path.join(this.homeDir, '.zsh', 'completion');
|
||||||
@@ -137,7 +145,7 @@ export class ShellCompletionInstaller {
|
|||||||
// Create zsh completion directory (with file conflict checking)
|
// Create zsh completion directory (with file conflict checking)
|
||||||
this.ensureDirectory(zshCompDir);
|
this.ensureDirectory(zshCompDir);
|
||||||
|
|
||||||
// Copy to zsh completion directory
|
// Copy to zsh completion directory (always update for force)
|
||||||
const destFile = path.join(zshCompDir, '_ccs');
|
const destFile = path.join(zshCompDir, '_ccs');
|
||||||
fs.copyFileSync(completionPath, destFile);
|
fs.copyFileSync(completionPath, destFile);
|
||||||
|
|
||||||
@@ -149,7 +157,15 @@ export class ShellCompletionInstaller {
|
|||||||
if (fs.existsSync(rcFile)) {
|
if (fs.existsSync(rcFile)) {
|
||||||
const content = fs.readFileSync(rcFile, 'utf8');
|
const content = fs.readFileSync(rcFile, 'utf8');
|
||||||
if (content.includes(marker)) {
|
if (content.includes(marker)) {
|
||||||
return { success: true, alreadyInstalled: true };
|
if (!force) {
|
||||||
|
return { success: true, alreadyInstalled: true };
|
||||||
|
}
|
||||||
|
// Force: files already updated above
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `Updated ${destFile}`,
|
||||||
|
reload: 'source ~/.zshrc',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +182,7 @@ export class ShellCompletionInstaller {
|
|||||||
/**
|
/**
|
||||||
* Install fish completion
|
* Install fish completion
|
||||||
*/
|
*/
|
||||||
private installFish(): InstallResult {
|
private installFish(force = false): InstallResult {
|
||||||
const completionPath = path.join(this.completionDir, 'ccs.fish');
|
const completionPath = path.join(this.completionDir, 'ccs.fish');
|
||||||
const fishCompDir = path.join(this.homeDir, '.config', 'fish', 'completions');
|
const fishCompDir = path.join(this.homeDir, '.config', 'fish', 'completions');
|
||||||
|
|
||||||
@@ -179,6 +195,12 @@ export class ShellCompletionInstaller {
|
|||||||
|
|
||||||
// Copy to fish completion directory (fish auto-loads from here)
|
// Copy to fish completion directory (fish auto-loads from here)
|
||||||
const destFile = path.join(fishCompDir, 'ccs.fish');
|
const destFile = path.join(fishCompDir, 'ccs.fish');
|
||||||
|
|
||||||
|
// Check if already installed
|
||||||
|
if (fs.existsSync(destFile) && !force) {
|
||||||
|
return { success: true, alreadyInstalled: true };
|
||||||
|
}
|
||||||
|
|
||||||
fs.copyFileSync(completionPath, destFile);
|
fs.copyFileSync(completionPath, destFile);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -191,7 +213,7 @@ export class ShellCompletionInstaller {
|
|||||||
/**
|
/**
|
||||||
* Install PowerShell completion
|
* Install PowerShell completion
|
||||||
*/
|
*/
|
||||||
private installPowerShell(): InstallResult {
|
private installPowerShell(force = false): InstallResult {
|
||||||
const profilePath =
|
const profilePath =
|
||||||
process.env.PROFILE ||
|
process.env.PROFILE ||
|
||||||
path.join(this.homeDir, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1');
|
path.join(this.homeDir, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1');
|
||||||
@@ -213,7 +235,15 @@ export class ShellCompletionInstaller {
|
|||||||
if (fs.existsSync(profilePath)) {
|
if (fs.existsSync(profilePath)) {
|
||||||
const content = fs.readFileSync(profilePath, 'utf8');
|
const content = fs.readFileSync(profilePath, 'utf8');
|
||||||
if (content.includes(marker)) {
|
if (content.includes(marker)) {
|
||||||
return { success: true, alreadyInstalled: true };
|
if (!force) {
|
||||||
|
return { success: true, alreadyInstalled: true };
|
||||||
|
}
|
||||||
|
// Force: completion files already updated by ensureCompletionFiles()
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `Updated completion files in ${this.completionDir}`,
|
||||||
|
reload: '. $PROFILE',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,8 +260,9 @@ export class ShellCompletionInstaller {
|
|||||||
/**
|
/**
|
||||||
* Install for detected or specified shell
|
* Install for detected or specified shell
|
||||||
*/
|
*/
|
||||||
install(shell: ShellType = null): InstallResult {
|
install(shell: ShellType = null, options: { force?: boolean } = {}): InstallResult {
|
||||||
const targetShell = shell || this.detectShell();
|
const targetShell = shell || this.detectShell();
|
||||||
|
const { force = false } = options;
|
||||||
|
|
||||||
if (!targetShell) {
|
if (!targetShell) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -239,19 +270,19 @@ export class ShellCompletionInstaller {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure completion files exist
|
// Ensure completion files exist (always copy latest)
|
||||||
this.ensureCompletionFiles();
|
this.ensureCompletionFiles();
|
||||||
|
|
||||||
// Install for target shell
|
// Install for target shell
|
||||||
switch (targetShell) {
|
switch (targetShell) {
|
||||||
case 'bash':
|
case 'bash':
|
||||||
return this.installBash();
|
return this.installBash(force);
|
||||||
case 'zsh':
|
case 'zsh':
|
||||||
return this.installZsh();
|
return this.installZsh(force);
|
||||||
case 'fish':
|
case 'fish':
|
||||||
return this.installFish();
|
return this.installFish(force);
|
||||||
case 'powershell':
|
case 'powershell':
|
||||||
return this.installPowerShell();
|
return this.installPowerShell(force);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported shell: ${targetShell}`);
|
throw new Error(`Unsupported shell: ${targetShell}`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user