mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 10:16:49 +00:00
feat(update): add automatic update check on startup
- check cached update result synchronously on every command - show warnBox notification if update available - skip check for version/help/update commands - refresh cache in background if stale (>24h) - use proper UI components (warnBox) for notification
This commit is contained in:
+70
-13
@@ -1,12 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* CCS (Claude Code Switch) - Entry Point
|
||||
*
|
||||
* Instant profile switching for Claude CLI.
|
||||
* Supports multiple accounts, alternative models (GLM, Kimi),
|
||||
* and cost-optimized delegation.
|
||||
*/
|
||||
|
||||
import { spawn, ChildProcess } from 'child_process';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
@@ -33,6 +24,16 @@ import { handleUpdateCommand } from './commands/update-command';
|
||||
// Import extracted utility functions
|
||||
import { execClaude, escapeShellArg } from './utils/shell-executor';
|
||||
|
||||
// Version and Update check utilities
|
||||
import { getVersion } from './utils/version';
|
||||
import {
|
||||
checkForUpdates,
|
||||
showUpdateNotification,
|
||||
checkCachedUpdate,
|
||||
isCacheStale,
|
||||
} from './utils/update-checker';
|
||||
import { detectInstallationMethod } from './utils/package-manager-detector';
|
||||
|
||||
// ========== Profile Detection ==========
|
||||
|
||||
interface DetectedProfile {
|
||||
@@ -66,7 +67,8 @@ async function execClaudeWithProxy(
|
||||
// 1. Read settings to get API key
|
||||
const settingsPath = getSettingsPath(profileName);
|
||||
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
||||
const apiKey = settings.env.ANTHROPIC_AUTH_TOKEN;
|
||||
const envData = settings.env;
|
||||
const apiKey = envData['ANTHROPIC_AUTH_TOKEN'];
|
||||
|
||||
if (!apiKey || apiKey === 'YOUR_GLM_API_KEY_HERE') {
|
||||
console.error('[X] GLMT profile requires Z.AI API key');
|
||||
@@ -87,7 +89,7 @@ async function execClaudeWithProxy(
|
||||
env: {
|
||||
...process.env,
|
||||
ANTHROPIC_AUTH_TOKEN: apiKey,
|
||||
ANTHROPIC_BASE_URL: settings.env.ANTHROPIC_BASE_URL,
|
||||
ANTHROPIC_BASE_URL: envData['ANTHROPIC_BASE_URL'],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -211,18 +213,73 @@ interface ProfileError extends Error {
|
||||
suggestions?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform background update check (refreshes cache, no notification)
|
||||
*/
|
||||
async function refreshUpdateCache(): Promise<void> {
|
||||
try {
|
||||
const currentVersion = getVersion();
|
||||
const installMethod = detectInstallationMethod();
|
||||
// Force=true to always fetch fresh data
|
||||
await checkForUpdates(currentVersion, true, installMethod);
|
||||
} catch (_e) {
|
||||
// Silently fail - update check shouldn't crash main CLI
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show update notification if cached result indicates update available
|
||||
* Returns true if notification was shown
|
||||
*/
|
||||
async function showCachedUpdateNotification(): Promise<boolean> {
|
||||
try {
|
||||
const currentVersion = getVersion();
|
||||
const updateInfo = checkCachedUpdate(currentVersion);
|
||||
|
||||
if (updateInfo) {
|
||||
await showUpdateNotification(updateInfo);
|
||||
return true;
|
||||
}
|
||||
} catch (_e) {
|
||||
// Silently fail
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const args = process.argv.slice(2);
|
||||
const firstArg = args[0];
|
||||
|
||||
// Trigger update check early for ALL commands except version/help/update
|
||||
// Only if TTY and not CI to avoid noise in automated environments
|
||||
const skipUpdateCheck = [
|
||||
'version',
|
||||
'--version',
|
||||
'-v',
|
||||
'help',
|
||||
'--help',
|
||||
'-h',
|
||||
'update',
|
||||
'--update',
|
||||
];
|
||||
if (process.stdout.isTTY && !process.env['CI'] && !skipUpdateCheck.includes(firstArg)) {
|
||||
// 1. Show cached update notification (async for proper UI)
|
||||
await showCachedUpdateNotification();
|
||||
|
||||
// 2. Refresh cache in background if stale (non-blocking)
|
||||
if (isCacheStale()) {
|
||||
refreshUpdateCache();
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-migrate to unified config format (silent if already migrated)
|
||||
// Skip if user is explicitly running migrate command
|
||||
if (args[0] !== 'migrate') {
|
||||
if (firstArg !== 'migrate') {
|
||||
const { autoMigrate } = await import('./config/migration-manager');
|
||||
await autoMigrate();
|
||||
}
|
||||
|
||||
// Special case: version command (check BEFORE profile detection)
|
||||
const firstArg = args[0];
|
||||
if (firstArg === 'version' || firstArg === '--version' || firstArg === '-v') {
|
||||
handleVersionCommand();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import * as https from 'https';
|
||||
import { warn, color } from './ui';
|
||||
|
||||
const CACHE_DIR = path.join(os.homedir(), '.ccs', 'cache');
|
||||
const UPDATE_CHECK_FILE = path.join(CACHE_DIR, 'update-check.json');
|
||||
@@ -301,15 +300,24 @@ export async function checkForUpdates(
|
||||
}
|
||||
|
||||
/**
|
||||
* Show update notification
|
||||
* Show update notification (async - initializes UI first)
|
||||
*/
|
||||
export function showUpdateNotification(updateInfo: { current: string; latest: string }): void {
|
||||
export async function showUpdateNotification(updateInfo: {
|
||||
current: string;
|
||||
latest: string;
|
||||
}): Promise<void> {
|
||||
// Lazy import UI to ensure modules are loaded
|
||||
const { initUI, warnBox, color } = await import('./ui');
|
||||
await initUI();
|
||||
|
||||
const content = [
|
||||
`Update available: ${updateInfo.current} -> ${updateInfo.latest}`,
|
||||
'',
|
||||
`Run ${color('ccs update', 'command')} to update`,
|
||||
].join('\n');
|
||||
|
||||
console.log('');
|
||||
console.log(color('═══════════════════════════════════════════════════════', 'info'));
|
||||
console.log(warn(`Update available: ${updateInfo.current} → ${updateInfo.latest}`));
|
||||
console.log(color('═══════════════════════════════════════════════════════', 'info'));
|
||||
console.log('');
|
||||
console.log(` Run ${color('ccs update', 'command')} to update`);
|
||||
console.log(warnBox(content, 'UPDATE AVAILABLE'));
|
||||
console.log('');
|
||||
}
|
||||
|
||||
@@ -321,3 +329,36 @@ export function dismissUpdate(version: string): void {
|
||||
cache.dismissed_version = version;
|
||||
writeCache(cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check cached update result synchronously (no network call)
|
||||
* Returns update info if cached result indicates update available, null otherwise
|
||||
*/
|
||||
export function checkCachedUpdate(
|
||||
currentVersion: string
|
||||
): { current: string; latest: string } | null {
|
||||
const cache = readCache();
|
||||
|
||||
// Check if cache has a newer version
|
||||
if (
|
||||
cache.latest_version &&
|
||||
compareVersionsWithPrerelease(cache.latest_version, currentVersion) > 0
|
||||
) {
|
||||
// Don't show if user dismissed this version
|
||||
if (cache.dismissed_version === cache.latest_version) {
|
||||
return null;
|
||||
}
|
||||
return { current: currentVersion, latest: cache.latest_version };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if cache is stale (older than CHECK_INTERVAL)
|
||||
*/
|
||||
export function isCacheStale(): boolean {
|
||||
const cache = readCache();
|
||||
const now = Date.now();
|
||||
return now - cache.last_check >= CHECK_INTERVAL;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user