mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 12:14:19 +00:00
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:
1 parent
aa04966ff4
commit
08d8c1f21c
3 files changed
+441
-159
No files matched your search
@@ -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
|
||||
@@ -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
@@ -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"
|
||||
Reference in new issue
Block a user