mirror of
https://github.com/tiennm99/ccs.git
synced 2026-08-05 08:22:29 +00:00
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:
committed by
kaitranntt
parent
31a21d13ad
commit
d34dbcac1f
+165
@@ -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');
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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=""
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user