fix(install): resolve critical standalone install bugs and refactor

- fix BASH_SOURCE unbound variable for piped execution (curl | bash)
- fix missing download logic for standalone installs (added GitHub fetch)
- fix installation method detection (check ccs file, not .git)
- fix git worktree detection (.git file support)
- add GLM default model env vars (ANTHROPIC_DEFAULT_*)
- enhance GLM profile creation (preserve existing API keys)
- improve error handling for mv operations
- refactor install.sh with helper functions (250 lines, cleaner)
- change profile name from 'sonnet' to 'son' (shorter)
- add installation workflow diagram (INSTALL-WORKFLOW.md)
- update documentation with troubleshooting and upgrade guides
This commit is contained in:
kaitranntt committed 2025-11-01 18:44:48 -04:00
1 parent aa04966ff4
commit 08d8c1f21c
3 files changed
+441 -159

No files matched your search

+154
View File
@@ -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<br/>INSTALL_DIR, SHARE_DIR, CLAUDE_DIR]
Init --> DetectMethod{Detect Install Method<br/>ccs file exists?}
DetectMethod -->|Yes: SCRIPT_DIR/ccs| GitMethod[Method: git]
DetectMethod -->|No| StandaloneMethod[Method: standalone]
GitMethod --> CreateDirs[Create Directories<br/>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<br/>chmod +x, ln -sf to INSTALL_DIR]
InstallExec -->|Standalone| DownloadCCS[Download ccs from GitHub<br/>to SHARE_DIR<br/>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<br/>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<br/>+ enhance with jq]
CreateGLM -->|No| CreateGLMTemplate[Create GLM template<br/>+ 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<br/>+ 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<br/>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<br/>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
+93 -16
View File
@@ -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
+194 -143
View File
@@ -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"