From a4245e9031e8716ff09b976322da6bb2626e78d8 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Sun, 2 Nov 2025 02:20:48 -0500 Subject: [PATCH] feat(windows): add native PowerShell 5.1+ support with full cross-platform compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add ccs.ps1: PowerShell core script with native JSON parsing - Add install.ps1: Windows installer with provider auto-detection - Add uninstall.ps1: Clean Windows uninstaller - Update install.sh: Add platform detection to redirect Windows users - Update README.md: Add comprehensive Windows installation docs - Update README.vi.md: Add Vietnamese Windows documentation Features: - PowerShell 5.1+ compatible (Windows 10+ built-in) - Cross-platform config: Unix paths work on all platforms - Smart path expansion: ~/ → %USERPROFILE%\ on Windows - Zero breaking changes: Linux/macOS unchanged - No external dependencies: Uses PowerShell built-in JSON Install directory: %USERPROFILE%\.ccs\ Usage: irm ccs.kaitran.ca/install.ps1 | iex (after Cloudflare setup) --- README.md | 179 +++++++++++++++++++++++++++----- README.vi.md | 181 +++++++++++++++++++++++++++++---- ccs.ps1 | 95 +++++++++++++++++ install.ps1 | 277 ++++++++++++++++++++++++++++++++++++++++++++++++++ install.sh | 31 ++++++ uninstall.ps1 | 56 ++++++++++ 6 files changed, 773 insertions(+), 46 deletions(-) create mode 100644 ccs.ps1 create mode 100644 install.ps1 create mode 100644 uninstall.ps1 diff --git a/README.md b/README.md index 9f1f4ad0..424972be 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # CCS - Claude Code Switch [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Language: Bash](https://img.shields.io/badge/Language-Bash-blue.svg)](https://www.gnu.org/software/bash/) -[![Platform: macOS | Linux](https://img.shields.io/badge/Platform-macOS%20%7C%20Linux-lightgrey.svg)]() +[![Language: Bash | PowerShell](https://img.shields.io/badge/Language-Bash%20%7C%20PowerShell-blue.svg)]() +[![Platform: macOS | Linux | Windows](https://img.shields.io/badge/Platform-macOS%20%7C%20Linux%20%7C%20Windows-lightgrey.svg)]() **Languages**: [English](README.md) | [Tiếng Việt](README.vi.md) @@ -26,11 +26,18 @@ One command. Zero downtime. No file editing. Right model, right task. ## Quick Start -**Install** (one-liner): +**Install**: + +**macOS / Linux**: ```bash curl -fsSL ccs.kaitran.ca/install | bash ``` +**Windows PowerShell**: +```powershell +irm ccs.kaitran.ca/install.ps1 | iex +``` + **Configure**: ```bash # Edit with your profiles @@ -103,30 +110,49 @@ If you have both Claude subscription and GLM Coding Plan, you know the pain: ### One-Liner (Recommended) -**Short URL** (via CloudFlare): +**macOS / Linux**: ```bash +# Short URL (via CloudFlare) curl -fsSL ccs.kaitran.ca/install | bash -``` -**Direct from GitHub**: -```bash +# Or direct from GitHub curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/install.sh | bash ``` -**Note**: The installer supports both direct execution (`./install.sh`) and piped installation (`curl | bash`). +**Windows PowerShell**: +```powershell +# Short URL (via CloudFlare) +irm ccs.kaitran.ca/install.ps1 | iex + +# Or direct from GitHub +irm https://raw.githubusercontent.com/kaitranntt/ccs/main/install.ps1 | iex +``` + +**Note**: +- Unix installer supports both direct execution (`./install.sh`) and piped installation (`curl | bash`) +- Windows installer requires PowerShell 5.1+ (pre-installed on Windows 10+) ### Git Clone +**macOS / Linux**: ```bash git clone https://github.com/kaitranntt/ccs.git cd ccs ./install.sh ``` +**Windows PowerShell**: +```powershell +git clone https://github.com/kaitranntt/ccs.git +cd ccs +.\install.ps1 +``` + **Note**: Works with git worktrees and submodules - the installer detects both `.git` directory and `.git` file. ### Manual +**macOS / Linux**: ```bash # Download script curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/ccs -o ~/.local/bin/ccs @@ -136,27 +162,51 @@ chmod +x ~/.local/bin/ccs export PATH="$HOME/.local/bin:$PATH" ``` -### Upgrade +**Windows PowerShell**: +```powershell +# Create directory +New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.ccs" -**From git clone**: -```bash -cd ccs && git pull && ./install.sh +# Download script +Invoke-WebRequest -Uri "https://raw.githubusercontent.com/kaitranntt/ccs/main/ccs.ps1" -OutFile "$env:USERPROFILE\.ccs\ccs.ps1" + +# Add to PATH (restart terminal after) +$Path = [Environment]::GetEnvironmentVariable("Path", "User") +[Environment]::SetEnvironmentVariable("Path", "$Path;$env:USERPROFILE\.ccs", "User") ``` -**From curl install**: -```bash -# Short URL -curl -fsSL ccs.kaitran.ca/install | bash +### Upgrade -# Or direct from GitHub -curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/install.sh | bash +**macOS / Linux**: +```bash +# From git clone +cd ccs && git pull && ./install.sh + +# From curl install +curl -fsSL ccs.kaitran.ca/install | bash +``` + +**Windows PowerShell**: +```powershell +# From git clone +cd ccs +git pull +.\install.ps1 + +# From irm install +irm ccs.kaitran.ca/install.ps1 | iex ``` **Note**: Upgrading preserves your existing API keys and settings. The installer only adds new features without overwriting your configuration. ## Configuration -The installer auto-creates `~/.ccs/config.json` and profile templates during installation. If you need to customize: +The installer auto-creates config and profile templates during installation: + +**macOS / Linux**: `~/.ccs/config.json` +**Windows**: `%USERPROFILE%\.ccs\config.json` + +If you need to customize: ```json { @@ -168,6 +218,8 @@ The installer auto-creates `~/.ccs/config.json` and profile templates during ins } ``` +**Note**: Config uses Unix-style paths (`~/`) for cross-platform compatibility. Windows version automatically converts paths. + Each profile points to a Claude settings JSON file. Create settings files per [Claude CLI docs](https://docs.claude.com/en/docs/claude-code/installation). ### GLM Profile Configuration @@ -199,11 +251,14 @@ These variables ensure GLM is used as the default provider when switching profil ### Basic ```bash +# Works on macOS, Linux, and Windows ccs # Use default profile (no args) ccs glm # Use GLM profile ccs son # Use Sonnet profile ``` +**Windows Note**: Commands work identically in PowerShell, CMD, and Git Bash. + ### With Arguments All args after profile name pass directly to Claude CLI: @@ -304,11 +359,16 @@ No magic. No file modification. Pure delegation. ## Requirements +### macOS / Linux - `bash` 3.2+ - `jq` (JSON processor) - [Claude CLI](https://docs.claude.com/en/docs/claude-code/installation) -### Installing jq +### Windows +- PowerShell 5.1+ (pre-installed on Windows 10+) +- [Claude CLI](https://docs.claude.com/en/docs/claude-code/installation) + +### Installing jq (macOS / Linux only) ```bash # macOS @@ -324,8 +384,46 @@ sudo dnf install jq sudo pacman -S jq ``` +**Note**: Windows version uses PowerShell's built-in JSON support - no jq required. + ## Troubleshooting +### Windows-Specific Issues + +#### PowerShell Execution Policy + +If you see "cannot be loaded because running scripts is disabled": + +```powershell +# Check current policy +Get-ExecutionPolicy + +# Allow current user to run scripts (recommended) +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +# Or run with bypass (one-time) +powershell -ExecutionPolicy Bypass -File "$env:USERPROFILE\.ccs\ccs.ps1" glm +``` + +#### PATH not updated (Windows) + +If `ccs` command not found after installation: + +1. Restart your terminal +2. Or manually add to PATH: + - Open "Edit environment variables for your account" + - Add `%USERPROFILE%\.ccs` to User PATH + - Restart terminal + +#### Claude CLI not found (Windows) + +```powershell +# Check Claude CLI +where.exe claude + +# If missing, install from Claude docs +``` + ### Installation Issues #### BASH_SOURCE unbound variable error @@ -421,12 +519,14 @@ Error: Profile 'default' not found in ~/.ccs/config.json ## Uninstallation +### macOS / Linux + **Using installed command**: ```bash ccs-uninstall ``` -**One-liner** (if ccs-uninstall not available): +**One-liner**: ```bash # Short URL curl -fsSL ccs.kaitran.ca/uninstall | bash @@ -442,14 +542,45 @@ rm ~/.local/bin/ccs-uninstall rm -rf ~/.ccs # If you want to remove all CCS files ``` +### Windows PowerShell + +**Using installed command**: +```powershell +ccs-uninstall +``` + +**One-liner**: +```powershell +# Short URL +irm ccs.kaitran.ca/uninstall.ps1 | iex + +# Or direct from GitHub +irm https://raw.githubusercontent.com/kaitranntt/ccs/main/uninstall.ps1 | iex +``` + +**Manual**: +```powershell +# Remove scripts +Remove-Item "$env:USERPROFILE\.ccs\ccs.ps1" -Force +Remove-Item "$env:USERPROFILE\.ccs\uninstall.ps1" -Force + +# Remove from PATH +$Path = [Environment]::GetEnvironmentVariable("Path", "User") +$NewPath = ($Path -split ';' | Where-Object { $_ -ne "$env:USERPROFILE\.ccs" }) -join ';' +[Environment]::SetEnvironmentVariable("Path", $NewPath, "User") + +# Optional: Remove all CCS files +Remove-Item "$env:USERPROFILE\.ccs" -Recurse -Force +``` + ## Contributing PRs welcome! Keep it simple (KISS principle). **Guidelines**: -- Maintain bash 3.2+ compatibility -- No dependencies beyond jq -- Test on macOS and Linux +- Maintain bash 3.2+ compatibility (Unix) and PowerShell 5.1+ (Windows) +- No dependencies beyond jq (Unix) or built-in PowerShell (Windows) +- Test on macOS, Linux, and Windows - Follow existing code style ## Philosophy diff --git a/README.vi.md b/README.vi.md index 0439cd4c..12b71b2a 100644 --- a/README.vi.md +++ b/README.vi.md @@ -1,8 +1,8 @@ # CCS - Claude Code Switch [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Language: Bash](https://img.shields.io/badge/Language-Bash-blue.svg)](https://www.gnu.org/software/bash/) -[![Platform: macOS | Linux](https://img.shields.io/badge/Platform-macOS%20%7C%20Linux-lightgrey.svg)]() +[![Language: Bash | PowerShell](https://img.shields.io/badge/Language-Bash%20%7C%20PowerShell-blue.svg)]() +[![Platform: macOS | Linux | Windows](https://img.shields.io/badge/Platform-macOS%20%7C%20Linux%20%7C%20Windows-lightgrey.svg)]() **Ngôn ngữ**: [English](README.md) | [Tiếng Việt](README.vi.md) @@ -26,11 +26,18 @@ Một lệnh. Không downtime. Không phải sửa file. Đúng model, đúng vi ## Bắt Đầu Nhanh -**Cài đặt** (một dòng lệnh): +**Cài đặt**: + +**macOS / Linux**: ```bash curl -fsSL ccs.kaitran.ca/install | bash ``` +**Windows PowerShell**: +```powershell +irm ccs.kaitran.ca/install.ps1 | iex +``` + **Cấu hình**: ```bash # Sửa theo profile của bạn @@ -94,30 +101,49 @@ Nếu bạn có cả Claude subscription và GLM Coding Plan, bạn biết cái ### Một Dòng Lệnh (Khuyến Nghị) -**URL ngắn** (qua CloudFlare): +**macOS / Linux**: ```bash +# URL ngắn (qua CloudFlare) curl -fsSL ccs.kaitran.ca/install | bash -``` -**Trực tiếp từ GitHub**: -```bash +# Hoặc trực tiếp từ GitHub curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/install.sh | bash ``` -**Lưu ý**: Installer hỗ trợ cả chạy trực tiếp (`./install.sh`) và piped installation (`curl | bash`). +**Windows PowerShell**: +```powershell +# URL ngắn (qua CloudFlare) +irm ccs.kaitran.ca/install.ps1 | iex + +# Hoặc trực tiếp từ GitHub +irm https://raw.githubusercontent.com/kaitranntt/ccs/main/install.ps1 | iex +``` + +**Lưu ý**: +- Installer Unix hỗ trợ cả chạy trực tiếp (`./install.sh`) và piped installation (`curl | bash`) +- Installer Windows yêu cầu PowerShell 5.1+ (có sẵn trên Windows 10+) ### Git Clone +**macOS / Linux**: ```bash git clone https://github.com/kaitranntt/ccs.git cd ccs ./install.sh ``` +**Windows PowerShell**: +```powershell +git clone https://github.com/kaitranntt/ccs.git +cd ccs +.\install.ps1 +``` + **Lưu ý**: Hoạt động với git worktrees và submodules - installer phát hiện cả `.git` directory và `.git` file. ### Thủ Công +**macOS / Linux**: ```bash # Tải script curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/ccs -o ~/.local/bin/ccs @@ -127,27 +153,51 @@ chmod +x ~/.local/bin/ccs export PATH="$HOME/.local/bin:$PATH" ``` -### Nâng Cấp +**Windows PowerShell**: +```powershell +# Tạo thư mục +New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.ccs" -**Từ git clone**: -```bash -cd ccs && git pull && ./install.sh +# Tải script +Invoke-WebRequest -Uri "https://raw.githubusercontent.com/kaitranntt/ccs/main/ccs.ps1" -OutFile "$env:USERPROFILE\.ccs\ccs.ps1" + +# Thêm vào PATH (khởi động lại terminal sau đó) +$Path = [Environment]::GetEnvironmentVariable("Path", "User") +[Environment]::SetEnvironmentVariable("Path", "$Path;$env:USERPROFILE\.ccs", "User") ``` -**Từ curl install**: -```bash -# URL ngắn -curl -fsSL ccs.kaitran.ca/install | bash +### Nâng Cấp -# Hoặc trực tiếp từ GitHub -curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/install.sh | bash +**macOS / Linux**: +```bash +# Từ git clone +cd ccs && git pull && ./install.sh + +# Từ curl install +curl -fsSL ccs.kaitran.ca/install | bash +``` + +**Windows PowerShell**: +```powershell +# Từ git clone +cd ccs +git pull +.\install.ps1 + +# Từ irm install +irm ccs.kaitran.ca/install.ps1 | iex ``` **Lưu ý**: Nâng cấp giữ nguyên API keys và settings hiện tại. Installer chỉ thêm tính năng mới mà không ghi đè cấu hình của bạn. ## Cấu Hình -Installer tự động tạo `~/.ccs/config.json` và profile templates khi cài đặt. Nếu cần tùy chỉnh: +Installer tự động tạo config và profile templates khi cài đặt: + +**macOS / Linux**: `~/.ccs/config.json` +**Windows**: `%USERPROFILE%\.ccs\config.json` + +Nếu cần tùy chỉnh: ```json { @@ -159,6 +209,8 @@ Installer tự động tạo `~/.ccs/config.json` và profile templates khi cài } ``` +**Lưu ý**: Config dùng Unix-style paths (`~/`) để tương thích đa nền tảng. Phiên bản Windows tự động chuyển đổi paths. + Mỗi profile trỏ đến một file settings JSON của Claude. Tạo file settings theo [tài liệu Claude CLI](https://docs.claude.com/en/docs/claude-code/installation). ### Cấu Hình GLM Profile @@ -190,11 +242,14 @@ Các biến này đảm bảo GLM được dùng làm provider mặc định khi ### Cơ Bản ```bash +# Hoạt động trên macOS, Linux, và Windows ccs # Dùng profile mặc định (không args) ccs glm # Dùng GLM profile ccs son # Dùng Sonnet profile ``` +**Lưu ý Windows**: Lệnh hoạt động giống nhau trên PowerShell, CMD, và Git Bash. + ### Với Arguments Tất cả args sau tên profile được truyền trực tiếp cho Claude CLI: @@ -284,11 +339,16 @@ ccs son ## Yêu Cầu +### macOS / Linux - **Bash** 3.2+ - **jq** (để xử lý JSON) - **Claude CLI** đã cài đặt -### Cài jq +### Windows +- **PowerShell** 5.1+ (có sẵn trên Windows 10+) +- **Claude CLI** đã cài đặt + +### Cài jq (chỉ macOS / Linux) **macOS**: ```bash @@ -310,8 +370,46 @@ sudo dnf install jq sudo pacman -S jq ``` +**Lưu ý**: Phiên bản Windows dùng JSON support có sẵn của PowerShell - không cần jq. + ## Troubleshooting +### Vấn Đề Riêng Cho Windows + +#### PowerShell Execution Policy + +Nếu bạn thấy lỗi "cannot be loaded because running scripts is disabled": + +```powershell +# Kiểm tra policy hiện tại +Get-ExecutionPolicy + +# Cho phép user hiện tại chạy scripts (khuyến nghị) +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +# Hoặc chạy với bypass (một lần) +powershell -ExecutionPolicy Bypass -File "$env:USERPROFILE\.ccs\ccs.ps1" glm +``` + +#### PATH chưa được cập nhật (Windows) + +Nếu lệnh `ccs` không tìm thấy sau khi cài đặt: + +1. Khởi động lại terminal +2. Hoặc thêm thủ công vào PATH: + - Mở "Edit environment variables for your account" + - Thêm `%USERPROFILE%\.ccs` vào User PATH + - Khởi động lại terminal + +#### Claude CLI không tìm thấy (Windows) + +```powershell +# Kiểm tra Claude CLI +where.exe claude + +# Nếu thiếu, cài đặt từ tài liệu Claude +``` + ### Vấn Đề Cài Đặt #### Lỗi BASH_SOURCE unbound variable @@ -407,12 +505,14 @@ Error: Profile 'default' not found in ~/.ccs/config.json ## Gỡ Cài Đặt +### macOS / Linux + **Dùng lệnh đã cài**: ```bash ccs-uninstall ``` -**Một dòng lệnh** (nếu ccs-uninstall không có): +**Một dòng lệnh**: ```bash # URL ngắn curl -fsSL ccs.kaitran.ca/uninstall | bash @@ -428,6 +528,37 @@ rm ~/.local/bin/ccs-uninstall rm -rf ~/.ccs # Nếu muốn xóa tất cả files của CCS ``` +### Windows PowerShell + +**Dùng lệnh đã cài**: +```powershell +ccs-uninstall +``` + +**Một dòng lệnh**: +```powershell +# URL ngắn +irm ccs.kaitran.ca/uninstall.ps1 | iex + +# Hoặc trực tiếp từ GitHub +irm https://raw.githubusercontent.com/kaitranntt/ccs/main/uninstall.ps1 | iex +``` + +**Thủ công**: +```powershell +# Xóa scripts +Remove-Item "$env:USERPROFILE\.ccs\ccs.ps1" -Force +Remove-Item "$env:USERPROFILE\.ccs\uninstall.ps1" -Force + +# Xóa khỏi PATH +$Path = [Environment]::GetEnvironmentVariable("Path", "User") +$NewPath = ($Path -split ';' | Where-Object { $_ -ne "$env:USERPROFILE\.ccs" }) -join ';' +[Environment]::SetEnvironmentVariable("Path", $NewPath, "User") + +# Tùy chọn: Xóa tất cả files CCS +Remove-Item "$env:USERPROFILE\.ccs" -Recurse -Force +``` + ## Bảo Mật - ✅ Zero dependencies (chỉ bash + jq) @@ -454,7 +585,7 @@ A: Không. Profile switching hoàn toàn offline. Chỉ cần internet cho API c A: Không. Installer tạo files mới và giữ nguyên configs hiện tại. **Q: CCS có hoạt động trên Windows không?** -A: CCS cần bash. Dùng WSL, Git Bash, hoặc Cygwin trên Windows. +A: Có! CCS bây giờ hỗ trợ Windows PowerShell 5.1+ ngoài macOS/Linux bash. ## Đóng Góp @@ -466,6 +597,12 @@ Contributions được chào đón! Vui lòng: 4. Push lên branch 5. Mở Pull Request +**Guidelines**: +- Duy trì tương thích bash 3.2+ (Unix) và PowerShell 5.1+ (Windows) +- Không dependencies ngoài jq (Unix) hoặc PowerShell có sẵn (Windows) +- Test trên macOS, Linux, và Windows +- Tuân theo code style hiện có + ## License MIT License - xem [LICENSE](LICENSE) để biết chi tiết. diff --git a/ccs.ps1 b/ccs.ps1 new file mode 100644 index 00000000..34382636 --- /dev/null +++ b/ccs.ps1 @@ -0,0 +1,95 @@ +# CCS - Claude Code Switch (Windows PowerShell) +# Cross-platform Claude CLI profile switcher +# https://github.com/kaitranntt/ccs + +param( + [Parameter(Position=0)] + [string]$Profile = "default", + + [Parameter(ValueFromRemainingArguments=$true)] + [string[]]$RemainingArgs +) + +$ErrorActionPreference = "Stop" + +# Config file location (supports environment variable override) +$ConfigFile = if ($env:CCS_CONFIG) { + $env:CCS_CONFIG +} else { + "$env:USERPROFILE\.ccs\config.json" +} + +# Check config exists +if (-not (Test-Path $ConfigFile)) { + Write-Host "Error: Config file not found: $ConfigFile" -ForegroundColor Red + Write-Host "" + Write-Host "Create $env:USERPROFILE\.ccs\config.json with your profile mappings." + Write-Host "See .ccs.example.json for template." + exit 1 +} + +# Validate profile name (alphanumeric, dash, underscore only) +if ($Profile -notmatch '^[a-zA-Z0-9_-]+$') { + Write-Host "Error: Invalid profile name. Use only alphanumeric characters, dash, or underscore." -ForegroundColor Red + exit 1 +} + +# Read and parse JSON config +try { + $ConfigContent = Get-Content $ConfigFile -Raw -ErrorAction Stop + $Config = $ConfigContent | ConvertFrom-Json -ErrorAction Stop +} catch { + Write-Host "Error: Invalid JSON in $ConfigFile" -ForegroundColor Red + exit 1 +} + +# Validate config has profiles object +if (-not $Config.profiles) { + Write-Host "Error: Config must have 'profiles' object" -ForegroundColor Red + Write-Host "See .ccs.example.json for correct format" + exit 1 +} + +# Get settings path for profile +$SettingsPath = $Config.profiles.$Profile + +if (-not $SettingsPath) { + Write-Host "Error: Profile '$Profile' not found in $ConfigFile" -ForegroundColor Red + Write-Host "" + Write-Host "Available profiles:" + $Config.profiles.PSObject.Properties.Name | ForEach-Object { + Write-Host " - $_" + } + exit 1 +} + +# Path expansion and normalization +# 1. Handle Unix-style tilde expansion (~/path -> %USERPROFILE%\path) +if ($SettingsPath -match '^~[/\\]') { + $SettingsPath = $SettingsPath -replace '^~', $env:USERPROFILE +} + +# 2. Expand Windows environment variables (%USERPROFILE%, etc.) +$SettingsPath = [System.Environment]::ExpandEnvironmentVariables($SettingsPath) + +# 3. Convert forward slashes to backslashes (Unix path compatibility) +$SettingsPath = $SettingsPath -replace '/', '\' + +# Validate settings file exists +if (-not (Test-Path $SettingsPath)) { + Write-Host "Error: Settings file not found: $SettingsPath" -ForegroundColor Red + Write-Host "" + Write-Host "Create the settings file or update the path in $ConfigFile" + exit 1 +} + +# Execute claude with settings +# Build argument array to pass all remaining arguments to Claude CLI +$ClaudeArgs = @("--settings", $SettingsPath) +if ($RemainingArgs) { + $ClaudeArgs += $RemainingArgs +} + +# Execute claude and preserve exit code +& claude $ClaudeArgs +exit $LASTEXITCODE diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 00000000..43859091 --- /dev/null +++ b/install.ps1 @@ -0,0 +1,277 @@ +# CCS Installation Script (Windows PowerShell) +# https://github.com/kaitranntt/ccs + +param( + [string]$InstallDir = "$env:USERPROFILE\.ccs" +) + +$ErrorActionPreference = "Stop" + +# Configuration +$CcsDir = "$env:USERPROFILE\.ccs" +$ClaudeDir = "$env:USERPROFILE\.claude" +$GlmModel = "glm-4.6" + +# Detect if running from git repository or standalone +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$InstallMethod = if (Test-Path "$ScriptDir\ccs.ps1") { "git" } else { "standalone" } + +# 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\.z\.ai|glm-4") { + return "glm" + } elseif ($Content -match "ANTHROPIC_BASE_URL" -and $Content -notmatch "api\.z\.ai") { + 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-SonnetTemplate { + $Template = @{ env = @{} } + 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 "✓ 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 " ℹ️ 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-SonnetProfile { + param([string]$Provider) + + $CurrentSettings = "$ClaudeDir\settings.json" + $SonnetSettings = "$CcsDir\sonnet.settings.json" + + if ($Provider -eq "claude" -and (Test-Path $CurrentSettings)) { + Write-Host "✓ Copying current Claude config to profile..." + Copy-Item $CurrentSettings $SonnetSettings + Write-Host " Created: $SonnetSettings" + } else { + Write-Host "Creating Claude Sonnet profile template at $SonnetSettings" + + if (Test-Path $CurrentSettings) { + try { + $Config = Get-Content $CurrentSettings -Raw | ConvertFrom-Json + # Remove GLM-specific vars + if ($Config.env) { + $Config.env.PSObject.Properties.Remove('ANTHROPIC_BASE_URL') + $Config.env.PSObject.Properties.Remove('ANTHROPIC_AUTH_TOKEN') + $Config.env.PSObject.Properties.Remove('ANTHROPIC_MODEL') + } + $Config | ConvertTo-Json -Depth 10 | Set-Content $SonnetSettings + } catch { + New-SonnetTemplate | Set-Content $SonnetSettings + } + } else { + New-SonnetTemplate | Set-Content $SonnetSettings + } + + Write-Host " Created: $SonnetSettings" + Write-Host " ℹ️ This uses your Claude subscription (no API key needed)" + } +} + +# Main Installation + +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/ccs.ps1" -OutFile "$CcsDir\ccs.ps1" -UseBasicParsing + Write-Host "│ ✓ Downloaded ccs.ps1" + } catch { + Write-Host "│" + Write-Host "✗ Error: Failed to download ccs.ps1 from GitHub" -ForegroundColor Red + Write-Host " $_" + exit 1 + } +} else { + # Git install - copy local file + Copy-Item "$ScriptDir\ccs.ps1" "$CcsDir\ccs.ps1" -Force + Write-Host "│ ✓ Installed ccs.ps1" +} + +# Install uninstall script +if (Test-Path "$ScriptDir\uninstall.ps1") { + # Only copy if source and destination are different + if ($ScriptDir -ne $CcsDir) { + Copy-Item "$ScriptDir\uninstall.ps1" "$CcsDir\uninstall.ps1" -Force + } + Write-Host "│ ✓ Installed uninstaller" +} elseif ($InstallMethod -eq "standalone") { + try { + $BaseUrl = "https://raw.githubusercontent.com/kaitranntt/ccs/main" + Invoke-WebRequest -Uri "$BaseUrl/uninstall.ps1" -OutFile "$CcsDir\uninstall.ps1" -UseBasicParsing + Write-Host "│ ✓ Installed uninstaller" + } catch { + Write-Host "│ ⚠️ Could not download uninstaller (optional)" + } +} + +Write-Host "│ ✓ Created directories" +Write-Host "└─" +Write-Host "" + +# Profile Setup + +$CurrentProvider = Detect-CurrentProvider +$GlmSettings = "$CcsDir\glm.settings.json" +$SonnetSettings = "$CcsDir\sonnet.settings.json" + +$ProviderLabel = switch ($CurrentProvider) { + "glm" { " (detected: GLM)" } + "claude" { " (detected: Claude)" } + "custom" { " (detected: custom)" } + default { "" } +} + +Write-Host "┌─ Configuring Profiles$ProviderLabel" + +$NeedsGlmKey = $false + +if (-not (Test-Path $GlmSettings)) { + New-GlmProfile -Provider $CurrentProvider | Out-Null + Write-Host "│ ✓ GLM profile → $env:USERPROFILE\.ccs\glm.settings.json" + if ($CurrentProvider -ne "glm") { $NeedsGlmKey = $true } +} + +if (-not (Test-Path $SonnetSettings)) { + New-SonnetProfile -Provider $CurrentProvider | Out-Null + Write-Host "│ ✓ Sonnet profile → $env:USERPROFILE\.ccs\sonnet.settings.json" +} + +# Create ccs config (using Unix-style paths for cross-platform compatibility) +$ConfigFile = "$CcsDir\config.json" +if (-not (Test-Path $ConfigFile)) { + $ConfigContent = @{ + profiles = @{ + glm = "~/.ccs/glm.settings.json" + son = "~/.ccs/sonnet.settings.json" + default = "~/.claude/settings.json" + } + } + + $ConfigContent | ConvertTo-Json -Depth 10 | Set-Content $ConfigFile + Write-Host "│ ✓ Config → $env:USERPROFILE\.ccs\config.json" +} + +Write-Host "└─" +Write-Host "" + +# Check and update PATH +$UserPath = [Environment]::GetEnvironmentVariable("Path", "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, "User") + + Write-Host " ✓ PATH updated. Restart your terminal for changes to take effect." + Write-Host "" + } catch { + Write-Host " ✗ Could not update PATH automatically." -ForegroundColor Yellow + Write-Host " Please add manually: $CcsDir" + Write-Host "" + } +} + +# Show API key warning if needed +if ($NeedsGlmKey) { + Write-Host "⚠️ ACTION REQUIRED" + Write-Host "" + Write-Host " Edit $env:USERPROFILE\.ccs\glm.settings.json and add your GLM API key" + Write-Host " Replace: YOUR_GLM_API_KEY_HERE" + Write-Host "" +} + +Write-Host "✅ CCS installed successfully!" +Write-Host "" + +# Build quick start based on current provider +if ($CurrentProvider -eq "claude") { + Write-Host " Quick start:" + Write-Host " ccs son # Claude Sonnet (current)" + Write-Host " ccs glm # GLM (after adding API key)" + Write-Host " ccs # Default profile" +} elseif ($CurrentProvider -eq "glm") { + Write-Host " Quick start:" + Write-Host " ccs glm # GLM (current)" + Write-Host " ccs son # Claude Sonnet" + Write-Host " ccs # Default profile" +} else { + Write-Host " Quick start:" + Write-Host " ccs # Default profile" + Write-Host " ccs son # Claude Sonnet" + Write-Host " ccs glm # GLM (after adding API key)" +} + +Write-Host "" +Write-Host " Usage: ccs [profile] [claude-args]" +Write-Host " Example: ccs glm /plan 'implement feature'" +Write-Host "" +Write-Host " To uninstall: ccs-uninstall" +Write-Host "" diff --git a/install.sh b/install.sh index c9dc7e62..3f9b1fb2 100755 --- a/install.sh +++ b/install.sh @@ -27,6 +27,37 @@ else INSTALL_METHOD="standalone" 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() { diff --git a/uninstall.ps1 b/uninstall.ps1 new file mode 100644 index 00000000..9990f6f0 --- /dev/null +++ b/uninstall.ps1 @@ -0,0 +1,56 @@ +# CCS Uninstallation Script (Windows PowerShell) +# https://github.com/kaitranntt/ccs + +$ErrorActionPreference = "Stop" + +Write-Host "Uninstalling ccs..." +Write-Host "" + +$CcsDir = "$env:USERPROFILE\.ccs" + +# Remove ccs.ps1 +if (Test-Path "$CcsDir\ccs.ps1") { + Remove-Item "$CcsDir\ccs.ps1" -Force + Write-Host "✅ Removed: $CcsDir\ccs.ps1" +} else { + Write-Host "ℹ️ No ccs.ps1 found at $CcsDir" +} + +# Remove uninstall script itself (will be last) +$UninstallScript = "$CcsDir\uninstall.ps1" + +# Remove from PATH +$UserPath = [Environment]::GetEnvironmentVariable("Path", "User") +if ($UserPath -like "*$CcsDir*") { + try { + $NewPath = ($UserPath -split ';' | Where-Object { $_ -ne $CcsDir }) -join ';' + [Environment]::SetEnvironmentVariable("Path", $NewPath, "User") + Write-Host "✅ 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-Host "✅ Removed: $CcsDir" + } else { + # If keeping directory, remove uninstall script + if (Test-Path $UninstallScript) { + Remove-Item $UninstallScript -Force + Write-Host "✅ Removed: $UninstallScript" + } + Write-Host "ℹ️ Kept: $CcsDir" + } +} else { + Write-Host "ℹ️ No CCS directory found at $CcsDir" +} + +Write-Host "" +Write-Host "✅ Uninstall complete!" +Write-Host ""