From 126cffc6dcf434abeee883a4109d3705cdb92a67 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 18 Dec 2025 16:52:57 -0500 Subject: [PATCH] refactor: remove deprecated native shell installers - Remove installers/ directory (install.sh, install.ps1, uninstall.sh, uninstall.ps1) - Update CloudFlare worker to 301 redirect /install* to npm docs - Remove detectInstallationMethod() - npm is now only install method - Simplify update-command.ts to npm-only updates - Clean up tests to remove direct install references BREAKING CHANGE: Native shell installers (curl/irm) no longer work. Use `npm install -g @kaitranntt/ccs` instead. --- installers/install.ps1 | 808 --------------- installers/install.sh | 927 ------------------ installers/uninstall.ps1 | 99 -- installers/uninstall.sh | 95 -- scripts/worker.js | 56 +- src/ccs.ts | 7 +- src/commands/update-command.ts | 165 +--- src/utils/package-manager-detector.ts | 66 +- .../update-command-beta-channel.test.js | 114 +-- .../update-command-force-reinstall.test.js | 99 +- 10 files changed, 51 insertions(+), 2385 deletions(-) delete mode 100644 installers/install.ps1 delete mode 100755 installers/install.sh delete mode 100644 installers/uninstall.ps1 delete mode 100755 installers/uninstall.sh diff --git a/installers/install.ps1 b/installers/install.ps1 deleted file mode 100644 index 27a4a0f1..00000000 --- a/installers/install.ps1 +++ /dev/null @@ -1,808 +0,0 @@ -# CCS Installation Script (v4.5.0) - Windows PowerShell - DEPRECATED -# DEPRECATED: This installer is deprecated. Use npm instead. -# Bootstrap-based: Installs lightweight shell wrappers (LEGACY) -# Requires: Node.js 14+ (npm recommended) -# https://github.com/kaitranntt/ccs - -param( - [string]$InstallDir = "$env:USERPROFILE\.ccs" -) - -$ErrorActionPreference = "Stop" - -# --- Deprecation Notice --- -Write-Host "" -Write-Host "=======================================================================" -ForegroundColor Yellow -Write-Host " " -ForegroundColor Yellow -Write-Host " [!] DEPRECATION NOTICE " -ForegroundColor Yellow -Write-Host " " -ForegroundColor Yellow -Write-Host " Native shell installers are deprecated and will be removed " -ForegroundColor Yellow -Write-Host " in a future version. Please use npm installation instead: " -ForegroundColor Yellow -Write-Host " " -ForegroundColor Yellow -Write-Host " npm install -g @kaitranntt/ccs " -ForegroundColor Yellow -Write-Host " " -ForegroundColor Yellow -Write-Host " Proceeding with legacy install (auto-runs npm if available)... " -ForegroundColor Yellow -Write-Host " " -ForegroundColor Yellow -Write-Host "=======================================================================" -ForegroundColor Yellow -Write-Host "" -Start-Sleep -Seconds 3 - -# --- Auto-redirect to npm installation --- -if (Get-Command npm -ErrorAction SilentlyContinue) { - Write-Host "[i] Node.js detected, using npm installation (recommended)..." -ForegroundColor Cyan - Write-Host "" - - npm install -g "@kaitranntt/ccs" - - if ($LASTEXITCODE -eq 0) { - Write-Host "" - Write-Host "[OK] CCS installed via npm successfully!" -ForegroundColor Green - Write-Host "" - Write-Host "Quick start:" - Write-Host " ccs # Use Claude (default)" - Write-Host " ccs glm # Use GLM" - Write-Host " ccs --help # Show all commands" - Write-Host "" - exit 0 - } else { - Write-Host "" - Write-Host "[!] npm installation failed. Falling back to legacy install..." -ForegroundColor Yellow - Write-Host "" - Start-Sleep -Seconds 2 - } -} else { - Write-Host "[!] npm not found. Falling back to legacy install..." -ForegroundColor Yellow - Write-Host "[!] Install Node.js from https://nodejs.org for the recommended method." -ForegroundColor Yellow - Write-Host "" - Start-Sleep -Seconds 2 -} - -# Continue with legacy PowerShell installation... - -# Configuration -$CcsDir = "$env:USERPROFILE\.ccs" -$ClaudeDir = "$env:USERPROFILE\.claude" -$GlmModel = "glm-4.6" -$KimiModel = "kimi-for-coding" - -# Detect if running from git repository or standalone -$ScriptDir = if ($MyInvocation.MyCommand.Path) { - Split-Path -Parent $MyInvocation.MyCommand.Path -} else { - # Running via irm | iex (in-memory, no file path) - $null -} - -$InstallMethod = if ($ScriptDir -and ((Test-Path "$ScriptDir\lib\ccs.ps1") -or (Test-Path "$ScriptDir\..\lib\ccs.ps1"))) { - "git" -} else { - "standalone" -} - -# Version configuration -# 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 = "6.5.0" - -# Try to read VERSION file for git installations -if ($ScriptDir) { - $VersionFile = if (Test-Path "$ScriptDir\VERSION") { - "$ScriptDir\VERSION" - } elseif (Test-Path "$ScriptDir\..\VERSION") { - "$ScriptDir\..\VERSION" - } else { - $null - } - - if ($VersionFile -and (Test-Path $VersionFile)) { - $CcsVersion = (Get-Content $VersionFile -Raw).Trim() - } -} - -# --- Color/Format Functions --- -function Write-Critical { - param([string]$Message) - Write-Host "" - Write-Host "╔═════════════════════════════════════════════╗" -ForegroundColor Red - Write-Host "║ ACTION REQUIRED ║" -ForegroundColor Red - Write-Host "╚═════════════════════════════════════════════╝" -ForegroundColor Red - Write-Host "" - Write-Host $Message -ForegroundColor Red - Write-Host "" -} - -function Write-WarningMsg { - param([string]$Message) - Write-Host "" - Write-Host "[!] WARNING" -ForegroundColor Yellow - Write-Host $Message -ForegroundColor Yellow - Write-Host "" -} - -function Write-Success { - param([string]$Message) - Write-Host "[OK] $Message" -ForegroundColor Green -} - -function Write-Info { - param([string]$Message) - Write-Host "[i] $Message" -} - -function Write-Section { - param([string]$Title) - Write-Host "" - Write-Host "===== $Title =====" -ForegroundColor Cyan - Write-Host "" -} - -# --- Node.js Detection (v4.5) --- -function Test-NodeJs { - $MIN_VERSION = 14 - - if (-not (Get-Command node -ErrorAction SilentlyContinue)) { - Write-WarningMsg @" -Node.js not found - -CCS v4.5+ requires Node.js 14+ to run. -The bootstrap scripts will check and install the npm package on first use. - -Install Node.js: https://nodejs.org (LTS recommended) - -Installation will continue, but 'ccs' will not work until Node.js is installed. -"@ - return $false - } - - $nodeVersion = (node -v) -replace 'v', '' - $nodeMajor = [int]($nodeVersion -split '\.')[0] - if ($nodeMajor -lt $MIN_VERSION) { - Write-WarningMsg @" -Node.js 14+ required (found: $(node -v)) - -CCS v4.5+ requires Node.js 14 or newer. -Upgrade from: https://nodejs.org - -Installation will continue, but 'ccs' may not work correctly. -"@ - return $false - } - - Write-Success "Node.js $(node -v) detected" - return $true -} - -# Helper Functions - -function Detect-CurrentProvider { - $SettingsFile = "$ClaudeDir\settings.json" - if (-not (Test-Path $SettingsFile)) { - return "unknown" - } - - try { - $Content = Get-Content $SettingsFile -Raw - if ($Content -match "api\.kimi\.com|kimi-for-coding") { - return "kimi" - } elseif ($Content -match "api\.z\.ai|glm-4") { - return "glm" - } elseif ($Content -match "ANTHROPIC_BASE_URL" -and $Content -notmatch "api\.z\.ai|api\.kimi\.com") { - return "custom" - } else { - return "claude" - } - } catch { - return "unknown" - } -} - -function New-GlmTemplate { - $Template = @{ - env = @{ - ANTHROPIC_BASE_URL = "https://api.z.ai/api/anthropic" - ANTHROPIC_AUTH_TOKEN = "YOUR_GLM_API_KEY_HERE" - ANTHROPIC_MODEL = $GlmModel - ANTHROPIC_DEFAULT_OPUS_MODEL = $GlmModel - ANTHROPIC_DEFAULT_SONNET_MODEL = $GlmModel - ANTHROPIC_DEFAULT_HAIKU_MODEL = $GlmModel - } - } - return $Template | ConvertTo-Json -Depth 10 -} - -function New-GlmProfile { - param([string]$Provider) - - $CurrentSettings = "$ClaudeDir\settings.json" - $GlmSettings = "$CcsDir\glm.settings.json" - - if ($Provider -eq "glm" -and (Test-Path $CurrentSettings)) { - Write-Host "[OK] Copying current GLM config to profile..." - - try { - $Config = Get-Content $CurrentSettings -Raw | ConvertFrom-Json - if (-not $Config.env) { - $Config | Add-Member -NotePropertyName env -NotePropertyValue @{} -Force - } - $Config.env | Add-Member -NotePropertyName ANTHROPIC_DEFAULT_OPUS_MODEL -NotePropertyValue $GlmModel -Force - $Config.env | Add-Member -NotePropertyName ANTHROPIC_DEFAULT_SONNET_MODEL -NotePropertyValue $GlmModel -Force - $Config.env | Add-Member -NotePropertyName ANTHROPIC_DEFAULT_HAIKU_MODEL -NotePropertyValue $GlmModel -Force - - $Config | ConvertTo-Json -Depth 10 | Set-Content $GlmSettings - Write-Host " Created: $GlmSettings with your existing API key + enhanced settings" - } catch { - Write-Host " [i] Copying current settings failed, using template" - New-GlmTemplate | Set-Content $GlmSettings - } - } else { - Write-Host "Creating GLM profile template at $GlmSettings" - New-GlmTemplate | Set-Content $GlmSettings - Write-Host " Created: $GlmSettings" - Write-Host " [!] Edit this file and replace YOUR_GLM_API_KEY_HERE with your actual GLM API key" - } -} - -function New-KimiTemplate { - $Template = @{ - env = @{ - ANTHROPIC_BASE_URL = "https://api.kimi.com/coding/" - ANTHROPIC_AUTH_TOKEN = "YOUR_KIMI_API_KEY_HERE" - ANTHROPIC_MODEL = $KimiModel - ANTHROPIC_SMALL_FAST_MODEL = $KimiModel - ANTHROPIC_DEFAULT_OPUS_MODEL = $KimiModel - ANTHROPIC_DEFAULT_SONNET_MODEL = $KimiModel - ANTHROPIC_DEFAULT_HAIKU_MODEL = $KimiModel - } - alwaysThinkingEnabled = $true - } - return $Template | ConvertTo-Json -Depth 10 -} - -function New-KimiProfile { - param([string]$Provider) - - $CurrentSettings = "$ClaudeDir\settings.json" - $KimiSettings = "$CcsDir\kimi.settings.json" - - if ($Provider -eq "kimi" -and (Test-Path $CurrentSettings)) { - Write-Host "[OK] Copying current Kimi config to profile..." - - try { - $Config = Get-Content $CurrentSettings -Raw | ConvertFrom-Json - if (-not $Config.env) { - $Config | Add-Member -NotePropertyName env -NotePropertyValue @{} -Force - } - $Config.env | Add-Member -NotePropertyName ANTHROPIC_SMALL_FAST_MODEL -NotePropertyValue $KimiModel -Force - $Config.env | Add-Member -NotePropertyName ANTHROPIC_DEFAULT_OPUS_MODEL -NotePropertyValue $KimiModel -Force - $Config.env | Add-Member -NotePropertyName ANTHROPIC_DEFAULT_SONNET_MODEL -NotePropertyValue $KimiModel -Force - $Config.env | Add-Member -NotePropertyName ANTHROPIC_DEFAULT_HAIKU_MODEL -NotePropertyValue $KimiModel -Force - $Config | Add-Member -NotePropertyName alwaysThinkingEnabled -NotePropertyValue $true -Force - - $Config | ConvertTo-Json -Depth 10 | Set-Content $KimiSettings - Write-Host " Created: $KimiSettings with your existing API key + enhanced settings" - } catch { - Write-Host " [i] Copying current settings failed, using template" - New-KimiTemplate | Set-Content $KimiSettings - } - } else { - Write-Host "Creating Kimi profile template at $KimiSettings" - New-KimiTemplate | Set-Content $KimiSettings - Write-Host " Created: $KimiSettings" - Write-Host " [!] Edit this file and replace YOUR_KIMI_API_KEY_HERE with your actual Kimi API key" - } -} - -function Install-ClaudeFolder { - param( - [string]$SourceDir - ) - - $TargetDir = "$CcsDir\.claude" - - # Check if already exists - if (Test-Path $TargetDir) { - Write-Host "| [i] .claude/ folder already exists, skipping" - return $true - } - - # Create directory structure - $null = New-Item -ItemType Directory -Force -Path "$TargetDir\commands" - $null = New-Item -ItemType Directory -Force -Path "$TargetDir\skills\ccs-delegation\references" - - if ($InstallMethod -eq "git" -and $SourceDir) { - # Copy from local git repo - $SourceClaudeDir = Join-Path $SourceDir ".claude" - if (Test-Path $SourceClaudeDir) { - try { - Copy-Item -Path "$SourceClaudeDir\*" -Destination $TargetDir -Recurse -Force - Write-Host "| [OK] Installed .claude/ folder" - return $true - } catch { - Write-Host "| [!] Failed to copy .claude/ folder" - return $false - } - } else { - Write-Host "| [!] .claude/ folder not found in source" - return $false - } - } else { - # Standalone: download from GitHub - try { - $BaseUrl = "https://raw.githubusercontent.com/kaitranntt/ccs/main/.claude" - - Invoke-WebRequest -Uri "$BaseUrl/commands/ccs.md" ` - -OutFile "$TargetDir\commands\ccs.md" -UseBasicParsing - Invoke-WebRequest -Uri "$BaseUrl/skills/ccs-delegation/SKILL.md" ` - -OutFile "$TargetDir\skills\ccs-delegation\SKILL.md" -UseBasicParsing - Invoke-WebRequest -Uri "$BaseUrl/skills/ccs-delegation/references/delegation-patterns.md" ` - -OutFile "$TargetDir\skills\ccs-delegation\references\delegation-patterns.md" -UseBasicParsing - - Write-Host "| [OK] Downloaded .claude/ folder" - return $true - } catch { - Write-Host "| [!] Failed to download .claude/ folder" - return $false - } - } -} - -# Main Installation - -# Check Node.js requirement (warn if missing, continue anyway) -$null = Test-NodeJs - -Write-Host '===== Installing CCS (Windows) =====' - -# Create directories -New-Item -ItemType Directory -Force -Path $CcsDir | Out-Null - -# Install main executable -if ($InstallMethod -eq "standalone") { - # Standalone install - download from GitHub - Write-Host "| Downloading CCS from GitHub..." - - try { - $BaseUrl = "https://raw.githubusercontent.com/kaitranntt/ccs/main" - Invoke-WebRequest -Uri "$BaseUrl/lib/ccs.ps1" -OutFile "$CcsDir\ccs.ps1" -UseBasicParsing - Write-Host "| [OK] Downloaded ccs.ps1" - - # Note: Shell dependencies (error-codes.ps1, progress-indicator.ps1, prompt.ps1) no longer needed - # Bootstrap delegates all functionality to Node.js via npx - - # Download shell completion files - $CompletionsDir = "$CcsDir\completions" - if (-not (Test-Path $CompletionsDir)) { - New-Item -ItemType Directory -Path $CompletionsDir -Force | Out-Null - } - - try { - Invoke-WebRequest -Uri "$BaseUrl/scripts/completion/ccs.ps1" -OutFile "$CompletionsDir\ccs.ps1" -UseBasicParsing - Write-Host "| [OK] Downloaded completion files" - } catch { - Write-Host "| [!] Warning: Failed to download completion files" - } - } catch { - Write-Host "|" - Write-Host "[X] Error: Failed to download ccs.ps1 from GitHub" -ForegroundColor Red - Write-Host " $_" - return - } -} else { - # Git install - copy local file - $CcsPs1Path = if (Test-Path "$ScriptDir\lib\ccs.ps1") { - "$ScriptDir\lib\ccs.ps1" - } elseif (Test-Path "$ScriptDir\..\lib\ccs.ps1") { - "$ScriptDir\..\lib\ccs.ps1" - } else { - throw "lib\ccs.ps1 not found" - } - Copy-Item $CcsPs1Path "$CcsDir\ccs.ps1" -Force - Write-Host "| [OK] Installed ccs.ps1" - - # Note: Shell dependencies (error-codes.ps1, progress-indicator.ps1, prompt.ps1) no longer needed - # Bootstrap delegates all functionality to Node.js via npx - - # Copy shell completion files - $CompletionsDir = "$CcsDir\completions" - if (-not (Test-Path $CompletionsDir)) { - New-Item -ItemType Directory -Path $CompletionsDir -Force | Out-Null - } - - $SourceCompletionDir = if (Test-Path "$ScriptDir\scripts\completion") { - "$ScriptDir\scripts\completion" - } elseif (Test-Path "$ScriptDir\..\scripts\completion") { - "$ScriptDir\..\scripts\completion" - } else { - $null - } - - if ($SourceCompletionDir -and (Test-Path "$SourceCompletionDir\ccs.ps1")) { - Copy-Item "$SourceCompletionDir\ccs.ps1" "$CompletionsDir\ccs.ps1" -Force -ErrorAction SilentlyContinue - Write-Host "| [OK] Copied completion files" - } -} - -# Install uninstall script as ccs-uninstall.ps1 -if ($ScriptDir -and (Test-Path "$ScriptDir\uninstall.ps1")) { - # Copy uninstall.ps1 as ccs-uninstall.ps1 (similar to Linux symlink approach) - if ($ScriptDir -ne $CcsDir) { - Copy-Item "$ScriptDir\uninstall.ps1" "$CcsDir\ccs-uninstall.ps1" -Force - } - # Clean up old uninstall.ps1 from previous installations - if (Test-Path "$CcsDir\uninstall.ps1") { - Remove-Item "$CcsDir\uninstall.ps1" -Force -ErrorAction SilentlyContinue - } - Write-Host "| [OK] Installed uninstaller" -} elseif ($InstallMethod -eq "standalone") { - try { - $BaseUrl = "https://raw.githubusercontent.com/kaitranntt/ccs/main" - # Download uninstall.ps1 as ccs-uninstall.ps1 - Invoke-WebRequest -Uri "$BaseUrl/installers/uninstall.ps1" -OutFile "$CcsDir\ccs-uninstall.ps1" -UseBasicParsing - # Clean up old uninstall.ps1 from previous installations - if (Test-Path "$CcsDir\uninstall.ps1") { - Remove-Item "$CcsDir\uninstall.ps1" -Force -ErrorAction SilentlyContinue - } - Write-Host "| [OK] Installed uninstaller" - } catch { - Write-Host "| [!] Could not download uninstaller (optional)" - } -} - -Write-Host "| [OK] Created directories" - -# Install .claude/ folder -if ($InstallMethod -eq "git" -and $ScriptDir) { - $ParentDir = Split-Path -Parent $ScriptDir - $null = Install-ClaudeFolder -SourceDir $ParentDir -} else { - $null = Install-ClaudeFolder -SourceDir "" -} - -Write-Host "=========================================" -Write-Host "" - -# Profile Setup - -$CurrentProvider = Detect-CurrentProvider - -$ProviderLabel = switch ($CurrentProvider) { - "glm" { ' (detected: GLM)' } - "kimi" { ' (detected: Kimi)' } - "claude" { ' (detected: Claude)' } - "custom" { ' (detected: custom)' } - default { "" } -} - -Write-Host "===== Configuring Profiles v$CcsVersion$ProviderLabel" - -# Backup existing config (single backup, no timestamp) -$ConfigFile = "$CcsDir\config.json" -$BackupFile = "$CcsDir\config.json.backup" -if (Test-Path $ConfigFile) { - Copy-Item $ConfigFile $BackupFile -Force -} - -$NeedsGlmKey = $false -$GlmSettings = "$CcsDir\glm.settings.json" - -# Create GLM profile if missing -if (-not (Test-Path $GlmSettings)) { - New-GlmProfile -Provider $CurrentProvider - if ($CurrentProvider -ne "glm") { - $NeedsGlmKey = $true - } -} else { - Write-Host '| [OK] GLM profile exists' -} - -$NeedsKimiKey = $false -$KimiSettings = "$CcsDir\kimi.settings.json" - -# Create Kimi profile if missing -if (-not (Test-Path $KimiSettings)) { - New-KimiProfile -Provider $CurrentProvider - if ($CurrentProvider -ne "kimi") { - $NeedsKimiKey = $true - } -} else { - Write-Host '| [OK] Kimi profile exists' -} - -# Create config if missing -if (-not (Test-Path $ConfigFile)) { - $ConfigContent = @{ - profiles = @{ - glm = "~/.ccs/glm.settings.json" - kimi = "~/.ccs/kimi.settings.json" - default = "~/.claude/settings.json" - } - } - $ConfigContent | ConvertTo-Json -Depth 10 | Set-Content $ConfigFile - Write-Host ('| OK: Config created at {0}\.ccs\config.json' -f $env:USERPROFILE) -} - -# Validate config JSON -if (Test-Path $ConfigFile) { - try { - $null = Get-Content $ConfigFile -Raw | ConvertFrom-Json - } catch { - Write-Host '| [!] Warning: Invalid JSON in config.json' -ForegroundColor Yellow - if (Test-Path $BackupFile) { - Write-Host ('| Restore from: {0}' -f $BackupFile) - } - } -} - -# Validate GLM settings JSON -if (Test-Path $GlmSettings) { - try { - $null = Get-Content $GlmSettings -Raw | ConvertFrom-Json - } catch { - Write-Host '| [!] Warning: Invalid JSON in glm.settings.json' -ForegroundColor Yellow - } -} - -Write-Host "=========================================" -Write-Host "" - -# Detect circular symlink -function Test-CircularSymlink { - param( - [string]$Target, - [string]$LinkPath - ) - - # Check if target exists and is symlink - if (-not (Test-Path $Target)) { - return $false - } - - try { - $Item = Get-Item $Target -ErrorAction Stop - if ($Item.LinkType -ne "SymbolicLink") { - return $false - } - - # Resolve target's link - $TargetLink = $Item.Target - $SharedDir = "$env:USERPROFILE\.ccs\shared" - - # Check if target points back to our shared dir - if ($TargetLink -like "$SharedDir*" -or $TargetLink -eq $LinkPath) { - Write-Host "[!] Circular symlink detected: $Target → $TargetLink" - return $true - } - } catch { - return $false - } - - return $false -} - -# Setup shared directories as symlinks to ~/.claude/ (v3.2.0) -function Initialize-SharedSymlinks { - $SharedDir = "$CcsDir\shared" - $ClaudeDir = "$env:USERPROFILE\.claude" - - # Create ~/.claude/ if missing - if (-not (Test-Path $ClaudeDir)) { - Write-Host "[i] Creating ~/.claude/ directory structure" - New-Item -ItemType Directory -Path $ClaudeDir -Force | Out-Null - @('commands', 'skills', 'agents') | ForEach-Object { - New-Item -ItemType Directory -Path "$ClaudeDir\$_" -Force | Out-Null - } - } - - # Create shared directory - if (-not (Test-Path $SharedDir)) { - New-Item -ItemType Directory -Path $SharedDir -Force | Out-Null - } - - # Create symlinks ~/.ccs/shared/* → ~/.claude/* - foreach ($Dir in @('commands', 'skills', 'agents')) { - $ClaudePath = "$ClaudeDir\$Dir" - $SharedPath = "$SharedDir\$Dir" - - # Create directory in ~/.claude/ if missing - if (-not (Test-Path $ClaudePath)) { - New-Item -ItemType Directory -Path $ClaudePath -Force | Out-Null - } - - # Check for circular symlink - if (Test-CircularSymlink -Target $ClaudePath -LinkPath $SharedPath) { - Write-Host "[!] Skipping $Dir`: circular symlink detected" - continue - } - - # If already correct symlink, skip - if (Test-Path $SharedPath) { - try { - $Item = Get-Item $SharedPath -ErrorAction Stop - if ($Item.LinkType -eq "SymbolicLink") { - $CurrentTarget = $Item.Target - if ($CurrentTarget -eq $ClaudePath) { - continue # Already correct - } - } - # Backup existing data before replacing - if ((Get-ChildItem $SharedPath -ErrorAction SilentlyContinue).Count -gt 0) { - Write-Host "[i] Migrating existing $Dir to ~/.claude/$Dir" - Get-ChildItem $SharedPath -ErrorAction SilentlyContinue | ForEach-Object { - $DestPath = Join-Path $ClaudePath $_.Name - if (-not (Test-Path $DestPath)) { - Copy-Item $_.FullName $DestPath -Recurse -ErrorAction SilentlyContinue - } - } - } - Remove-Item $SharedPath -Recurse -Force -ErrorAction SilentlyContinue - } catch { - # Continue to recreate - } - } - - # Create symlink (requires Developer Mode or admin) - try { - New-Item -ItemType SymbolicLink -Path $SharedPath -Target $ClaudePath -Force -ErrorAction Stop | Out-Null - } catch { - Write-Host "[!] Symlink failed for $Dir, copying instead (enable Developer Mode)" - if (-not (Test-Path $SharedPath)) { - New-Item -ItemType Directory -Path $SharedPath -Force | Out-Null - } - if (Test-Path $ClaudePath) { - Copy-Item "$ClaudePath\*" $SharedPath -Recurse -ErrorAction SilentlyContinue - } - } - } -} - -Write-Host "[i] Setting up shared directories..." -Initialize-SharedSymlinks -Write-Host "" - -# Install CCS items to ~/.claude/ via symlinks (v4.1.0) -Write-Host "[i] Installing CCS items to ~/.claude/..." -if (Get-Command node -ErrorAction SilentlyContinue) { - # Check if .claude/ was successfully installed - if (Test-Path "$CcsDir\.claude") { - # Download or copy claude-symlink-manager.js - $UtilsDir = "$CcsDir\bin\utils" - if (-not (Test-Path $UtilsDir)) { - New-Item -ItemType Directory -Path $UtilsDir -Force | Out-Null - } - - if ($InstallMethod -eq "git" -and $ScriptDir) { - # Git install - copy from local repo - $SourcePath = $null - if (Test-Path "$ScriptDir\..\bin\utils\claude-symlink-manager.js") { - $SourcePath = "$ScriptDir\..\bin\utils\claude-symlink-manager.js" - } elseif (Test-Path "$ScriptDir\bin\utils\claude-symlink-manager.js") { - $SourcePath = "$ScriptDir\bin\utils\claude-symlink-manager.js" - } - - if ($SourcePath) { - Copy-Item $SourcePath "$UtilsDir\claude-symlink-manager.js" -Force - } - } else { - # Standalone install - download from GitHub - try { - Invoke-WebRequest -Uri "https://raw.githubusercontent.com/kaitranntt/ccs/main/bin/utils/claude-symlink-manager.js" ` - -OutFile "$UtilsDir\claude-symlink-manager.js" -UseBasicParsing - } catch { - Write-Host "[!] Failed to download claude-symlink-manager.js" - } - } - - # Call ClaudeSymlinkManager if available - if (Test-Path "$UtilsDir\claude-symlink-manager.js") { - try { - $scriptBlock = @" - try { - const ClaudeSymlinkManager = require('$($UtilsDir -replace '\\', '/')/claude-symlink-manager.js'); - const manager = new ClaudeSymlinkManager(); - manager.install(); - } catch (err) { - console.log('[!] CCS item installation warning: ' + err.message); - console.log(' Run "ccs sync" to retry'); - } -"@ - node -e $scriptBlock 2>$null - if (-not $?) { - Write-Host "[!] CCS item installation skipped (run 'ccs sync' later)" - } - } catch { - Write-Host "[!] CCS item installation failed: $($_.Exception.Message)" - Write-Host " Run 'ccs sync' after installation to complete setup" - } - } else { - Write-Host "[!] claude-symlink-manager.js not found, skipping" - Write-Host " Run 'ccs sync' after installation to complete setup" - } - } else { - Write-Host "[!] .claude/ folder not found, skipping CCS item installation" - } -} else { - Write-Host "[!] Node.js not found, skipping CCS item installation" - Write-Host " Install Node.js and run 'ccs sync' to complete setup" -} -Write-Host "" -Write-Host "[i] Note: Windows symlink support requires Developer Mode (v4.2 will add fallback)" -Write-Host "" - -# Check and update PATH -$UserPath = [Environment]::GetEnvironmentVariable("Path", [System.EnvironmentVariableTarget]::User) -if ($UserPath -notlike "*$CcsDir*") { - Write-Host "[!] PATH Configuration Required" - Write-Host "" - Write-Host " Adding $CcsDir to your PATH..." - - try { - $NewPath = if ($UserPath) { "$UserPath;$CcsDir" } else { $CcsDir } - [Environment]::SetEnvironmentVariable("Path", $NewPath, [System.EnvironmentVariableTarget]::User) - - Write-Host " [OK] PATH updated. Restart your terminal for changes to take effect." - Write-Host "" - } catch { - Write-Host " [X] Could not update PATH automatically." -ForegroundColor Yellow - Write-Host " Please add manually: $CcsDir" - Write-Host "" - } -} - -# Show API key warning if needed -if ($NeedsGlmKey) { - Write-Critical @" -Configure GLM API Key: - - 1. Get API key from: https://api.z.ai - - 2. Edit: $env:USERPROFILE\.ccs\glm.settings.json - - 3. Replace: YOUR_GLM_API_KEY_HERE - With your actual API key - - 4. Test: ccs glm --version -"@ -} - -# Show API key warning for Kimi if needed -if ($NeedsKimiKey) { - Write-Critical @" -Configure Kimi API Key: - - 1. Get API key from: https://www.kimi.com/coding - - 2. Edit: $env:USERPROFILE\.ccs\kimi.settings.json - - 3. Replace: YOUR_KIMI_API_KEY_HERE - With your actual API key - - 4. Test: ccs kimi --version -"@ -} - -Write-Success "CCS installed successfully!" -Write-Host "" -Write-Host " Installed components:" -Write-Host " * ccs command -> $CcsDir\ccs.ps1" -Write-Host " * config -> $CcsDir\config.json" -Write-Host " * glm profile -> $CcsDir\glm.settings.json" -Write-Host " * kimi profile -> $CcsDir\kimi.settings.json" -Write-Host " * .claude/ folder -> $CcsDir\.claude\" -Write-Host "" -Write-Host " Requirements:" -$nodeVer = if (Get-Command node -ErrorAction SilentlyContinue) { node -v } else { "NOT FOUND" } -Write-Host " * Node.js 14+ (detected: $nodeVer)" -Write-Host " * npm 5.2+ (for npx, comes with Node.js 8.2+)" -Write-Host "" -Write-Host " First Run:" -Write-Host " The first time you run 'ccs', it will automatically install" -Write-Host " the @kaitranntt/ccs npm package globally via npx." -Write-Host "" -Write-Host " Quick start:" -Write-Host " ccs # Use Claude subscription (default)" -Write-Host " ccs glm # Use GLM fallback" -Write-Host " ccs kimi # Use Kimi for Coding" -Write-Host "" -Write-Host " To uninstall: ccs-uninstall" -Write-Host "" diff --git a/installers/install.sh b/installers/install.sh deleted file mode 100755 index 0b56b411..00000000 --- a/installers/install.sh +++ /dev/null @@ -1,927 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# ============================================================================ -# CCS Installation Script (v4.5.0) - DEPRECATED -# DEPRECATED: This installer is deprecated. Use npm instead. -# Bootstrap-based: Installs lightweight shell wrappers (LEGACY) -# Requires: Node.js 14+ (npm recommended) -# ============================================================================ - -# --- Deprecation Notice --- -echo "" -echo "=======================================================================" -echo " " -echo " [!] DEPRECATION NOTICE " -echo " " -echo " Native shell installers are deprecated and will be removed " -echo " in a future version. Please use npm installation instead: " -echo " " -echo " npm install -g @kaitranntt/ccs " -echo " " -echo " Proceeding with legacy install (auto-runs npm if available)... " -echo " " -echo "=======================================================================" -echo "" -sleep 3 # Give users time to read - -# --- Auto-redirect to npm installation --- -if command -v npm &> /dev/null; then - echo "[i] Node.js detected, using npm installation (recommended)..." - echo "" - npm install -g @kaitranntt/ccs - exit_code=$? - - if [ $exit_code -eq 0 ]; then - echo "" - echo "[OK] CCS installed via npm successfully!" - echo "" - echo "Quick start:" - echo " ccs # Use Claude (default)" - echo " ccs glm # Use GLM" - echo " ccs --help # Show all commands" - echo "" - exit 0 - else - echo "" - echo "[!] npm installation failed. Falling back to legacy install..." - echo "" - sleep 2 - fi -else - echo "[!] npm not found. Falling back to legacy install..." - echo "[!] Install Node.js from https://nodejs.org for the recommended method." - echo "" - sleep 2 -fi - -# Continue with legacy bash installation... - -# --- Configuration --- -INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}" -CCS_DIR="$HOME/.ccs" -CLAUDE_DIR="$HOME/.claude" -GLM_MODEL="glm-4.6" -KIMI_MODEL="kimi-k2-thinking-turbo" - -# Resolve script directory (handles both file-based and piped execution) -if [[ -n "${BASH_SOURCE[0]:-}" ]]; then - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -else - SCRIPT_DIR="$(cd "$(dirname "${0:-$PWD}")" && pwd)" -fi - -# Detect installation method (git vs standalone) -# Check if ccs executable exists in SCRIPT_DIR or parent (real git install) -# Don't just check .git (user might run curl | bash inside their own git repo) -if [[ -f "$SCRIPT_DIR/lib/ccs" ]] || [[ -f "$SCRIPT_DIR/../lib/ccs" ]]; then - INSTALL_METHOD="git" -else - INSTALL_METHOD="standalone" -fi - -# Version configuration -# 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="6.5.0" - -# Try to read VERSION file for git installations -if [[ -f "$SCRIPT_DIR/VERSION" ]]; then - CCS_VERSION="$(cat "$SCRIPT_DIR/VERSION" | tr -d '\n' | tr -d '\r')" -elif [[ -f "$SCRIPT_DIR/../VERSION" ]]; then - CCS_VERSION="$(cat "$SCRIPT_DIR/../VERSION" | tr -d '\n' | tr -d '\r')" -fi - -# --- Platform Detection --- -# Detect platform and redirect to Windows installer if needed -detect_platform() { - case "$OSTYPE" in - msys*|mingw*|cygwin*|win32*) - echo "windows" - ;; - *) - echo "unix" - ;; - esac -} - -PLATFORM=$(detect_platform) - -if [[ "$PLATFORM" == "windows" ]]; then - echo "Windows detected. Using PowerShell installer..." - - if [[ -f "$SCRIPT_DIR/install.ps1" ]]; then - powershell.exe -ExecutionPolicy Bypass -File "$SCRIPT_DIR/install.ps1" - exit $? - else - echo "Error: install.ps1 not found." - echo "Please download the full CCS package from:" - echo " https://github.com/kaitranntt/ccs" - exit 1 - fi -fi - -# Continue with Unix installation... - -# --- Helper Functions --- - -detect_current_provider() { - local settings="$CLAUDE_DIR/settings.json" - if [[ ! -f "$settings" ]]; then - echo "unknown" - return - fi - - if grep -q "api.kimi.com\|kimi-for-coding" "$settings" 2>/dev/null; then - echo "kimi" - elif grep -q "api.z.ai\|glm-4" "$settings" 2>/dev/null; then - echo "glm" - elif grep -q "ANTHROPIC_BASE_URL" "$settings" 2>/dev/null && ! grep -q "api.z.ai\|api.kimi.com" "$settings" 2>/dev/null; then - echo "custom" - else - echo "claude" - fi -} - -# --- Color/Format Functions (ANSI) --- -setup_colors() { - if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then - RED='\033[0;31m' - GREEN='\033[0;32m' - YELLOW='\033[1;33m' - CYAN='\033[0;36m' - BOLD='\033[1m' - RESET='\033[0m' - else - RED='' GREEN='' YELLOW='' CYAN='' BOLD='' RESET='' - fi -} - -msg_critical() { - echo "" >&2 - echo -e "${RED}${BOLD}╔═════════════════════════════════════════════╗${RESET}" >&2 - echo -e "${RED}${BOLD}║ ACTION REQUIRED ║${RESET}" >&2 - echo -e "${RED}${BOLD}╚═════════════════════════════════════════════╝${RESET}" >&2 - echo "" >&2 - echo -e "${RED}$1${RESET}" >&2 - echo "" >&2 -} - -msg_warning() { - echo "" >&2 - echo -e "${YELLOW}${BOLD}[!] WARNING${RESET}" >&2 - echo -e "${YELLOW}$1${RESET}" >&2 - echo "" >&2 -} - -msg_success() { - echo -e "${GREEN}[OK] $1${RESET}" -} - -msg_info() { - echo -e "[i] $1" -} - -msg_section() { - echo "" - echo -e "${BOLD}===== $1 =====${RESET}" - echo "" -} - -setup_colors - -# --- Node.js Detection (v4.5) --- -check_nodejs() { - if ! command -v node &> /dev/null; then - msg_warning "Node.js not found - - CCS v4.5+ requires Node.js 14+ to run. - The bootstrap scripts will check and install the npm package on first use. - - Install Node.js: https://nodejs.org (LTS recommended) - - Installation will continue, but 'ccs' will not work until Node.js is installed." - return 1 - fi - - local node_major - node_major=$(node -v | cut -d'v' -f2 | cut -d'.' -f1) - if [[ $node_major -lt 14 ]]; then - msg_warning "Node.js 14+ required (found: $(node -v)) - - CCS v4.5+ requires Node.js 14 or newer. - Upgrade from: https://nodejs.org - - Installation will continue, but 'ccs' may not work correctly." - return 1 - fi - - msg_success "Node.js $(node -v) detected" - return 0 -} - -# --- Shell Profile Management --- - -detect_shell_profile() { - # Safe extraction of shell name (no command substitution) - local shell_path="${SHELL:-/bin/bash}" - local shell_name="${shell_path##*/}" - - # Validate shell_name is alphanumeric (defense in depth) - if [[ ! "$shell_name" =~ ^[a-zA-Z0-9_-]+$ ]]; then - shell_name="bash" - fi - - case "$shell_name" in - zsh) - echo "$HOME/.zshrc" - ;; - bash) - if [[ "$OSTYPE" == darwin* ]]; then - # macOS prefers bash_profile - [[ -f "$HOME/.bash_profile" ]] && echo "$HOME/.bash_profile" || echo "$HOME/.bashrc" - else - echo "$HOME/.bashrc" - fi - ;; - fish) - echo "$HOME/.config/fish/config.fish" - ;; - *) - # Default to bashrc - echo "$HOME/.bashrc" - ;; - esac -} - -check_path_configured() { - [[ ":$PATH:" == *":$HOME/.local/bin:"* ]] -} - -add_to_path() { - local profile_file="$1" - local dir_to_add="$HOME/.local/bin" - - # Create profile file if doesn't exist - if [[ ! -f "$profile_file" ]]; then - local profile_dir="$(dirname "$profile_file")" - - if ! mkdir -p "$profile_dir" 2>/dev/null; then - echo "[!] Failed to create directory: $profile_dir" >&2 - return 1 - fi - - if ! touch "$profile_file" 2>/dev/null; then - echo "[!] Failed to create profile file: $profile_file" >&2 - return 1 - fi - fi - - # Check if already in profile (avoid duplicates) - if grep -q "# CCS: Added by Claude Code Switch installer" "$profile_file" 2>/dev/null; then - return 0 # Already added - fi - - # Check for fish shell (different syntax) - if [[ "$profile_file" == *"config.fish" ]]; then - cat >> "$profile_file" << 'EOF' - -# CCS: Added by Claude Code Switch installer -set -gx PATH $HOME/.local/bin $PATH -EOF - else - # Bash/Zsh syntax - cat >> "$profile_file" << 'EOF' - -# CCS: Added by Claude Code Switch installer -export PATH="$HOME/.local/bin:$PATH" -EOF - fi - - return 0 -} - -configure_shell_path() { - if check_path_configured; then - msg_info "PATH already configured for ~/.local/bin" - return 0 - fi - - local profile_file=$(detect_shell_profile) - - echo "" - msg_section "Configuring Shell PATH" - msg_info "Detected shell profile: $profile_file" - - if add_to_path "$profile_file"; then - msg_success "Added ~/.local/bin to PATH in $profile_file" - echo "" - - # Show reload instructions - msg_critical "Reload your shell to use 'ccs' command: - - Option 1 (current session): - source $profile_file - - Option 2 (new session): - Open a new terminal window - - Then verify: - ccs --version" - - return 0 - else - msg_warning "Could not auto-configure PATH - - Manually add this line to $profile_file: - export PATH=\"\$HOME/.local/bin:\$PATH\" - - Then reload: - source $profile_file" - return 1 - fi -} - -create_glm_template() { - cat << EOF -{ - "env": { - "ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic", - "ANTHROPIC_AUTH_TOKEN": "YOUR_GLM_API_KEY_HERE", - "ANTHROPIC_MODEL": "$GLM_MODEL", - "ANTHROPIC_DEFAULT_OPUS_MODEL": "$GLM_MODEL", - "ANTHROPIC_DEFAULT_SONNET_MODEL": "$GLM_MODEL", - "ANTHROPIC_DEFAULT_HAIKU_MODEL": "$GLM_MODEL" - } -} -EOF -} - -create_kimi_template() { - cat << EOF -{ - "env": { - "ANTHROPIC_BASE_URL": "https://api.kimi.com/coding/", - "ANTHROPIC_AUTH_TOKEN": "YOUR_KIMI_API_KEY_HERE", - "ANTHROPIC_MODEL": "$KIMI_MODEL", - "ANTHROPIC_SMALL_FAST_MODEL": "$KIMI_MODEL", - "ANTHROPIC_DEFAULT_OPUS_MODEL": "$KIMI_MODEL", - "ANTHROPIC_DEFAULT_SONNET_MODEL": "$KIMI_MODEL", - "ANTHROPIC_DEFAULT_HAIKU_MODEL": "$KIMI_MODEL" - }, - "alwaysThinkingEnabled": true -} -EOF -} - -atomic_mv() { - local src="$1" - local dest="$2" - if mv "$src" "$dest" 2>/dev/null; then - return 0 - else - rm -f "$src" - echo " [X] Error: Failed to create $dest (check permissions)" - exit 1 - fi -} - -download_file() { - local url="$1" - local dest="$2" - - if ! curl -fsSL "$url" -o "$dest"; then - echo " [!] Failed to download: $(basename "$dest")" - return 1 - fi - return 0 -} - -install_claude_folder() { - local source_dir="$1" - local target_dir="$CCS_DIR/.claude" - - # Check if already exists - if [[ -d "$target_dir" ]]; then - echo "| [i] .claude/ folder already exists, skipping" - return 0 - fi - - mkdir -p "$target_dir/commands" "$target_dir/skills/ccs-delegation/references" - - if [[ "$INSTALL_METHOD" == "git" ]]; then - # Copy from local git repo - if [[ -d "$source_dir/.claude" ]]; then - cp -r "$source_dir/.claude"/* "$target_dir/" 2>/dev/null || { - echo "| [!] Failed to copy .claude/ folder" - return 1 - } - echo "| [OK] Installed .claude/ folder" - else - echo "| [!] .claude/ folder not found in source" - return 1 - fi - else - # Standalone: download from GitHub - local base_url="https://raw.githubusercontent.com/kaitranntt/ccs/main/.claude" - - download_file "$base_url/commands/ccs.md" "$target_dir/commands/ccs.md" || return 1 - download_file "$base_url/skills/ccs-delegation/SKILL.md" "$target_dir/skills/ccs-delegation/SKILL.md" || return 1 - download_file "$base_url/skills/ccs-delegation/references/delegation-patterns.md" "$target_dir/skills/ccs-delegation/references/delegation-patterns.md" || return 1 - - echo "| [OK] Downloaded .claude/ folder" - fi - - return 0 -} - -create_glm_profile() { - local current_settings="$CLAUDE_DIR/settings.json" - local glm_settings="$CCS_DIR/glm.settings.json" - local provider="$1" - - if [[ "$provider" == "glm" ]]; then - echo "[OK] Copying current GLM config to profile..." - if command -v jq &> /dev/null; then - if jq '.env |= (. // {}) + { - "ANTHROPIC_DEFAULT_OPUS_MODEL": "'"$GLM_MODEL"'", - "ANTHROPIC_DEFAULT_SONNET_MODEL": "'"$GLM_MODEL"'", - "ANTHROPIC_DEFAULT_HAIKU_MODEL": "'"$GLM_MODEL"'" - }' "$current_settings" > "$glm_settings.tmp" 2>/dev/null; then - atomic_mv "$glm_settings.tmp" "$glm_settings" - echo " Created: $glm_settings (with your existing API key + enhanced settings)" - else - rm -f "$glm_settings.tmp" - cp "$current_settings" "$glm_settings" - echo " Created: $glm_settings (copied as-is, jq enhancement failed)" - fi - else - cp "$current_settings" "$glm_settings" - echo " Created: $glm_settings (copied as-is, jq not available)" - fi - else - echo "Creating GLM profile template at $glm_settings" - if [[ -f "$current_settings" ]] && command -v jq &> /dev/null; then - if jq '.env |= (. // {}) + { - "ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic", - "ANTHROPIC_AUTH_TOKEN": "YOUR_GLM_API_KEY_HERE", - "ANTHROPIC_MODEL": "'"$GLM_MODEL"'", - "ANTHROPIC_DEFAULT_OPUS_MODEL": "'"$GLM_MODEL"'", - "ANTHROPIC_DEFAULT_SONNET_MODEL": "'"$GLM_MODEL"'", - "ANTHROPIC_DEFAULT_HAIKU_MODEL": "'"$GLM_MODEL"'" - }' "$current_settings" > "$glm_settings.tmp" 2>/dev/null; then - atomic_mv "$glm_settings.tmp" "$glm_settings" - else - rm -f "$glm_settings.tmp" - echo " [i] jq failed, using basic template" - create_glm_template > "$glm_settings" - fi - else - create_glm_template > "$glm_settings" - fi - echo " Created: $glm_settings" - echo " [!] Edit this file and replace YOUR_GLM_API_KEY_HERE with your actual GLM API key" - fi -} - -create_kimi_profile() { - local current_settings="$CLAUDE_DIR/settings.json" - local kimi_settings="$CCS_DIR/kimi.settings.json" - local provider="$1" - - if [[ "$provider" == "kimi" ]]; then - echo "[OK] Copying current Kimi config to profile..." - if command -v jq &> /dev/null; then - if jq '.env |= (. // {}) + { - "ANTHROPIC_SMALL_FAST_MODEL": "'"$KIMI_MODEL"'", - "ANTHROPIC_DEFAULT_OPUS_MODEL": "'"$KIMI_MODEL"'", - "ANTHROPIC_DEFAULT_SONNET_MODEL": "'"$KIMI_MODEL"'", - "ANTHROPIC_DEFAULT_HAIKU_MODEL": "'"$KIMI_MODEL"'" - }' "$current_settings" > "$kimi_settings.tmp" 2>/dev/null; then - atomic_mv "$kimi_settings.tmp" "$kimi_settings" - echo " Created: $kimi_settings (with your existing API key + enhanced settings)" - else - rm -f "$kimi_settings.tmp" - cp "$current_settings" "$kimi_settings" - echo " Created: $kimi_settings (copied as-is, jq enhancement failed)" - fi - else - cp "$current_settings" "$kimi_settings" - echo " Created: $kimi_settings (copied as-is, jq not available)" - fi - else - echo "Creating Kimi profile template at $kimi_settings" - if [[ -f "$current_settings" ]] && command -v jq &> /dev/null; then - if jq '.env |= (. // {}) + { - "ANTHROPIC_BASE_URL": "https://api.kimi.com/coding/", - "ANTHROPIC_AUTH_TOKEN": "YOUR_KIMI_API_KEY_HERE", - "ANTHROPIC_MODEL": "'"$KIMI_MODEL"'", - "ANTHROPIC_SMALL_FAST_MODEL": "'"$KIMI_MODEL"'", - "ANTHROPIC_DEFAULT_OPUS_MODEL": "'"$KIMI_MODEL"'", - "ANTHROPIC_DEFAULT_SONNET_MODEL": "'"$KIMI_MODEL"'", - "ANTHROPIC_DEFAULT_HAIKU_MODEL": "'"$KIMI_MODEL"'" - } | . + {"alwaysThinkingEnabled": true}' "$current_settings" > "$kimi_settings.tmp" 2>/dev/null; then - atomic_mv "$kimi_settings.tmp" "$kimi_settings" - else - rm -f "$kimi_settings.tmp" - echo " [i] jq failed, using basic template" - create_kimi_template > "$kimi_settings" - fi - else - create_kimi_template > "$kimi_settings" - fi - echo " Created: $kimi_settings" - echo " [!] Edit this file and replace YOUR_KIMI_API_KEY_HERE with your actual Kimi API key" - fi -} - -# --- Main Installation --- - -# Check Node.js requirement (warn if missing, continue anyway) -check_nodejs || true - -echo "┌─ Installing CCS" - -# Create directories -mkdir -p "$INSTALL_DIR" "$CCS_DIR" - -# Install main executable -if [[ "$INSTALL_METHOD" == "standalone" ]]; then - # Standalone install - download ccs from GitHub - if ! command -v curl &> /dev/null; then - echo "[X] Error: curl is required for standalone installation" - exit 1 - fi - - BASE_URL="https://raw.githubusercontent.com/kaitranntt/ccs/main" - - # Download main executable - if curl -fsSL "$BASE_URL/lib/ccs" -o "$CCS_DIR/ccs"; then - chmod +x "$CCS_DIR/ccs" - ln -sf "$CCS_DIR/ccs" "$INSTALL_DIR/ccs" - echo "| [OK] Downloaded executable" - else - echo "|" - echo "[X] Error: Failed to download ccs from GitHub" - exit 1 - fi - - # Note: Shell dependencies (error-codes.sh, progress-indicator.sh, prompt.sh) no longer needed - # Bootstrap delegates all functionality to Node.js via npx - - # Download shell completion files - mkdir -p "$CCS_DIR/completions" - if curl -fsSL "$BASE_URL/scripts/completion/ccs.bash" -o "$CCS_DIR/completions/ccs.bash" 2>/dev/null; then - echo "| [OK] Downloaded completion files" - fi - curl -fsSL "$BASE_URL/scripts/completion/ccs.zsh" -o "$CCS_DIR/completions/ccs.zsh" 2>/dev/null || true - curl -fsSL "$BASE_URL/scripts/completion/ccs.fish" -o "$CCS_DIR/completions/ccs.fish" 2>/dev/null || true -else - # Git install - use local ccs file - # Handle both running from root or from installers/ subdirectory - local LIB_DIR="" - if [[ -f "$SCRIPT_DIR/lib/ccs" ]]; then - chmod +x "$SCRIPT_DIR/lib/ccs" - ln -sf "$SCRIPT_DIR/lib/ccs" "$INSTALL_DIR/ccs" - LIB_DIR="$SCRIPT_DIR/lib" - elif [[ -f "$SCRIPT_DIR/../lib/ccs" ]]; then - chmod +x "$SCRIPT_DIR/../lib/ccs" - ln -sf "$SCRIPT_DIR/../lib/ccs" "$INSTALL_DIR/ccs" - LIB_DIR="$SCRIPT_DIR/../lib" - else - echo "|" - echo "[X] Error: lib/ccs executable not found" - exit 1 - fi - echo "| [OK] Installed executable" - - # Note: Shell dependencies (error-codes.sh, progress-indicator.sh, prompt.sh) no longer needed - # Bootstrap delegates all functionality to Node.js via npx - - # Copy shell completion files - mkdir -p "$CCS_DIR/completions" - local COMPLETION_DIR="" - if [[ -d "$SCRIPT_DIR/scripts/completion" ]]; then - COMPLETION_DIR="$SCRIPT_DIR/scripts/completion" - elif [[ -d "$SCRIPT_DIR/../scripts/completion" ]]; then - COMPLETION_DIR="$SCRIPT_DIR/../scripts/completion" - fi - - if [[ -n "$COMPLETION_DIR" ]]; then - cp "$COMPLETION_DIR/ccs.bash" "$CCS_DIR/completions/ccs.bash" 2>/dev/null || true - cp "$COMPLETION_DIR/ccs.zsh" "$CCS_DIR/completions/ccs.zsh" 2>/dev/null || true - cp "$COMPLETION_DIR/ccs.fish" "$CCS_DIR/completions/ccs.fish" 2>/dev/null || true - echo "| [OK] Copied completion files" - fi -fi - -if [[ ! -L "$INSTALL_DIR/ccs" ]]; then - echo "|" - echo "[X] Error: Failed to create symlink at $INSTALL_DIR/ccs" - echo " Check directory permissions and try again." - exit 1 -fi - -# Install uninstall script (with idempotency check) -if [[ -f "$SCRIPT_DIR/uninstall.sh" ]]; then - # Only copy if source and destination are different - if [[ "$SCRIPT_DIR/uninstall.sh" != "$CCS_DIR/uninstall.sh" ]]; then - cp "$SCRIPT_DIR/uninstall.sh" "$CCS_DIR/uninstall.sh" - fi - chmod +x "$CCS_DIR/uninstall.sh" - ln -sf "$CCS_DIR/uninstall.sh" "$INSTALL_DIR/ccs-uninstall" - echo "| [OK] Installed uninstaller" -elif [[ "$INSTALL_METHOD" == "standalone" ]] && command -v curl &> /dev/null; then - if curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/installers/uninstall.sh -o "$CCS_DIR/uninstall.sh"; then - chmod +x "$CCS_DIR/uninstall.sh" - ln -sf "$CCS_DIR/uninstall.sh" "$INSTALL_DIR/ccs-uninstall" - echo "| [OK] Installed uninstaller" - fi -fi - -echo "| [OK] Created directories" - -# Install .claude/ folder -if [[ "$INSTALL_METHOD" == "git" ]]; then - install_claude_folder "$SCRIPT_DIR/.." || echo "| [!] Optional .claude/ installation skipped" -else - install_claude_folder "" || echo "| [!] Optional .claude/ installation skipped" -fi - -echo "└─" -echo "" - -# --- Profile Setup --- - -CURRENT_PROVIDER=$(detect_current_provider) -GLM_SETTINGS="$CCS_DIR/glm.settings.json" -KIMI_SETTINGS="$CCS_DIR/kimi.settings.json" - -# Build provider label -PROVIDER_LABEL="" -[[ "$CURRENT_PROVIDER" == "glm" ]] && PROVIDER_LABEL=" (detected: GLM)" -[[ "$CURRENT_PROVIDER" == "kimi" ]] && PROVIDER_LABEL=" (detected: Kimi)" -[[ "$CURRENT_PROVIDER" == "claude" ]] && PROVIDER_LABEL=" (detected: Claude)" -[[ "$CURRENT_PROVIDER" == "custom" ]] && PROVIDER_LABEL=" (detected: custom)" - -echo "┌─ Configuring Profiles (v${CCS_VERSION})${PROVIDER_LABEL}" - -# Backup existing config if present (single backup, no timestamp) -BACKUP_FILE="$CCS_DIR/config.json.backup" -if [[ -f "$CCS_DIR/config.json" ]]; then - cp "$CCS_DIR/config.json" "$BACKUP_FILE" -fi - -# Track if GLM needs API key -NEEDS_GLM_KEY=false - -# Create GLM profile if missing -if [[ ! -f "$GLM_SETTINGS" ]]; then - create_glm_profile "$CURRENT_PROVIDER" >/dev/null 2>&1 - echo "| [OK] GLM profile -> ~/.ccs/glm.settings.json" - [[ "$CURRENT_PROVIDER" != "glm" ]] && NEEDS_GLM_KEY=true -fi - -# Track if Kimi needs API key -NEEDS_KIMI_KEY=false - -# Create Kimi profile if missing -if [[ ! -f "$KIMI_SETTINGS" ]]; then - create_kimi_profile "$CURRENT_PROVIDER" >/dev/null 2>&1 - echo "| [OK] Kimi profile -> ~/.ccs/kimi.settings.json" - [[ "$CURRENT_PROVIDER" != "kimi" ]] && NEEDS_KIMI_KEY=true -fi - -# Create config if missing -if [[ ! -f "$CCS_DIR/config.json" ]]; then - cat > "$CCS_DIR/config.json.tmp" << 'EOF' -{ - "profiles": { - "glm": "~/.ccs/glm.settings.json", - "kimi": "~/.ccs/kimi.settings.json", - "default": "~/.claude/settings.json" - } -} -EOF - atomic_mv "$CCS_DIR/config.json.tmp" "$CCS_DIR/config.json" - echo "| [OK] Config -> ~/.ccs/config.json" -fi - -# Validate config JSON -if [[ -f "$CCS_DIR/config.json" ]]; then - if command -v jq &> /dev/null; then - if ! jq -e . "$CCS_DIR/config.json" &>/dev/null; then - echo "| [!] Warning: Invalid JSON in config.json" - if [[ -f "$BACKUP_FILE" ]]; then - echo "| Restore from: $BACKUP_FILE" - fi - fi - fi -fi - -# Validate GLM settings JSON -if [[ -f "$GLM_SETTINGS" ]]; then - if command -v jq &> /dev/null; then - if ! jq -e . "$GLM_SETTINGS" &>/dev/null; then - echo "| [!] Warning: Invalid JSON in glm.settings.json" - fi - fi -fi - -echo "└─" -echo "" - -# Detect circular symlink -detect_circular_symlink() { - local target="$1" - local link_path="$2" - - # Check if target exists and is symlink - if [[ ! -L "$target" ]]; then - return 1 # Not circular - fi - - # Resolve target's link - local target_link=$(readlink "$target" 2>/dev/null || echo "") - local shared_dir="$HOME/.ccs/shared" - - # Check if target points back to our shared dir - if [[ "$target_link" == "$shared_dir"* ]] || [[ "$target_link" == "$link_path" ]]; then - echo "[!] Circular symlink detected: $target → $target_link" - return 0 # Circular - fi - - return 1 # Not circular -} - -# Setup shared directories as symlinks to ~/.claude/ (v3.2.0) -setup_shared_symlinks() { - local shared_dir="$CCS_DIR/shared" - local claude_dir="$HOME/.claude" - - # Create ~/.claude/ if missing - if [[ ! -d "$claude_dir" ]]; then - echo "[i] Creating ~/.claude/ directory structure" - mkdir -p "$claude_dir"/{commands,skills,agents} - fi - - # Create shared directory - mkdir -p "$shared_dir" - - # Create symlinks ~/.ccs/shared/* → ~/.claude/* - for dir in commands skills agents; do - local claude_path="$claude_dir/$dir" - local shared_path="$shared_dir/$dir" - - # Create directory in ~/.claude/ if missing - if [[ ! -d "$claude_path" ]]; then - mkdir -p "$claude_path" - fi - - # Check for circular symlink - if detect_circular_symlink "$claude_path" "$shared_path"; then - echo "[!] Skipping $dir: circular symlink detected" - continue - fi - - # If already correct symlink, skip - if [[ -L "$shared_path" ]]; then - local current_target=$(readlink "$shared_path" 2>/dev/null || echo "") - if [[ "$current_target" == "$claude_path" ]]; then - continue # Already correct - fi - rm -rf "$shared_path" - elif [[ -e "$shared_path" ]]; then - # Backup existing data before replacing - if [[ -d "$shared_path" ]] && [[ -n "$(ls -A "$shared_path" 2>/dev/null)" ]]; then - echo "[i] Migrating existing $dir to ~/.claude/$dir" - # Copy to claude dir (preserve user modifications) - for item in "$shared_path"/*; do - [[ -e "$item" ]] || continue - local basename=$(basename "$item") - if [[ ! -e "$claude_path/$basename" ]]; then - cp -r "$item" "$claude_path/" 2>/dev/null - fi - done - fi - rm -rf "$shared_path" - fi - - # Create symlink - ln -s "$claude_path" "$shared_path" 2>/dev/null || { - echo "[!] Failed to create symlink for $dir, copying instead" - mkdir -p "$shared_path" - if [[ -d "$claude_path" ]]; then - cp -r "$claude_path"/* "$shared_path/" 2>/dev/null || true - fi - } - done -} - -echo "[i] Setting up shared directories..." -setup_shared_symlinks -echo "" - -# Install CCS items to ~/.claude/ via symlinks (v4.1.0) -echo "[i] Installing CCS items to ~/.claude/..." -if command -v node &> /dev/null; then - # Check if .claude/ was successfully installed - if [[ -d "$CCS_DIR/.claude" ]]; then - # Download or copy claude-symlink-manager.js - mkdir -p "$CCS_DIR/bin/utils" - - if [[ "$INSTALL_METHOD" == "git" ]]; then - # Git install - copy from local repo - if [[ -f "$SCRIPT_DIR/../bin/utils/claude-symlink-manager.js" ]]; then - cp "$SCRIPT_DIR/../bin/utils/claude-symlink-manager.js" "$CCS_DIR/bin/utils/claude-symlink-manager.js" - elif [[ -f "$SCRIPT_DIR/bin/utils/claude-symlink-manager.js" ]]; then - cp "$SCRIPT_DIR/bin/utils/claude-symlink-manager.js" "$CCS_DIR/bin/utils/claude-symlink-manager.js" - fi - else - # Standalone install - download from GitHub - if ! curl -fsSL "https://raw.githubusercontent.com/kaitranntt/ccs/main/bin/utils/claude-symlink-manager.js" -o "$CCS_DIR/bin/utils/claude-symlink-manager.js" 2>/dev/null; then - echo "[!] Failed to download claude-symlink-manager.js" - fi - fi - - # Call ClaudeSymlinkManager if available - if [[ -f "$CCS_DIR/bin/utils/claude-symlink-manager.js" ]]; then - node -e " - try { - const ClaudeSymlinkManager = require('$CCS_DIR/bin/utils/claude-symlink-manager.js'); - const manager = new ClaudeSymlinkManager(); - manager.install(); - } catch (err) { - console.log('[!] CCS item installation warning: ' + err.message); - console.log(' Run \"ccs sync\" to retry'); - } - " 2>/dev/null || echo "[!] CCS item installation skipped (run 'ccs sync' later)" - else - echo "[!] claude-symlink-manager.js not found, skipping" - echo " Run 'ccs sync' after installation to complete setup" - fi - else - echo "[!] .claude/ folder not found, skipping CCS item installation" - fi -else - echo "[!] Node.js not found, skipping CCS item installation" - echo " Install Node.js and run 'ccs sync' to complete setup" -fi -echo "" - -# Auto-configure PATH if needed (all Unix platforms) -configure_shell_path - -# Show API key warning if needed -if [[ "$NEEDS_GLM_KEY" == "true" ]]; then - msg_critical "Configure GLM API Key: - - 1. Get API key from: https://api.z.ai - - 2. Edit: ~/.ccs/glm.settings.json - - 3. Replace: YOUR_GLM_API_KEY_HERE - With your actual API key - - 4. Test: ccs glm --version" -fi - -# Show API key warning for Kimi if needed -if [[ "$NEEDS_KIMI_KEY" == "true" ]]; then - msg_critical "Configure Kimi API Key: - - 1. Get API key from: https://www.kimi.com/coding - - 2. Edit: ~/.ccs/kimi.settings.json - - 3. Replace: YOUR_KIMI_API_KEY_HERE - With your actual API key - - 4. Test: ccs kimi --version" -fi - -msg_success "CCS installed successfully!" -echo "" -echo " Installed components:" -echo " * ccs command -> ~/.local/bin/ccs" -echo " * config -> ~/.ccs/config.json" -echo " * glm profile -> ~/.ccs/glm.settings.json" -echo " * kimi profile -> ~/.ccs/kimi.settings.json" -echo " * .claude/ folder -> ~/.ccs/.claude/" -echo "" -echo " Requirements:" -echo " * Node.js 14+ (detected: $(node -v 2>/dev/null || echo 'NOT FOUND'))" -echo " * npm 5.2+ (for npx, comes with Node.js 8.2+)" -echo "" -echo " First Run:" -echo " The first time you run 'ccs', it will automatically install" -echo " the @kaitranntt/ccs npm package globally via npx." -echo "" -echo " Quick start:" -echo " ccs # Use Claude subscription (default)" -echo " ccs glm # Use GLM fallback" -echo " ccs kimi # Use Kimi for Coding" -echo "" -echo " To uninstall: ccs-uninstall" -echo "" diff --git a/installers/uninstall.ps1 b/installers/uninstall.ps1 deleted file mode 100644 index fca77726..00000000 --- a/installers/uninstall.ps1 +++ /dev/null @@ -1,99 +0,0 @@ -# CCS Uninstallation Script (Windows PowerShell) -# https://github.com/kaitranntt/ccs - -$ErrorActionPreference = "Stop" - -# --- Color/Format Functions --- -function Write-Success { - param([string]$Message) - Write-Host "[OK] $Message" -ForegroundColor Green -} - -function Write-Info { - param([string]$Message) - Write-Host "[i] $Message" -ForegroundColor Cyan -} - -# --- Selective Cleanup Function --- -function Invoke-SelectiveCleanup { - param([string]$CcsDir) - - $Removed = @() - $Kept = @() - - # Remove executables and version metadata - $FilesToRemove = @("ccs.ps1", "VERSION") - - # Also remove the uninstall script itself - $UninstallScript = $PSCommandPath - if ($UninstallScript -and (Test-Path $UninstallScript)) { - $FilesToRemove += $UninstallScript - } - - foreach ($File in $FilesToRemove) { - $FilePath = if ([System.IO.Path]::IsPathRooted($File)) { $File } else { Join-Path $CcsDir $File } - if (Test-Path $FilePath) { - Remove-Item $FilePath -Force - $Removed += Split-Path $FilePath -Leaf - } - } - - # Remove .claude folder - if (Test-Path "$CcsDir\.claude") { - Remove-Item "$CcsDir\.claude" -Recurse -Force - $Removed += ".claude/" - } - - # Track kept files - if (Test-Path "$CcsDir\config.json") { $Kept += "config.json" } - if (Test-Path "$CcsDir\config.json.backup") { $Kept += "config.json.backup" } - Get-ChildItem "$CcsDir\*.settings.json" -ErrorAction SilentlyContinue | ForEach-Object { - $Kept += $_.Name - } - - # Report results - if ($Removed.Count -gt 0) { - Write-Info "Cleaned up: $($Removed -join ', ')" - } - - if ($Kept.Count -gt 0) { - Write-Info "Kept config files: $($Kept -join ', ')" - } -} - -Write-Host "Uninstalling ccs..." -Write-Host "" - -$CcsDir = "$env:USERPROFILE\.ccs" - -# Remove from PATH -$UserPath = [Environment]::GetEnvironmentVariable("Path", [System.EnvironmentVariableTarget]::User) -if ($UserPath -like "*$CcsDir*") { - try { - $NewPath = ($UserPath -split ';' | Where-Object { $_ -ne $CcsDir }) -join ';' - [Environment]::SetEnvironmentVariable("Path", $NewPath, [System.EnvironmentVariableTarget]::User) - Write-Success "Removed from PATH: $CcsDir" - Write-Host " Restart your terminal for changes to take effect." - } catch { - Write-Host "[!] Could not remove from PATH automatically. Please remove manually: $CcsDir" -ForegroundColor Yellow - } -} - -# Ask about ~/.ccs directory -if (Test-Path $CcsDir) { - Write-Host "" - $Response = Read-Host "Remove CCS directory $CcsDir`? This includes config and profiles. (y/N)" - if ($Response -match '^[Yy]$') { - Remove-Item $CcsDir -Recurse -Force - Write-Success "Removed: $CcsDir" - } else { - Write-Host "" - Invoke-SelectiveCleanup -CcsDir $CcsDir - } -} else { - Write-Info "No CCS directory found at $CcsDir" -} - -Write-Host "" -Write-Success "Uninstall complete!" -Write-Host "" diff --git a/installers/uninstall.sh b/installers/uninstall.sh deleted file mode 100755 index e0a407d8..00000000 --- a/installers/uninstall.sh +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# --- Color/Format Functions --- -setup_colors() { - if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then - GREEN='\033[0;32m' - CYAN='\033[0;36m' - RESET='\033[0m' - else - GREEN='' CYAN='' RESET='' - fi -} - -msg_success() { - echo -e "${GREEN}[OK] $1${RESET}" -} - -msg_info() { - echo -e "${CYAN}[i] $1${RESET}" -} - -# --- Selective Cleanup Function --- -selective_cleanup() { - local ccs_dir="$1" - local removed=() - local kept=() - - # Remove executables, version metadata, and .claude folder - for file in "ccs" "uninstall.sh" "VERSION"; do - if [[ -f "$ccs_dir/$file" ]]; then - rm "$ccs_dir/$file" - removed+=("$file") - fi - done - - # Remove .claude folder - if [[ -d "$ccs_dir/.claude" ]]; then - rm -rf "$ccs_dir/.claude" - removed+=(".claude/") - fi - - # Track kept files - [[ -f "$ccs_dir/config.json" ]] && kept+=("config.json") - [[ -f "$ccs_dir/config.json.backup" ]] && kept+=("config.json.backup") - for settings in "$ccs_dir"/*.settings.json; do - [[ -f "$settings" ]] && kept+=("$(basename "$settings")") - done - - # Report results - if [[ ${#removed[@]} -gt 0 ]]; then - msg_info "Cleaned up: ${removed[*]}" - fi - - if [[ ${#kept[@]} -gt 0 ]]; then - msg_info "Kept config files: ${kept[*]}" - fi -} - -setup_colors - -echo "Uninstalling ccs..." -echo "" - -# Remove from ~/.local/bin (standard location) -if [[ -L "$HOME/.local/bin/ccs" ]]; then - rm "$HOME/.local/bin/ccs" - msg_success "Removed: $HOME/.local/bin/ccs" -elif [[ -f "$HOME/.local/bin/ccs" ]]; then - rm "$HOME/.local/bin/ccs" - msg_success "Removed: $HOME/.local/bin/ccs" -fi - -if [[ -L "$HOME/.local/bin/ccs-uninstall" ]]; then - rm "$HOME/.local/bin/ccs-uninstall" - msg_success "Removed: $HOME/.local/bin/ccs-uninstall" -fi - -# Ask about ~/.ccs directory -if [[ -d "$HOME/.ccs" ]]; then - read -p "Remove CCS directory ~/.ccs? This includes config and profiles. (y/N) " -n 1 -r - echo - if [[ $REPLY =~ ^[Yy]$ ]]; then - rm -rf "$HOME/.ccs" - msg_success "Removed: $HOME/.ccs" - else - echo "" - selective_cleanup "$HOME/.ccs" - fi -else - msg_info "No CCS directory found at $HOME/.ccs" -fi - -echo "" -msg_success "Uninstall complete!" diff --git a/scripts/worker.js b/scripts/worker.js index ca0d7fdb..2e74b4f1 100644 --- a/scripts/worker.js +++ b/scripts/worker.js @@ -1,46 +1,26 @@ +/** + * CCS CloudFlare Worker - Redirect to npm Installation + * + * Legacy shell installers are deprecated. This worker now redirects + * all /install* and /uninstall* requests to the npm installation docs. + */ export default { async fetch(request) { const url = new URL(request.url); + const docsUrl = 'https://docs.ccs.kaitran.ca/getting-started/installation'; - // Detect platform from User-Agent header - const userAgent = request.headers.get('user-agent') || ''; - const isWindows = userAgent.includes('Windows') || userAgent.includes('Win32'); - const isPowerShell = userAgent.includes('PowerShell') || userAgent.includes('pwsh'); - - // Smart routing with platform detection - let filePath; - if (url.pathname === '/install' || url.pathname === '/install.sh') { - filePath = (isWindows && isPowerShell) ? 'installers/install.ps1' : 'installers/install.sh'; - } else if (url.pathname === '/install.ps1') { - filePath = 'installers/install.ps1'; - } else if (url.pathname === '/uninstall' || url.pathname === '/uninstall.sh') { - filePath = (isWindows && isPowerShell) ? 'installers/uninstall.ps1' : 'installers/uninstall.sh'; - } else if (url.pathname === '/uninstall.ps1') { - filePath = 'installers/uninstall.ps1'; - } else { - return new Response('Not Found', { status: 404 }); + // Redirect all install/uninstall paths to npm installation docs + if ( + url.pathname === '/install' || + url.pathname === '/install.sh' || + url.pathname === '/install.ps1' || + url.pathname === '/uninstall' || + url.pathname === '/uninstall.sh' || + url.pathname === '/uninstall.ps1' + ) { + return Response.redirect(docsUrl, 301); } - try { - const githubUrl = `https://raw.githubusercontent.com/kaitranntt/ccs/main/${filePath}`; - const response = await fetch(githubUrl); - - if (!response.ok) { - return new Response('File not found on GitHub', { status: 404 }); - } - - const contentType = filePath.endsWith('.ps1') - ? 'text/plain; charset=utf-8' - : 'text/x-shellscript; charset=utf-8'; - - return new Response(response.body, { - headers: { - 'Content-Type': contentType, - 'Cache-Control': 'public, max-age=300' - } - }); - } catch (error) { - return new Response('Server Error', { status: 500 }); - } + return new Response('Not Found', { status: 404 }); } }; \ No newline at end of file diff --git a/src/ccs.ts b/src/ccs.ts index 2477cfb4..bc48ea45 100644 --- a/src/ccs.ts +++ b/src/ccs.ts @@ -32,7 +32,7 @@ import { checkCachedUpdate, isCacheStale, } from './utils/update-checker'; -import { detectInstallationMethod } from './utils/package-manager-detector'; +// Note: npm is now the only supported installation method // ========== Profile Detection ========== @@ -219,9 +219,8 @@ interface ProfileError extends Error { async function refreshUpdateCache(): Promise { try { const currentVersion = getVersion(); - const installMethod = detectInstallationMethod(); - // Force=true to always fetch fresh data - await checkForUpdates(currentVersion, true, installMethod); + // npm is now the only supported installation method + await checkForUpdates(currentVersion, true, 'npm'); } catch (_e) { // Silently fail - update check shouldn't crash main CLI } diff --git a/src/commands/update-command.ts b/src/commands/update-command.ts index 5cef805a..a172e4da 100644 --- a/src/commands/update-command.ts +++ b/src/commands/update-command.ts @@ -2,12 +2,12 @@ * Update Command Handler * * Handles `ccs update` command - checks for updates and installs latest version. - * Supports both npm and direct installation methods. + * Uses npm/yarn/pnpm/bun package managers exclusively. */ import { spawn } from 'child_process'; import { initUI, header, ok, fail, warn, info, color } from '../utils/ui'; -import { detectInstallationMethod, detectPackageManager } from '../utils/package-manager-detector'; +import { detectPackageManager } from '../utils/package-manager-detector'; import { compareVersionsWithPrerelease } from '../utils/update-checker'; import { getVersion } from '../utils/version'; @@ -35,33 +35,20 @@ export async function handleUpdateCommand(options: UpdateOptions = {}): Promise< console.log(header('Checking for updates...')); console.log(''); - const installMethod = detectInstallationMethod(); - const isNpmInstall = installMethod === 'npm'; - // Force reinstall - skip update check if (force) { console.log(info(`Force reinstall from @${targetTag} channel...`)); console.log(''); - - if (isNpmInstall) { - await performNpmUpdate(targetTag, true); - } else { - // Direct install doesn't support --beta - if (beta) { - handleDirectBetaNotSupported(); - return; - } - await performDirectUpdate(); - } + await performNpmUpdate(targetTag, true); return; } const { checkForUpdates } = await import('../utils/update-checker'); - const updateResult = await checkForUpdates(CCS_VERSION, true, installMethod, targetTag); + const updateResult = await checkForUpdates(CCS_VERSION, true, 'npm', targetTag); if (updateResult.status === 'check_failed') { - handleCheckFailed(updateResult.message ?? 'Update check failed', isNpmInstall, targetTag); + handleCheckFailed(updateResult.message ?? 'Update check failed', targetTag); return; } @@ -102,21 +89,13 @@ export async function handleUpdateCommand(options: UpdateOptions = {}): Promise< console.log(''); } - if (isNpmInstall) { - await performNpmUpdate(targetTag); - } else { - await performDirectUpdate(); - } + await performNpmUpdate(targetTag); } /** * Handle failed update check */ -function handleCheckFailed( - message: string, - isNpmInstall: boolean, - targetTag: string = 'latest' -): void { +function handleCheckFailed(message: string, targetTag: string = 'latest'): void { console.log(fail(message)); console.log(''); console.log(warn('Possible causes:')); @@ -126,36 +105,27 @@ function handleCheckFailed( console.log(''); console.log('Try again later or update manually:'); - if (isNpmInstall) { - const packageManager = detectPackageManager(); - let manualCommand: string; + const packageManager = detectPackageManager(); + let manualCommand: string; - switch (packageManager) { - case 'npm': - manualCommand = `npm install -g @kaitranntt/ccs@${targetTag}`; - break; - case 'yarn': - manualCommand = `yarn global add @kaitranntt/ccs@${targetTag}`; - break; - case 'pnpm': - manualCommand = `pnpm add -g @kaitranntt/ccs@${targetTag}`; - break; - case 'bun': - manualCommand = `bun add -g @kaitranntt/ccs@${targetTag}`; - break; - default: - manualCommand = `npm install -g @kaitranntt/ccs@${targetTag}`; - } - - console.log(color(` ${manualCommand}`, 'command')); - } else { - const isWindows = process.platform === 'win32'; - if (isWindows) { - console.log(color(' irm ccs.kaitran.ca/install | iex', 'command')); - } else { - console.log(color(' curl -fsSL ccs.kaitran.ca/install | bash', 'command')); - } + switch (packageManager) { + case 'npm': + manualCommand = `npm install -g @kaitranntt/ccs@${targetTag}`; + break; + case 'yarn': + manualCommand = `yarn global add @kaitranntt/ccs@${targetTag}`; + break; + case 'pnpm': + manualCommand = `pnpm add -g @kaitranntt/ccs@${targetTag}`; + break; + case 'bun': + manualCommand = `bun add -g @kaitranntt/ccs@${targetTag}`; + break; + default: + manualCommand = `npm install -g @kaitranntt/ccs@${targetTag}`; } + + console.log(color(` ${manualCommand}`, 'command')); console.log(''); process.exit(1); } @@ -300,86 +270,3 @@ async function performNpmUpdate( performUpdate(); } } - -/** - * Handle direct install beta not supported error - */ -function handleDirectBetaNotSupported(): void { - console.log(fail('--beta flag requires npm installation')); - console.log(''); - console.log('Current installation method: direct installer'); - console.log('To use beta releases, install via npm:'); - console.log(''); - console.log(color(' npm install -g @kaitranntt/ccs', 'command')); - console.log(color(' ccs update --beta', 'command')); - console.log(''); - console.log('Or continue using stable releases via direct installer.'); - console.log(''); - process.exit(1); -} - -/** - * Perform update via direct installer (curl/irm) - */ -async function performDirectUpdate(): Promise { - console.log(info('Updating via installer...')); - console.log(''); - - const isWindows = process.platform === 'win32'; - let command: string; - let args: string[]; - - if (isWindows) { - command = 'powershell.exe'; - args = [ - '-NoProfile', - '-ExecutionPolicy', - 'Bypass', - '-Command', - 'irm ccs.kaitran.ca/install | iex', - ]; - } else { - command = '/bin/bash'; - args = ['-c', 'curl -fsSL ccs.kaitran.ca/install | bash']; - } - - const child = spawn(command, args, { - stdio: 'inherit', - }); - - child.on('exit', (code) => { - if (code === 0) { - console.log(''); - console.log(ok('Update successful!')); - console.log(''); - console.log(`Run ${color('ccs --version', 'command')} to verify`); - console.log(''); - } else { - console.log(''); - console.log(fail('Update failed')); - console.log(''); - console.log('Try manually:'); - if (isWindows) { - console.log(color(' irm ccs.kaitran.ca/install | iex', 'command')); - } else { - console.log(color(' curl -fsSL ccs.kaitran.ca/install | bash', 'command')); - } - console.log(''); - } - process.exit(code || 0); - }); - - child.on('error', () => { - console.log(''); - console.log(fail('Failed to run installer')); - console.log(''); - console.log('Try manually:'); - if (isWindows) { - console.log(color(' irm ccs.kaitran.ca/install | iex', 'command')); - } else { - console.log(color(' curl -fsSL ccs.kaitran.ca/install | bash', 'command')); - } - console.log(''); - process.exit(1); - }); -} diff --git a/src/utils/package-manager-detector.ts b/src/utils/package-manager-detector.ts index a30fb39b..ac5840a0 100644 --- a/src/utils/package-manager-detector.ts +++ b/src/utils/package-manager-detector.ts @@ -2,77 +2,13 @@ * Package Manager Detector Utilities * * Cross-platform package manager detection utilities for CCS. + * Now only supports npm-based installation (npm/yarn/pnpm/bun). */ import * as path from 'path'; import * as fs from 'fs'; import { spawnSync } from 'child_process'; -/** - * Detect installation method - */ -export function detectInstallationMethod(): 'npm' | 'direct' { - const scriptPath = process.argv[1]; - - // Method 1: Check if script is inside node_modules - if (scriptPath.includes('node_modules')) { - return 'npm'; - } - - // Method 2: Check if script is in npm global bin directory - const npmGlobalBinPatterns = [ - /\.npm\/global\/bin\//, - /\/\.nvm\/versions\/node\/[^/]+\/bin\//, - /\/usr\/local\/bin\//, - /\/usr\/bin\//, - ]; - - for (const pattern of npmGlobalBinPatterns) { - if (pattern.test(scriptPath)) { - try { - const binDir = path.dirname(scriptPath); - const nodeModulesDir = path.join(binDir, '..', 'lib', 'node_modules', '@kaitranntt', 'ccs'); - const globalModulesDir = path.join(binDir, '..', 'node_modules', '@kaitranntt', 'ccs'); - - if (fs.existsSync(nodeModulesDir) || fs.existsSync(globalModulesDir)) { - return 'npm'; - } - } catch (_err) { - // Continue checking other patterns - } - } - } - - // Method 3: Check if package.json exists in parent directory - const packageJsonPath = path.join(__dirname, '..', 'package.json'); - - if (fs.existsSync(packageJsonPath)) { - try { - const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); - if (pkg.name === '@kaitranntt/ccs') { - return 'npm'; - } - } catch (_err) { - // Ignore parse errors - } - } - - // Method 4: Check if script is a symlink pointing to node_modules - try { - const stats = fs.lstatSync(scriptPath); - if (stats.isSymbolicLink()) { - const targetPath = fs.readlinkSync(scriptPath); - if (targetPath.includes('node_modules') || targetPath.includes('@kaitranntt/ccs')) { - return 'npm'; - } - } - } catch (_err) { - // Continue to default - } - - return 'direct'; -} - /** * Detect which package manager was used for installation */ diff --git a/tests/unit/commands/update-command-beta-channel.test.js b/tests/unit/commands/update-command-beta-channel.test.js index 52a84a6b..3f9ccbd1 100644 --- a/tests/unit/commands/update-command-beta-channel.test.js +++ b/tests/unit/commands/update-command-beta-channel.test.js @@ -118,9 +118,7 @@ describe.skip('Update Command Beta Channel Implementation (Phase 3)', function ( describe('Beta stability warning display', function () { it('should show beta warning when installing from dev channel', function () { // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; const originalDetectPackageManager = packageManagerDetectorModule.detectPackageManager; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; packageManagerDetectorModule.detectPackageManager = () => 'npm'; // Mock update checker to return update available @@ -166,7 +164,6 @@ describe.skip('Update Command Beta Channel Implementation (Phase 3)', function ( assert(returnStable, 'should show return to stable instruction'); } finally { // Restore original functions - packageManagerDetectorModule.detectInstallationMethod = originalDetectInstallationMethod; packageManagerDetectorModule.detectPackageManager = originalDetectPackageManager; updateCheckerModule.checkForUpdates = originalCheckForUpdates; require('child_process').spawn = originalSpawn; @@ -175,9 +172,7 @@ describe.skip('Update Command Beta Channel Implementation (Phase 3)', function ( it('should NOT show beta warning for stable channel', function () { // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; const originalDetectPackageManager = packageManagerDetectorModule.detectPackageManager; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; packageManagerDetectorModule.detectPackageManager = () => 'npm'; // Mock update checker to return update available @@ -204,7 +199,6 @@ describe.skip('Update Command Beta Channel Implementation (Phase 3)', function ( assert(!unstableWarning, 'should not show production warning for stable channel'); } finally { // Restore original functions - packageManagerDetectorModule.detectInstallationMethod = originalDetectInstallationMethod; packageManagerDetectorModule.detectPackageManager = originalDetectPackageManager; updateCheckerModule.checkForUpdates = originalCheckForUpdates; } @@ -212,9 +206,7 @@ describe.skip('Update Command Beta Channel Implementation (Phase 3)', function ( it('should show beta warning even with force flag', function () { // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; const originalDetectPackageManager = packageManagerDetectorModule.detectPackageManager; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; packageManagerDetectorModule.detectPackageManager = () => 'npm'; try { @@ -228,7 +220,6 @@ describe.skip('Update Command Beta Channel Implementation (Phase 3)', function ( assert(betaWarning, 'should show beta warning even with force'); } finally { // Restore original functions - packageManagerDetectorModule.detectInstallationMethod = originalDetectInstallationMethod; packageManagerDetectorModule.detectPackageManager = originalDetectPackageManager; } }); @@ -237,9 +228,7 @@ describe.skip('Update Command Beta Channel Implementation (Phase 3)', function ( describe('handleCheckFailed with targetTag parameter', function () { it('should show manual update command with dev tag for npm install', function () { // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; const originalDetectPackageManager = packageManagerDetectorModule.detectPackageManager; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; packageManagerDetectorModule.detectPackageManager = () => 'npm'; try { @@ -265,9 +254,7 @@ describe.skip('Update Command Beta Channel Implementation (Phase 3)', function ( it('should show manual update command with latest tag for stable', function () { // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; const originalDetectPackageManager = packageManagerDetectorModule.detectPackageManager; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; packageManagerDetectorModule.detectPackageManager = () => 'npm'; try { @@ -304,9 +291,7 @@ describe.skip('Update Command Beta Channel Implementation (Phase 3)', function ( consoleOutput = []; // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; const originalDetectPackageManager = packageManagerDetectorModule.detectPackageManager; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; packageManagerDetectorModule.detectPackageManager = () => name; try { @@ -330,96 +315,13 @@ describe.skip('Update Command Beta Channel Implementation (Phase 3)', function ( assert(manualCommand, `should show manual ${name} command with dev tag`); // Restore functions - packageManagerDetectorModule.detectInstallationMethod = originalDetectInstallationMethod; packageManagerDetectorModule.detectPackageManager = originalDetectPackageManager; }); }); - - it('should show direct install commands when npm detection fails', function () { - // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; - const originalDetectPackageManager = packageManagerDetectorModule.detectPackageManager; - packageManagerDetectorModule.detectInstallationMethod = () => 'direct'; - packageManagerDetectorModule.detectPackageManager = () => 'npm'; - - try { - // Mock checkForUpdates to return failed - const originalCheckForUpdates = updateCheckerModule.checkForUpdates; - updateCheckerModule.checkForUpdates = async () => ({ - status: 'check_failed', - message: 'Failed to check for updates' - }); - - // Call with beta: false (beta not supported for direct) - updateCommandModule.handleUpdateCommand({ beta: false }); - } catch (e) { - // Expected to exit - } - - // Should show direct install commands - if (process.platform === 'win32') { - const powershellCmd = consoleOutput.find(output => - output[0] && output[0].includes('irm ccs.kaitran.ca/install | iex') - ); - assert(powershellCmd, 'should show PowerShell command for Windows'); - } else { - const curlCmd = consoleOutput.find(output => - output[0] && output[0].includes('curl -fsSL ccs.kaitran.ca/install | bash') - ); - assert(curlCmd, 'should show curl command for Unix'); - } - }); - - it('should show beta not supported message for direct install with beta', function () { - // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; - packageManagerDetectorModule.detectInstallationMethod = () => 'direct'; - - try { - // Mock checkForUpdates to return beta not supported - const originalCheckForUpdates = updateCheckerModule.checkForUpdates; - updateCheckerModule.checkForUpdates = async () => ({ - status: 'check_failed', - reason: 'beta_not_supported', - message: '--beta requires npm installation method' - }); - - // Call with beta: true - updateCommandModule.handleUpdateCommand({ beta: true }); - } catch (e) { - // Expected to exit - } - - // Should show beta not supported message - const betaError = consoleOutput.find(output => - output[0] && output[0].includes('[X] --beta requires npm installation') - ); - assert(betaError, 'should show beta not supported error'); - - const currentMethod = consoleOutput.find(output => - output[0] && output[0].includes('Current installation method: direct installer') - ); - assert(currentMethod, 'should show current installation method'); - - // Should show npm install instructions - const npmInstall = consoleOutput.find(output => - output[0] && output[0].includes('npm install -g @kaitranntt/ccs') - ); - assert(npmInstall, 'should show npm install instructions'); - - const ccsUpdateBeta = consoleOutput.find(output => - output[0] && output[0].includes('ccs update --beta') - ); - assert(ccsUpdateBeta, 'should show ccs update --beta instruction'); - }); }); describe('Error handling', function () { it('should handle checkForUpdates throwing error', function () { - // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; - try { // Mock checkForUpdates to throw error const originalCheckForUpdates = updateCheckerModule.checkForUpdates; @@ -435,10 +337,6 @@ describe.skip('Update Command Beta Channel Implementation (Phase 3)', function ( }); it('should exit with error code 1 when check fails', function () { - // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; - try { // Mock checkForUpdates to return failed const originalCheckForUpdates = updateCheckerModule.checkForUpdates; @@ -458,10 +356,6 @@ describe.skip('Update Command Beta Channel Implementation (Phase 3)', function ( describe('Integration with update checker', function () { it('should pass correct targetTag to checkForUpdates', function () { - // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; - // Track calls to checkForUpdates let checkForUpdatesCalls = []; const originalCheckForUpdates = updateCheckerModule.checkForUpdates; @@ -480,16 +374,11 @@ describe.skip('Update Command Beta Channel Implementation (Phase 3)', function ( assert.strictEqual(devCall.installMethod, 'npm'); } finally { // Restore function - packageManagerDetectorModule.detectInstallationMethod = originalDetectInstallationMethod; updateCheckerModule.checkForUpdates = originalCheckForUpdates; } }); it('should pass force parameter correctly', function () { - // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; - // Track calls to checkForUpdates let checkForUpdatesCalls = []; const originalCheckForUpdates = updateCheckerModule.checkForUpdates; @@ -507,9 +396,8 @@ describe.skip('Update Command Beta Channel Implementation (Phase 3)', function ( assert.strictEqual(checkForUpdatesCalls[0].force, true, 'should pass force parameter'); } finally { // Restore function - packageManagerDetectorModule.detectInstallationMethod = originalDetectInstallationMethod; updateCheckerModule.checkForUpdates = originalCheckForUpdates; } }); }); -}); \ No newline at end of file +}); diff --git a/tests/unit/commands/update-command-force-reinstall.test.js b/tests/unit/commands/update-command-force-reinstall.test.js index d12a9513..aa85a57d 100644 --- a/tests/unit/commands/update-command-force-reinstall.test.js +++ b/tests/unit/commands/update-command-force-reinstall.test.js @@ -6,7 +6,6 @@ * - Skip update check when force is true * - Target tag calculation (latest vs dev) based on beta flag * - performNpmUpdate function with targetTag parameter - * - handleDirectBetaNotSupported function for direct installs * - Success messages showing "Reinstall" vs "Update" * * NOTE: These tests are currently skipped because they require proper mocking @@ -113,9 +112,7 @@ describe.skip('Update Command - Force Reinstall Implementation (Phase 2)', funct describe('Target tag calculation based on beta flag', function () { it('should set targetTag to "latest" when beta flag is false', function () { // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; const originalDetectPackageManager = packageManagerDetectorModule.detectPackageManager; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; packageManagerDetectorModule.detectPackageManager = () => 'npm'; try { @@ -130,16 +127,13 @@ describe.skip('Update Command - Force Reinstall Implementation (Phase 2)', funct assert(latestCall, 'should install latest tag when beta is false'); } finally { // Restore original functions - packageManagerDetectorModule.detectInstallationMethod = originalDetectInstallationMethod; packageManagerDetectorModule.detectPackageManager = originalDetectPackageManager; } }); it('should set targetTag to "dev" when beta flag is true', function () { // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; const originalDetectPackageManager = packageManagerDetectorModule.detectPackageManager; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; packageManagerDetectorModule.detectPackageManager = () => 'npm'; try { @@ -154,7 +148,6 @@ describe.skip('Update Command - Force Reinstall Implementation (Phase 2)', funct assert(devCall, 'should install dev tag when beta is true'); } finally { // Restore original functions - packageManagerDetectorModule.detectInstallationMethod = originalDetectInstallationMethod; packageManagerDetectorModule.detectPackageManager = originalDetectPackageManager; } }); @@ -162,10 +155,6 @@ describe.skip('Update Command - Force Reinstall Implementation (Phase 2)', funct describe('Force flag behavior', function () { it('should show force reinstall message when force is true', function () { - // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; - try { // Call with force: true updateCommandModule.handleUpdateCommand({ force: true, beta: false }); @@ -176,16 +165,13 @@ describe.skip('Update Command - Force Reinstall Implementation (Phase 2)', funct ); assert(forceMessage, 'should show force reinstall message'); } finally { - // Restore original function - packageManagerDetectorModule.detectInstallationMethod = originalDetectInstallationMethod; + // No cleanup needed } }); it('should bypass update check when force is true', function () { // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; const originalDetectPackageManager = packageManagerDetectorModule.detectPackageManager; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; packageManagerDetectorModule.detectPackageManager = () => 'npm'; try { @@ -201,7 +187,6 @@ describe.skip('Update Command - Force Reinstall Implementation (Phase 2)', funct assert(npmCall.args.includes('install'), 'should call install command'); } finally { // Restore original functions - packageManagerDetectorModule.detectInstallationMethod = originalDetectInstallationMethod; packageManagerDetectorModule.detectPackageManager = originalDetectPackageManager; } }); @@ -210,9 +195,7 @@ describe.skip('Update Command - Force Reinstall Implementation (Phase 2)', funct describe('Package manager tag syntax', function () { it('should use correct tag syntax for npm', function () { // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; const originalDetectPackageManager = packageManagerDetectorModule.detectPackageManager; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; packageManagerDetectorModule.detectPackageManager = () => 'npm'; try { @@ -226,16 +209,13 @@ describe.skip('Update Command - Force Reinstall Implementation (Phase 2)', funct assert(npmCall.args.includes('-g'), 'should use global flag for npm'); } finally { // Restore original functions - packageManagerDetectorModule.detectInstallationMethod = originalDetectInstallationMethod; packageManagerDetectorModule.detectPackageManager = originalDetectPackageManager; } }); it('should use correct tag syntax for yarn', function () { // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; const originalDetectPackageManager = packageManagerDetectorModule.detectPackageManager; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; packageManagerDetectorModule.detectPackageManager = () => 'yarn'; try { @@ -249,16 +229,13 @@ describe.skip('Update Command - Force Reinstall Implementation (Phase 2)', funct assert(yarnCall.args.includes('global'), 'should use global flag for yarn'); } finally { // Restore original functions - packageManagerDetectorModule.detectInstallationMethod = originalDetectInstallationMethod; packageManagerDetectorModule.detectPackageManager = originalDetectPackageManager; } }); it('should use correct tag syntax for pnpm', function () { // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; const originalDetectPackageManager = packageManagerDetectorModule.detectPackageManager; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; packageManagerDetectorModule.detectPackageManager = () => 'pnpm'; try { @@ -272,16 +249,13 @@ describe.skip('Update Command - Force Reinstall Implementation (Phase 2)', funct assert(pnpmCall.args.includes('-g'), 'should use global flag for pnpm'); } finally { // Restore original functions - packageManagerDetectorModule.detectInstallationMethod = originalDetectInstallationMethod; packageManagerDetectorModule.detectPackageManager = originalDetectPackageManager; } }); it('should use correct tag syntax for bun', function () { // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; const originalDetectPackageManager = packageManagerDetectorModule.detectPackageManager; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; packageManagerDetectorModule.detectPackageManager = () => 'bun'; try { @@ -295,80 +269,15 @@ describe.skip('Update Command - Force Reinstall Implementation (Phase 2)', funct assert(bunCall.args.includes('-g'), 'should use global flag for bun'); } finally { // Restore original functions - packageManagerDetectorModule.detectInstallationMethod = originalDetectInstallationMethod; packageManagerDetectorModule.detectPackageManager = originalDetectPackageManager; } }); }); - describe('Direct install beta not supported', function () { - it('should show error for direct install with --beta', function () { - // Mock installation method detection as direct - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; - packageManagerDetectorModule.detectInstallationMethod = () => 'direct'; - - try { - // Call with force: true, beta: true - updateCommandModule.handleUpdateCommand({ force: true, beta: true }); - - // Should show beta not supported error - const betaError = consoleOutput.find(output => - output[0] && output[0].includes('--beta flag requires npm installation') - ); - assert(betaError, 'should show beta not supported error'); - - const directInstallMsg = consoleOutput.find(output => - output[0] && output[0].includes('Current installation method: direct installer') - ); - assert(directInstallMsg, 'should show direct installer message'); - - // Should exit with error code - assert(processExitCalls.length > 0, 'should call process.exit'); - assert(processExitCalls[0] === 1, 'should exit with error code 1'); - } finally { - // Restore original function - packageManagerDetectorModule.detectInstallationMethod = originalDetectInstallationMethod; - } - }); - - it('should allow force reinstall with direct install when beta is false', function () { - // Mock installation method detection as direct - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; - packageManagerDetectorModule.detectInstallationMethod = () => 'direct'; - - try { - // Call with force: true, beta: false - updateCommandModule.handleUpdateCommand({ force: true, beta: false }); - - // Should NOT show beta error - const betaError = consoleOutput.find(output => - output[0] && output[0].includes('--beta flag requires npm installation') - ); - assert(!betaError, 'should not show beta error when beta is false'); - - // Should call spawn for direct update - assert(spawnCalls.length > 0, 'should call spawn for direct update'); - - // Should call curl or powershell - const directUpdateCall = spawnCalls[0]; - if (process.platform === 'win32') { - assert(directUpdateCall.command === 'powershell.exe', 'should call powershell on Windows'); - } else { - assert(directUpdateCall.command === '/bin/bash', 'should call bash on Unix'); - } - } finally { - // Restore original function - packageManagerDetectorModule.detectInstallationMethod = originalDetectInstallationMethod; - } - }); - }); - describe('Success messages', function () { it('should show "Reinstalling" message when force is true', function () { // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; const originalDetectPackageManager = packageManagerDetectorModule.detectPackageManager; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; packageManagerDetectorModule.detectPackageManager = () => 'npm'; try { @@ -382,7 +291,6 @@ describe.skip('Update Command - Force Reinstall Implementation (Phase 2)', funct assert(reinstallingMsg, 'should show reinstalling message'); } finally { // Restore original functions - packageManagerDetectorModule.detectInstallationMethod = originalDetectInstallationMethod; packageManagerDetectorModule.detectPackageManager = originalDetectPackageManager; } }); @@ -391,9 +299,7 @@ describe.skip('Update Command - Force Reinstall Implementation (Phase 2)', funct describe('Combined force and beta behavior', function () { it('should handle force with beta for npm install', function () { // Mock package manager detection - const originalDetectInstallationMethod = packageManagerDetectorModule.detectInstallationMethod; const originalDetectPackageManager = packageManagerDetectorModule.detectPackageManager; - packageManagerDetectorModule.detectInstallationMethod = () => 'npm'; packageManagerDetectorModule.detectPackageManager = () => 'npm'; try { @@ -412,9 +318,8 @@ describe.skip('Update Command - Force Reinstall Implementation (Phase 2)', funct assert(forceMessage, 'should show force reinstall from dev channel message'); } finally { // Restore original functions - packageManagerDetectorModule.detectInstallationMethod = originalDetectInstallationMethod; packageManagerDetectorModule.detectPackageManager = originalDetectPackageManager; } }); }); -}); \ No newline at end of file +});