diff --git a/.ccs.example.json b/.ccs.example.json index 547e7f40..a72ad40b 100644 --- a/.ccs.example.json +++ b/.ccs.example.json @@ -2,7 +2,6 @@ "profiles": { "glm": "~/.claude/glm.settings.json", "sonnet": "~/.claude/sonnet.settings.json", - "opus": "~/.claude/opus.settings.json", "default": "~/.claude/settings.json" } } diff --git a/README.md b/README.md index fd957d35..6d6a24c6 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,168 @@ # CCS - Claude Code Switch -Ultra-simple wrapper for `claude --settings`. Switch between Claude profiles with friendly aliases. +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Bash](https://img.shields.io/badge/bash-3.2%2B-blue.svg)](https://www.gnu.org/software/bash/) +[![GitHub Stars](https://img.shields.io/github/stars/kaitranntt/ccs.svg)](https://github.com/kaitranntt/ccs/stargazers) -## Why? +> Ultra-simple Claude CLI profile switcher. One command instead of long paths. -Claude CLI supports `--settings` flag but it's verbose: +**Before**: `claude --settings ~/.claude/glm.settings.json --verbose` +**After**: `ccs glm --verbose` +## Quick Start + +**Install** (one-liner): ```bash -claude --settings ~/.claude/glm.settings.json -claude --settings ~/.claude/sonnet.settings.json --verbose +curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/install.sh | bash ``` -CCS makes it friendly: - +**Configure**: ```bash -ccs glm -ccs sonnet --verbose +# Edit with your profiles +cat > ~/.ccs.json << 'EOF' +{ + "profiles": { + "glm": "~/.claude/glm.settings.json", + "sonnet": "~/.claude/sonnet.settings.json", + "default": "~/.claude/settings.json" + } +} +EOF ``` +**Use**: +```bash +ccs # Use default profile +ccs glm # Use GLM profile +ccs sonnet # Use Sonnet profile +``` + +## Why CCS? + +Claude CLI's `--settings` flag is powerful but verbose. CCS gives you friendly aliases. + +**Features**: +- Single command profile switching +- Pass-through all Claude CLI args +- Zero configuration complexity +- No proxies, no magic—just bash + jq + ## Installation +### One-Liner (Recommended) + +```bash +curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/install.sh | bash +``` + +### Git Clone + ```bash git clone https://github.com/kaitranntt/ccs.git cd ccs ./install.sh ``` -Or as a one-liner: +### Manual ```bash -git clone https://github.com/kaitranntt/ccs.git && cd ccs && ./install.sh +# Download script +curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/ccs -o ~/.local/bin/ccs +chmod +x ~/.local/bin/ccs + +# Ensure ~/.local/bin in PATH +export PATH="$HOME/.local/bin:$PATH" ``` ## Configuration -Copy example config: - -```bash -cp .ccs.example.json ~/.ccs.json -``` - -Edit `~/.ccs.json` with your profiles: +Create `~/.ccs.json` with profile mappings: ```json { "profiles": { "glm": "~/.claude/glm.settings.json", - "sonnet": "~/.claude/sonnet.settings.json" + "sonnet": "~/.claude/sonnet.settings.json", + "default": "~/.claude/settings.json" } } ``` +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). + ## Usage ### Basic ```bash -ccs glm # Use GLM profile -ccs sonnet # Use Sonnet profile +ccs # Use default profile (no args) +ccs glm # Use GLM profile +ccs sonnet # Use Sonnet profile ``` -### Pass Arguments Transparently +### With Arguments -All arguments after the profile name are passed directly to Claude: +All args after profile name pass directly to Claude CLI: ```bash ccs glm --verbose ccs sonnet /plan "add feature" -ccs opus --model claude-opus-4 +ccs default --model claude-sonnet-4 ``` +### Custom Config Location + +```bash +export CCS_CONFIG=~/my-custom-ccs.json +ccs glm +``` + +## Use Cases + +### Claude Subscription + GLM Coding Plan + +Switch between Claude sub and GLM plan: + +```json +{ + "profiles": { + "claude": "~/.claude/claude-sub.settings.json", + "glm": "~/.claude/glm.settings.json", + "default": "~/.claude/settings.json" + } +} +``` + +```bash +ccs claude # Use Claude subscription +ccs glm # Use GLM coding plan +``` + +### Different Models + +```json +{ + "profiles": { + "sonnet": "~/.claude/sonnet.settings.json", + "haiku": "~/.claude/haiku.settings.json", + "default": "~/.claude/settings.json" + } +} +``` + +## How It Works + +1. Reads profile name (defaults to "default" if omitted) +2. Looks up settings file path in `~/.ccs.json` +3. Executes `claude --settings [remaining-args]` + +No magic. No file modification. Pure delegation. + ## Requirements -- `bash` (3.2+ compatible) +- `bash` 3.2+ - `jq` (JSON processor) -- [Claude CLI](https://docs.claude.com/en/docs/claude-code/installation) installed +- [Claude CLI](https://docs.claude.com/en/docs/claude-code/installation) ### Installing jq @@ -87,95 +175,67 @@ sudo apt install jq # Fedora sudo dnf install jq + +# Arch +sudo pacman -S jq ``` -## How It Works - -1. Reads profile name from first argument -2. Looks up settings file path in `~/.ccs.json` -3. Executes `claude --settings [remaining-args]` - -That's it. No magic, no proxies, no file modification. - ## Troubleshooting ### Profile not found -```bash +``` Error: Profile 'foo' not found in ~/.ccs.json ``` -**Solution**: Add the profile to your `~/.ccs.json` config file. +**Fix**: Add profile to `~/.ccs.json`: +```json +{ + "profiles": { + "foo": "~/.claude/foo.settings.json" + } +} +``` ### Settings file missing -```bash +``` Error: Settings file not found: ~/.claude/foo.settings.json ``` -**Solution**: Create the settings file or update the path in your config. +**Fix**: Create settings file or fix path in config. ### jq not installed -```bash +``` Error: jq is required but not installed ``` -**Solution**: Install jq using your package manager (see Requirements section). +**Fix**: Install jq (see Requirements). ### PATH not set -```bash +``` ⚠️ Warning: ~/.local/bin is not in PATH ``` -**Solution**: Add to your shell profile (`~/.bashrc` or `~/.zshrc`): - +**Fix**: Add to `~/.bashrc` or `~/.zshrc`: ```bash export PATH="$HOME/.local/bin:$PATH" ``` +Then `source ~/.bashrc` or restart shell. -Then restart your shell or run `source ~/.bashrc` (or `~/.zshrc`). +### Default profile missing -## Use Cases - -### Claude Subscription + GLM Coding Plan - -If you have both Claude subscription and GLM coding plan: +``` +Error: Profile 'default' not found in ~/.ccs.json +``` +**Fix**: Add "default" profile or always specify profile name: ```json { "profiles": { - "claude": "~/.claude/sonnet.settings.json", - "glm": "~/.claude/glm.settings.json" - } -} -``` - -```bash -ccs claude # Use Claude subscription -ccs glm # Use GLM coding plan -``` - -### Multiple Anthropic Accounts - -```json -{ - "profiles": { - "work": "~/.claude/work.settings.json", - "personal": "~/.claude/personal.settings.json" - } -} -``` - -### Different Models - -```json -{ - "profiles": { - "sonnet": "~/.claude/sonnet.settings.json", - "opus": "~/.claude/opus.settings.json", - "haiku": "~/.claude/haiku.settings.json" + "default": "~/.claude/settings.json" } } ``` @@ -183,22 +243,40 @@ ccs glm # Use GLM coding plan ## Uninstallation ```bash -rm ~/.local/bin/ccs -rm ~/.ccs.json +ccs-uninstall ``` -## License - -MIT +Or manual: +```bash +rm ~/.local/bin/ccs +rm ~/.local/bin/ccs-uninstall +rm ~/.ccs.json # If you want to remove config +``` ## Contributing PRs welcome! Keep it simple (KISS principle). +**Guidelines**: +- Maintain bash 3.2+ compatibility +- No dependencies beyond jq +- Test on macOS and Linux +- Follow existing code style + ## Philosophy -**YAGNI**: We don't add features "just in case" -**KISS**: Simple bash script, no complexity -**DRY**: One source of truth (config file) +- **YAGNI**: No features "just in case" +- **KISS**: Simple bash, no complexity +- **DRY**: One source of truth (config) -This tool does ONE thing well: map profile names to settings files. Nothing more. +This tool does ONE thing well: map profile names to settings files. + +## License + +MIT © [Kai Tran](https://github.com/kaitranntt) + +## Links + +- [Claude CLI Docs](https://docs.claude.com/en/docs/claude-code/installation) +- [Report Issues](https://github.com/kaitranntt/ccs/issues) +- [Changelog](https://github.com/kaitranntt/ccs/releases) diff --git a/ccs b/ccs index d2f0e68e..64848072 100755 --- a/ccs +++ b/ccs @@ -2,16 +2,7 @@ set -euo pipefail CONFIG_FILE="${CCS_CONFIG:-$HOME/.ccs.json}" -PROFILE="${1:-}" - -# Validate usage -if [[ -z "$PROFILE" ]]; then - echo "Usage: ccs [claude-args...]" - echo "Example: ccs glm --verbose" - echo "" - echo "Available profiles are defined in: $CONFIG_FILE" - exit 1 -fi +PROFILE="${1:-default}" # Check config exists if [[ ! -f "$CONFIG_FILE" ]]; then diff --git a/install.sh b/install.sh index a44a8bc5..4d423300 100755 --- a/install.sh +++ b/install.sh @@ -3,12 +3,21 @@ set -euo pipefail INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SHARE_DIR="$HOME/.local/share/ccs" + +# Determine if running from git clone or curl +if [[ -d "$SCRIPT_DIR/.git" ]]; then + INSTALL_METHOD="git" +else + INSTALL_METHOD="standalone" +fi echo "Installing ccs to $INSTALL_DIR..." echo "" # Create install dir if needed mkdir -p "$INSTALL_DIR" +mkdir -p "$SHARE_DIR" # Make executable first chmod +x "$SCRIPT_DIR/ccs" @@ -23,6 +32,23 @@ if [[ ! -L "$INSTALL_DIR/ccs" ]]; then exit 1 fi +# Install uninstall script +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 + 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 +fi + # Check if in PATH if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then echo "⚠️ Warning: $INSTALL_DIR is not in PATH" @@ -35,10 +61,29 @@ fi echo "✅ Installation complete!" echo "" echo "Next steps:" -echo "1. Copy example config: cp $SCRIPT_DIR/.ccs.example.json ~/.ccs.json" -echo "2. Edit ~/.ccs.json with your profile mappings" -echo "3. Run: ccs " +if [[ ! -f "$HOME/.ccs.json" ]]; then + if [[ -f "$SCRIPT_DIR/.ccs.example.json" ]]; then + echo "1. Copy example config: cp $SCRIPT_DIR/.ccs.example.json ~/.ccs.json" + else + echo "1. Create config at ~/.ccs.json:" + echo " {" + echo " \"profiles\": {" + echo " \"glm\": \"~/.claude/glm.settings.json\"," + echo " \"sonnet\": \"~/.claude/sonnet.settings.json\"," + echo " \"default\": \"~/.claude/settings.json\"" + echo " }" + echo " }" + fi + echo "2. Edit ~/.ccs.json with your profile mappings" + echo "3. Run: ccs [profile]" +else + echo "1. Config already exists at ~/.ccs.json" + echo "2. Run: ccs [profile]" +fi echo "" echo "Example:" -echo " ccs glm" +echo " ccs # Uses default profile" +echo " ccs glm # Uses glm profile" echo " ccs sonnet --verbose" +echo "" +echo "To uninstall: ccs-uninstall" diff --git a/uninstall.sh b/uninstall.sh new file mode 100755 index 00000000..0d614fba --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "Uninstalling ccs..." +echo "" + +# Remove symlink +if [[ -L "$HOME/.local/bin/ccs" ]]; then + rm "$HOME/.local/bin/ccs" + echo "✅ Removed: $HOME/.local/bin/ccs" +elif [[ -f "$HOME/.local/bin/ccs" ]]; then + rm "$HOME/.local/bin/ccs" + echo "✅ Removed: $HOME/.local/bin/ccs" +else + echo "ℹ️ No ccs binary found at $HOME/.local/bin/ccs" +fi + +# Remove uninstall symlink +if [[ -L "$HOME/.local/bin/ccs-uninstall" ]]; then + rm "$HOME/.local/bin/ccs-uninstall" + echo "✅ Removed: $HOME/.local/bin/ccs-uninstall" +fi + +# Remove share directory +if [[ -d "$HOME/.local/share/ccs" ]]; then + rm -rf "$HOME/.local/share/ccs" + echo "✅ Removed: $HOME/.local/share/ccs" +fi + +# Ask about config +if [[ -f "$HOME/.ccs.json" ]]; then + read -p "Remove config file ~/.ccs.json? (y/N) " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + rm "$HOME/.ccs.json" + echo "✅ Removed: $HOME/.ccs.json" + else + echo "ℹ️ Kept: $HOME/.ccs.json" + fi +else + echo "ℹ️ No config file found at $HOME/.ccs.json" +fi + +echo "" +echo "✅ Uninstall complete!"