diff --git a/VERSION b/VERSION index 561ad334..6aba2b24 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.1.6 +4.2.0 diff --git a/bin/ccs.js b/bin/ccs.js index f189b5dc..d477c09b 100755 --- a/bin/ccs.js +++ b/bin/ccs.js @@ -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'); diff --git a/bin/utils/update-checker.js b/bin/utils/update-checker.js new file mode 100644 index 00000000..278e88f7 --- /dev/null +++ b/bin/utils/update-checker.js @@ -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} - 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} - 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} - 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 +}; diff --git a/installers/install.ps1 b/installers/install.ps1 index 30364486..bf904244 100644 --- a/installers/install.ps1 +++ b/installers/install.ps1 @@ -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) { diff --git a/installers/install.sh b/installers/install.sh index edc1a055..632b3e0d 100755 --- a/installers/install.sh +++ b/installers/install.sh @@ -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 diff --git a/lib/ccs b/lib/ccs index 9ad95b11..b0d1a22b 100755 --- a/lib/ccs +++ b/lib/ccs @@ -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." diff --git a/lib/ccs.ps1 b/lib/ccs.ps1 index dfaeb3d3..349097a7 100644 --- a/lib/ccs.ps1 +++ b/lib/ccs.ps1 @@ -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." diff --git a/package.json b/package.json index 05842b3c..3767b98b 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scripts/completion/ccs.bash b/scripts/completion/ccs.bash index 027791ae..63f07fb1 100644 --- a/scripts/completion/ccs.bash +++ b/scripts/completion/ccs.bash @@ -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="" diff --git a/scripts/completion/ccs.fish b/scripts/completion/ccs.fish index aab2eb70..0688853c 100644 --- a/scripts/completion/ccs.fish +++ b/scripts/completion/ccs.fish @@ -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' diff --git a/scripts/completion/ccs.ps1 b/scripts/completion/ccs.ps1 index 1fec0d9f..9c5fada5 100644 --- a/scripts/completion/ccs.ps1 +++ b/scripts/completion/ccs.ps1 @@ -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') diff --git a/scripts/completion/ccs.zsh b/scripts/completion/ccs.zsh index c31129a9..501ee4c7 100644 --- a/scripts/completion/ccs.zsh +++ b/scripts/completion/ccs.zsh @@ -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)