feat: add 'ccs update' command with smart update notifications

Add comprehensive update functionality with intelligent notification system.
Supports manual update checking, automatic notifications every 24h, and works
across npm and direct installations with proper error handling.

Closes #12
This commit is contained in:
Kai (Tam Nhu) Tran
2025-11-18 01:19:28 -05:00
committed by kaitranntt
parent 31a21d13ad
commit d34dbcac1f
12 changed files with 686 additions and 18 deletions
+1 -1
View File
@@ -1 +1 @@
4.1.6
4.2.0
+165
View File
@@ -181,6 +181,7 @@ function handleHelpCommand() {
console.log(colored('Diagnostics:', 'cyan'));
console.log(` ${colored('ccs doctor', 'yellow')} Run health check and diagnostics`);
console.log(` ${colored('ccs sync', 'yellow')} Sync delegation commands and skills`);
console.log(` ${colored('ccs update', 'yellow')} Update CCS to latest version`);
console.log('');
// Flags
@@ -285,6 +286,164 @@ async function handleSyncCommand() {
process.exit(0);
}
async function handleUpdateCommand() {
const { checkForUpdates } = require('./utils/update-checker');
const { spawn } = require('child_process');
console.log('');
console.log(colored('Checking for updates...', 'cyan'));
console.log('');
// Detect installation method for proper update source
const isNpmInstall = process.argv[1].includes('node_modules');
const installMethod = isNpmInstall ? 'npm' : 'direct';
// Check for updates (force check)
const updateResult = await checkForUpdates(CCS_VERSION, true, installMethod);
if (updateResult.status === 'check_failed') {
console.log(colored(`[X] ${updateResult.message}`, 'red'));
console.log('');
console.log(colored('[i] Possible causes:', 'yellow'));
console.log(' • Network connection issues');
console.log(' • Firewall blocking requests');
console.log(' • GitHub/npm API temporarily unavailable');
console.log('');
console.log('Try again later or update manually:');
if (isNpmInstall) {
console.log(colored(' npm install -g @kaitranntt/ccs@latest', 'yellow'));
} else {
const isWindows = process.platform === 'win32';
if (isWindows) {
console.log(colored(' irm ccs.kaitran.ca/install | iex', 'yellow'));
} else {
console.log(colored(' curl -fsSL ccs.kaitran.ca/install | bash', 'yellow'));
}
}
console.log('');
process.exit(1);
}
if (updateResult.status === 'no_update') {
let message = `You are already on the latest version (${CCS_VERSION})`;
// Add context for why no update is shown
switch (updateResult.reason) {
case 'dismissed':
message = `Update dismissed. You are on version ${CCS_VERSION}`;
console.log(colored(`[i] ${message}`, 'yellow'));
break;
case 'cached':
message = `No updates available (cached result). You are on version ${CCS_VERSION}`;
console.log(colored(`[i] ${message}`, 'cyan'));
break;
default:
console.log(colored(`[OK] ${message}`, 'green'));
}
console.log('');
process.exit(0);
}
// Update available
console.log(colored(`[i] Update available: ${updateResult.current}${updateResult.latest}`, 'yellow'));
console.log('');
if (isNpmInstall) {
// npm installation - use npm update
console.log(colored('Updating via npm...', 'cyan'));
console.log('');
const child = spawn('npm', ['install', '-g', '@kaitranntt/ccs@latest'], {
stdio: 'inherit',
shell: true
});
child.on('exit', (code) => {
if (code === 0) {
console.log('');
console.log(colored('[OK] Update successful!', 'green'));
console.log('');
console.log(`Run ${colored('ccs --version', 'yellow')} to verify`);
console.log('');
} else {
console.log('');
console.log(colored('[X] Update failed', 'red'));
console.log('');
console.log('Try manually:');
console.log(colored(' npm install -g @kaitranntt/ccs@latest', 'yellow'));
console.log('');
}
process.exit(code || 0);
});
child.on('error', (err) => {
console.log('');
console.log(colored('[X] Failed to run npm update', 'red'));
console.log('');
console.log('Try manually:');
console.log(colored(' npm install -g @kaitranntt/ccs@latest', 'yellow'));
console.log('');
process.exit(1);
});
} else {
// Direct installation - re-run installer
console.log(colored('Updating via installer...', 'cyan'));
console.log('');
const isWindows = process.platform === 'win32';
let command, args;
if (isWindows) {
command = 'powershell.exe';
args = ['-Command', 'irm ccs.kaitran.ca/install | iex'];
} else {
command = 'bash';
args = ['-c', 'curl -fsSL ccs.kaitran.ca/install | bash'];
}
const child = spawn(command, args, {
stdio: 'inherit',
shell: true
});
child.on('exit', (code) => {
if (code === 0) {
console.log('');
console.log(colored('[OK] Update successful!', 'green'));
console.log('');
console.log(`Run ${colored('ccs --version', 'yellow')} to verify`);
console.log('');
} else {
console.log('');
console.log(colored('[X] Update failed', 'red'));
console.log('');
console.log('Try manually:');
if (isWindows) {
console.log(colored(' irm ccs.kaitran.ca/install | iex', 'yellow'));
} else {
console.log(colored(' curl -fsSL ccs.kaitran.ca/install | bash', 'yellow'));
}
console.log('');
}
process.exit(code || 0);
});
child.on('error', (err) => {
console.log('');
console.log(colored('[X] Failed to run installer', 'red'));
console.log('');
console.log('Try manually:');
if (isWindows) {
console.log(colored(' irm ccs.kaitran.ca/install | iex', 'yellow'));
} else {
console.log(colored(' curl -fsSL ccs.kaitran.ca/install | bash', 'yellow'));
}
console.log('');
process.exit(1);
});
}
}
// Smart profile detection
function detectProfile(args) {
if (args.length === 0 || args[0].startsWith('-')) {
@@ -528,6 +687,12 @@ async function main() {
return;
}
// Special case: update command (update CCS to latest version)
if (firstArg === 'update' || firstArg === '--update') {
await handleUpdateCommand();
return;
}
// Special case: auth command (multi-account management)
if (firstArg === 'auth') {
const AuthCommands = require('./auth/auth-commands');
+243
View File
@@ -0,0 +1,243 @@
'use strict';
const fs = require('fs');
const path = require('path');
const os = require('os');
const https = require('https');
const { colored } = require('./helpers');
const UPDATE_CHECK_FILE = path.join(os.homedir(), '.ccs', 'update-check.json');
const CHECK_INTERVAL = 24 * 60 * 60 * 1000; // 24 hours
const GITHUB_API_URL = 'https://api.github.com/repos/kaitranntt/ccs/releases/latest';
const NPM_REGISTRY_URL = 'https://registry.npmjs.org/@kaitranntt/ccs/latest';
const REQUEST_TIMEOUT = 5000; // 5 seconds
/**
* Compare semantic versions
* @param {string} v1 - First version (e.g., "4.1.6")
* @param {string} v2 - Second version
* @returns {number} - 1 if v1 > v2, -1 if v1 < v2, 0 if equal
*/
function compareVersions(v1, v2) {
const parts1 = v1.replace(/^v/, '').split('.').map(Number);
const parts2 = v2.replace(/^v/, '').split('.').map(Number);
for (let i = 0; i < 3; i++) {
const p1 = parts1[i] || 0;
const p2 = parts2[i] || 0;
if (p1 > p2) return 1;
if (p1 < p2) return -1;
}
return 0;
}
/**
* Fetch latest version from GitHub releases
* @returns {Promise<string|null>} - Latest version or null on error
*/
function fetchLatestVersionFromGitHub() {
return new Promise((resolve) => {
const req = https.get(GITHUB_API_URL, {
headers: { 'User-Agent': 'CCS-Update-Checker' },
timeout: REQUEST_TIMEOUT
}, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
if (res.statusCode !== 200) {
resolve(null);
return;
}
const release = JSON.parse(data);
const version = release.tag_name?.replace(/^v/, '') || null;
resolve(version);
} catch (err) {
resolve(null);
}
});
});
req.on('error', () => resolve(null));
req.on('timeout', () => {
req.destroy();
resolve(null);
});
});
}
/**
* Fetch latest version from npm registry
* @returns {Promise<string|null>} - Latest version or null on error
*/
function fetchLatestVersionFromNpm() {
return new Promise((resolve) => {
const req = https.get(NPM_REGISTRY_URL, {
headers: { 'User-Agent': 'CCS-Update-Checker' },
timeout: REQUEST_TIMEOUT
}, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
if (res.statusCode !== 200) {
resolve(null);
return;
}
const packageData = JSON.parse(data);
const version = packageData.version || null;
resolve(version);
} catch (err) {
resolve(null);
}
});
});
req.on('error', () => resolve(null));
req.on('timeout', () => {
req.destroy();
resolve(null);
});
});
}
/**
* Read update check cache
* @returns {Object} - Cache object
*/
function readCache() {
try {
if (!fs.existsSync(UPDATE_CHECK_FILE)) {
return { last_check: 0, latest_version: null, dismissed_version: null };
}
const data = fs.readFileSync(UPDATE_CHECK_FILE, 'utf8');
return JSON.parse(data);
} catch (err) {
return { last_check: 0, latest_version: null, dismissed_version: null };
}
}
/**
* Write update check cache
* @param {Object} cache - Cache object to write
*/
function writeCache(cache) {
try {
const ccsDir = path.join(os.homedir(), '.ccs');
if (!fs.existsSync(ccsDir)) {
fs.mkdirSync(ccsDir, { recursive: true, mode: 0o700 });
}
fs.writeFileSync(UPDATE_CHECK_FILE, JSON.stringify(cache, null, 2), 'utf8');
} catch (err) {
// Silently fail - not critical
}
}
/**
* Check for updates (async, non-blocking)
* @param {string} currentVersion - Current CCS version
* @param {boolean} force - Force check even if within interval
* @param {string} installMethod - Installation method ('npm' or 'direct')
* @returns {Promise<Object>} - Update result object with status and data
*/
async function checkForUpdates(currentVersion, force = false, installMethod = 'direct') {
const cache = readCache();
const now = Date.now();
// Check if we should check for updates
if (!force && (now - cache.last_check < CHECK_INTERVAL)) {
// Use cached result if available
if (cache.latest_version && compareVersions(cache.latest_version, currentVersion) > 0) {
// Don't show if user dismissed this version
if (cache.dismissed_version === cache.latest_version) {
return { status: 'no_update', reason: 'dismissed' };
}
return { status: 'update_available', latest: cache.latest_version, current: currentVersion };
}
return { status: 'no_update', reason: 'cached' };
}
// Fetch latest version from appropriate source
let latestVersion;
let fetchError = null;
if (installMethod === 'npm') {
latestVersion = await fetchLatestVersionFromNpm();
if (!latestVersion) fetchError = 'npm_registry_error';
} else {
latestVersion = await fetchLatestVersionFromGitHub();
if (!latestVersion) fetchError = 'github_api_error';
}
// Update cache
cache.last_check = now;
if (latestVersion) {
cache.latest_version = latestVersion;
}
writeCache(cache);
// Handle fetch errors
if (fetchError) {
return {
status: 'check_failed',
reason: fetchError,
message: `Failed to check for updates: ${fetchError.replace(/_/g, ' ')}`
};
}
// Check if update available
if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) {
// Don't show if user dismissed this version
if (cache.dismissed_version === latestVersion) {
return { status: 'no_update', reason: 'dismissed' };
}
return { status: 'update_available', latest: latestVersion, current: currentVersion };
}
return { status: 'no_update', reason: 'latest' };
}
/**
* Show update notification
* @param {Object} updateInfo - Update information
*/
function showUpdateNotification(updateInfo) {
console.log('');
console.log(colored('═══════════════════════════════════════════════════════', 'cyan'));
console.log(colored(` Update available: ${updateInfo.current}${updateInfo.latest}`, 'yellow'));
console.log(colored('═══════════════════════════════════════════════════════', 'cyan'));
console.log('');
console.log(` Run ${colored('ccs update', 'yellow')} to update`);
console.log('');
}
/**
* Dismiss update notification for a specific version
* @param {string} version - Version to dismiss
*/
function dismissUpdate(version) {
const cache = readCache();
cache.dismissed_version = version;
writeCache(cache);
}
module.exports = {
compareVersions,
checkForUpdates,
showUpdateNotification,
dismissUpdate,
readCache,
writeCache
};
+1 -1
View File
@@ -31,7 +31,7 @@ $InstallMethod = if ($ScriptDir -and ((Test-Path "$ScriptDir\lib\ccs.ps1") -or (
# IMPORTANT: Update this version when releasing new versions!
# This hardcoded version is used for standalone installations (irm | iex)
# For git installations, VERSION file is read if available
$CcsVersion = "4.1.6"
$CcsVersion = "4.2.0"
# Try to read VERSION file for git installations
if ($ScriptDir) {
+1 -1
View File
@@ -32,7 +32,7 @@ fi
# IMPORTANT: Update this version when releasing new versions!
# This hardcoded version is used for standalone installations (curl | bash)
# For git installations, VERSION file is read if available
CCS_VERSION="4.1.6"
CCS_VERSION="4.2.0"
# Try to read VERSION file for git installations
if [[ -f "$SCRIPT_DIR/VERSION" ]]; then
+126 -1
View File
@@ -2,7 +2,7 @@
set -euo pipefail
# Version (updated by scripts/bump-version.sh)
CCS_VERSION="4.1.6"
CCS_VERSION="4.2.0"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly CONFIG_FILE="${CCS_CONFIG:-$HOME/.ccs/config.json}"
readonly PROFILES_JSON="$HOME/.ccs/profiles.json"
@@ -199,6 +199,7 @@ show_help() {
echo -e "${CYAN}Diagnostics:${RESET}"
echo -e " ${YELLOW}ccs doctor${RESET} Run health check and diagnostics"
echo -e " ${YELLOW}ccs sync${RESET} Sync delegation commands and skills"
echo -e " ${YELLOW}ccs update${RESET} Update CCS to latest version"
echo ""
echo -e "${CYAN}Flags:${RESET}"
@@ -591,6 +592,124 @@ sync_run() {
echo ""
}
# --- Update Command ---
update_run() {
echo ""
echo -e "${CYAN}Checking for updates...${RESET}"
echo ""
# Detect installation method
local install_method="direct"
if command -v npm &>/dev/null && npm list -g @kaitranntt/ccs &>/dev/null 2>&1; then
install_method="npm"
fi
# Fetch latest version from appropriate source
local latest_version=""
if command -v curl &>/dev/null; then
if [[ "$install_method" == "npm" ]]; then
# Check npm registry for npm installations
latest_version=$(curl -fsSL https://registry.npmjs.org/@kaitranntt/ccs/latest 2>/dev/null | \
grep '"version"' | head -1 | sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([0-9.]+)".*/\1/')
else
# Check GitHub releases for direct installations
latest_version=$(curl -fsSL https://api.github.com/repos/kaitranntt/ccs/releases/latest 2>/dev/null | \
grep '"tag_name"' | sed -E 's/.*"v?([0-9.]+)".*/\1/')
fi
fi
if [[ -z "$latest_version" ]]; then
echo -e "${YELLOW}[!] Unable to check for updates${RESET}"
echo ""
echo "Try manually:"
if [[ "$install_method" == "npm" ]]; then
echo -e " ${YELLOW}npm install -g @kaitranntt/ccs@latest${RESET}"
else
echo -e " ${YELLOW}curl -fsSL ccs.kaitran.ca/install | bash${RESET}"
fi
echo ""
exit 1
fi
# Compare versions
if [[ "$latest_version" == "$CCS_VERSION" ]]; then
echo -e "${GREEN}[OK] You are already on the latest version (${CCS_VERSION})${RESET}"
echo ""
exit 0
fi
# Check if update available
local current_major=$(echo "$CCS_VERSION" | cut -d. -f1)
local current_minor=$(echo "$CCS_VERSION" | cut -d. -f2)
local current_patch=$(echo "$CCS_VERSION" | cut -d. -f3)
local latest_major=$(echo "$latest_version" | cut -d. -f1)
local latest_minor=$(echo "$latest_version" | cut -d. -f2)
local latest_patch=$(echo "$latest_version" | cut -d. -f3)
local is_newer=0
if [[ $latest_major -gt $current_major ]]; then
is_newer=1
elif [[ $latest_major -eq $current_major ]] && [[ $latest_minor -gt $current_minor ]]; then
is_newer=1
elif [[ $latest_major -eq $current_major ]] && [[ $latest_minor -eq $current_minor ]] && [[ $latest_patch -gt $current_patch ]]; then
is_newer=1
fi
if [[ $is_newer -eq 0 ]]; then
echo -e "${GREEN}[OK] You are on version ${CCS_VERSION} (latest is ${latest_version})${RESET}"
echo ""
exit 0
fi
echo -e "${YELLOW}[i] Update available: ${CCS_VERSION} → ${latest_version}${RESET}"
echo ""
# Perform update based on installation method
if [[ "$install_method" == "npm" ]]; then
echo -e "${CYAN}Updating via npm...${RESET}"
echo ""
if npm install -g @kaitranntt/ccs@latest; then
echo ""
echo -e "${GREEN}[OK] Update successful!${RESET}"
echo ""
echo -e "Run ${YELLOW}ccs --version${RESET} to verify"
echo ""
exit 0
else
echo ""
echo -e "${RED}[X] Update failed${RESET}"
echo ""
echo "Try manually:"
echo -e " ${YELLOW}npm install -g @kaitranntt/ccs@latest${RESET}"
echo ""
exit 1
fi
else
echo -e "${CYAN}Updating via installer...${RESET}"
echo ""
if curl -fsSL ccs.kaitran.ca/install | bash; then
echo ""
echo -e "${GREEN}[OK] Update successful!${RESET}"
echo ""
echo -e "Run ${YELLOW}ccs --version${RESET} to verify"
echo ""
exit 0
else
echo ""
echo -e "${RED}[X] Update failed${RESET}"
echo ""
echo "Try manually:"
echo -e " ${YELLOW}curl -fsSL ccs.kaitran.ca/install | bash${RESET}"
echo ""
exit 1
fi
fi
}
# --- Claude CLI Detection Logic ---
detect_claude_cli() {
@@ -1669,6 +1788,12 @@ if [[ $# -gt 0 ]] && [[ "${1}" == "sync" || "${1}" == "--sync" ]]; then
exit $?
fi
# Special case: update command
if [[ $# -gt 0 ]] && [[ "${1}" == "update" || "${1}" == "--update" ]]; then
update_run
exit $?
fi
# Run auto-recovery before main logic
auto_recover || {
msg_error "Auto-recovery failed. Check permissions."
+134 -1
View File
@@ -12,7 +12,7 @@ param(
$ErrorActionPreference = "Stop"
# Version (updated by scripts/bump-version.sh)
$CcsVersion = "4.1.6"
$CcsVersion = "4.2.0"
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$ConfigFile = if ($env:CCS_CONFIG) { $env:CCS_CONFIG } else { "$env:USERPROFILE\.ccs\config.json" }
$ProfilesJson = "$env:USERPROFILE\.ccs\profiles.json"
@@ -246,6 +246,7 @@ function Show-Help {
Write-ColorLine "Diagnostics:" "Cyan"
Write-ColorLine " ccs doctor Run health check and diagnostics" "Yellow"
Write-ColorLine " ccs sync Sync delegation commands and skills" "Yellow"
Write-ColorLine " ccs update Update CCS to latest version" "Yellow"
Write-Host ""
Write-ColorLine "Flags:" "Cyan"
@@ -1031,6 +1032,132 @@ function Sync-Run {
Write-Host ""
}
# --- Update Command ---
function Update-Run {
Write-Host ""
Write-Host "Checking for updates..." -ForegroundColor Cyan
Write-Host ""
# Detect installation method
$InstallMethod = "direct"
try {
$NpmList = npm list -g @kaitranntt/ccs 2>&1
if ($LASTEXITCODE -eq 0) {
$InstallMethod = "npm"
}
} catch {
# npm not available or not installed via npm
}
# Fetch latest version from appropriate source
$LatestVersion = ""
try {
if ($InstallMethod -eq "npm") {
# Check npm registry for npm installations
$Response = Invoke-RestMethod -Uri "https://registry.npmjs.org/@kaitranntt/ccs/latest" -TimeoutSec 5
$LatestVersion = $Response.version
} else {
# Check GitHub releases for direct installations
$Response = Invoke-RestMethod -Uri "https://api.github.com/repos/kaitranntt/ccs/releases/latest" -TimeoutSec 5
$LatestVersion = $Response.tag_name -replace '^v', ''
}
} catch {
Write-Host "[!] Unable to check for updates" -ForegroundColor Yellow
Write-Host ""
Write-Host "Try manually:"
if ($InstallMethod -eq "npm") {
Write-Host " npm install -g @kaitranntt/ccs@latest" -ForegroundColor Yellow
} else {
Write-Host " irm ccs.kaitran.ca/install | iex" -ForegroundColor Yellow
}
Write-Host ""
exit 1
}
# Compare versions
if ($LatestVersion -eq $CcsVersion) {
Write-Host "[OK] You are already on the latest version ($CcsVersion)" -ForegroundColor Green
Write-Host ""
exit 0
}
# Check if update available
$CurrentParts = $CcsVersion.Split('.')
$LatestParts = $LatestVersion.Split('.')
$IsNewer = $false
for ($i = 0; $i -lt 3; $i++) {
$Current = [int]$CurrentParts[$i]
$Latest = [int]$LatestParts[$i]
if ($Latest -gt $Current) {
$IsNewer = $true
break
} elseif ($Latest -lt $Current) {
break
}
}
if (-not $IsNewer) {
Write-Host "[OK] You are on version $CcsVersion (latest is $LatestVersion)" -ForegroundColor Green
Write-Host ""
exit 0
}
Write-Host "[i] Update available: $CcsVersion$LatestVersion" -ForegroundColor Yellow
Write-Host ""
# Perform update based on installation method
if ($InstallMethod -eq "npm") {
Write-Host "Updating via npm..." -ForegroundColor Cyan
Write-Host ""
try {
npm install -g @kaitranntt/ccs@latest
if ($LASTEXITCODE -eq 0) {
Write-Host ""
Write-Host "[OK] Update successful!" -ForegroundColor Green
Write-Host ""
Write-Host "Run ccs --version to verify" -ForegroundColor Yellow
Write-Host ""
exit 0
} else {
throw "npm install failed"
}
} catch {
Write-Host ""
Write-Host "[X] Update failed" -ForegroundColor Red
Write-Host ""
Write-Host "Try manually:"
Write-Host " npm install -g @kaitranntt/ccs@latest" -ForegroundColor Yellow
Write-Host ""
exit 1
}
} else {
Write-Host "Updating via installer..." -ForegroundColor Cyan
Write-Host ""
try {
irm ccs.kaitran.ca/install | iex
Write-Host ""
Write-Host "[OK] Update successful!" -ForegroundColor Green
Write-Host ""
Write-Host "Run ccs --version to verify" -ForegroundColor Yellow
Write-Host ""
exit 0
} catch {
Write-Host ""
Write-Host "[X] Update failed" -ForegroundColor Red
Write-Host ""
Write-Host "Try manually:"
Write-Host " irm ccs.kaitran.ca/install | iex" -ForegroundColor Yellow
Write-Host ""
exit 1
}
}
}
# --- Auth Commands (Phase 3) ---
function Show-AuthHelp {
@@ -1519,6 +1646,12 @@ if ($RemainingArgs.Count -gt 0 -and ($RemainingArgs[0] -eq "sync" -or $Remaining
exit 0
}
# Special case: update command
if ($RemainingArgs.Count -gt 0 -and ($RemainingArgs[0] -eq "update" -or $RemainingArgs[0] -eq "--update")) {
Update-Run
exit 0
}
# Run auto-recovery before main logic
if (-not (Invoke-AutoRecovery)) {
Write-ErrorMsg "Auto-recovery failed. Check permissions."
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@kaitranntt/ccs",
"version": "4.1.6",
"version": "4.2.0",
"description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
"keywords": [
"cli",
+1 -1
View File
@@ -18,7 +18,7 @@ _ccs_completion() {
# Top-level completion (first argument)
if [[ ${COMP_CWORD} -eq 1 ]]; then
local commands="auth doctor sync"
local commands="auth doctor sync update"
local flags="--help --version --shell-completion -h -v -sc"
local profiles=""
+10 -9
View File
@@ -73,21 +73,22 @@ complete -c ccs -s v -l version -d 'Show version information'
complete -c ccs -s sc -l shell-completion -d 'Install shell completion'
# Top-level commands (blue color for commands)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync' -a 'auth' -d (set_color blue)'Manage multiple Claude accounts'(set_color normal)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync' -a 'doctor' -d (set_color blue)'Run health check and diagnostics'(set_color normal)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync' -a 'sync' -d (set_color blue)'Sync delegation commands and skills'(set_color normal)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a 'auth' -d (set_color blue)'Manage multiple Claude accounts'(set_color normal)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a 'doctor' -d (set_color blue)'Run health check and diagnostics'(set_color normal)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a 'sync' -d (set_color blue)'Sync delegation commands and skills'(set_color normal)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a 'update' -d (set_color blue)'Update CCS to latest version'(set_color normal)
# Top-level known settings profiles (green color for model profiles)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync' -a 'default' -d (set_color green)'Default Claude Sonnet 4.5'(set_color normal)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync' -a 'glm' -d (set_color green)'GLM-4.6 (cost-optimized)'(set_color normal)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync' -a 'glmt' -d (set_color green)'GLM-4.6 with thinking mode'(set_color normal)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync' -a 'kimi' -d (set_color green)'Kimi for Coding (long-context)'(set_color normal)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a 'default' -d (set_color green)'Default Claude Sonnet 4.5'(set_color normal)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a 'glm' -d (set_color green)'GLM-4.6 (cost-optimized)'(set_color normal)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a 'glmt' -d (set_color green)'GLM-4.6 with thinking mode'(set_color normal)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a 'kimi' -d (set_color green)'Kimi for Coding (long-context)'(set_color normal)
# Top-level custom settings profiles (dynamic, with generic description in green)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync' -a '(__fish_ccs_get_custom_settings_profiles)' -d (set_color green)'Settings-based profile'(set_color normal)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a '(__fish_ccs_get_custom_settings_profiles)' -d (set_color green)'Settings-based profile'(set_color normal)
# Top-level account profiles (dynamic, yellow color for account profiles)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync' -a '(__fish_ccs_get_account_profiles)' -d (set_color yellow)'Account profile'(set_color normal)
complete -c ccs -n 'not __fish_seen_subcommand_from auth doctor sync update' -a '(__fish_ccs_get_account_profiles)' -d (set_color yellow)'Account profile'(set_color normal)
# 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'
+1 -1
View File
@@ -12,7 +12,7 @@
Register-ArgumentCompleter -CommandName ccs -ScriptBlock {
param($commandName, $wordToComplete, $commandAst, $fakeBoundParameters)
$commands = @('auth', 'doctor', 'sync', '--help', '--version', '--shell-completion', '-h', '-v', '-sc')
$commands = @('auth', 'doctor', 'sync', 'update', '--help', '--version', '--shell-completion', '-h', '-v', '-sc')
$authCommands = @('create', 'list', 'show', 'remove', 'default', '--help', '-h')
$shellCompletionFlags = @('--bash', '--zsh', '--fish', '--powershell')
$listFlags = @('--verbose', '--json')
+2 -1
View File
@@ -16,7 +16,7 @@
# 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
# The leading '=' means no color for whole match, then each '=' assigns to each group
zstyle ':completion:*:*:ccs:*:commands' list-colors '=(#b)(auth|doctor|sync)([[:space:]]#--[[:space:]]#*)==0\;34=2\;37'
zstyle ':completion:*:*:ccs:*:commands' list-colors '=(#b)(auth|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:*:account-profiles' list-colors '=(#b)([^[:space:]]##)([[:space:]]#--[[:space:]]#*)==0\;33=2\;37'
zstyle ':completion:*:*:ccs:*' group-name ''
@@ -35,6 +35,7 @@ _ccs() {
'auth:Manage multiple Claude accounts'
'doctor:Run health check and diagnostics'
'sync:Sync delegation commands and skills'
'update:Update CCS to latest version'
)
# Define known settings profiles with descriptions (consistent padding)