diff --git a/INSTALL-WORKFLOW.md b/INSTALL-WORKFLOW.md new file mode 100644 index 00000000..87c6128a --- /dev/null +++ b/INSTALL-WORKFLOW.md @@ -0,0 +1,154 @@ +# CCS Installation Workflow + +This diagram illustrates the installation flow for the `ccs` (claude-code-switch) tool, including initialization, installation, profile setup, and completion steps. + +## Workflow Diagram + +```mermaid +flowchart TD + Start([Start Installation]) --> Init[Initialize Configuration
INSTALL_DIR, SHARE_DIR, CLAUDE_DIR] + + Init --> DetectMethod{Detect Install Method
ccs file exists?} + DetectMethod -->|Yes: SCRIPT_DIR/ccs| GitMethod[Method: git] + DetectMethod -->|No| StandaloneMethod[Method: standalone] + + GitMethod --> CreateDirs[Create Directories
mkdir INSTALL_DIR, SHARE_DIR] + StandaloneMethod --> CheckCurl{curl available?} + CheckCurl -->|No| ErrorCurl[❌ Error: curl required] + CheckCurl -->|Yes| CreateDirs + + ErrorCurl --> Exit0([Exit 1]) + + CreateDirs --> InstallExec{Install Method?} + InstallExec -->|Git| InstallGit[Use local ccs from SCRIPT_DIR
chmod +x, ln -sf to INSTALL_DIR] + InstallExec -->|Standalone| DownloadCCS[Download ccs from GitHub
to SHARE_DIR
chmod +x, ln -sf to INSTALL_DIR] + + InstallGit --> CheckSymlink{Symlink Created?} + DownloadCCS --> CheckDownload{Download success?} + CheckDownload -->|No| ErrorDownload[❌ Error: Failed to download] + CheckDownload -->|Yes| CheckSymlink + ErrorDownload --> Exit5([Exit 1]) + CheckSymlink -->|No| ErrorSymlink[❌ Error: Symlink Failed] + CheckSymlink -->|Yes| InstallUninstaller + + ErrorSymlink --> Exit1([Exit 1]) + + InstallUninstaller{uninstall.sh exists?} + InstallUninstaller -->|Local file| CopyUninstall[Copy uninstall.sh] + InstallUninstaller -->|Standalone + curl| FetchUninstall[Fetch from GitHub] + InstallUninstall -->|Neither| SkipUninstall + + CopyUninstall --> CheckPath + FetchUninstall --> CheckPath + SkipUninstall --> CheckPath + + CheckPath{INSTALL_DIR in PATH?} + CheckPath -->|No| WarnPath[⚠️ Warn: Add to PATH] + CheckPath -->|Yes| DetectProvider + WarnPath --> DetectProvider + + DetectProvider[Detect Current Provider
check settings.json] --> ProviderResult{Provider?} + ProviderResult -->|glm| SetGLM[Provider: glm] + ProviderResult -->|claude| SetClaude[Provider: claude] + ProviderResult -->|custom| SetCustom[Provider: custom] + ProviderResult -->|unknown| SetUnknown[Provider: unknown] + + SetGLM --> CheckProfiles + SetClaude --> CheckProfiles + SetCustom --> CheckProfiles + SetUnknown --> CheckProfiles + + CheckProfiles{Missing Profiles?} + CheckProfiles -->|GLM missing| CreateGLM + CheckProfiles -->|Sonnet missing| CreateSonnet + CheckProfiles -->|Both missing| CreateBoth + CheckProfiles -->|None missing| CreateCCSConfig + + CreateGLM{Current Provider = glm?} + CreateGLM -->|Yes| CopyGLMConfig[Copy current config
+ enhance with jq] + CreateGLM -->|No| CreateGLMTemplate[Create GLM template
+ merge with jq if available] + + CopyGLMConfig --> CheckJQGLM{jq available?} + CheckJQGLM -->|Yes| EnhanceGLM[Add model settings] + CheckJQGLM -->|No| CopyAsIsGLM[Copy as-is] + EnhanceGLM --> AtomicMVGLM + CopyAsIsGLM --> AtomicMVGLM + + CreateGLMTemplate --> CheckJQTemplate{jq available?} + CheckJQTemplate -->|Yes| MergeTemplate[Merge with current] + CheckJQTemplate -->|No| BasicTemplate[Use basic template] + MergeTemplate --> CheckMerge{Merge success?} + CheckMerge -->|Yes| AtomicMVGLM[atomic_mv to glm.settings.json] + CheckMerge -->|No| BasicTemplate + BasicTemplate --> WriteGLM[Write glm.settings.json] + + AtomicMVGLM --> CheckAtomicGLM{Move success?} + CheckAtomicGLM -->|No| ErrorAtomicGLM[❌ Error: Permission denied] + CheckAtomicGLM -->|Yes| WarnAPIKey + WriteGLM --> WarnAPIKey[⚠️ Warn: Replace API key] + + ErrorAtomicGLM --> Exit2([Exit 1]) + + WarnAPIKey --> CreateSonnet + + CreateBoth --> CreateGLM + + CreateSonnet{Current Provider = claude?} + CreateSonnet -->|Yes| CopySonnetConfig[Copy current config] + CreateSonnet -->|No| CreateSonnetTemplate[Create Sonnet template
+ remove custom settings with jq] + + CopySonnetConfig --> WriteSonnet[Write sonnet.settings.json] + CreateSonnetTemplate --> CheckJQSonnet{jq available?} + CheckJQSonnet -->|Yes| RemoveCustom[Remove ANTHROPIC_BASE_URL, etc.] + CheckJQSonnet -->|No| BasicSonnet[Use basic template] + RemoveCustom --> CheckRemove{Remove success?} + CheckRemove -->|Yes| AtomicMVSonnet[atomic_mv to sonnet.settings.json] + CheckRemove -->|No| BasicSonnet + BasicSonnet --> WriteSonnet + + AtomicMVSonnet --> CheckAtomicSonnet{Move success?} + CheckAtomicSonnet -->|No| ErrorAtomicSonnet[❌ Error: Permission denied] + CheckAtomicSonnet -->|Yes| WriteSonnet + + ErrorAtomicSonnet --> Exit3([Exit 1]) + + WriteSonnet --> CreateCCSConfig + + CreateCCSConfig{~/.ccs.json exists?} + CreateCCSConfig -->|No| WriteCCSConfig[Create ~/.ccs.json
with profile mappings] + CreateCCSConfig -->|Yes| SkipCCSConfig[Skip: Already exists] + + WriteCCSConfig --> CheckCCSWrite{Write success?} + CheckCCSWrite -->|No| ErrorCCSWrite[❌ Error: Permission denied] + CheckCCSWrite -->|Yes| Complete + + ErrorCCSWrite --> Exit4([Exit 1]) + SkipCCSConfig --> Complete + + Complete[✅ Setup Complete
Display Quick Start Guide] --> End([End]) +``` + +## Key Decision Points + +1. **Installation Method**: Detects `ccs` file existence in SCRIPT_DIR (not `.git`) + - Git install: `ccs` file exists → use local file + - Standalone: no `ccs` file → download from GitHub +2. **Provider Detection**: Analyzes `~/.claude/settings.json` to determine current provider (glm, claude, custom, unknown) +3. **Profile Creation**: Creates missing profile files based on current provider +4. **jq Enhancement**: Uses jq for JSON manipulation if available, falls back to basic templates +5. **Atomic Operations**: Uses atomic_mv for safe file operations with permission checks + +## Error Handling Paths + +- curl not available (standalone install) → Exit 1 +- GitHub download failure (standalone install) → Exit 1 +- Symlink creation failure → Exit 1 +- Atomic file move failures (permissions) → Exit 1 +- Missing PATH warning (non-fatal) +- Missing API key warning (non-fatal) + +## Profile Templates + +- **GLM Profile**: Configured for api.z.ai with glm-4.6 model +- **Sonnet Profile**: Default Claude configuration (no custom base URL) +- **CCS Config**: Maps profile shortcuts (glm, son, default) to settings files diff --git a/README.md b/README.md index daf093fb..fdd7f1ce 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Manual switching is tedious and error-prone. **The Solution**: ```bash -ccs sonnet # Complex refactoring? Use Claude Sonnet 4.5 +ccs son # Complex refactoring? Use Claude Sonnet 4.5 ccs glm # Simple bug fix? Use GLM 4.6 # Hit rate limit? Switch instantly: ccs glm # Continue working with GLM @@ -47,7 +47,7 @@ EOF ```bash ccs # Use default profile ccs glm # Use GLM profile -ccs sonnet # Use Sonnet profile +ccs son # Use Sonnet profile ``` ## Why CCS? @@ -72,7 +72,7 @@ ccs sonnet # Use Sonnet profile **With CCS**: Switch models based on task complexity, maximize quality while managing costs. ```bash -ccs sonnet # Planning new feature architecture +ccs son # Planning new feature architecture # Got the plan? Implement with GLM: ccs glm # Write the straightforward code ``` @@ -85,7 +85,7 @@ If you have both Claude subscription and GLM Coding Plan, you know the pain: - Repeat 10x per day **CCS solves this**: -- One command to switch: `ccs glm` or `ccs sonnet` +- One command to switch: `ccs glm` or `ccs son` - Keep both configs saved as profiles - Switch in <1 second - No file editing, no copy-paste, no mistakes @@ -105,6 +105,8 @@ If you have both Claude subscription and GLM Coding Plan, you know the pain: 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`). + ### Git Clone ```bash @@ -113,6 +115,8 @@ cd ccs ./install.sh ``` +**Note**: Works with git worktrees and submodules - the installer detects both `.git` directory and `.git` file. + ### Manual ```bash @@ -124,9 +128,23 @@ chmod +x ~/.local/bin/ccs export PATH="$HOME/.local/bin:$PATH" ``` +### Upgrade + +**From git clone**: +```bash +cd ccs && git pull && ./install.sh +``` + +**From curl install**: +```bash +curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/install.sh | bash +``` + +**Note**: Upgrading preserves your existing API keys and settings. The installer only adds new features without overwriting your configuration. + ## Configuration -Create `~/.ccs.json` with profile mappings: +The installer auto-creates `~/.ccs.json` and profile templates during installation. If you need to customize: ```json { @@ -140,6 +158,30 @@ Create `~/.ccs.json` with profile mappings: 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 + +The installer automatically configures GLM profiles with enhanced environment variables: + +```json +{ + "env": { + "ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic", + "ANTHROPIC_AUTH_TOKEN": "YOUR_GLM_API_KEY_HERE", + "ANTHROPIC_MODEL": "glm-4.6", + "ANTHROPIC_DEFAULT_OPUS_MODEL": "glm-4.6", + "ANTHROPIC_DEFAULT_SONNET_MODEL": "glm-4.6", + "ANTHROPIC_DEFAULT_HAIKU_MODEL": "glm-4.6" + } +} +``` + +**Environment Variables**: +- `ANTHROPIC_DEFAULT_OPUS_MODEL`: Default model for Opus-class requests +- `ANTHROPIC_DEFAULT_SONNET_MODEL`: Default model for Sonnet-class requests +- `ANTHROPIC_DEFAULT_HAIKU_MODEL`: Default model for Haiku-class requests + +These variables ensure GLM is used as the default provider when switching profiles. + ## Usage ### Basic @@ -147,7 +189,7 @@ Each profile points to a Claude settings JSON file. Create settings files per [C ```bash ccs # Use default profile (no args) ccs glm # Use GLM profile -ccs sonnet # Use Sonnet profile +ccs son # Use Sonnet profile ``` ### With Arguments @@ -156,7 +198,7 @@ All args after profile name pass directly to Claude CLI: ```bash ccs glm --verbose -ccs sonnet /plan "add feature" +ccs son /plan "add feature" ccs default --model claude-sonnet-4 ``` @@ -175,7 +217,7 @@ ccs glm ```bash # Step 1: Architecture & Planning (needs Claude's intelligence) -ccs sonnet +ccs son /plan "Design payment integration with Stripe, handle webhooks, errors, retries" # → Claude Sonnet 4.5 thinks deeply about edge cases, security, architecture @@ -185,7 +227,7 @@ ccs glm # → GLM 4.6 writes the code efficiently, saves Claude usage # Step 3: Code Review (needs deep analysis) -ccs sonnet +ccs son /review "check the payment handler for security issues" # → Claude Sonnet 4.5 catches subtle vulnerabilities @@ -201,7 +243,7 @@ ccs glm ```bash # Working on complex refactoring with Claude -ccs sonnet +ccs son /plan "refactor authentication system" # Claude hits rate limit mid-task @@ -212,7 +254,7 @@ ccs glm # Continue working without interruption # Rate limit resets? Switch back -ccs sonnet +ccs son ``` ### Configuration Examples @@ -272,7 +314,30 @@ sudo pacman -S jq ## Troubleshooting -### Profile not found +### Installation Issues + +#### BASH_SOURCE unbound variable error + +This error occurs when running the installer in some shells or environments. + +**Fixed in latest version**: The installer now handles both piped execution (`curl | bash`) and direct execution (`./install.sh`). + +**Solution**: Upgrade to the latest version: +```bash +curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/install.sh | bash +``` + +#### Git worktree not detected + +If installing from a git worktree or submodule, older versions may fail to detect the git repository. + +**Fixed in latest version**: The installer now detects both `.git` directory (standard clone) and `.git` file (worktree/submodule). + +**Solution**: Upgrade to the latest version or use the curl installation method. + +### Configuration Issues + +#### Profile not found ``` Error: Profile 'foo' not found in ~/.ccs.json @@ -287,7 +352,7 @@ Error: Profile 'foo' not found in ~/.ccs.json } ``` -### Settings file missing +#### Settings file missing ``` Error: Settings file not found: ~/.claude/foo.settings.json @@ -295,7 +360,7 @@ Error: Settings file not found: ~/.claude/foo.settings.json **Fix**: Create settings file or fix path in config. -### jq not installed +#### jq not installed ``` Error: jq is required but not installed @@ -303,7 +368,11 @@ Error: jq is required but not installed **Fix**: Install jq (see Requirements). -### PATH not set +**Note**: The installer creates basic templates even without jq, but enhanced features require jq. + +### Environment Issues + +#### PATH not set ``` ⚠️ Warning: ~/.local/bin is not in PATH @@ -315,7 +384,7 @@ export PATH="$HOME/.local/bin:$PATH" ``` Then `source ~/.bashrc` or restart shell. -### Default profile missing +#### Default profile missing ``` Error: Profile 'default' not found in ~/.ccs.json @@ -330,6 +399,14 @@ Error: Profile 'default' not found in ~/.ccs.json } ``` +### Upgrade Issues + +#### API keys lost after upgrade + +**Not a problem**: The installer preserves existing API keys when upgrading. If you're using GLM, your API key is automatically preserved and the profile is enhanced with new default model variables. + +**Verification**: Check `~/.claude/glm.settings.json` - your `ANTHROPIC_AUTH_TOKEN` should still be present. + ## Uninstallation ```bash diff --git a/install.sh b/install.sh index 8df38f2a..cc3cc269 100755 --- a/install.sh +++ b/install.sh @@ -1,31 +1,192 @@ #!/usr/bin/env bash set -euo pipefail -INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}" -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SHARE_DIR="$HOME/.local/share/ccs" +# ============================================================================ +# CCS Installation Script +# ============================================================================ -# Determine if running from git clone or curl -if [[ -d "$SCRIPT_DIR/.git" ]]; then +# --- Configuration --- +INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}" +SHARE_DIR="$HOME/.local/share/ccs" +CLAUDE_DIR="$HOME/.claude" +GLM_MODEL="glm-4.6" + +# 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 (real git install) +# Don't just check .git (user might run curl | bash inside their own git repo) +if [[ -f "$SCRIPT_DIR/ccs" ]]; then INSTALL_METHOD="git" else INSTALL_METHOD="standalone" fi +# --- Helper Functions --- + +detect_current_provider() { + local settings="$CLAUDE_DIR/settings.json" + if [[ ! -f "$settings" ]]; then + echo "unknown" + return + fi + + if 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" "$settings" 2>/dev/null; then + echo "custom" + else + echo "claude" + 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_sonnet_template() { + cat << 'EOF' +{ + "env": {} +} +EOF +} + +atomic_mv() { + local src="$1" + local dest="$2" + if mv "$src" "$dest" 2>/dev/null; then + return 0 + else + rm -f "$src" + echo " ❌ Error: Failed to create $dest (check permissions)" + exit 1 + fi +} + +create_glm_profile() { + local current_settings="$CLAUDE_DIR/settings.json" + local glm_settings="$CLAUDE_DIR/glm.settings.json" + local provider="$1" + + if [[ "$provider" == "glm" ]]; then + echo "✓ 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 " ℹ️ 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_sonnet_profile() { + local current_settings="$CLAUDE_DIR/settings.json" + local sonnet_settings="$CLAUDE_DIR/sonnet.settings.json" + local provider="$1" + + if [[ "$provider" == "claude" ]]; then + echo "✓ Copying current Claude config to profile..." + cp "$current_settings" "$sonnet_settings" + echo " Created: $sonnet_settings" + else + echo "Creating Claude Sonnet profile template at $sonnet_settings" + if [[ -f "$current_settings" ]] && command -v jq &> /dev/null; then + if jq 'del(.env.ANTHROPIC_BASE_URL, .env.ANTHROPIC_AUTH_TOKEN, .env.ANTHROPIC_MODEL) | + if (.env | length) == 0 then .env = {} else . end' "$current_settings" > "$sonnet_settings.tmp" 2>/dev/null; then + atomic_mv "$sonnet_settings.tmp" "$sonnet_settings" + else + rm -f "$sonnet_settings.tmp" + echo " ℹ️ jq failed, using basic template" + create_sonnet_template > "$sonnet_settings" + fi + else + create_sonnet_template > "$sonnet_settings" + fi + echo " Created: $sonnet_settings" + echo " ℹ️ This uses your Claude subscription (no API key needed)" + fi +} + +# --- Main Installation --- + echo "Installing ccs to $INSTALL_DIR..." echo "" -# Create install dir if needed -mkdir -p "$INSTALL_DIR" -mkdir -p "$SHARE_DIR" +# Create directories +mkdir -p "$INSTALL_DIR" "$SHARE_DIR" -# Make executable first -chmod +x "$SCRIPT_DIR/ccs" +# Install main executable +if [[ "$INSTALL_METHOD" == "standalone" ]]; then + # Standalone install - download ccs from GitHub + if ! command -v curl &> /dev/null; then + echo "❌ Error: curl is required for standalone installation" + exit 1 + fi -# Symlink ccs script -ln -sf "$SCRIPT_DIR/ccs" "$INSTALL_DIR/ccs" + echo "Fetching ccs executable..." + if curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/ccs -o "$SHARE_DIR/ccs"; then + chmod +x "$SHARE_DIR/ccs" + ln -sf "$SHARE_DIR/ccs" "$INSTALL_DIR/ccs" + else + echo "❌ Error: Failed to download ccs from GitHub" + exit 1 + fi +else + # Git install - use local ccs file + chmod +x "$SCRIPT_DIR/ccs" + ln -sf "$SCRIPT_DIR/ccs" "$INSTALL_DIR/ccs" +fi -# Verify installation if [[ ! -L "$INSTALL_DIR/ccs" ]]; then echo "❌ Error: Failed to create symlink at $INSTALL_DIR/ccs" echo "Check directory permissions and try again." @@ -37,19 +198,14 @@ if [[ -f "$SCRIPT_DIR/uninstall.sh" ]]; then cp "$SCRIPT_DIR/uninstall.sh" "$SHARE_DIR/uninstall.sh" chmod +x "$SHARE_DIR/uninstall.sh" ln -sf "$SHARE_DIR/uninstall.sh" "$INSTALL_DIR/ccs-uninstall" -elif [[ "$INSTALL_METHOD" == "standalone" ]]; then - # Fetch uninstall script for curl installs +elif [[ "$INSTALL_METHOD" == "standalone" ]] && command -v curl &> /dev/null; then echo "Fetching uninstall script..." - if command -v curl &> /dev/null; then - curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/uninstall.sh -o "$SHARE_DIR/uninstall.sh" - chmod +x "$SHARE_DIR/uninstall.sh" - ln -sf "$SHARE_DIR/uninstall.sh" "$INSTALL_DIR/ccs-uninstall" - else - echo "⚠️ Warning: curl not found, skipping uninstall script" - fi + curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/uninstall.sh -o "$SHARE_DIR/uninstall.sh" + chmod +x "$SHARE_DIR/uninstall.sh" + ln -sf "$SHARE_DIR/uninstall.sh" "$INSTALL_DIR/ccs-uninstall" fi -# Check if in PATH +# Check PATH if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then echo "⚠️ Warning: $INSTALL_DIR is not in PATH" echo "" @@ -61,143 +217,37 @@ fi echo "✅ Installation complete!" echo "" -# Smart setup: detect current provider and help create profiles -CLAUDE_DIR="$HOME/.claude" -CURRENT_SETTINGS="$CLAUDE_DIR/settings.json" +# --- Profile Setup --- + +CURRENT_PROVIDER=$(detect_current_provider) GLM_SETTINGS="$CLAUDE_DIR/glm.settings.json" SONNET_SETTINGS="$CLAUDE_DIR/sonnet.settings.json" -# Detect current provider -CURRENT_PROVIDER="unknown" -if [[ -f "$CURRENT_SETTINGS" ]]; then - if grep -q "api.z.ai" "$CURRENT_SETTINGS" 2>/dev/null || grep -q "glm-4" "$CURRENT_SETTINGS" 2>/dev/null; then - CURRENT_PROVIDER="glm" - elif grep -q "ANTHROPIC_BASE_URL" "$CURRENT_SETTINGS" 2>/dev/null && ! grep -q "api.z.ai" "$CURRENT_SETTINGS" 2>/dev/null; then - CURRENT_PROVIDER="custom" - else - CURRENT_PROVIDER="claude" - fi -fi +[[ "$CURRENT_PROVIDER" != "unknown" ]] && echo "🔍 Detected current provider: $CURRENT_PROVIDER" && echo "" -# Check for existing profile files -HAS_GLM=false -HAS_SONNET=false -[[ -f "$GLM_SETTINGS" ]] && HAS_GLM=true -[[ -f "$SONNET_SETTINGS" ]] && HAS_SONNET=true - -# Smart setup guidance -if [[ "$CURRENT_PROVIDER" != "unknown" ]]; then - echo "🔍 Detected current provider: $CURRENT_PROVIDER" - echo "" -fi - -# Offer to create missing profiles -if [[ "$HAS_GLM" == false ]] || [[ "$HAS_SONNET" == false ]]; then +# Create missing profiles +if [[ ! -f "$GLM_SETTINGS" ]] || [[ ! -f "$SONNET_SETTINGS" ]]; then echo "📝 Setup wizard: Creating profile files..." echo "" - # Create GLM profile if missing - if [[ "$HAS_GLM" == false ]]; then - if [[ "$CURRENT_PROVIDER" == "glm" ]]; then - echo "✓ Copying current GLM config to profile..." - cp "$CURRENT_SETTINGS" "$GLM_SETTINGS" - echo " Created: $GLM_SETTINGS" - else - echo "Creating GLM profile template at $GLM_SETTINGS" - # Preserve user's existing env vars if they exist - if [[ -f "$CURRENT_SETTINGS" ]] && command -v jq &> /dev/null; then - # Merge user's env with GLM essentials (atomic operation) - if jq '.env |= (. // {}) + { - "ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic", - "ANTHROPIC_AUTH_TOKEN": "YOUR_GLM_API_KEY_HERE", - "ANTHROPIC_MODEL": "glm-4.6" - }' "$CURRENT_SETTINGS" > "$GLM_SETTINGS.tmp" 2>/dev/null; then - mv "$GLM_SETTINGS.tmp" "$GLM_SETTINGS" - else - rm -f "$GLM_SETTINGS.tmp" - echo " ℹ️ jq failed, using basic template" - # Fallback to minimal template - cat > "$GLM_SETTINGS" << 'EOF' -{ - "env": { - "ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic", - "ANTHROPIC_AUTH_TOKEN": "YOUR_GLM_API_KEY_HERE", - "ANTHROPIC_MODEL": "glm-4.6" - } -} -EOF - fi - else - # Minimal GLM template - cat > "$GLM_SETTINGS" << 'EOF' -{ - "env": { - "ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic", - "ANTHROPIC_AUTH_TOKEN": "YOUR_GLM_API_KEY_HERE", - "ANTHROPIC_MODEL": "glm-4.6" - } -} -EOF - fi - echo " Created: $GLM_SETTINGS" - echo " ⚠️ Edit this file and replace YOUR_GLM_API_KEY_HERE with your actual GLM API key" - fi - echo "" - fi - - # Create Sonnet profile if missing - if [[ "$HAS_SONNET" == false ]]; then - if [[ "$CURRENT_PROVIDER" == "claude" ]]; then - echo "✓ Copying current Claude config to profile..." - cp "$CURRENT_SETTINGS" "$SONNET_SETTINGS" - echo " Created: $SONNET_SETTINGS" - else - echo "Creating Claude Sonnet profile template at $SONNET_SETTINGS" - # Preserve user's existing settings, remove GLM-specific fields - if [[ -f "$CURRENT_SETTINGS" ]] && command -v jq &> /dev/null; then - # Remove GLM-specific env vars, keep user's other settings (atomic) - if jq 'del(.env.ANTHROPIC_BASE_URL, .env.ANTHROPIC_AUTH_TOKEN, .env.ANTHROPIC_MODEL) | - if (.env | length) == 0 then .env = {} else . end' "$CURRENT_SETTINGS" > "$SONNET_SETTINGS.tmp" 2>/dev/null; then - mv "$SONNET_SETTINGS.tmp" "$SONNET_SETTINGS" - else - rm -f "$SONNET_SETTINGS.tmp" - echo " ℹ️ jq failed, using basic template" - # Fallback to minimal template - cat > "$SONNET_SETTINGS" << 'EOF' -{ - "env": {} -} -EOF - fi - else - # Minimal Claude template (uses default authentication) - cat > "$SONNET_SETTINGS" << 'EOF' -{ - "env": {} -} -EOF - fi - echo " Created: $SONNET_SETTINGS" - echo " ℹ️ This uses your Claude subscription (no API key needed)" - fi - echo "" - fi + [[ ! -f "$GLM_SETTINGS" ]] && create_glm_profile "$CURRENT_PROVIDER" && echo "" + [[ ! -f "$SONNET_SETTINGS" ]] && create_sonnet_profile "$CURRENT_PROVIDER" && echo "" fi -# Auto-create ~/.ccs.json if missing (atomic operation) +# Create ccs config if [[ ! -f "$HOME/.ccs.json" ]]; then echo "Creating ~/.ccs.json config..." cat > "$HOME/.ccs.json.tmp" << 'EOF' { "profiles": { "glm": "~/.claude/glm.settings.json", - "sonnet": "~/.claude/sonnet.settings.json", + "son": "~/.claude/sonnet.settings.json", "default": "~/.claude/settings.json" } } EOF - # Atomic move only if file still doesn't exist (prevents race condition) - if mv -n "$HOME/.ccs.json.tmp" "$HOME/.ccs.json" 2>/dev/null; then + if [[ ! -f "$HOME/.ccs.json" ]]; then + atomic_mv "$HOME/.ccs.json.tmp" "$HOME/.ccs.json" echo " ✓ Created: ~/.ccs.json" else rm -f "$HOME/.ccs.json.tmp" @@ -212,7 +262,8 @@ echo "Quick start:" echo "" echo "Example:" echo " ccs # Uses default profile" -echo " ccs glm # Uses glm profile" -echo " ccs sonnet --verbose" +echo " ccs glm # Uses GLM profile" +echo " ccs son # Uses Claude Sonnet profile" +echo " ccs son --verbose" echo "" echo "To uninstall: ccs-uninstall"