Files
ccs/installers/install.ps1
T
semantic-release-botandkaitranntt bd46c8de12 fix(tests): migrate test suite from mocha to bun test runner
- Replace before()/after() with beforeAll()/afterAll()
- Remove this.timeout() calls (unsupported by bun)
- Update package.json scripts to use bun test
- Fix error message regex for cross-runtime compatibility
- Skip integration tests requiring network/child process mocking
- Format source files with prettier
2025-12-03 21:43:29 -05:00

809 lines
29 KiB
PowerShell

# 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 = "5.4.1-dev.1"
# 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 ""