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"