@kaitranntt/ccs 4.1.5 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -753,7 +753,7 @@ Status: Installation healthy
753
753
  If you modify CCS items or need to re-install symlinks:
754
754
 
755
755
  ```bash
756
- ccs update
756
+ ccs sync
757
757
  ```
758
758
 
759
759
  **What it does**:
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.1.5
1
+ 4.2.0
package/bin/ccs.js CHANGED
@@ -181,6 +181,7 @@ function handleHelpCommand() {
181
181
  console.log(colored('Diagnostics:', 'cyan'));
182
182
  console.log(` ${colored('ccs doctor', 'yellow')} Run health check and diagnostics`);
183
183
  console.log(` ${colored('ccs sync', 'yellow')} Sync delegation commands and skills`);
184
+ console.log(` ${colored('ccs update', 'yellow')} Update CCS to latest version`);
184
185
  console.log('');
185
186
 
186
187
  // Flags
@@ -285,6 +286,164 @@ async function handleSyncCommand() {
285
286
  process.exit(0);
286
287
  }
287
288
 
289
+ async function handleUpdateCommand() {
290
+ const { checkForUpdates } = require('./utils/update-checker');
291
+ const { spawn } = require('child_process');
292
+
293
+ console.log('');
294
+ console.log(colored('Checking for updates...', 'cyan'));
295
+ console.log('');
296
+
297
+ // Detect installation method for proper update source
298
+ const isNpmInstall = process.argv[1].includes('node_modules');
299
+ const installMethod = isNpmInstall ? 'npm' : 'direct';
300
+
301
+ // Check for updates (force check)
302
+ const updateResult = await checkForUpdates(CCS_VERSION, true, installMethod);
303
+
304
+ if (updateResult.status === 'check_failed') {
305
+ console.log(colored(`[X] ${updateResult.message}`, 'red'));
306
+ console.log('');
307
+ console.log(colored('[i] Possible causes:', 'yellow'));
308
+ console.log(' • Network connection issues');
309
+ console.log(' • Firewall blocking requests');
310
+ console.log(' • GitHub/npm API temporarily unavailable');
311
+ console.log('');
312
+ console.log('Try again later or update manually:');
313
+ if (isNpmInstall) {
314
+ console.log(colored(' npm install -g @kaitranntt/ccs@latest', 'yellow'));
315
+ } else {
316
+ const isWindows = process.platform === 'win32';
317
+ if (isWindows) {
318
+ console.log(colored(' irm ccs.kaitran.ca/install | iex', 'yellow'));
319
+ } else {
320
+ console.log(colored(' curl -fsSL ccs.kaitran.ca/install | bash', 'yellow'));
321
+ }
322
+ }
323
+ console.log('');
324
+ process.exit(1);
325
+ }
326
+
327
+ if (updateResult.status === 'no_update') {
328
+ let message = `You are already on the latest version (${CCS_VERSION})`;
329
+
330
+ // Add context for why no update is shown
331
+ switch (updateResult.reason) {
332
+ case 'dismissed':
333
+ message = `Update dismissed. You are on version ${CCS_VERSION}`;
334
+ console.log(colored(`[i] ${message}`, 'yellow'));
335
+ break;
336
+ case 'cached':
337
+ message = `No updates available (cached result). You are on version ${CCS_VERSION}`;
338
+ console.log(colored(`[i] ${message}`, 'cyan'));
339
+ break;
340
+ default:
341
+ console.log(colored(`[OK] ${message}`, 'green'));
342
+ }
343
+ console.log('');
344
+ process.exit(0);
345
+ }
346
+
347
+ // Update available
348
+ console.log(colored(`[i] Update available: ${updateResult.current} → ${updateResult.latest}`, 'yellow'));
349
+ console.log('');
350
+
351
+ if (isNpmInstall) {
352
+ // npm installation - use npm update
353
+ console.log(colored('Updating via npm...', 'cyan'));
354
+ console.log('');
355
+
356
+ const child = spawn('npm', ['install', '-g', '@kaitranntt/ccs@latest'], {
357
+ stdio: 'inherit',
358
+ shell: true
359
+ });
360
+
361
+ child.on('exit', (code) => {
362
+ if (code === 0) {
363
+ console.log('');
364
+ console.log(colored('[OK] Update successful!', 'green'));
365
+ console.log('');
366
+ console.log(`Run ${colored('ccs --version', 'yellow')} to verify`);
367
+ console.log('');
368
+ } else {
369
+ console.log('');
370
+ console.log(colored('[X] Update failed', 'red'));
371
+ console.log('');
372
+ console.log('Try manually:');
373
+ console.log(colored(' npm install -g @kaitranntt/ccs@latest', 'yellow'));
374
+ console.log('');
375
+ }
376
+ process.exit(code || 0);
377
+ });
378
+
379
+ child.on('error', (err) => {
380
+ console.log('');
381
+ console.log(colored('[X] Failed to run npm update', 'red'));
382
+ console.log('');
383
+ console.log('Try manually:');
384
+ console.log(colored(' npm install -g @kaitranntt/ccs@latest', 'yellow'));
385
+ console.log('');
386
+ process.exit(1);
387
+ });
388
+ } else {
389
+ // Direct installation - re-run installer
390
+ console.log(colored('Updating via installer...', 'cyan'));
391
+ console.log('');
392
+
393
+ const isWindows = process.platform === 'win32';
394
+ let command, args;
395
+
396
+ if (isWindows) {
397
+ command = 'powershell.exe';
398
+ args = ['-Command', 'irm ccs.kaitran.ca/install | iex'];
399
+ } else {
400
+ command = 'bash';
401
+ args = ['-c', 'curl -fsSL ccs.kaitran.ca/install | bash'];
402
+ }
403
+
404
+ const child = spawn(command, args, {
405
+ stdio: 'inherit',
406
+ shell: true
407
+ });
408
+
409
+ child.on('exit', (code) => {
410
+ if (code === 0) {
411
+ console.log('');
412
+ console.log(colored('[OK] Update successful!', 'green'));
413
+ console.log('');
414
+ console.log(`Run ${colored('ccs --version', 'yellow')} to verify`);
415
+ console.log('');
416
+ } else {
417
+ console.log('');
418
+ console.log(colored('[X] Update failed', 'red'));
419
+ console.log('');
420
+ console.log('Try manually:');
421
+ if (isWindows) {
422
+ console.log(colored(' irm ccs.kaitran.ca/install | iex', 'yellow'));
423
+ } else {
424
+ console.log(colored(' curl -fsSL ccs.kaitran.ca/install | bash', 'yellow'));
425
+ }
426
+ console.log('');
427
+ }
428
+ process.exit(code || 0);
429
+ });
430
+
431
+ child.on('error', (err) => {
432
+ console.log('');
433
+ console.log(colored('[X] Failed to run installer', 'red'));
434
+ console.log('');
435
+ console.log('Try manually:');
436
+ if (isWindows) {
437
+ console.log(colored(' irm ccs.kaitran.ca/install | iex', 'yellow'));
438
+ } else {
439
+ console.log(colored(' curl -fsSL ccs.kaitran.ca/install | bash', 'yellow'));
440
+ }
441
+ console.log('');
442
+ process.exit(1);
443
+ });
444
+ }
445
+ }
446
+
288
447
  // Smart profile detection
289
448
  function detectProfile(args) {
290
449
  if (args.length === 0 || args[0].startsWith('-')) {
@@ -528,6 +687,12 @@ async function main() {
528
687
  return;
529
688
  }
530
689
 
690
+ // Special case: update command (update CCS to latest version)
691
+ if (firstArg === 'update' || firstArg === '--update') {
692
+ await handleUpdateCommand();
693
+ return;
694
+ }
695
+
531
696
  // Special case: auth command (multi-account management)
532
697
  if (firstArg === 'auth') {
533
698
  const AuthCommands = require('./auth/auth-commands');
@@ -366,7 +366,7 @@ class Doctor {
366
366
  'CCS Symlinks',
367
367
  'warning',
368
368
  health.issues.join(', '),
369
- 'Run: ccs update'
369
+ 'Run: ccs sync'
370
370
  );
371
371
  }
372
372
  } catch (e) {
@@ -375,7 +375,7 @@ class Doctor {
375
375
  'CCS Symlinks',
376
376
  'warning',
377
377
  'Could not check CCS symlinks: ' + e.message,
378
- 'Run: ccs update'
378
+ 'Run: ccs sync'
379
379
  );
380
380
  }
381
381
  }
@@ -214,10 +214,10 @@ class ClaudeSymlinkManager {
214
214
 
215
215
  // Check target
216
216
  if (!fs.existsSync(targetPath)) {
217
- issues.push(`Not installed: ${item.target} (run 'ccs update' to install)`);
217
+ issues.push(`Not installed: ${item.target} (run 'ccs sync' to install)`);
218
218
  healthy = false;
219
219
  } else if (!this._isOurSymlink(targetPath, sourcePath)) {
220
- issues.push(`Not a CCS symlink: ${item.target} (run 'ccs update' to fix)`);
220
+ issues.push(`Not a CCS symlink: ${item.target} (run 'ccs sync' to fix)`);
221
221
  healthy = false;
222
222
  }
223
223
  }
@@ -0,0 +1,243 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const https = require('https');
7
+ const { colored } = require('./helpers');
8
+
9
+ const UPDATE_CHECK_FILE = path.join(os.homedir(), '.ccs', 'update-check.json');
10
+ const CHECK_INTERVAL = 24 * 60 * 60 * 1000; // 24 hours
11
+ const GITHUB_API_URL = 'https://api.github.com/repos/kaitranntt/ccs/releases/latest';
12
+ const NPM_REGISTRY_URL = 'https://registry.npmjs.org/@kaitranntt/ccs/latest';
13
+ const REQUEST_TIMEOUT = 5000; // 5 seconds
14
+
15
+ /**
16
+ * Compare semantic versions
17
+ * @param {string} v1 - First version (e.g., "4.1.6")
18
+ * @param {string} v2 - Second version
19
+ * @returns {number} - 1 if v1 > v2, -1 if v1 < v2, 0 if equal
20
+ */
21
+ function compareVersions(v1, v2) {
22
+ const parts1 = v1.replace(/^v/, '').split('.').map(Number);
23
+ const parts2 = v2.replace(/^v/, '').split('.').map(Number);
24
+
25
+ for (let i = 0; i < 3; i++) {
26
+ const p1 = parts1[i] || 0;
27
+ const p2 = parts2[i] || 0;
28
+ if (p1 > p2) return 1;
29
+ if (p1 < p2) return -1;
30
+ }
31
+ return 0;
32
+ }
33
+
34
+ /**
35
+ * Fetch latest version from GitHub releases
36
+ * @returns {Promise<string|null>} - Latest version or null on error
37
+ */
38
+ function fetchLatestVersionFromGitHub() {
39
+ return new Promise((resolve) => {
40
+ const req = https.get(GITHUB_API_URL, {
41
+ headers: { 'User-Agent': 'CCS-Update-Checker' },
42
+ timeout: REQUEST_TIMEOUT
43
+ }, (res) => {
44
+ let data = '';
45
+
46
+ res.on('data', (chunk) => {
47
+ data += chunk;
48
+ });
49
+
50
+ res.on('end', () => {
51
+ try {
52
+ if (res.statusCode !== 200) {
53
+ resolve(null);
54
+ return;
55
+ }
56
+
57
+ const release = JSON.parse(data);
58
+ const version = release.tag_name?.replace(/^v/, '') || null;
59
+ resolve(version);
60
+ } catch (err) {
61
+ resolve(null);
62
+ }
63
+ });
64
+ });
65
+
66
+ req.on('error', () => resolve(null));
67
+ req.on('timeout', () => {
68
+ req.destroy();
69
+ resolve(null);
70
+ });
71
+ });
72
+ }
73
+
74
+ /**
75
+ * Fetch latest version from npm registry
76
+ * @returns {Promise<string|null>} - Latest version or null on error
77
+ */
78
+ function fetchLatestVersionFromNpm() {
79
+ return new Promise((resolve) => {
80
+ const req = https.get(NPM_REGISTRY_URL, {
81
+ headers: { 'User-Agent': 'CCS-Update-Checker' },
82
+ timeout: REQUEST_TIMEOUT
83
+ }, (res) => {
84
+ let data = '';
85
+
86
+ res.on('data', (chunk) => {
87
+ data += chunk;
88
+ });
89
+
90
+ res.on('end', () => {
91
+ try {
92
+ if (res.statusCode !== 200) {
93
+ resolve(null);
94
+ return;
95
+ }
96
+
97
+ const packageData = JSON.parse(data);
98
+ const version = packageData.version || null;
99
+ resolve(version);
100
+ } catch (err) {
101
+ resolve(null);
102
+ }
103
+ });
104
+ });
105
+
106
+ req.on('error', () => resolve(null));
107
+ req.on('timeout', () => {
108
+ req.destroy();
109
+ resolve(null);
110
+ });
111
+ });
112
+ }
113
+
114
+ /**
115
+ * Read update check cache
116
+ * @returns {Object} - Cache object
117
+ */
118
+ function readCache() {
119
+ try {
120
+ if (!fs.existsSync(UPDATE_CHECK_FILE)) {
121
+ return { last_check: 0, latest_version: null, dismissed_version: null };
122
+ }
123
+
124
+ const data = fs.readFileSync(UPDATE_CHECK_FILE, 'utf8');
125
+ return JSON.parse(data);
126
+ } catch (err) {
127
+ return { last_check: 0, latest_version: null, dismissed_version: null };
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Write update check cache
133
+ * @param {Object} cache - Cache object to write
134
+ */
135
+ function writeCache(cache) {
136
+ try {
137
+ const ccsDir = path.join(os.homedir(), '.ccs');
138
+ if (!fs.existsSync(ccsDir)) {
139
+ fs.mkdirSync(ccsDir, { recursive: true, mode: 0o700 });
140
+ }
141
+
142
+ fs.writeFileSync(UPDATE_CHECK_FILE, JSON.stringify(cache, null, 2), 'utf8');
143
+ } catch (err) {
144
+ // Silently fail - not critical
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Check for updates (async, non-blocking)
150
+ * @param {string} currentVersion - Current CCS version
151
+ * @param {boolean} force - Force check even if within interval
152
+ * @param {string} installMethod - Installation method ('npm' or 'direct')
153
+ * @returns {Promise<Object>} - Update result object with status and data
154
+ */
155
+ async function checkForUpdates(currentVersion, force = false, installMethod = 'direct') {
156
+ const cache = readCache();
157
+ const now = Date.now();
158
+
159
+ // Check if we should check for updates
160
+ if (!force && (now - cache.last_check < CHECK_INTERVAL)) {
161
+ // Use cached result if available
162
+ if (cache.latest_version && compareVersions(cache.latest_version, currentVersion) > 0) {
163
+ // Don't show if user dismissed this version
164
+ if (cache.dismissed_version === cache.latest_version) {
165
+ return { status: 'no_update', reason: 'dismissed' };
166
+ }
167
+ return { status: 'update_available', latest: cache.latest_version, current: currentVersion };
168
+ }
169
+ return { status: 'no_update', reason: 'cached' };
170
+ }
171
+
172
+ // Fetch latest version from appropriate source
173
+ let latestVersion;
174
+ let fetchError = null;
175
+
176
+ if (installMethod === 'npm') {
177
+ latestVersion = await fetchLatestVersionFromNpm();
178
+ if (!latestVersion) fetchError = 'npm_registry_error';
179
+ } else {
180
+ latestVersion = await fetchLatestVersionFromGitHub();
181
+ if (!latestVersion) fetchError = 'github_api_error';
182
+ }
183
+
184
+ // Update cache
185
+ cache.last_check = now;
186
+ if (latestVersion) {
187
+ cache.latest_version = latestVersion;
188
+ }
189
+ writeCache(cache);
190
+
191
+ // Handle fetch errors
192
+ if (fetchError) {
193
+ return {
194
+ status: 'check_failed',
195
+ reason: fetchError,
196
+ message: `Failed to check for updates: ${fetchError.replace(/_/g, ' ')}`
197
+ };
198
+ }
199
+
200
+ // Check if update available
201
+ if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) {
202
+ // Don't show if user dismissed this version
203
+ if (cache.dismissed_version === latestVersion) {
204
+ return { status: 'no_update', reason: 'dismissed' };
205
+ }
206
+ return { status: 'update_available', latest: latestVersion, current: currentVersion };
207
+ }
208
+
209
+ return { status: 'no_update', reason: 'latest' };
210
+ }
211
+
212
+ /**
213
+ * Show update notification
214
+ * @param {Object} updateInfo - Update information
215
+ */
216
+ function showUpdateNotification(updateInfo) {
217
+ console.log('');
218
+ console.log(colored('═══════════════════════════════════════════════════════', 'cyan'));
219
+ console.log(colored(` Update available: ${updateInfo.current} → ${updateInfo.latest}`, 'yellow'));
220
+ console.log(colored('═══════════════════════════════════════════════════════', 'cyan'));
221
+ console.log('');
222
+ console.log(` Run ${colored('ccs update', 'yellow')} to update`);
223
+ console.log('');
224
+ }
225
+
226
+ /**
227
+ * Dismiss update notification for a specific version
228
+ * @param {string} version - Version to dismiss
229
+ */
230
+ function dismissUpdate(version) {
231
+ const cache = readCache();
232
+ cache.dismissed_version = version;
233
+ writeCache(cache);
234
+ }
235
+
236
+ module.exports = {
237
+ compareVersions,
238
+ checkForUpdates,
239
+ showUpdateNotification,
240
+ dismissUpdate,
241
+ readCache,
242
+ writeCache
243
+ };
package/lib/ccs CHANGED
@@ -2,7 +2,7 @@
2
2
  set -euo pipefail
3
3
 
4
4
  # Version (updated by scripts/bump-version.sh)
5
- CCS_VERSION="4.1.5"
5
+ CCS_VERSION="4.2.0"
6
6
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
7
  readonly CONFIG_FILE="${CCS_CONFIG:-$HOME/.ccs/config.json}"
8
8
  readonly PROFILES_JSON="$HOME/.ccs/profiles.json"
@@ -199,6 +199,7 @@ show_help() {
199
199
  echo -e "${CYAN}Diagnostics:${RESET}"
200
200
  echo -e " ${YELLOW}ccs doctor${RESET} Run health check and diagnostics"
201
201
  echo -e " ${YELLOW}ccs sync${RESET} Sync delegation commands and skills"
202
+ echo -e " ${YELLOW}ccs update${RESET} Update CCS to latest version"
202
203
  echo ""
203
204
 
204
205
  echo -e "${CYAN}Flags:${RESET}"
@@ -591,6 +592,124 @@ sync_run() {
591
592
  echo ""
592
593
  }
593
594
 
595
+ # --- Update Command ---
596
+
597
+ update_run() {
598
+ echo ""
599
+ echo -e "${CYAN}Checking for updates...${RESET}"
600
+ echo ""
601
+
602
+ # Detect installation method
603
+ local install_method="direct"
604
+ if command -v npm &>/dev/null && npm list -g @kaitranntt/ccs &>/dev/null 2>&1; then
605
+ install_method="npm"
606
+ fi
607
+
608
+ # Fetch latest version from appropriate source
609
+ local latest_version=""
610
+ if command -v curl &>/dev/null; then
611
+ if [[ "$install_method" == "npm" ]]; then
612
+ # Check npm registry for npm installations
613
+ latest_version=$(curl -fsSL https://registry.npmjs.org/@kaitranntt/ccs/latest 2>/dev/null | \
614
+ grep '"version"' | head -1 | sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([0-9.]+)".*/\1/')
615
+ else
616
+ # Check GitHub releases for direct installations
617
+ latest_version=$(curl -fsSL https://api.github.com/repos/kaitranntt/ccs/releases/latest 2>/dev/null | \
618
+ grep '"tag_name"' | sed -E 's/.*"v?([0-9.]+)".*/\1/')
619
+ fi
620
+ fi
621
+
622
+ if [[ -z "$latest_version" ]]; then
623
+ echo -e "${YELLOW}[!] Unable to check for updates${RESET}"
624
+ echo ""
625
+ echo "Try manually:"
626
+ if [[ "$install_method" == "npm" ]]; then
627
+ echo -e " ${YELLOW}npm install -g @kaitranntt/ccs@latest${RESET}"
628
+ else
629
+ echo -e " ${YELLOW}curl -fsSL ccs.kaitran.ca/install | bash${RESET}"
630
+ fi
631
+ echo ""
632
+ exit 1
633
+ fi
634
+
635
+ # Compare versions
636
+ if [[ "$latest_version" == "$CCS_VERSION" ]]; then
637
+ echo -e "${GREEN}[OK] You are already on the latest version (${CCS_VERSION})${RESET}"
638
+ echo ""
639
+ exit 0
640
+ fi
641
+
642
+ # Check if update available
643
+ local current_major=$(echo "$CCS_VERSION" | cut -d. -f1)
644
+ local current_minor=$(echo "$CCS_VERSION" | cut -d. -f2)
645
+ local current_patch=$(echo "$CCS_VERSION" | cut -d. -f3)
646
+
647
+ local latest_major=$(echo "$latest_version" | cut -d. -f1)
648
+ local latest_minor=$(echo "$latest_version" | cut -d. -f2)
649
+ local latest_patch=$(echo "$latest_version" | cut -d. -f3)
650
+
651
+ local is_newer=0
652
+ if [[ $latest_major -gt $current_major ]]; then
653
+ is_newer=1
654
+ elif [[ $latest_major -eq $current_major ]] && [[ $latest_minor -gt $current_minor ]]; then
655
+ is_newer=1
656
+ elif [[ $latest_major -eq $current_major ]] && [[ $latest_minor -eq $current_minor ]] && [[ $latest_patch -gt $current_patch ]]; then
657
+ is_newer=1
658
+ fi
659
+
660
+ if [[ $is_newer -eq 0 ]]; then
661
+ echo -e "${GREEN}[OK] You are on version ${CCS_VERSION} (latest is ${latest_version})${RESET}"
662
+ echo ""
663
+ exit 0
664
+ fi
665
+
666
+ echo -e "${YELLOW}[i] Update available: ${CCS_VERSION} → ${latest_version}${RESET}"
667
+ echo ""
668
+
669
+ # Perform update based on installation method
670
+ if [[ "$install_method" == "npm" ]]; then
671
+ echo -e "${CYAN}Updating via npm...${RESET}"
672
+ echo ""
673
+
674
+ if npm install -g @kaitranntt/ccs@latest; then
675
+ echo ""
676
+ echo -e "${GREEN}[OK] Update successful!${RESET}"
677
+ echo ""
678
+ echo -e "Run ${YELLOW}ccs --version${RESET} to verify"
679
+ echo ""
680
+ exit 0
681
+ else
682
+ echo ""
683
+ echo -e "${RED}[X] Update failed${RESET}"
684
+ echo ""
685
+ echo "Try manually:"
686
+ echo -e " ${YELLOW}npm install -g @kaitranntt/ccs@latest${RESET}"
687
+ echo ""
688
+ exit 1
689
+ fi
690
+ else
691
+ echo -e "${CYAN}Updating via installer...${RESET}"
692
+ echo ""
693
+
694
+ if curl -fsSL ccs.kaitran.ca/install | bash; then
695
+ echo ""
696
+ echo -e "${GREEN}[OK] Update successful!${RESET}"
697
+ echo ""
698
+ echo -e "Run ${YELLOW}ccs --version${RESET} to verify"
699
+ echo ""
700
+ exit 0
701
+ else
702
+ echo ""
703
+ echo -e "${RED}[X] Update failed${RESET}"
704
+ echo ""
705
+ echo "Try manually:"
706
+ echo -e " ${YELLOW}curl -fsSL ccs.kaitran.ca/install | bash${RESET}"
707
+ echo ""
708
+ exit 1
709
+ fi
710
+ fi
711
+ }
712
+
594
713
  # --- Claude CLI Detection Logic ---
595
714
 
596
715
  detect_claude_cli() {
@@ -1669,6 +1788,12 @@ if [[ $# -gt 0 ]] && [[ "${1}" == "sync" || "${1}" == "--sync" ]]; then
1669
1788
  exit $?
1670
1789
  fi
1671
1790
 
1791
+ # Special case: update command
1792
+ if [[ $# -gt 0 ]] && [[ "${1}" == "update" || "${1}" == "--update" ]]; then
1793
+ update_run
1794
+ exit $?
1795
+ fi
1796
+
1672
1797
  # Run auto-recovery before main logic
1673
1798
  auto_recover || {
1674
1799
  msg_error "Auto-recovery failed. Check permissions."
package/lib/ccs.ps1 CHANGED
@@ -12,7 +12,7 @@ param(
12
12
  $ErrorActionPreference = "Stop"
13
13
 
14
14
  # Version (updated by scripts/bump-version.sh)
15
- $CcsVersion = "4.1.5"
15
+ $CcsVersion = "4.2.0"
16
16
  $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
17
17
  $ConfigFile = if ($env:CCS_CONFIG) { $env:CCS_CONFIG } else { "$env:USERPROFILE\.ccs\config.json" }
18
18
  $ProfilesJson = "$env:USERPROFILE\.ccs\profiles.json"
@@ -246,6 +246,7 @@ function Show-Help {
246
246
  Write-ColorLine "Diagnostics:" "Cyan"
247
247
  Write-ColorLine " ccs doctor Run health check and diagnostics" "Yellow"
248
248
  Write-ColorLine " ccs sync Sync delegation commands and skills" "Yellow"
249
+ Write-ColorLine " ccs update Update CCS to latest version" "Yellow"
249
250
  Write-Host ""
250
251
 
251
252
  Write-ColorLine "Flags:" "Cyan"
@@ -1031,6 +1032,132 @@ function Sync-Run {
1031
1032
  Write-Host ""
1032
1033
  }
1033
1034
 
1035
+ # --- Update Command ---
1036
+
1037
+ function Update-Run {
1038
+ Write-Host ""
1039
+ Write-Host "Checking for updates..." -ForegroundColor Cyan
1040
+ Write-Host ""
1041
+
1042
+ # Detect installation method
1043
+ $InstallMethod = "direct"
1044
+ try {
1045
+ $NpmList = npm list -g @kaitranntt/ccs 2>&1
1046
+ if ($LASTEXITCODE -eq 0) {
1047
+ $InstallMethod = "npm"
1048
+ }
1049
+ } catch {
1050
+ # npm not available or not installed via npm
1051
+ }
1052
+
1053
+ # Fetch latest version from appropriate source
1054
+ $LatestVersion = ""
1055
+ try {
1056
+ if ($InstallMethod -eq "npm") {
1057
+ # Check npm registry for npm installations
1058
+ $Response = Invoke-RestMethod -Uri "https://registry.npmjs.org/@kaitranntt/ccs/latest" -TimeoutSec 5
1059
+ $LatestVersion = $Response.version
1060
+ } else {
1061
+ # Check GitHub releases for direct installations
1062
+ $Response = Invoke-RestMethod -Uri "https://api.github.com/repos/kaitranntt/ccs/releases/latest" -TimeoutSec 5
1063
+ $LatestVersion = $Response.tag_name -replace '^v', ''
1064
+ }
1065
+ } catch {
1066
+ Write-Host "[!] Unable to check for updates" -ForegroundColor Yellow
1067
+ Write-Host ""
1068
+ Write-Host "Try manually:"
1069
+ if ($InstallMethod -eq "npm") {
1070
+ Write-Host " npm install -g @kaitranntt/ccs@latest" -ForegroundColor Yellow
1071
+ } else {
1072
+ Write-Host " irm ccs.kaitran.ca/install | iex" -ForegroundColor Yellow
1073
+ }
1074
+ Write-Host ""
1075
+ exit 1
1076
+ }
1077
+
1078
+ # Compare versions
1079
+ if ($LatestVersion -eq $CcsVersion) {
1080
+ Write-Host "[OK] You are already on the latest version ($CcsVersion)" -ForegroundColor Green
1081
+ Write-Host ""
1082
+ exit 0
1083
+ }
1084
+
1085
+ # Check if update available
1086
+ $CurrentParts = $CcsVersion.Split('.')
1087
+ $LatestParts = $LatestVersion.Split('.')
1088
+
1089
+ $IsNewer = $false
1090
+ for ($i = 0; $i -lt 3; $i++) {
1091
+ $Current = [int]$CurrentParts[$i]
1092
+ $Latest = [int]$LatestParts[$i]
1093
+
1094
+ if ($Latest -gt $Current) {
1095
+ $IsNewer = $true
1096
+ break
1097
+ } elseif ($Latest -lt $Current) {
1098
+ break
1099
+ }
1100
+ }
1101
+
1102
+ if (-not $IsNewer) {
1103
+ Write-Host "[OK] You are on version $CcsVersion (latest is $LatestVersion)" -ForegroundColor Green
1104
+ Write-Host ""
1105
+ exit 0
1106
+ }
1107
+
1108
+ Write-Host "[i] Update available: $CcsVersion → $LatestVersion" -ForegroundColor Yellow
1109
+ Write-Host ""
1110
+
1111
+ # Perform update based on installation method
1112
+ if ($InstallMethod -eq "npm") {
1113
+ Write-Host "Updating via npm..." -ForegroundColor Cyan
1114
+ Write-Host ""
1115
+
1116
+ try {
1117
+ npm install -g @kaitranntt/ccs@latest
1118
+ if ($LASTEXITCODE -eq 0) {
1119
+ Write-Host ""
1120
+ Write-Host "[OK] Update successful!" -ForegroundColor Green
1121
+ Write-Host ""
1122
+ Write-Host "Run ccs --version to verify" -ForegroundColor Yellow
1123
+ Write-Host ""
1124
+ exit 0
1125
+ } else {
1126
+ throw "npm install failed"
1127
+ }
1128
+ } catch {
1129
+ Write-Host ""
1130
+ Write-Host "[X] Update failed" -ForegroundColor Red
1131
+ Write-Host ""
1132
+ Write-Host "Try manually:"
1133
+ Write-Host " npm install -g @kaitranntt/ccs@latest" -ForegroundColor Yellow
1134
+ Write-Host ""
1135
+ exit 1
1136
+ }
1137
+ } else {
1138
+ Write-Host "Updating via installer..." -ForegroundColor Cyan
1139
+ Write-Host ""
1140
+
1141
+ try {
1142
+ irm ccs.kaitran.ca/install | iex
1143
+ Write-Host ""
1144
+ Write-Host "[OK] Update successful!" -ForegroundColor Green
1145
+ Write-Host ""
1146
+ Write-Host "Run ccs --version to verify" -ForegroundColor Yellow
1147
+ Write-Host ""
1148
+ exit 0
1149
+ } catch {
1150
+ Write-Host ""
1151
+ Write-Host "[X] Update failed" -ForegroundColor Red
1152
+ Write-Host ""
1153
+ Write-Host "Try manually:"
1154
+ Write-Host " irm ccs.kaitran.ca/install | iex" -ForegroundColor Yellow
1155
+ Write-Host ""
1156
+ exit 1
1157
+ }
1158
+ }
1159
+ }
1160
+
1034
1161
  # --- Auth Commands (Phase 3) ---
1035
1162
 
1036
1163
  function Show-AuthHelp {
@@ -1519,6 +1646,12 @@ if ($RemainingArgs.Count -gt 0 -and ($RemainingArgs[0] -eq "sync" -or $Remaining
1519
1646
  exit 0
1520
1647
  }
1521
1648
 
1649
+ # Special case: update command
1650
+ if ($RemainingArgs.Count -gt 0 -and ($RemainingArgs[0] -eq "update" -or $RemainingArgs[0] -eq "--update")) {
1651
+ Update-Run
1652
+ exit 0
1653
+ }
1654
+
1522
1655
  # Run auto-recovery before main logic
1523
1656
  if (-not (Invoke-AutoRecovery)) {
1524
1657
  Write-ErrorMsg "Auto-recovery failed. Check permissions."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaitranntt/ccs",
3
- "version": "4.1.5",
3
+ "version": "4.2.0",
4
4
  "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
5
5
  "keywords": [
6
6
  "cli",
@@ -18,7 +18,7 @@ _ccs_completion() {
18
18
 
19
19
  # Top-level completion (first argument)
20
20
  if [[ ${COMP_CWORD} -eq 1 ]]; then
21
- local commands="auth doctor sync"
21
+ local commands="auth doctor sync update"
22
22
  local flags="--help --version --shell-completion -h -v -sc"
23
23
  local profiles=""
24
24
 
@@ -73,21 +73,22 @@ complete -c ccs -s v -l version -d 'Show version information'
73
73
  complete -c ccs -s sc -l shell-completion -d 'Install shell completion'
74
74
 
75
75
  # Top-level commands (blue color for commands)
76
- 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)
77
- 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)
78
- 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)
76
+ 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)
77
+ 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)
78
+ 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)
79
+ 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)
79
80
 
80
81
  # Top-level known settings profiles (green color for model profiles)
81
- 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)
82
- 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)
83
- 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)
84
- 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)
82
+ 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)
83
+ 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)
84
+ 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)
85
+ 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)
85
86
 
86
87
  # Top-level custom settings profiles (dynamic, with generic description in green)
87
- 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)
88
+ 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)
88
89
 
89
90
  # Top-level account profiles (dynamic, yellow color for account profiles)
90
- 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)
91
+ 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)
91
92
 
92
93
  # shell-completion subflags
93
94
  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 @@
12
12
  Register-ArgumentCompleter -CommandName ccs -ScriptBlock {
13
13
  param($commandName, $wordToComplete, $commandAst, $fakeBoundParameters)
14
14
 
15
- $commands = @('auth', 'doctor', 'sync', '--help', '--version', '--shell-completion', '-h', '-v', '-sc')
15
+ $commands = @('auth', 'doctor', 'sync', 'update', '--help', '--version', '--shell-completion', '-h', '-v', '-sc')
16
16
  $authCommands = @('create', 'list', 'show', 'remove', 'default', '--help', '-h')
17
17
  $shellCompletionFlags = @('--bash', '--zsh', '--fish', '--powershell')
18
18
  $listFlags = @('--verbose', '--json')
@@ -16,7 +16,7 @@
16
16
  # Color codes: 0;34=blue, 0;32=green, 0;33=yellow, 2;37=dim white
17
17
  # Pattern format: =(#b)(group1)(group2)==color_for_group1=color_for_group2
18
18
  # The leading '=' means no color for whole match, then each '=' assigns to each group
19
- zstyle ':completion:*:*:ccs:*:commands' list-colors '=(#b)(auth|doctor|sync)([[:space:]]#--[[:space:]]#*)==0\;34=2\;37'
19
+ zstyle ':completion:*:*:ccs:*:commands' list-colors '=(#b)(auth|doctor|sync|update)([[:space:]]#--[[:space:]]#*)==0\;34=2\;37'
20
20
  zstyle ':completion:*:*:ccs:*:model-profiles' list-colors '=(#b)(default|glm|glmt|kimi|[^[:space:]]##)([[:space:]]#--[[:space:]]#*)==0\;32=2\;37'
21
21
  zstyle ':completion:*:*:ccs:*:account-profiles' list-colors '=(#b)([^[:space:]]##)([[:space:]]#--[[:space:]]#*)==0\;33=2\;37'
22
22
  zstyle ':completion:*:*:ccs:*' group-name ''
@@ -35,6 +35,7 @@ _ccs() {
35
35
  'auth:Manage multiple Claude accounts'
36
36
  'doctor:Run health check and diagnostics'
37
37
  'sync:Sync delegation commands and skills'
38
+ 'update:Update CCS to latest version'
38
39
  )
39
40
 
40
41
  # Define known settings profiles with descriptions (consistent padding)
@@ -121,7 +121,7 @@ function createConfigFiles() {
121
121
  claudeSymlinkManager.install();
122
122
  } catch (err) {
123
123
  console.warn('[!] CCS item installation warning:', err.message);
124
- console.warn(' Run "ccs update" to retry');
124
+ console.warn(' Run "ccs sync" to retry');
125
125
  }
126
126
  console.log('');
127
127