mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 16:19:27 +00:00
feat: initial commit - ultra-simple claude code switcher
- Core ccs bash script with profile switching - Installation script with PATH verification - Example config template - Comprehensive README with troubleshooting - Security: input validation, injection prevention - MIT license
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"profiles": {
|
||||
"glm": "~/.claude/glm.settings.json",
|
||||
"sonnet": "~/.claude/sonnet.settings.json",
|
||||
"opus": "~/.claude/opus.settings.json",
|
||||
"default": "~/.claude/settings.json"
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
# User config files
|
||||
.ccs.json
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Editor
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 CCS Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,204 @@
|
||||
# CCS - Claude Code Switch
|
||||
|
||||
Ultra-simple wrapper for `claude --settings`. Switch between Claude profiles with friendly aliases.
|
||||
|
||||
## Why?
|
||||
|
||||
Claude CLI supports `--settings` flag but it's verbose:
|
||||
|
||||
```bash
|
||||
claude --settings ~/.claude/glm.settings.json
|
||||
claude --settings ~/.claude/sonnet.settings.json --verbose
|
||||
```
|
||||
|
||||
CCS makes it friendly:
|
||||
|
||||
```bash
|
||||
ccs glm
|
||||
ccs sonnet --verbose
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
git clone https://github.com/yourusername/ccs.git
|
||||
cd ccs
|
||||
./install.sh
|
||||
```
|
||||
|
||||
Or as a one-liner:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/yourusername/ccs.git && cd ccs && ./install.sh
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Copy example config:
|
||||
|
||||
```bash
|
||||
cp .ccs.example.json ~/.ccs.json
|
||||
```
|
||||
|
||||
Edit `~/.ccs.json` with your profiles:
|
||||
|
||||
```json
|
||||
{
|
||||
"profiles": {
|
||||
"glm": "~/.claude/glm.settings.json",
|
||||
"sonnet": "~/.claude/sonnet.settings.json"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic
|
||||
|
||||
```bash
|
||||
ccs glm # Use GLM profile
|
||||
ccs sonnet # Use Sonnet profile
|
||||
```
|
||||
|
||||
### Pass Arguments Transparently
|
||||
|
||||
All arguments after the profile name are passed directly to Claude:
|
||||
|
||||
```bash
|
||||
ccs glm --verbose
|
||||
ccs sonnet /plan "add feature"
|
||||
ccs opus --model claude-opus-4
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- `bash` (3.2+ compatible)
|
||||
- `jq` (JSON processor)
|
||||
- [Claude CLI](https://docs.claude.com/en/docs/claude-code/installation) installed
|
||||
|
||||
### Installing jq
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install jq
|
||||
|
||||
# Ubuntu/Debian
|
||||
sudo apt install jq
|
||||
|
||||
# Fedora
|
||||
sudo dnf install jq
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Reads profile name from first argument
|
||||
2. Looks up settings file path in `~/.ccs.json`
|
||||
3. Executes `claude --settings <path> [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.
|
||||
|
||||
### 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.
|
||||
|
||||
### jq not installed
|
||||
|
||||
```bash
|
||||
Error: jq is required but not installed
|
||||
```
|
||||
|
||||
**Solution**: Install jq using your package manager (see Requirements section).
|
||||
|
||||
### PATH not set
|
||||
|
||||
```bash
|
||||
⚠️ Warning: ~/.local/bin is not in PATH
|
||||
```
|
||||
|
||||
**Solution**: Add to your shell profile (`~/.bashrc` or `~/.zshrc`):
|
||||
|
||||
```bash
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
```
|
||||
|
||||
Then restart your shell or run `source ~/.bashrc` (or `~/.zshrc`).
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Claude Subscription + GLM Coding Plan
|
||||
|
||||
If you have both Claude subscription and GLM coding plan:
|
||||
|
||||
```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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Uninstallation
|
||||
|
||||
```bash
|
||||
rm ~/.local/bin/ccs
|
||||
rm ~/.ccs.json
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Contributing
|
||||
|
||||
PRs welcome! Keep it simple (KISS principle).
|
||||
|
||||
## Philosophy
|
||||
|
||||
**YAGNI**: We don't add features "just in case"
|
||||
**KISS**: Simple bash script, no complexity
|
||||
**DRY**: One source of truth (config file)
|
||||
|
||||
This tool does ONE thing well: map profile names to settings files. Nothing more.
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CONFIG_FILE="${CCS_CONFIG:-$HOME/.ccs.json}"
|
||||
PROFILE="${1:-}"
|
||||
|
||||
# Validate usage
|
||||
if [[ -z "$PROFILE" ]]; then
|
||||
echo "Usage: ccs <profile> [claude-args...]"
|
||||
echo "Example: ccs glm --verbose"
|
||||
echo ""
|
||||
echo "Available profiles are defined in: $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check config exists
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
echo "Error: Config file not found: $CONFIG_FILE"
|
||||
echo ""
|
||||
echo "Create ~/.ccs.json with your profile mappings."
|
||||
echo "See .ccs.example.json for template."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check jq installed
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "Error: jq is required but not installed"
|
||||
echo ""
|
||||
echo "Install jq:"
|
||||
echo " macOS: brew install jq"
|
||||
echo " Ubuntu: sudo apt install jq"
|
||||
echo " Fedora: sudo dnf install jq"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate profile name (alphanumeric, dash, underscore only)
|
||||
if [[ "$PROFILE" =~ [^a-zA-Z0-9_-] ]]; then
|
||||
echo "Error: Invalid profile name. Use only alphanumeric characters, dash, or underscore."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate JSON syntax
|
||||
if ! jq -e . "$CONFIG_FILE" &>/dev/null; then
|
||||
echo "Error: Invalid JSON in $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate config has profiles object
|
||||
if ! jq -e '.profiles' "$CONFIG_FILE" &>/dev/null; then
|
||||
echo "Error: Config must have 'profiles' object"
|
||||
echo "See .ccs.example.json for correct format"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get settings path for profile (using --arg to prevent injection)
|
||||
SETTINGS_PATH=$(jq -r --arg profile "$PROFILE" '.profiles[$profile] // empty' "$CONFIG_FILE")
|
||||
|
||||
if [[ -z "$SETTINGS_PATH" ]]; then
|
||||
echo "Error: Profile '$PROFILE' not found in $CONFIG_FILE"
|
||||
echo ""
|
||||
echo "Available profiles:"
|
||||
jq -r '.profiles | keys[]' "$CONFIG_FILE" 2>/dev/null | sed 's/^/ - /'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Expand ~ in path
|
||||
SETTINGS_PATH="${SETTINGS_PATH/#\~/$HOME}"
|
||||
|
||||
# Validate settings file exists
|
||||
if [[ ! -f "$SETTINGS_PATH" ]]; then
|
||||
echo "Error: Settings file not found: $SETTINGS_PATH"
|
||||
echo ""
|
||||
echo "Create the settings file or update the path in $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Shift profile arg, pass rest to claude
|
||||
shift
|
||||
|
||||
# Execute claude with settings
|
||||
exec claude --settings "$SETTINGS_PATH" "$@"
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo "Installing ccs to $INSTALL_DIR..."
|
||||
echo ""
|
||||
|
||||
# Create install dir if needed
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
|
||||
# Make executable first
|
||||
chmod +x "$SCRIPT_DIR/ccs"
|
||||
|
||||
# Symlink ccs script
|
||||
ln -sf "$SCRIPT_DIR/ccs" "$INSTALL_DIR/ccs"
|
||||
|
||||
# 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."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if in PATH
|
||||
if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
|
||||
echo "⚠️ Warning: $INSTALL_DIR is not in PATH"
|
||||
echo ""
|
||||
echo "Add to your shell profile (~/.bashrc or ~/.zshrc):"
|
||||
echo " export PATH=\"\$HOME/.local/bin:\$PATH\""
|
||||
echo ""
|
||||
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 <profile>"
|
||||
echo ""
|
||||
echo "Example:"
|
||||
echo " ccs glm"
|
||||
echo " ccs sonnet --verbose"
|
||||
Reference in New Issue
Block a user