mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-15 10:20:39 +00:00
fix(core): add VERSION file handling and comprehensive test coverage
This commit fixes version management and argument parsing issues across both Windows and Linux/macOS platforms, achieving 100% test coverage. Changes: - Add VERSION file installation to both installers (install.sh, install.ps1) - Fix version/help command detection when using 'powershell -File' syntax - Rename PowerShell param from $Profile to $ProfileOrFlag for clarity - Add $FirstArg detection to check both ProfileOrFlag and RemainingArgs - Create comprehensive edge case test suites for both platforms: * tests/edge-cases.ps1 - 31 tests for Windows (100% pass) * tests/edge-cases.sh - 37 tests for Linux/macOS (100% pass) - Fix multiline regex matching in tests for error messages - Update test expectations to match platform-specific behavior - Reorganize project structure: * Move installers to installers/ directory * Add scripts/ directory for version management * Add config/ directory for configuration templates * Add docs/ directory for documentation Test Results: - Windows (PowerShell): 31/31 tests passing (100%) - Linux/macOS (Bash): 37/37 tests passing (100%) Breaking Changes: None - Installers moved but GitHub URLs updated in README files - All existing functionality preserved Co-authored-by: Claude Code <claude@anthropic.com>
This commit is contained in:
co-authored by
Claude Code
parent
7bd2e9e4ba
commit
e9eb215d1f
@@ -0,0 +1,197 @@
|
||||
---
|
||||
allowed-tools: Glob, Read, Bash(jq:*), Task
|
||||
description: Delegate commands to alternative models (GLM, Haiku) for token optimization
|
||||
argument-hint: [profile] /command [args...]
|
||||
model: haiku
|
||||
---
|
||||
|
||||
# /ccs - Delegate to Alternative Models
|
||||
|
||||
You are a **delegation orchestrator**. Your job is to delegate the user's command to an alternative AI model (GLM, Haiku, etc.) using the CCS (Claude Code Switch) system.
|
||||
|
||||
## User's Input
|
||||
|
||||
The user invoked: `/ccs {{args}}`
|
||||
|
||||
## Your Task
|
||||
|
||||
Follow these steps to delegate the command:
|
||||
|
||||
### Step 1: Parse Arguments
|
||||
|
||||
Extract from the user's input:
|
||||
- **Profile**: Model profile to use (e.g., `glm`, `haiku`, `son`) - defaults to `glm` if omitted
|
||||
- **Command**: The slash command to delegate (e.g., `plan`, `code`, `debug`)
|
||||
- **Arguments**: Additional arguments to pass to the command
|
||||
|
||||
**Examples**:
|
||||
- `/ccs glm /plan "add auth"` → profile=`glm`, command=`plan`, args=`"add auth"`
|
||||
- `/ccs /code "fix bug"` → profile=`glm` (default), command=`code`, args=`"fix bug"`
|
||||
- `/ccs haiku /ask "what is X?"` → profile=`haiku`, command=`ask`, args=`"what is X?"`
|
||||
|
||||
### Step 2: Validate Profile
|
||||
|
||||
Check if the profile exists in `~/.ccs/config.json`:
|
||||
|
||||
```bash
|
||||
jq -e '.profiles["<profile>"]' ~/.ccs/config.json
|
||||
```
|
||||
|
||||
If the profile doesn't exist:
|
||||
```
|
||||
❌ Error: Profile '<profile>' not found in ~/.ccs/config.json
|
||||
|
||||
Available profiles:
|
||||
<list profiles from config>
|
||||
|
||||
Usage: /ccs [profile] /command [args]
|
||||
Example: /ccs glm /plan "add authentication"
|
||||
```
|
||||
|
||||
### Step 3: Resolve Command Path
|
||||
|
||||
Find the command file in this priority order:
|
||||
1. **User-scope**: `~/.ccs/commands/<command>.md`
|
||||
2. **Project-scope**: `.claude/commands/<command>.md`
|
||||
|
||||
Use Glob tool to check both locations. If not found in either:
|
||||
|
||||
```
|
||||
❌ Error: Command '/<command>' not found
|
||||
|
||||
Searched locations:
|
||||
- ~/.ccs/commands/<command>.md (user-scope)
|
||||
- .claude/commands/<command>.md (project-scope)
|
||||
|
||||
Check command name spelling or create the command file.
|
||||
```
|
||||
|
||||
### Step 4: Launch Subagent with Task Tool
|
||||
|
||||
Use the Task tool to delegate execution:
|
||||
|
||||
**Parameters**:
|
||||
```typescript
|
||||
{
|
||||
subagent_type: "general-purpose",
|
||||
model: "haiku",
|
||||
description: "Delegating /<command> to <profile> profile",
|
||||
prompt: `You are executing a delegated command via CCS (Claude Code Switch).
|
||||
|
||||
**Delegation Context**:
|
||||
- Profile: <profile>
|
||||
- Command: /<command>
|
||||
- Arguments: <args>
|
||||
- Command file: <resolved-path>
|
||||
|
||||
**CRITICAL**: Before executing, switch to the CCS profile:
|
||||
\`\`\`bash
|
||||
ccs <profile>
|
||||
\`\`\`
|
||||
|
||||
**Instructions**:
|
||||
1. Run \`ccs <profile>\` to switch to the correct model
|
||||
2. Read the command file at <resolved-path>
|
||||
3. Execute the command with arguments: <args>
|
||||
4. Provide a clear summary of what was accomplished
|
||||
|
||||
Execute now.`
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Format and Return Result
|
||||
|
||||
Present the subagent's output in this format:
|
||||
|
||||
```markdown
|
||||
🤖 **CCS Delegation Result**
|
||||
|
||||
**Profile**: <profile>
|
||||
**Command**: /<command>
|
||||
|
||||
---
|
||||
|
||||
<subagent-output>
|
||||
|
||||
---
|
||||
|
||||
💡 *Token optimization: This task was delegated to the '<profile>' profile to conserve primary model usage.*
|
||||
```
|
||||
|
||||
## When to Use /ccs
|
||||
|
||||
### ✅ Good Use Cases
|
||||
- Simple planning tasks
|
||||
- Straightforward code implementation
|
||||
- Documentation writing
|
||||
- Basic debugging
|
||||
- Quick questions
|
||||
|
||||
### ❌ Don't Delegate
|
||||
- Complex reasoning/architecture
|
||||
- Security-critical code
|
||||
- Deep code review
|
||||
- Context-dependent tasks
|
||||
- Already using right model
|
||||
|
||||
## Error Handling
|
||||
|
||||
If delegation fails, provide helpful error message with:
|
||||
1. What went wrong
|
||||
2. Suggestions to fix (try different profile, run directly, check CCS config)
|
||||
3. How to verify CCS setup (`ccs --version`)
|
||||
|
||||
## Configuration Check
|
||||
|
||||
If profile validation fails, show how to check config:
|
||||
```bash
|
||||
# View available profiles
|
||||
cat ~/.ccs/config.json
|
||||
|
||||
# Test CCS is working
|
||||
ccs --version
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
**Example 1**: Simple planning with GLM
|
||||
```
|
||||
User: /ccs glm /plan "add user authentication"
|
||||
You: Parse → profile=glm, command=plan, args="add user authentication"
|
||||
Validate → Check glm exists in config ✓
|
||||
Resolve → Find ~/.ccs/commands/plan.md ✓
|
||||
Launch → Task tool with ccs glm
|
||||
Return → Formatted result
|
||||
```
|
||||
|
||||
**Example 2**: Quick question with Haiku
|
||||
```
|
||||
User: /ccs haiku /ask "explain JWT tokens"
|
||||
You: Parse → profile=haiku, command=ask, args="explain JWT tokens"
|
||||
Validate → Check haiku exists ✓
|
||||
Resolve → Find command ✓
|
||||
Launch → Delegate to haiku
|
||||
Return → Result
|
||||
```
|
||||
|
||||
**Example 3**: Default profile
|
||||
```
|
||||
User: /ccs /debug "API 500 error"
|
||||
You: Parse → profile=glm (default), command=debug, args="API 500 error"
|
||||
Continue with delegation...
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- This is a **meta-command** that orchestrates other commands
|
||||
- Each delegation creates an **isolated subagent session**
|
||||
- The subagent switches to the specified CCS profile automatically
|
||||
- **Token optimization** is the primary benefit
|
||||
- User-scope commands (`~/.ccs/commands/`) are checked first
|
||||
- Project-scope commands (`.claude/commands/`) are fallback
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- Setup guide: `tools/ccs/SETUP-DELEGATION.md`
|
||||
- Delegation patterns: `tools/ccs/skills/ccs-delegation.md`
|
||||
- CCS Tool: `tools/ccs/README.md`
|
||||
@@ -0,0 +1,187 @@
|
||||
---
|
||||
name: ccs-delegation
|
||||
description: Use this skill when the user invokes the `/ccs` command or requests delegating tasks to alternative models (GLM, Haiku) for token optimization. This skill guides when and how to delegate commands to save primary model tokens.
|
||||
---
|
||||
|
||||
# CCS Delegation
|
||||
|
||||
Intelligent task delegation to alternative AI models (GLM, Haiku, etc.) for token optimization using the `/ccs` meta-command.
|
||||
|
||||
## Purpose
|
||||
|
||||
The `/ccs` command delegates simple tasks to alternative models while staying in the primary session, optimizing:
|
||||
- **Token efficiency**: Save primary model tokens for complex work
|
||||
- **Task-model matching**: Use appropriate model for each task
|
||||
- **Cost optimization**: Route simple tasks to lower-cost models
|
||||
|
||||
## When to Invoke This Skill
|
||||
|
||||
Load this skill when:
|
||||
- User explicitly invokes `/ccs [profile] /command [args]`
|
||||
- User requests delegating tasks to alternative models
|
||||
- User asks to use GLM/Haiku for a task
|
||||
- User requests token conservation strategies
|
||||
|
||||
## Decision Framework
|
||||
|
||||
### ✅ Delegate to Alternative Models
|
||||
|
||||
Recommend `/ccs` when:
|
||||
|
||||
**Simple, straightforward tasks**:
|
||||
- Basic planning (CRUD operations, simple features)
|
||||
- Straightforward code implementation
|
||||
- Documentation writing
|
||||
- Simple bug fixes
|
||||
- Routine refactoring
|
||||
|
||||
**Token conservation scenarios**:
|
||||
- Working on complex project, saving tokens for hard parts
|
||||
- Rate limit approaching on primary model
|
||||
- Cost-conscious development
|
||||
|
||||
**User explicitly requests**:
|
||||
- "Use GLM for this"
|
||||
- "Delegate to cheaper model"
|
||||
- "Save tokens on this task"
|
||||
|
||||
### ❌ Keep in Primary Model
|
||||
|
||||
Don't recommend delegation when:
|
||||
|
||||
**Complex reasoning required**:
|
||||
- Architecture decisions
|
||||
- System design patterns
|
||||
- Complex debugging (multiple files)
|
||||
- Security-critical code review
|
||||
|
||||
**Context-dependent**:
|
||||
- Needs current session context
|
||||
- Requires back-and-forth interaction
|
||||
- Builds on previous conversation
|
||||
|
||||
**Quality-critical**:
|
||||
- Production-critical code
|
||||
- Security implementations
|
||||
- Performance-sensitive algorithms
|
||||
- Public-facing API design
|
||||
|
||||
## Quick Decision Tree
|
||||
|
||||
```
|
||||
Is task simple and straightforward?
|
||||
↓ NO → Keep in current model
|
||||
↓ YES
|
||||
↓
|
||||
Does task need deep context from current session?
|
||||
↓ YES → Keep in current model
|
||||
↓ NO
|
||||
↓
|
||||
Is task security or quality critical?
|
||||
↓ YES → Keep in current model
|
||||
↓ NO
|
||||
↓
|
||||
✅ RECOMMEND /ccs delegation
|
||||
```
|
||||
|
||||
## Usage Patterns
|
||||
|
||||
### Pattern 1: Explicit Delegation Request
|
||||
|
||||
When user explicitly requests alternative model:
|
||||
|
||||
```
|
||||
User: "Use GLM to plan the authentication feature"
|
||||
Claude: "I'll delegate this planning task to GLM to conserve tokens."
|
||||
<Invokes: /ccs glm /plan "design authentication feature">
|
||||
```
|
||||
|
||||
### Pattern 2: Proactive Token Optimization
|
||||
|
||||
When task is clearly simple, proactively suggest:
|
||||
|
||||
```
|
||||
User: "/plan add a simple CRUD endpoint for users"
|
||||
Claude: "This is straightforward. I'll delegate to GLM to save tokens."
|
||||
<Invokes: /ccs glm /plan "add CRUD endpoint for users">
|
||||
```
|
||||
|
||||
### Pattern 3: Automatic Model Selection
|
||||
|
||||
For simple tasks without explicit profile request:
|
||||
|
||||
```
|
||||
User: "/ccs /code 'implement the auth endpoints'"
|
||||
Claude: "Delegating to GLM (default profile) for implementation."
|
||||
<Invokes command with default glm profile>
|
||||
```
|
||||
|
||||
## Profile Selection Guide
|
||||
|
||||
**GLM (glm profile)**:
|
||||
- Simple coding tasks
|
||||
- Basic planning
|
||||
- Documentation
|
||||
- Routine fixes
|
||||
- Default choice for simple tasks
|
||||
|
||||
**Haiku (haiku profile)**:
|
||||
- Quick questions
|
||||
- Simple explanations
|
||||
- Fast, lightweight tasks
|
||||
- When speed matters
|
||||
|
||||
**Sonnet (son profile)**:
|
||||
- Don't delegate—use directly
|
||||
- Complex reasoning
|
||||
- Architecture decisions
|
||||
- Security-critical work
|
||||
|
||||
## Command Format
|
||||
|
||||
```bash
|
||||
/ccs [profile] /command [args...]
|
||||
```
|
||||
|
||||
**Examples**:
|
||||
- `/ccs glm /plan "add user authentication"`
|
||||
- `/ccs haiku /ask "explain JWT tokens"`
|
||||
- `/ccs /code "implement feature"` (defaults to glm)
|
||||
|
||||
## Error Handling
|
||||
|
||||
If `/ccs` invocation fails:
|
||||
1. Check if CCS is properly configured: `ccs --version`
|
||||
2. Verify profile exists in `~/.ccs/config.json`
|
||||
3. Ensure command exists in `~/.ccs/commands/` or `.claude/commands/`
|
||||
4. Suggest running command directly if delegation problematic
|
||||
|
||||
## Configuration Check
|
||||
|
||||
Guide user to verify CCS setup:
|
||||
|
||||
```bash
|
||||
# Check CCS version
|
||||
ccs --version
|
||||
|
||||
# View available profiles
|
||||
cat ~/.ccs/config.json
|
||||
|
||||
# List available commands
|
||||
ls ~/.ccs/commands/
|
||||
```
|
||||
|
||||
## Integration Notes
|
||||
|
||||
- `/ccs` is a meta-command that orchestrates other slash commands
|
||||
- Each delegation creates isolated subagent session
|
||||
- Subagent automatically switches to specified CCS profile
|
||||
- User-scope commands (`~/.ccs/commands/`) checked first
|
||||
- Project-scope commands (`.claude/commands/`) as fallback
|
||||
|
||||
## Related Resources
|
||||
|
||||
- Command implementation: `~/.ccs/commands/ccs.md`
|
||||
- Detailed patterns: `references/delegation-patterns.md`
|
||||
- Setup guide: `tools/ccs/SETUP-DELEGATION.md`
|
||||
- CCS Tool: `tools/ccs/README.md`
|
||||
@@ -0,0 +1,295 @@
|
||||
# CCS Delegation Patterns - Detailed Reference
|
||||
|
||||
This reference provides comprehensive patterns and examples for CCS task delegation.
|
||||
|
||||
## Advanced Usage Patterns
|
||||
|
||||
### Pattern 4: Task Splitting (Complex + Simple)
|
||||
|
||||
Split work between models for optimal efficiency:
|
||||
|
||||
```
|
||||
User: "Design and implement payment system"
|
||||
|
||||
Claude (in Sonnet):
|
||||
1. Design architecture (Sonnet handles complex reasoning)
|
||||
- Security considerations
|
||||
- Data flow diagrams
|
||||
- API contracts
|
||||
|
||||
2. Delegate implementation to GLM:
|
||||
/ccs glm /code "implement payment webhook handler based on this design"
|
||||
|
||||
3. Review security (back to Sonnet):
|
||||
Review the implemented code for security vulnerabilities
|
||||
```
|
||||
|
||||
### Pattern 5: Batch Operations
|
||||
|
||||
Delegate multiple simple tasks sequentially:
|
||||
|
||||
```bash
|
||||
# Multiple planning tasks
|
||||
/ccs glm /plan "feature A: user profile"
|
||||
/ccs glm /plan "feature B: notifications"
|
||||
/ccs glm /plan "feature C: search functionality"
|
||||
|
||||
# Then review all plans together in Sonnet
|
||||
"Review all three plans for consistency and integration points"
|
||||
```
|
||||
|
||||
### Pattern 6: Iterative Refinement
|
||||
|
||||
Use GLM for initial implementation, Sonnet for refinement:
|
||||
|
||||
```
|
||||
1. /ccs glm /code "implement basic CRUD API"
|
||||
2. (Sonnet reviews): "Add error handling, input validation, rate limiting"
|
||||
3. /ccs glm /fix "add suggested improvements"
|
||||
```
|
||||
|
||||
## Model Capability Matrix
|
||||
|
||||
### GLM 4.6 - Best For:
|
||||
- ✅ Simple feature planning (CRUD, basic workflows)
|
||||
- ✅ Straightforward code implementation
|
||||
- ✅ Documentation and README files
|
||||
- ✅ Basic bug fixes (clear error messages)
|
||||
- ✅ Refactoring with clear scope
|
||||
- ✅ Test writing (unit tests, simple integration tests)
|
||||
- ✅ Configuration files (package.json, tsconfig, etc.)
|
||||
- ❌ Complex algorithms
|
||||
- ❌ Security-critical code
|
||||
- ❌ Performance optimization
|
||||
- ❌ Architecture decisions
|
||||
|
||||
### Haiku - Best For:
|
||||
- ✅ Quick questions ("what is X?", "explain Y")
|
||||
- ✅ Simple explanations
|
||||
- ✅ Code snippet explanations
|
||||
- ✅ Quick documentation lookups
|
||||
- ✅ Simple troubleshooting
|
||||
- ❌ Code implementation
|
||||
- ❌ Complex analysis
|
||||
- ❌ Multi-step tasks
|
||||
|
||||
### Sonnet 4.5 - Best For:
|
||||
- ✅ Complex architecture and design
|
||||
- ✅ Security-critical code review
|
||||
- ✅ Performance optimization
|
||||
- ✅ Complex debugging (multiple files, unclear root cause)
|
||||
- ✅ API design and contracts
|
||||
- ✅ Database schema design
|
||||
- ✅ Integration planning
|
||||
- ✅ Ambiguous requirement clarification
|
||||
|
||||
## Context Preservation Strategies
|
||||
|
||||
### When Delegation Makes Sense
|
||||
|
||||
**Low context requirements**:
|
||||
```
|
||||
# Current session has 50 lines discussing feature X
|
||||
# User wants to add unrelated feature Y
|
||||
/ccs glm /plan "add feature Y" # ✅ Good - Y doesn't need X's context
|
||||
```
|
||||
|
||||
**Self-contained tasks**:
|
||||
```
|
||||
# Task has all info in the prompt
|
||||
/ccs glm /code "implement function that validates email addresses" # ✅ Good
|
||||
```
|
||||
|
||||
### When to Keep in Current Session
|
||||
|
||||
**High context dependency**:
|
||||
```
|
||||
# Current session has extensive discussion about auth flow
|
||||
# User: "now implement the login function"
|
||||
# ❌ Don't delegate - needs current context
|
||||
```
|
||||
|
||||
**Iterative refinement**:
|
||||
```
|
||||
# Session has 10 messages refining a complex algorithm
|
||||
# User: "adjust the algorithm to handle edge case X"
|
||||
# ❌ Don't delegate - needs full conversation history
|
||||
```
|
||||
|
||||
## Token Efficiency Analysis
|
||||
|
||||
### Token Savings Example
|
||||
|
||||
**Without delegation**:
|
||||
- Complex architecture discussion: 15,000 tokens (Sonnet)
|
||||
- Simple implementation: 5,000 tokens (Sonnet)
|
||||
- Total: 20,000 Sonnet tokens
|
||||
|
||||
**With delegation**:
|
||||
- Complex architecture discussion: 15,000 tokens (Sonnet)
|
||||
- Delegate implementation: 5,000 tokens (GLM)
|
||||
- Total: 15,000 Sonnet + 5,000 GLM tokens
|
||||
- **Savings**: 5,000 Sonnet tokens
|
||||
|
||||
### When Delegation Overhead Outweighs Benefits
|
||||
|
||||
**Very simple tasks** (< 200 tokens):
|
||||
```
|
||||
User: "add a comment to this line"
|
||||
# ❌ Don't delegate - overhead > benefit
|
||||
```
|
||||
|
||||
**Tasks needing immediate context** (recent discussion):
|
||||
```
|
||||
User: "based on what we just discussed, implement X"
|
||||
# ❌ Don't delegate - needs immediate context
|
||||
```
|
||||
|
||||
## Real-World Workflows
|
||||
|
||||
### Workflow 1: New Feature Development
|
||||
|
||||
```
|
||||
1. Requirements gathering (Sonnet)
|
||||
- Clarify ambiguous requirements
|
||||
- Discuss trade-offs
|
||||
- Design API contracts
|
||||
|
||||
2. Planning (GLM)
|
||||
/ccs glm /plan "implement user profile feature based on requirements"
|
||||
|
||||
3. Implementation (GLM)
|
||||
/ccs glm /code "implement the user profile API endpoints"
|
||||
|
||||
4. Review (Sonnet)
|
||||
- Security review
|
||||
- Performance check
|
||||
- Integration verification
|
||||
|
||||
5. Fixes (GLM if simple, Sonnet if complex)
|
||||
/ccs glm /fix "address code review comments"
|
||||
```
|
||||
|
||||
### Workflow 2: Bug Investigation & Fix
|
||||
|
||||
```
|
||||
1. Investigation (Sonnet)
|
||||
- Analyze logs
|
||||
- Trace root cause
|
||||
- Understand system state
|
||||
|
||||
2. Simple fix (GLM)
|
||||
/ccs glm /fix "update validation in UserController to handle null emails"
|
||||
|
||||
3. Verification (Sonnet)
|
||||
- Verify fix addresses root cause
|
||||
- Check for regressions
|
||||
```
|
||||
|
||||
### Workflow 3: Documentation Sprint
|
||||
|
||||
```
|
||||
1. /ccs glm /docs "document the authentication API endpoints"
|
||||
2. /ccs glm /docs "write setup guide for new developers"
|
||||
3. /ccs glm /docs "create API usage examples"
|
||||
4. (Sonnet reviews for completeness and accuracy)
|
||||
```
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
### Anti-Pattern 1: Over-Delegation
|
||||
|
||||
❌ **Bad**:
|
||||
```
|
||||
/ccs glm /plan "design entire microservices architecture"
|
||||
# Too complex for GLM
|
||||
```
|
||||
|
||||
✅ **Good**:
|
||||
```
|
||||
# Sonnet handles architecture
|
||||
# Then: /ccs glm /code "implement user service based on architecture"
|
||||
```
|
||||
|
||||
### Anti-Pattern 2: Delegation with Hidden Context
|
||||
|
||||
❌ **Bad**:
|
||||
```
|
||||
# After 20 messages discussing custom auth flow
|
||||
User: "implement the login"
|
||||
/ccs glm /code "implement login"
|
||||
# GLM doesn't have context about custom flow
|
||||
```
|
||||
|
||||
✅ **Good**:
|
||||
```
|
||||
# Include context in delegation
|
||||
/ccs glm /code "implement login using JWT with custom claims: userId, role, tenantId"
|
||||
```
|
||||
|
||||
### Anti-Pattern 3: Micro-Delegation
|
||||
|
||||
❌ **Bad**:
|
||||
```
|
||||
/ccs glm /code "add variable x"
|
||||
/ccs glm /code "add function y"
|
||||
/ccs glm /code "add class z"
|
||||
# Too much delegation overhead
|
||||
```
|
||||
|
||||
✅ **Good**:
|
||||
```
|
||||
/ccs glm /code "implement user management module with CRUD operations"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: Delegated Task Failed
|
||||
|
||||
**Symptoms**:
|
||||
- GLM produces incorrect code
|
||||
- Implementation doesn't match requirements
|
||||
- Security vulnerabilities introduced
|
||||
|
||||
**Solutions**:
|
||||
1. Check if task was too complex for GLM
|
||||
2. Provide more explicit requirements
|
||||
3. Use Sonnet for complex parts, GLM only for straightforward implementation
|
||||
4. Review GLM output in Sonnet before accepting
|
||||
|
||||
### Issue: Context Loss
|
||||
|
||||
**Symptoms**:
|
||||
- Delegated task doesn't align with previous discussion
|
||||
- Implementation misses important constraints
|
||||
- Style doesn't match existing codebase
|
||||
|
||||
**Solutions**:
|
||||
1. Include more context in delegation prompt
|
||||
2. Don't delegate context-dependent tasks
|
||||
3. Provide explicit style guidelines in prompt
|
||||
4. Consider keeping task in current session
|
||||
|
||||
### Issue: Frequent Delegation Failures
|
||||
|
||||
**Symptoms**:
|
||||
- Multiple retries needed
|
||||
- Tasks keep failing validation
|
||||
- Time spent > time saved
|
||||
|
||||
**Solutions**:
|
||||
1. Re-evaluate task complexity
|
||||
2. Delegate fewer, larger tasks instead of many small ones
|
||||
3. Use more specific instructions
|
||||
4. Consider if delegation is appropriate for this workflow
|
||||
|
||||
## Best Practices Summary
|
||||
|
||||
1. **Delegate simple, self-contained tasks**
|
||||
2. **Keep complex reasoning in Sonnet**
|
||||
3. **Include sufficient context in delegation**
|
||||
4. **Review delegated output before proceeding**
|
||||
5. **Monitor token savings vs overhead**
|
||||
6. **Prefer batch operations over micro-delegations**
|
||||
7. **Document delegation patterns that work well**
|
||||
8. **Iterate on delegation strategies based on results**
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Deploy CCS CloudFlare Worker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'scripts/worker.js'
|
||||
- 'installers/**'
|
||||
|
||||
# Allow manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy ccs-installer to CloudFlare
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Deploy Worker
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
workingDirectory: scripts
|
||||
command: deploy worker.js --name ccs-installer
|
||||
@@ -0,0 +1,85 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to CCS will be documented here.
|
||||
|
||||
Format based on [Keep a Changelog](https://keepachangelog.com/).
|
||||
|
||||
## [2.1.0] - 2025-11-02
|
||||
|
||||
### Changed
|
||||
- **MAJOR SIMPLIFICATION**: Windows PowerShell now uses `--settings` flag (confirmed working in Claude CLI 2.0.31+)
|
||||
- Removed 64 lines of environment variable management code from ccs.ps1
|
||||
- Windows and Unix/Linux/macOS now use identical approach
|
||||
- Updated all documentation to reflect cross-platform consistency
|
||||
- ccs.ps1: 235 lines → 171 lines (27% reduction)
|
||||
|
||||
### Technical Details
|
||||
- Windows Claude CLI DOES support `--settings` flag (contrary to previous assumptions)
|
||||
- No longer manually sets/restores environment variables
|
||||
- Simpler, cleaner, more maintainable codebase
|
||||
- Settings file format unchanged (still uses `{"env": {...}}` structure)
|
||||
|
||||
## [2.0.0] - 2025-11-02
|
||||
|
||||
### BREAKING CHANGES
|
||||
- Removed `ccs son` profile - use `ccs` (default) for Claude subscription
|
||||
- Config structure simplified - `sonnet` profile removed from default config
|
||||
|
||||
### Added
|
||||
- `config/` folder with organized templates (base-glm, base-dsp, config.example)
|
||||
- `config/README.md` - comprehensive config documentation
|
||||
- `installers/` folder for clean project structure (install/uninstall scripts)
|
||||
- Smart installer with validation and self-healing
|
||||
- Non-invasive approach - never modifies `~/.claude/settings.json`
|
||||
- Version pinning support: `curl ccs.kaitran.ca/v2.0.0/install | bash`
|
||||
- CHANGELOG.md for release tracking
|
||||
- WORKFLOW.md - comprehensive workflow documentation
|
||||
- Migration detection and auto-migration from v1.x configs
|
||||
- Config backup before modifications with timestamp
|
||||
- JSON validation for all config files
|
||||
- GitHub Actions workflow for auto-deploying CloudFlare Worker
|
||||
- VERSION file for centralized version management
|
||||
|
||||
### Fixed
|
||||
- **CRITICAL**: PowerShell env var bug - strict filtering prevents crashes on non-string values
|
||||
- PowerShell now requires `env` object in settings files (prevents crashes on root-level fields)
|
||||
- Type validation for environment variables (strings only)
|
||||
- Installer now validates all JSON before processing
|
||||
- Better error messages with actionable solutions
|
||||
|
||||
### Changed
|
||||
- `ccs` now default behavior (uses Claude subscription, no profile needed)
|
||||
- Simplified profile management (glm fallback only)
|
||||
- Moved `.ccs.example.json` → `config/config.example.json`
|
||||
- Reorganized project: install/uninstall scripts → `installers/` folder
|
||||
- Enhanced error messages with solutions and reinstall instructions
|
||||
- Removed sonnet profile creation from installers
|
||||
- Config structure: `{ "glm": "...", "default": "~/.claude/settings.json" }`
|
||||
- Worker.js routing updated for new installers/ path
|
||||
|
||||
### Migration Guide
|
||||
- Old users: `ccs son` → `ccs` (automatic deprecation warning during install)
|
||||
- Config auto-migrates during installation (son/sonnet profiles removed)
|
||||
- GLM API keys preserved during upgrade
|
||||
- Backup created automatically: `~/.ccs/config.json.backup.TIMESTAMP`
|
||||
- No action needed unless you customized `sonnet` profile
|
||||
|
||||
## [1.1.0] - 2025-11-01
|
||||
|
||||
### Added
|
||||
- Support for git worktrees and submodules
|
||||
- Enhanced GLM profile with default model variables
|
||||
- Improved installer detection logic
|
||||
|
||||
### Fixed
|
||||
- BASH_SOURCE unbound variable error in installer
|
||||
- Git worktree detection
|
||||
|
||||
## [1.0.0] - 2025-10-31
|
||||
|
||||
### Added
|
||||
- Initial release
|
||||
- Profile-based switching between Claude and GLM
|
||||
- Cross-platform support (macOS, Linux, Windows)
|
||||
- One-line installation
|
||||
- Auto-detection of current provider
|
||||
@@ -1,154 +0,0 @@
|
||||
# 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, CCS_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, CCS_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 CCS_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 ~/.ccs/glm.settings.json]
|
||||
CheckMerge -->|No| BasicTemplate
|
||||
BasicTemplate --> WriteGLM[Write ~/.ccs/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 ~/.ccs/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 ~/.ccs/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/config.json exists?}
|
||||
CreateCCSConfig -->|No| WriteCCSConfig[Create ~/.ccs/config.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
|
||||
@@ -16,8 +16,8 @@ Manual switching is tedious and error-prone.
|
||||
|
||||
**The Solution**:
|
||||
```bash
|
||||
ccs son # Complex refactoring? Use Claude Sonnet 4.5
|
||||
ccs glm # Simple bug fix? Use GLM 4.6
|
||||
ccs # Use Claude subscription (default)
|
||||
ccs glm # Switch to GLM fallback
|
||||
# Hit rate limit? Switch instantly:
|
||||
ccs glm # Continue working with GLM
|
||||
```
|
||||
@@ -38,25 +38,33 @@ curl -fsSL ccs.kaitran.ca/install | bash
|
||||
irm ccs.kaitran.ca/install.ps1 | iex
|
||||
```
|
||||
|
||||
**Configure**:
|
||||
**What Gets Installed**:
|
||||
```bash
|
||||
# Edit with your profiles
|
||||
cat > ~/.ccs/config.json << 'EOF'
|
||||
~/.ccs/
|
||||
├── ccs # Main executable
|
||||
├── config.json # Profile configuration
|
||||
├── glm.settings.json # GLM profile
|
||||
└── .claude/ # Claude Code integration
|
||||
├── commands/ccs.md # /ccs meta-command
|
||||
└── skills/ # Delegation skills
|
||||
```
|
||||
|
||||
**Configure**:
|
||||
```json
|
||||
# Installer creates config automatically
|
||||
# Config: ~/.ccs/config.json
|
||||
{
|
||||
"profiles": {
|
||||
"glm": "~/.ccs/glm.settings.json",
|
||||
"son": "~/.ccs/sonnet.settings.json",
|
||||
"default": "~/.claude/settings.json"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
**Use**:
|
||||
```bash
|
||||
ccs # Use default profile
|
||||
ccs glm # Use GLM profile
|
||||
ccs son # Use Sonnet profile
|
||||
ccs # Use Claude subscription (default)
|
||||
ccs glm # Use GLM fallback
|
||||
|
||||
# Utility commands
|
||||
ccs --version # Show CCS version
|
||||
@@ -85,7 +93,7 @@ ccs --help # Show Claude CLI help
|
||||
**With CCS**: Switch models based on task complexity, maximize quality while managing costs.
|
||||
|
||||
```bash
|
||||
ccs son # Planning new feature architecture
|
||||
ccs # Planning new feature architecture
|
||||
# Got the plan? Implement with GLM:
|
||||
ccs glm # Write the straightforward code
|
||||
```
|
||||
@@ -98,7 +106,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 son`
|
||||
- One command to switch: `ccs` (default) or `ccs glm` (fallback)
|
||||
- Keep both configs saved as profiles
|
||||
- Switch in <1 second
|
||||
- No file editing, no copy-paste, no mistakes
|
||||
@@ -110,6 +118,31 @@ If you have both Claude subscription and GLM Coding Plan, you know the pain:
|
||||
- Auto-creates configs during install
|
||||
- No proxies, no magic—just bash + jq
|
||||
|
||||
## New: Task Delegation
|
||||
|
||||
**CCS now includes intelligent task delegation** via the `/ccs` meta-command:
|
||||
|
||||
```bash
|
||||
# Delegate planning to GLM (saves Sonnet tokens)
|
||||
/ccs glm /plan "add user authentication"
|
||||
|
||||
# Delegate coding to GLM
|
||||
/ccs glm /code "implement auth endpoints"
|
||||
|
||||
# Quick questions with Haiku
|
||||
/ccs haiku /ask "explain this error"
|
||||
```
|
||||
|
||||
**Documentation**:
|
||||
- Command reference: [`commands/ccs.md`](commands/ccs.md)
|
||||
- Delegation patterns: [`skills/ccs-delegation.md`](skills/ccs-delegation.md)
|
||||
|
||||
**Benefits**:
|
||||
- ✅ Save tokens by delegating simple tasks to cheaper models
|
||||
- ✅ Use right model for each task automatically
|
||||
- ✅ Reusable commands across all projects (user-scope)
|
||||
- ✅ Seamless integration with existing workflows
|
||||
|
||||
## Installation
|
||||
|
||||
### One-Liner (Recommended)
|
||||
@@ -120,7 +153,7 @@ If you have both Claude subscription and GLM Coding Plan, you know the pain:
|
||||
curl -fsSL ccs.kaitran.ca/install | bash
|
||||
|
||||
# Or direct from GitHub
|
||||
curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/install.sh | bash
|
||||
curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/installers/install.sh | bash
|
||||
```
|
||||
|
||||
**Windows PowerShell**:
|
||||
@@ -129,7 +162,7 @@ curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/install.sh | ba
|
||||
irm ccs.kaitran.ca/install.ps1 | iex
|
||||
|
||||
# Or direct from GitHub
|
||||
irm https://raw.githubusercontent.com/kaitranntt/ccs/main/install.ps1 | iex
|
||||
irm https://raw.githubusercontent.com/kaitranntt/ccs/main/installers/install.ps1 | iex
|
||||
```
|
||||
|
||||
**Note**:
|
||||
@@ -142,14 +175,14 @@ irm https://raw.githubusercontent.com/kaitranntt/ccs/main/install.ps1 | iex
|
||||
```bash
|
||||
git clone https://github.com/kaitranntt/ccs.git
|
||||
cd ccs
|
||||
./install.sh
|
||||
./installers/install.sh
|
||||
```
|
||||
|
||||
**Windows PowerShell**:
|
||||
```powershell
|
||||
git clone https://github.com/kaitranntt/ccs.git
|
||||
cd ccs
|
||||
.\install.ps1
|
||||
.\installers\install.ps1
|
||||
```
|
||||
|
||||
**Note**: Works with git worktrees and submodules - the installer detects both `.git` directory and `.git` file.
|
||||
@@ -201,8 +234,6 @@ git pull
|
||||
irm ccs.kaitran.ca/install.ps1 | iex
|
||||
```
|
||||
|
||||
**Note**: Upgrading preserves your existing API keys and settings. The installer only adds new features without overwriting your configuration.
|
||||
|
||||
## Configuration
|
||||
|
||||
The installer auto-creates config and profile templates during installation:
|
||||
@@ -218,7 +249,6 @@ Uses settings file paths:
|
||||
{
|
||||
"profiles": {
|
||||
"glm": "~/.ccs/glm.settings.json",
|
||||
"sonnet": "~/.ccs/sonnet.settings.json",
|
||||
"default": "~/.claude/settings.json"
|
||||
}
|
||||
}
|
||||
@@ -228,16 +258,13 @@ Each profile points to a Claude settings JSON file. Create settings files per [C
|
||||
|
||||
### Windows Configuration
|
||||
|
||||
**Important**: Windows Claude CLI uses **environment variables** instead of --settings flag.
|
||||
|
||||
Windows uses the same file structure as Linux, but settings files contain environment variables:
|
||||
Windows uses the same file structure and approach as Linux/macOS.
|
||||
|
||||
**Config format** (`~/.ccs/config.json`):
|
||||
```json
|
||||
{
|
||||
"profiles": {
|
||||
"glm": "~/.ccs/glm.settings.json",
|
||||
"son": "~/.ccs/sonnet.settings.json",
|
||||
"default": "~/.claude/settings.json"
|
||||
}
|
||||
}
|
||||
@@ -246,29 +273,25 @@ Windows uses the same file structure as Linux, but settings files contain enviro
|
||||
**GLM profile** (`~/.ccs/glm.settings.json`):
|
||||
```json
|
||||
{
|
||||
"ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic",
|
||||
"ANTHROPIC_AUTH_TOKEN": "your_glm_api_key",
|
||||
"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"
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic",
|
||||
"ANTHROPIC_AUTH_TOKEN": "your_glm_api_key",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Claude profile** (`~/.ccs/sonnet.settings.json`):
|
||||
```json
|
||||
{
|
||||
"env": {}
|
||||
}
|
||||
```
|
||||
**Claude (Default)**:
|
||||
- Uses `~/.claude/settings.json` (your existing Claude CLI config)
|
||||
- CCS never modifies this file (non-invasive approach)
|
||||
|
||||
**How it works**:
|
||||
- CCS reads the settings file for the selected profile
|
||||
- Temporarily sets environment variables from the settings file
|
||||
- Executes Claude CLI with those variables
|
||||
- Restores original environment variables after execution
|
||||
|
||||
**Compatibility**: Settings files support both direct format (Windows) and `{"env": {...}}` wrapper (Linux compatibility).
|
||||
- CCS reads the config to find your profile's settings file
|
||||
- Executes `claude --settings <file>` with your selected profile
|
||||
- Simple, clean, cross-platform
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -276,9 +299,8 @@ Windows uses the same file structure as Linux, but settings files contain enviro
|
||||
|
||||
```bash
|
||||
# Works on macOS, Linux, and Windows
|
||||
ccs # Use default profile (no args)
|
||||
ccs glm # Use GLM profile
|
||||
ccs son # Use Sonnet profile
|
||||
ccs # Use Claude subscription (default)
|
||||
ccs glm # Use GLM fallback
|
||||
```
|
||||
|
||||
**Windows Note**: Commands work identically in PowerShell, CMD, and Git Bash.
|
||||
@@ -289,8 +311,8 @@ All args after profile name pass directly to Claude CLI:
|
||||
|
||||
```bash
|
||||
ccs glm --verbose
|
||||
ccs son /plan "add feature"
|
||||
ccs default --model claude-sonnet-4
|
||||
ccs /plan "add feature"
|
||||
ccs glm /code "implement feature"
|
||||
```
|
||||
|
||||
### Custom Config Location
|
||||
@@ -308,7 +330,7 @@ ccs glm
|
||||
|
||||
```bash
|
||||
# Step 1: Architecture & Planning (needs Claude's intelligence)
|
||||
ccs son
|
||||
ccs
|
||||
/plan "Design payment integration with Stripe, handle webhooks, errors, retries"
|
||||
# → Claude Sonnet 4.5 thinks deeply about edge cases, security, architecture
|
||||
|
||||
@@ -318,7 +340,7 @@ ccs glm
|
||||
# → GLM 4.6 writes the code efficiently, saves Claude usage
|
||||
|
||||
# Step 3: Code Review (needs deep analysis)
|
||||
ccs son
|
||||
ccs
|
||||
/review "check the payment handler for security issues"
|
||||
# → Claude Sonnet 4.5 catches subtle vulnerabilities
|
||||
|
||||
@@ -334,7 +356,7 @@ ccs glm
|
||||
|
||||
```bash
|
||||
# Working on complex refactoring with Claude
|
||||
ccs son
|
||||
ccs
|
||||
/plan "refactor authentication system"
|
||||
|
||||
# Claude hits rate limit mid-task
|
||||
@@ -345,7 +367,7 @@ ccs glm
|
||||
# Continue working without interruption
|
||||
|
||||
# Rate limit resets? Switch back
|
||||
ccs son
|
||||
ccs
|
||||
```
|
||||
|
||||
### Configuration Examples
|
||||
@@ -355,19 +377,18 @@ ccs son
|
||||
{
|
||||
"profiles": {
|
||||
"glm": "~/.ccs/glm.settings.json",
|
||||
"sonnet": "~/.ccs/sonnet.settings.json",
|
||||
"default": "~/.claude/settings.json"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Advanced setup** (multiple providers):
|
||||
**Advanced setup** (multiple profiles):
|
||||
```json
|
||||
{
|
||||
"profiles": {
|
||||
"sonnet": "~/.ccs/sonnet.settings.json",
|
||||
"glm": "~/.ccs/glm.settings.json",
|
||||
"haiku": "~/.ccs/haiku.settings.json",
|
||||
"custom": "~/.ccs/custom.settings.json",
|
||||
"default": "~/.claude/settings.json"
|
||||
}
|
||||
}
|
||||
@@ -379,7 +400,7 @@ ccs son
|
||||
2. Looks up settings file path in `~/.ccs/config.json`
|
||||
3. Executes `claude --settings <path> [remaining-args]`
|
||||
|
||||
No magic. No file modification. Pure delegation.
|
||||
No magic. No file modification. Pure delegation. Works identically across all platforms.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -458,7 +479,7 @@ This error occurs when running the installer in some shells or environments.
|
||||
|
||||
**Solution**: Upgrade to the latest version:
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/install.sh | bash
|
||||
curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/installers/install.sh | bash
|
||||
```
|
||||
|
||||
#### Git worktree not detected
|
||||
@@ -533,14 +554,6 @@ Error: Profile 'default' not found in ~/.ccs/config.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 `~/.ccs/glm.settings.json` - your `ANTHROPIC_AUTH_TOKEN` should still be present.
|
||||
|
||||
## Uninstallation
|
||||
|
||||
### macOS / Linux
|
||||
@@ -556,7 +569,7 @@ ccs-uninstall
|
||||
curl -fsSL ccs.kaitran.ca/uninstall | bash
|
||||
|
||||
# Or direct from GitHub
|
||||
curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/uninstall.sh | bash
|
||||
curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/installers/uninstall.sh | bash
|
||||
```
|
||||
|
||||
**Manual**:
|
||||
@@ -579,7 +592,7 @@ ccs-uninstall
|
||||
irm ccs.kaitran.ca/uninstall.ps1 | iex
|
||||
|
||||
# Or direct from GitHub
|
||||
irm https://raw.githubusercontent.com/kaitranntt/ccs/main/uninstall.ps1 | iex
|
||||
irm https://raw.githubusercontent.com/kaitranntt/ccs/main/installers/uninstall.ps1 | iex
|
||||
```
|
||||
|
||||
**Manual**:
|
||||
|
||||
+18
-21
@@ -111,7 +111,7 @@ Nếu bạn có cả Claude subscription và GLM Coding Plan, bạn biết cái
|
||||
curl -fsSL ccs.kaitran.ca/install | bash
|
||||
|
||||
# Hoặc trực tiếp từ GitHub
|
||||
curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/install.sh | bash
|
||||
curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/installers/install.sh | bash
|
||||
```
|
||||
|
||||
**Windows PowerShell**:
|
||||
@@ -120,7 +120,7 @@ curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/install.sh | ba
|
||||
irm ccs.kaitran.ca/install.ps1 | iex
|
||||
|
||||
# Hoặc trực tiếp từ GitHub
|
||||
irm https://raw.githubusercontent.com/kaitranntt/ccs/main/install.ps1 | iex
|
||||
irm https://raw.githubusercontent.com/kaitranntt/ccs/main/installers/install.ps1 | iex
|
||||
```
|
||||
|
||||
**Lưu ý**:
|
||||
@@ -133,14 +133,14 @@ irm https://raw.githubusercontent.com/kaitranntt/ccs/main/install.ps1 | iex
|
||||
```bash
|
||||
git clone https://github.com/kaitranntt/ccs.git
|
||||
cd ccs
|
||||
./install.sh
|
||||
./installers/install.sh
|
||||
```
|
||||
|
||||
**Windows PowerShell**:
|
||||
```powershell
|
||||
git clone https://github.com/kaitranntt/ccs.git
|
||||
cd ccs
|
||||
.\install.ps1
|
||||
.\installers\install.ps1
|
||||
```
|
||||
|
||||
**Lưu ý**: Hoạt động với git worktrees và submodules - installer phát hiện cả `.git` directory và `.git` file.
|
||||
@@ -231,9 +231,7 @@ Mỗi profile trỏ đến một file settings JSON của Claude. Tạo file set
|
||||
|
||||
### Cấu Hình Windows
|
||||
|
||||
**Quan trọng**: Claude CLI trên Windows dùng **biến môi trường** thay vì --settings flag.
|
||||
|
||||
Windows dùng cùng cấu trúc file như Linux, nhưng settings files chứa environment variables:
|
||||
Windows dùng cùng cấu trúc file và phương pháp như Linux/macOS.
|
||||
|
||||
**Config format** (`~/.ccs/config.json`):
|
||||
```json
|
||||
@@ -249,12 +247,14 @@ Windows dùng cùng cấu trúc file như Linux, nhưng settings files chứa en
|
||||
**GLM profile** (`~/.ccs/glm.settings.json`):
|
||||
```json
|
||||
{
|
||||
"ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic",
|
||||
"ANTHROPIC_AUTH_TOKEN": "GLM_API_KEY_CUA_BAN",
|
||||
"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"
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic",
|
||||
"ANTHROPIC_AUTH_TOKEN": "GLM_API_KEY_CUA_BAN",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -266,12 +266,9 @@ Windows dùng cùng cấu trúc file như Linux, nhưng settings files chứa en
|
||||
```
|
||||
|
||||
**Cách hoạt động**:
|
||||
- CCS đọc settings file của profile được chọn
|
||||
- Tạm thời set biến môi trường từ settings file
|
||||
- Chạy Claude CLI với các biến đó
|
||||
- Khôi phục biến môi trường gốc sau khi thực thi
|
||||
|
||||
**Tương thích**: Settings files hỗ trợ cả format trực tiếp (Windows) và wrapper `{"env": {...}}` (tương thích Linux).
|
||||
- CCS đọc config để tìm settings file của profile
|
||||
- Chạy `claude --settings <file>` với profile đã chọn
|
||||
- Đơn giản, rõ ràng, đa nền tảng
|
||||
|
||||
## Sử Dụng
|
||||
|
||||
@@ -554,7 +551,7 @@ ccs-uninstall
|
||||
curl -fsSL ccs.kaitran.ca/uninstall | bash
|
||||
|
||||
# Hoặc trực tiếp từ GitHub
|
||||
curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/uninstall.sh | bash
|
||||
curl -fsSL https://raw.githubusercontent.com/kaitranntt/ccs/main/installers/uninstall.sh | bash
|
||||
```
|
||||
|
||||
**Thủ công**:
|
||||
@@ -577,7 +574,7 @@ ccs-uninstall
|
||||
irm ccs.kaitran.ca/uninstall.ps1 | iex
|
||||
|
||||
# Hoặc trực tiếp từ GitHub
|
||||
irm https://raw.githubusercontent.com/kaitranntt/ccs/main/uninstall.ps1 | iex
|
||||
irm https://raw.githubusercontent.com/kaitranntt/ccs/main/installers/uninstall.ps1 | iex
|
||||
```
|
||||
|
||||
**Thủ công**:
|
||||
|
||||
@@ -1,31 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Version
|
||||
CCS_VERSION="1.1.0"
|
||||
# Version - Read from VERSION file
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CCS_VERSION="$(cat "$SCRIPT_DIR/VERSION" 2>/dev/null || echo "unknown")"
|
||||
|
||||
CONFIG_FILE="${CCS_CONFIG:-$HOME/.ccs/config.json}"
|
||||
PROFILE="${1:-default}"
|
||||
|
||||
# Special case: version command
|
||||
if [[ "$PROFILE" == "version" || "$PROFILE" == "--version" || "$PROFILE" == "-v" ]]; then
|
||||
# Special case: version command (check BEFORE profile detection)
|
||||
if [[ $# -gt 0 ]] && [[ "${1}" == "version" || "${1}" == "--version" || "${1}" == "-v" ]]; then
|
||||
echo "CCS (Claude Code Switch) version $CCS_VERSION"
|
||||
echo "https://github.com/kaitranntt/ccs"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Special case: help command (forward to Claude CLI)
|
||||
if [[ "$PROFILE" == "--help" || "$PROFILE" == "-h" || "$PROFILE" == "help" ]]; then
|
||||
# Special case: help command (check BEFORE profile detection)
|
||||
if [[ $# -gt 0 ]] && [[ "${1}" == "--help" || "${1}" == "-h" || "${1}" == "help" ]]; then
|
||||
shift # Remove the help argument
|
||||
exec claude --help "$@"
|
||||
fi
|
||||
|
||||
# Smart profile detection: if first arg starts with '-', it's a flag not a profile
|
||||
if [[ $# -eq 0 ]] || [[ "${1}" =~ ^- ]]; then
|
||||
# No args or first arg is a flag → use default profile
|
||||
PROFILE="default"
|
||||
else
|
||||
# First arg doesn't start with '-' → treat as profile name
|
||||
PROFILE="${1}"
|
||||
fi
|
||||
|
||||
# Check config exists
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
echo "Error: Config file not found: $CONFIG_FILE"
|
||||
echo ""
|
||||
echo "Create ~/.ccs/config.json with your profile mappings."
|
||||
echo "See .ccs.example.json for template."
|
||||
echo "See config/config.example.json for template."
|
||||
echo ""
|
||||
echo "Or reinstall: curl -fsSL ccs.kaitran.ca/install | bash"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -55,7 +66,9 @@ 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"
|
||||
echo ""
|
||||
echo "See config/config.example.json for correct format"
|
||||
echo "Or reinstall: curl -fsSL ccs.kaitran.ca/install | bash"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -81,8 +94,8 @@ if [[ ! -f "$SETTINGS_PATH" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Shift profile arg if it was provided, pass rest to claude
|
||||
if [[ $# -gt 0 ]]; then
|
||||
# Shift profile arg only if first arg was NOT a flag
|
||||
if [[ $# -gt 0 ]] && [[ ! "${1}" =~ ^- ]]; then
|
||||
shift
|
||||
fi
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
param(
|
||||
[Parameter(Position=0)]
|
||||
[string]$Profile = "default",
|
||||
[string]$ProfileOrFlag = "default",
|
||||
|
||||
[Parameter(ValueFromRemainingArguments=$true)]
|
||||
[string[]]$RemainingArgs
|
||||
@@ -12,18 +12,26 @@ param(
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Version
|
||||
$CCS_VERSION = "1.1.0"
|
||||
# Version - Read from VERSION file
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$VersionFile = Join-Path $ScriptDir "VERSION"
|
||||
$CCS_VERSION = if (Test-Path $VersionFile) {
|
||||
(Get-Content $VersionFile -Raw).Trim()
|
||||
} else {
|
||||
"unknown"
|
||||
}
|
||||
|
||||
# Special case: version command
|
||||
if ($Profile -eq "version" -or $Profile -eq "--version" -or $Profile -eq "-v") {
|
||||
# Special case: version command (check BEFORE profile detection)
|
||||
# Check both $ProfileOrFlag and first element of $RemainingArgs
|
||||
$FirstArg = if ($ProfileOrFlag -ne "default") { $ProfileOrFlag } elseif ($RemainingArgs.Count -gt 0) { $RemainingArgs[0] } else { $null }
|
||||
if ($FirstArg -eq "version" -or $FirstArg -eq "--version" -or $FirstArg -eq "-v") {
|
||||
Write-Host "CCS (Claude Code Switch) version $CCS_VERSION"
|
||||
Write-Host "https://github.com/kaitranntt/ccs"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Special case: help command (forward to Claude CLI)
|
||||
if ($Profile -eq "--help" -or $Profile -eq "-h" -or $Profile -eq "help") {
|
||||
# Special case: help command (check BEFORE profile detection)
|
||||
if ($FirstArg -eq "--help" -or $FirstArg -eq "-h" -or $FirstArg -eq "help") {
|
||||
try {
|
||||
if ($RemainingArgs) {
|
||||
& claude --help @RemainingArgs
|
||||
@@ -38,6 +46,22 @@ if ($Profile -eq "--help" -or $Profile -eq "-h" -or $Profile -eq "help") {
|
||||
}
|
||||
}
|
||||
|
||||
# Smart profile detection: if first arg starts with '-', it's a flag not a profile
|
||||
if ($ProfileOrFlag -match '^-') {
|
||||
# First arg is a flag → use default profile, keep all args
|
||||
$Profile = "default"
|
||||
# Prepend $ProfileOrFlag to $RemainingArgs (it's actually a flag, not a profile)
|
||||
if ($RemainingArgs) {
|
||||
$RemainingArgs = @($ProfileOrFlag) + $RemainingArgs
|
||||
} else {
|
||||
$RemainingArgs = @($ProfileOrFlag)
|
||||
}
|
||||
} else {
|
||||
# First arg is a profile name
|
||||
$Profile = $ProfileOrFlag
|
||||
# $RemainingArgs already contains correct args (PowerShell handles this)
|
||||
}
|
||||
|
||||
# Special case: "default" profile just runs claude directly (no profile switching)
|
||||
if ($Profile -eq "default") {
|
||||
try {
|
||||
@@ -122,7 +146,7 @@ if (-not (Test-Path $SettingsPath)) {
|
||||
Write-Host "Error: Settings file not found: $SettingsPath" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "Solutions:" -ForegroundColor Yellow
|
||||
Write-Host " 1. Create the settings file with empty env:"
|
||||
Write-Host " 1. Create the settings file:"
|
||||
Write-Host " New-Item -ItemType File -Force -Path '$SettingsPath'"
|
||||
Write-Host " Set-Content -Path '$SettingsPath' -Value '{`"env`":{}}`'"
|
||||
Write-Host ""
|
||||
@@ -132,7 +156,7 @@ if (-not (Test-Path $SettingsPath)) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Read settings file (contains environment variables for Windows)
|
||||
# Validate settings file is valid JSON (basic check)
|
||||
try {
|
||||
$SettingsContent = Get-Content $SettingsPath -Raw -ErrorAction Stop
|
||||
$Settings = $SettingsContent | ConvertFrom-Json -ErrorAction Stop
|
||||
@@ -150,63 +174,16 @@ try {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Windows Claude CLI uses environment variables instead of --settings flag
|
||||
# Settings file contains env vars directly or nested in "env" object (Linux compat)
|
||||
|
||||
# Check if settings contain "env" wrapper (Linux format) or direct env vars (Windows format)
|
||||
$EnvVars = if ($Settings.env) {
|
||||
# Linux format: { "env": { "ANTHROPIC_AUTH_TOKEN": "..." } }
|
||||
$Settings.env
|
||||
} else {
|
||||
# Windows format: { "ANTHROPIC_AUTH_TOKEN": "..." }
|
||||
$Settings
|
||||
}
|
||||
|
||||
# Save original environment variables to restore after execution
|
||||
$OriginalEnvVars = @{}
|
||||
|
||||
if ($EnvVars.PSObject.Properties.Count -gt 0) {
|
||||
foreach ($Property in $EnvVars.PSObject.Properties) {
|
||||
$VarName = $Property.Name
|
||||
$VarValue = $Property.Value
|
||||
|
||||
try {
|
||||
# Save original value
|
||||
$OriginalEnvVars[$VarName] = [System.Environment]::GetEnvironmentVariable($VarName, [System.EnvironmentVariableTarget]::Process)
|
||||
|
||||
# Set new value for this process only
|
||||
[System.Environment]::SetEnvironmentVariable($VarName, $VarValue, [System.EnvironmentVariableTarget]::Process)
|
||||
} catch {
|
||||
Write-Host "Warning: Could not set environment variable '$VarName'" -ForegroundColor Yellow
|
||||
Write-Host " Error: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Execute claude with environment variables set
|
||||
# Execute claude with settings file (using --settings flag)
|
||||
try {
|
||||
if ($RemainingArgs) {
|
||||
& claude @RemainingArgs
|
||||
& claude --settings $SettingsPath @RemainingArgs
|
||||
} else {
|
||||
& claude
|
||||
}
|
||||
$ExitCode = $LASTEXITCODE
|
||||
} finally {
|
||||
# Restore original environment variables
|
||||
foreach ($VarName in $OriginalEnvVars.Keys) {
|
||||
$OriginalValue = $OriginalEnvVars[$VarName]
|
||||
try {
|
||||
if ($null -eq $OriginalValue) {
|
||||
# Variable didn't exist before, remove it
|
||||
[System.Environment]::SetEnvironmentVariable($VarName, $null, [System.EnvironmentVariableTarget]::Process)
|
||||
} else {
|
||||
# Restore original value
|
||||
[System.Environment]::SetEnvironmentVariable($VarName, $OriginalValue, [System.EnvironmentVariableTarget]::Process)
|
||||
}
|
||||
} catch {
|
||||
Write-Host "Warning: Could not restore environment variable '$VarName'" -ForegroundColor Yellow
|
||||
}
|
||||
& claude --settings $SettingsPath
|
||||
}
|
||||
exit $LASTEXITCODE
|
||||
} catch {
|
||||
Write-Host "Error: Failed to execute claude" -ForegroundColor Red
|
||||
Write-Host $_.Exception.Message
|
||||
exit 1
|
||||
}
|
||||
|
||||
exit $ExitCode
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
{
|
||||
"profiles": {
|
||||
"glm": "~/.ccs/glm.settings.json",
|
||||
"sonnet": "~/.ccs/sonnet.settings.json",
|
||||
"default": "~/.claude/settings.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
# CCS Workflow Documentation
|
||||
|
||||
Workflow documentation for CCS v2.0.0 covering installation, runtime behavior, and troubleshooting.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
User Command: ccs [profile] [claude-args...]
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ ccs (wrapper) │ ← Bash (Unix) or PowerShell (Windows)
|
||||
└────────┬────────┘
|
||||
│
|
||||
├─ 1. Read ~/.ccs/config.json
|
||||
├─ 2. Lookup profile → settings file
|
||||
├─ 3. Validate settings file exists
|
||||
│
|
||||
▼
|
||||
┌────────────────┐
|
||||
│ Profile System │
|
||||
└───────┬────────┘
|
||||
│
|
||||
├─ default: ~/.claude/settings.json
|
||||
└─ glm: ~/.ccs/glm.settings.json
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ Claude CLI │
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
### Key Components
|
||||
|
||||
1. **ccs wrapper**: Lightweight script (bash/PowerShell)
|
||||
2. **Config file**: `~/.ccs/config.json` (profile → settings mappings)
|
||||
3. **Settings files**: Claude CLI settings JSON format
|
||||
4. **Claude CLI**: Official Anthropic CLI (unchanged)
|
||||
|
||||
### Design Principles
|
||||
|
||||
- **YAGNI**: Only 2 profiles (default/glm)
|
||||
- **KISS**: Simple delegation, no magic
|
||||
- **DRY**: One source of truth (config.json)
|
||||
- **Non-invasive**: Never modifies ~/.claude/settings.json
|
||||
|
||||
---
|
||||
|
||||
## Installation Workflow
|
||||
|
||||
### Process
|
||||
|
||||
1. **Create directories**: `~/.ccs/`, `~/.local/bin/`
|
||||
2. **Install executables**:
|
||||
- Git mode: Symlink from repo
|
||||
- Standalone: Download from GitHub
|
||||
3. **Install .claude folder**: Commands and skills
|
||||
4. **Create config**: `config.json` if missing
|
||||
5. **Create GLM profile**: `glm.settings.json` if missing
|
||||
6. **Backup**: Single `config.json.backup` (overwrites)
|
||||
7. **Validate**: Check JSON syntax
|
||||
|
||||
### Files Created
|
||||
|
||||
```
|
||||
~/.ccs/
|
||||
├── ccs # Main executable
|
||||
├── config.json # Profile mappings
|
||||
├── config.json.backup # Single backup (no timestamp)
|
||||
├── glm.settings.json # GLM profile
|
||||
├── uninstall.sh # Uninstaller
|
||||
└── .claude/ # Claude Code integration
|
||||
├── commands/ccs.md
|
||||
└── skills/ccs-delegation/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Runtime Workflow
|
||||
|
||||
### Unix/Linux/macOS
|
||||
|
||||
```
|
||||
ccs [profile] [args]
|
||||
↓
|
||||
Read config.json
|
||||
↓
|
||||
Lookup profile → settings file path
|
||||
↓
|
||||
Validate file exists
|
||||
↓
|
||||
exec claude --settings <path> [args]
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
```
|
||||
ccs [profile] [args]
|
||||
↓
|
||||
Read config.json
|
||||
↓
|
||||
Lookup profile → settings file path
|
||||
↓
|
||||
Validate file exists
|
||||
↓
|
||||
exec claude --settings <path> [args]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Profile System
|
||||
|
||||
### Config Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"profiles": {
|
||||
"glm": "~/.ccs/glm.settings.json",
|
||||
"default": "~/.claude/settings.json"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Settings File Format
|
||||
|
||||
**GLM Profile** (`~/.ccs/glm.settings.json`):
|
||||
```json
|
||||
{
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic",
|
||||
"ANTHROPIC_AUTH_TOKEN": "your_api_key",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Claude (Default)** (`~/.claude/settings.json`):
|
||||
- User-managed, CCS never modifies it
|
||||
- Referenced by default profile
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Profile Not Found
|
||||
|
||||
| Check | Fix |
|
||||
|-------|-----|
|
||||
| config.json exists? | Run installer |
|
||||
| Valid JSON? | Fix syntax or restore from backup |
|
||||
| Profile in config? | Add profile or use default/glm |
|
||||
| Settings file exists? | Create from template |
|
||||
|
||||
### PowerShell Crash (Windows)
|
||||
|
||||
**Error**: `SetEnvironmentVariable` error
|
||||
|
||||
**Fix**:
|
||||
1. Check settings format has `{"env": {...}}`
|
||||
2. Ensure all values are strings (not booleans/objects)
|
||||
3. Remove non-env fields from settings file
|
||||
|
||||
### Installation Issues
|
||||
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| 404 Not Found | Check installers exist in GitHub |
|
||||
| Permission denied | Check ~/.ccs/ permissions |
|
||||
| jq not found | Install jq: `brew install jq` (Unix) |
|
||||
| PATH warning | Add `~/.local/bin` to PATH |
|
||||
|
||||
---
|
||||
|
||||
## Key Points
|
||||
|
||||
1. **Installation**: Creates 2 profiles (glm + default), validates JSON
|
||||
2. **Runtime**: Simple delegation to Claude CLI via `--settings` flag
|
||||
3. **Cross-platform**: Identical behavior on Unix/Linux/macOS/Windows
|
||||
4. **Non-invasive**: Never touches `~/.claude/settings.json`
|
||||
5. **Validation**: JSON syntax checking prevents errors
|
||||
6. **Backup**: Single file, overwrites each install
|
||||
|
||||
---
|
||||
|
||||
**Version**: v2.0.0
|
||||
**Updated**: 2025-11-02
|
||||
@@ -0,0 +1,101 @@
|
||||
# Version Management
|
||||
|
||||
## Overview
|
||||
|
||||
CCS uses a centralized version management system to ensure consistency across all components.
|
||||
|
||||
## Version Locations
|
||||
|
||||
The version number must be kept in sync across these files:
|
||||
|
||||
1. **`VERSION`** - Primary version file (read by ccs/ccs.ps1 at runtime)
|
||||
2. **`installers/install.sh`** - Hardcoded for standalone installations (`curl | bash`)
|
||||
3. **`installers/install.ps1`** - Hardcoded for standalone installations (`irm | iex`)
|
||||
|
||||
## Why Hardcoded Versions in Installers?
|
||||
|
||||
When users run:
|
||||
- `curl -fsSL ccs.kaitran.ca/install | bash`
|
||||
- `irm ccs.kaitran.ca/install.ps1 | iex`
|
||||
|
||||
The installer script is downloaded and executed directly **without** the VERSION file. Therefore, installers must have a hardcoded version as fallback.
|
||||
|
||||
For git-based installations, the VERSION file is read if available, overriding the hardcoded version.
|
||||
|
||||
## Updating Version
|
||||
|
||||
### Automated Method (Recommended)
|
||||
|
||||
Use the provided script to bump the version automatically:
|
||||
|
||||
```bash
|
||||
# Bump patch version (2.1.1 -> 2.1.2)
|
||||
./scripts/bump-version.sh patch
|
||||
|
||||
# Bump minor version (2.1.1 -> 2.2.0)
|
||||
./scripts/bump-version.sh minor
|
||||
|
||||
# Bump major version (2.1.1 -> 3.0.0)
|
||||
./scripts/bump-version.sh major
|
||||
```
|
||||
|
||||
This updates:
|
||||
- VERSION file
|
||||
- installers/install.sh (hardcoded version)
|
||||
- installers/install.ps1 (hardcoded version)
|
||||
- Creates git tag (if in git repo)
|
||||
|
||||
### Manual Method
|
||||
|
||||
If updating manually, update version in ALL three locations:
|
||||
|
||||
1. **VERSION file**:
|
||||
```bash
|
||||
echo "2.1.2" > VERSION
|
||||
```
|
||||
|
||||
2. **installers/install.sh** (line ~34):
|
||||
```bash
|
||||
CCS_VERSION="2.1.2"
|
||||
```
|
||||
|
||||
3. **installers/install.ps1** (line ~33):
|
||||
```powershell
|
||||
$CcsVersion = "2.1.2"
|
||||
```
|
||||
|
||||
## Release Checklist
|
||||
|
||||
When releasing a new version:
|
||||
|
||||
- [ ] Update version using `./scripts/update-version.sh X.Y.Z`
|
||||
- [ ] Review changes: `git diff`
|
||||
- [ ] Update CHANGELOG.md with release notes
|
||||
- [ ] Commit changes: `git commit -am "chore: bump version to X.Y.Z"`
|
||||
- [ ] Create tag: `git tag -a vX.Y.Z -m "Release vX.Y.Z"`
|
||||
- [ ] Push: `git push && git push --tags`
|
||||
- [ ] Verify CloudFlare worker serves updated installer
|
||||
|
||||
## Version Display
|
||||
|
||||
After installation, users can check version:
|
||||
|
||||
```bash
|
||||
# Shows CCS version (from VERSION file if available)
|
||||
ccs --version
|
||||
|
||||
# Shows Claude CLI version
|
||||
ccs version
|
||||
```
|
||||
|
||||
## Semantic Versioning
|
||||
|
||||
CCS follows [Semantic Versioning](https://semver.org/):
|
||||
|
||||
- **MAJOR** (X.0.0): Breaking changes
|
||||
- **MINOR** (0.X.0): New features (backward compatible)
|
||||
- **PATCH** (0.0.X): Bug fixes
|
||||
|
||||
Current version: **2.1.1**
|
||||
- 2.1.0: Added task delegation feature
|
||||
- 2.1.1: Fixed argument parsing bug (flags treated as profiles)
|
||||
@@ -20,12 +20,33 @@ $ScriptDir = if ($MyInvocation.MyCommand.Path) {
|
||||
$null
|
||||
}
|
||||
|
||||
$InstallMethod = if ($ScriptDir -and (Test-Path "$ScriptDir\ccs.ps1")) {
|
||||
$InstallMethod = if ($ScriptDir -and ((Test-Path "$ScriptDir\ccs.ps1") -or (Test-Path "$ScriptDir\..\ccs.ps1"))) {
|
||||
"git"
|
||||
} else {
|
||||
"standalone"
|
||||
}
|
||||
|
||||
# Version configuration
|
||||
# IMPORTANT: Update this version when releasing new versions!
|
||||
# This hardcoded version is used for standalone installations (irm | iex)
|
||||
# For git installations, VERSION file is read if available
|
||||
$CcsVersion = "2.1.1"
|
||||
|
||||
# Try to read VERSION file for git installations
|
||||
if ($ScriptDir) {
|
||||
$VersionFile = if (Test-Path "$ScriptDir\VERSION") {
|
||||
"$ScriptDir\VERSION"
|
||||
} elseif (Test-Path "$ScriptDir\..\VERSION") {
|
||||
"$ScriptDir\..\VERSION"
|
||||
} else {
|
||||
$null
|
||||
}
|
||||
|
||||
if ($VersionFile -and (Test-Path $VersionFile)) {
|
||||
$CcsVersion = (Get-Content $VersionFile -Raw).Trim()
|
||||
}
|
||||
}
|
||||
|
||||
# Helper Functions
|
||||
|
||||
function Detect-CurrentProvider {
|
||||
@@ -62,11 +83,6 @@ function New-GlmTemplate {
|
||||
return $Template | ConvertTo-Json -Depth 10
|
||||
}
|
||||
|
||||
function New-SonnetTemplate {
|
||||
$Template = @{ env = @{} }
|
||||
return $Template | ConvertTo-Json -Depth 10
|
||||
}
|
||||
|
||||
function New-GlmProfile {
|
||||
param([string]$Provider)
|
||||
|
||||
@@ -86,7 +102,7 @@ function New-GlmProfile {
|
||||
$Config.env | Add-Member -NotePropertyName ANTHROPIC_DEFAULT_HAIKU_MODEL -NotePropertyValue $GlmModel -Force
|
||||
|
||||
$Config | ConvertTo-Json -Depth 10 | Set-Content $GlmSettings
|
||||
Write-Host " Created: $GlmSettings (with your existing API key + enhanced settings)"
|
||||
Write-Host " Created: $GlmSettings with your existing API key + enhanced settings"
|
||||
} catch {
|
||||
Write-Host " [i] Copying current settings failed, using template"
|
||||
New-GlmTemplate | Set-Content $GlmSettings
|
||||
@@ -99,44 +115,63 @@ function New-GlmProfile {
|
||||
}
|
||||
}
|
||||
|
||||
function New-SonnetProfile {
|
||||
param([string]$Provider)
|
||||
function Install-ClaudeFolder {
|
||||
param(
|
||||
[string]$SourceDir
|
||||
)
|
||||
|
||||
$CurrentSettings = "$ClaudeDir\settings.json"
|
||||
$SonnetSettings = "$CcsDir\sonnet.settings.json"
|
||||
$TargetDir = "$CcsDir\.claude"
|
||||
|
||||
if ($Provider -eq "claude" -and (Test-Path $CurrentSettings)) {
|
||||
Write-Host "[OK] Copying current Claude config to profile..."
|
||||
Copy-Item $CurrentSettings $SonnetSettings
|
||||
Write-Host " Created: $SonnetSettings"
|
||||
} else {
|
||||
Write-Host "Creating Claude Sonnet profile template at $SonnetSettings"
|
||||
# Check if already exists
|
||||
if (Test-Path $TargetDir) {
|
||||
Write-Host "| [i] .claude/ folder already exists, skipping"
|
||||
return $true
|
||||
}
|
||||
|
||||
if (Test-Path $CurrentSettings) {
|
||||
# Create directory structure
|
||||
$null = New-Item -ItemType Directory -Force -Path "$TargetDir\commands"
|
||||
$null = New-Item -ItemType Directory -Force -Path "$TargetDir\skills\ccs-delegation\references"
|
||||
|
||||
if ($InstallMethod -eq "git" -and $SourceDir) {
|
||||
# Copy from local git repo
|
||||
$SourceClaudeDir = Join-Path $SourceDir ".claude"
|
||||
if (Test-Path $SourceClaudeDir) {
|
||||
try {
|
||||
$Config = Get-Content $CurrentSettings -Raw | ConvertFrom-Json
|
||||
# Remove GLM-specific vars
|
||||
if ($Config.env) {
|
||||
$Config.env.PSObject.Properties.Remove('ANTHROPIC_BASE_URL')
|
||||
$Config.env.PSObject.Properties.Remove('ANTHROPIC_AUTH_TOKEN')
|
||||
$Config.env.PSObject.Properties.Remove('ANTHROPIC_MODEL')
|
||||
}
|
||||
$Config | ConvertTo-Json -Depth 10 | Set-Content $SonnetSettings
|
||||
Copy-Item -Path "$SourceClaudeDir\*" -Destination $TargetDir -Recurse -Force
|
||||
Write-Host "| [OK] Installed .claude/ folder"
|
||||
return $true
|
||||
} catch {
|
||||
New-SonnetTemplate | Set-Content $SonnetSettings
|
||||
Write-Host "| [!] Failed to copy .claude/ folder"
|
||||
return $false
|
||||
}
|
||||
} else {
|
||||
New-SonnetTemplate | Set-Content $SonnetSettings
|
||||
Write-Host "| [!] .claude/ folder not found in source"
|
||||
return $false
|
||||
}
|
||||
} else {
|
||||
# Standalone: download from GitHub
|
||||
try {
|
||||
$BaseUrl = "https://raw.githubusercontent.com/kaitranntt/ccs/main/.claude"
|
||||
|
||||
Write-Host " Created: $SonnetSettings"
|
||||
Write-Host " [i] This uses your Claude subscription (no API key needed)"
|
||||
Invoke-WebRequest -Uri "$BaseUrl/commands/ccs.md" `
|
||||
-OutFile "$TargetDir\commands\ccs.md" -UseBasicParsing
|
||||
Invoke-WebRequest -Uri "$BaseUrl/skills/ccs-delegation/SKILL.md" `
|
||||
-OutFile "$TargetDir\skills\ccs-delegation\SKILL.md" -UseBasicParsing
|
||||
Invoke-WebRequest -Uri "$BaseUrl/skills/ccs-delegation/references/delegation-patterns.md" `
|
||||
-OutFile "$TargetDir\skills\ccs-delegation\references\delegation-patterns.md" -UseBasicParsing
|
||||
|
||||
Write-Host "| [OK] Downloaded .claude/ folder"
|
||||
return $true
|
||||
} catch {
|
||||
Write-Host "| [!] Failed to download .claude/ folder"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Main Installation
|
||||
|
||||
Write-Host "┌─ Installing CCS (Windows)"
|
||||
Write-Host '===== Installing CCS (Windows) ====='
|
||||
|
||||
# Create directories
|
||||
New-Item -ItemType Directory -Force -Path $CcsDir | Out-Null
|
||||
@@ -158,8 +193,28 @@ if ($InstallMethod -eq "standalone") {
|
||||
}
|
||||
} else {
|
||||
# Git install - copy local file
|
||||
Copy-Item "$ScriptDir\ccs.ps1" "$CcsDir\ccs.ps1" -Force
|
||||
$CcsPs1Path = if (Test-Path "$ScriptDir\ccs.ps1") {
|
||||
"$ScriptDir\ccs.ps1"
|
||||
} elseif (Test-Path "$ScriptDir\..\ccs.ps1") {
|
||||
"$ScriptDir\..\ccs.ps1"
|
||||
} else {
|
||||
throw "ccs.ps1 not found"
|
||||
}
|
||||
Copy-Item $CcsPs1Path "$CcsDir\ccs.ps1" -Force
|
||||
Write-Host "| [OK] Installed ccs.ps1"
|
||||
|
||||
# Copy VERSION file if available (for proper version display)
|
||||
$VersionPath = if (Test-Path "$ScriptDir\VERSION") {
|
||||
"$ScriptDir\VERSION"
|
||||
} elseif (Test-Path "$ScriptDir\..\VERSION") {
|
||||
"$ScriptDir\..\VERSION"
|
||||
} else {
|
||||
$null
|
||||
}
|
||||
if ($VersionPath) {
|
||||
Copy-Item $VersionPath "$CcsDir\VERSION" -Force
|
||||
Write-Host "| [OK] Installed VERSION file"
|
||||
}
|
||||
}
|
||||
|
||||
# Install uninstall script as ccs-uninstall.ps1
|
||||
@@ -189,7 +244,16 @@ if ($ScriptDir -and (Test-Path "$ScriptDir\uninstall.ps1")) {
|
||||
}
|
||||
|
||||
Write-Host "| [OK] Created directories"
|
||||
Write-Host "└─"
|
||||
|
||||
# Install .claude/ folder
|
||||
if ($InstallMethod -eq "git" -and $ScriptDir) {
|
||||
$ParentDir = Split-Path -Parent $ScriptDir
|
||||
$null = Install-ClaudeFolder -SourceDir $ParentDir
|
||||
} else {
|
||||
$null = Install-ClaudeFolder -SourceDir ""
|
||||
}
|
||||
|
||||
Write-Host "========================================="
|
||||
Write-Host ""
|
||||
|
||||
# Profile Setup
|
||||
@@ -197,50 +261,68 @@ Write-Host ""
|
||||
$CurrentProvider = Detect-CurrentProvider
|
||||
|
||||
$ProviderLabel = switch ($CurrentProvider) {
|
||||
"glm" { " (detected: GLM)" }
|
||||
"claude" { " (detected: Claude)" }
|
||||
"custom" { " (detected: custom)" }
|
||||
"glm" { ' (detected: GLM)' }
|
||||
"claude" { ' (detected: Claude)' }
|
||||
"custom" { ' (detected: custom)' }
|
||||
default { "" }
|
||||
}
|
||||
|
||||
Write-Host "┌─ Configuring Profiles$ProviderLabel"
|
||||
Write-Host "===== Configuring Profiles v$CcsVersion$ProviderLabel"
|
||||
|
||||
$NeedsGlmKey = $false
|
||||
|
||||
# Create ccs config with file paths (same structure as Linux)
|
||||
# Backup existing config (single backup, no timestamp)
|
||||
$ConfigFile = "$CcsDir\config.json"
|
||||
if (-not (Test-Path $ConfigFile)) {
|
||||
$ConfigContent = @{
|
||||
profiles = @{
|
||||
glm = "~/.ccs/glm.settings.json"
|
||||
son = "~/.ccs/sonnet.settings.json"
|
||||
}
|
||||
}
|
||||
|
||||
$ConfigContent | ConvertTo-Json -Depth 10 | Set-Content $ConfigFile
|
||||
Write-Host "| [OK] Config -> $env:USERPROFILE\.ccs\config.json"
|
||||
$BackupFile = "$CcsDir\config.json.backup"
|
||||
if (Test-Path $ConfigFile) {
|
||||
Copy-Item $ConfigFile $BackupFile -Force
|
||||
}
|
||||
|
||||
# Create profile settings files
|
||||
$NeedsGlmKey = $false
|
||||
$GlmSettings = "$CcsDir\glm.settings.json"
|
||||
$SonnetSettings = "$CcsDir\sonnet.settings.json"
|
||||
|
||||
# Create GLM profile if missing
|
||||
if (-not (Test-Path $GlmSettings)) {
|
||||
New-GlmProfile -Provider $CurrentProvider
|
||||
if ($CurrentProvider -ne "glm") {
|
||||
$NeedsGlmKey = $true
|
||||
}
|
||||
} else {
|
||||
Write-Host "| [OK] GLM profile exists: $GlmSettings"
|
||||
Write-Host '| [OK] GLM profile exists'
|
||||
}
|
||||
|
||||
if (-not (Test-Path $SonnetSettings)) {
|
||||
New-SonnetProfile -Provider $CurrentProvider
|
||||
} else {
|
||||
Write-Host "| [OK] Sonnet profile exists: $SonnetSettings"
|
||||
# Create config if missing
|
||||
if (-not (Test-Path $ConfigFile)) {
|
||||
$ConfigContent = @{
|
||||
profiles = @{
|
||||
glm = "~/.ccs/glm.settings.json"
|
||||
default = "~/.claude/settings.json"
|
||||
}
|
||||
}
|
||||
$ConfigContent | ConvertTo-Json -Depth 10 | Set-Content $ConfigFile
|
||||
Write-Host ('| OK: Config created at {0}\.ccs\config.json' -f $env:USERPROFILE)
|
||||
}
|
||||
|
||||
Write-Host "└─"
|
||||
# Validate config JSON
|
||||
if (Test-Path $ConfigFile) {
|
||||
try {
|
||||
$null = Get-Content $ConfigFile -Raw | ConvertFrom-Json
|
||||
} catch {
|
||||
Write-Host '| [!] Warning: Invalid JSON in config.json' -ForegroundColor Yellow
|
||||
if (Test-Path $BackupFile) {
|
||||
Write-Host ('| Restore from: {0}' -f $BackupFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Validate GLM settings JSON
|
||||
if (Test-Path $GlmSettings) {
|
||||
try {
|
||||
$null = Get-Content $GlmSettings -Raw | ConvertFrom-Json
|
||||
} catch {
|
||||
Write-Host '| [!] Warning: Invalid JSON in glm.settings.json' -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "========================================="
|
||||
Write-Host ""
|
||||
|
||||
# Check and update PATH
|
||||
@@ -274,25 +356,16 @@ if ($NeedsGlmKey) {
|
||||
|
||||
Write-Host "[SUCCESS] CCS installed successfully!"
|
||||
Write-Host ""
|
||||
|
||||
# Build quick start based on current provider
|
||||
if ($CurrentProvider -eq "claude") {
|
||||
Write-Host " Quick start:"
|
||||
Write-Host " ccs son # Claude Sonnet (current)"
|
||||
Write-Host " ccs glm # GLM (after adding API key)"
|
||||
Write-Host " ccs # Default (no profile)"
|
||||
} elseif ($CurrentProvider -eq "glm") {
|
||||
Write-Host " Quick start:"
|
||||
Write-Host " ccs glm # GLM (current)"
|
||||
Write-Host " ccs son # Claude Sonnet"
|
||||
Write-Host " ccs # Default (no profile)"
|
||||
} else {
|
||||
Write-Host " Quick start:"
|
||||
Write-Host " ccs # Default (no profile)"
|
||||
Write-Host " ccs son # Claude Sonnet"
|
||||
Write-Host " ccs glm # GLM (after adding API key)"
|
||||
}
|
||||
|
||||
Write-Host " Installed components:"
|
||||
Write-Host " * ccs command -> $CcsDir\ccs.ps1"
|
||||
Write-Host " * config -> $CcsDir\config.json"
|
||||
Write-Host " * glm profile -> $CcsDir\glm.settings.json"
|
||||
Write-Host " * .claude/ folder -> $CcsDir\.claude\"
|
||||
Write-Host ""
|
||||
Write-Host " Quick start:"
|
||||
Write-Host " ccs # Use Claude subscription - default"
|
||||
Write-Host " ccs glm # Use GLM fallback"
|
||||
Write-Host ""
|
||||
Write-Host ""
|
||||
Write-Host " Usage: ccs [profile] [claude-args]"
|
||||
Write-Host " Example: ccs glm /plan 'implement feature'"
|
||||
@@ -19,14 +19,27 @@ else
|
||||
fi
|
||||
|
||||
# Detect installation method (git vs standalone)
|
||||
# Check if ccs executable exists in SCRIPT_DIR (real git install)
|
||||
# Check if ccs executable exists in SCRIPT_DIR or parent (real git install)
|
||||
# Don't just check .git (user might run curl | bash inside their own git repo)
|
||||
if [[ -f "$SCRIPT_DIR/ccs" ]]; then
|
||||
if [[ -f "$SCRIPT_DIR/ccs" ]] || [[ -f "$SCRIPT_DIR/../ccs" ]]; then
|
||||
INSTALL_METHOD="git"
|
||||
else
|
||||
INSTALL_METHOD="standalone"
|
||||
fi
|
||||
|
||||
# Version configuration
|
||||
# IMPORTANT: Update this version when releasing new versions!
|
||||
# This hardcoded version is used for standalone installations (curl | bash)
|
||||
# For git installations, VERSION file is read if available
|
||||
CCS_VERSION="2.1.1"
|
||||
|
||||
# Try to read VERSION file for git installations
|
||||
if [[ -f "$SCRIPT_DIR/VERSION" ]]; then
|
||||
CCS_VERSION="$(cat "$SCRIPT_DIR/VERSION" | tr -d '\n' | tr -d '\r')"
|
||||
elif [[ -f "$SCRIPT_DIR/../VERSION" ]]; then
|
||||
CCS_VERSION="$(cat "$SCRIPT_DIR/../VERSION" | tr -d '\n' | tr -d '\r')"
|
||||
fi
|
||||
|
||||
# --- Platform Detection ---
|
||||
# Detect platform and redirect to Windows installer if needed
|
||||
detect_platform() {
|
||||
@@ -91,14 +104,6 @@ create_glm_template() {
|
||||
EOF
|
||||
}
|
||||
|
||||
create_sonnet_template() {
|
||||
cat << 'EOF'
|
||||
{
|
||||
"env": {}
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
atomic_mv() {
|
||||
local src="$1"
|
||||
local dest="$2"
|
||||
@@ -106,11 +111,60 @@ atomic_mv() {
|
||||
return 0
|
||||
else
|
||||
rm -f "$src"
|
||||
echo " ❌ Error: Failed to create $dest (check permissions)"
|
||||
echo " ✗ Error: Failed to create $dest (check permissions)"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
download_file() {
|
||||
local url="$1"
|
||||
local dest="$2"
|
||||
|
||||
if ! curl -fsSL "$url" -o "$dest"; then
|
||||
echo " ⚠ Failed to download: $(basename "$dest")"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
install_claude_folder() {
|
||||
local source_dir="$1"
|
||||
local target_dir="$CCS_DIR/.claude"
|
||||
|
||||
# Check if already exists
|
||||
if [[ -d "$target_dir" ]]; then
|
||||
echo "│ ℹ .claude/ folder already exists, skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
mkdir -p "$target_dir/commands" "$target_dir/skills/ccs-delegation/references"
|
||||
|
||||
if [[ "$INSTALL_METHOD" == "git" ]]; then
|
||||
# Copy from local git repo
|
||||
if [[ -d "$source_dir/.claude" ]]; then
|
||||
cp -r "$source_dir/.claude"/* "$target_dir/" 2>/dev/null || {
|
||||
echo "│ ⚠ Failed to copy .claude/ folder"
|
||||
return 1
|
||||
}
|
||||
echo "│ ✓ Installed .claude/ folder"
|
||||
else
|
||||
echo "│ ⚠ .claude/ folder not found in source"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
# Standalone: download from GitHub
|
||||
local base_url="https://raw.githubusercontent.com/kaitranntt/ccs/main/.claude"
|
||||
|
||||
download_file "$base_url/commands/ccs.md" "$target_dir/commands/ccs.md" || return 1
|
||||
download_file "$base_url/skills/ccs-delegation/SKILL.md" "$target_dir/skills/ccs-delegation/SKILL.md" || return 1
|
||||
download_file "$base_url/skills/ccs-delegation/references/delegation-patterns.md" "$target_dir/skills/ccs-delegation/references/delegation-patterns.md" || return 1
|
||||
|
||||
echo "│ ✓ Downloaded .claude/ folder"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
create_glm_profile() {
|
||||
local current_settings="$CLAUDE_DIR/settings.json"
|
||||
local glm_settings="$CCS_DIR/glm.settings.json"
|
||||
@@ -149,50 +203,14 @@ create_glm_profile() {
|
||||
atomic_mv "$glm_settings.tmp" "$glm_settings"
|
||||
else
|
||||
rm -f "$glm_settings.tmp"
|
||||
echo " ℹ️ jq failed, using basic template"
|
||||
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="$CCS_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
|
||||
# Remove GLM-specific vars, but keep ANTHROPIC_DEFAULT_* if they contain "claude" (user preference)
|
||||
# Filter logic assumes Claude model IDs contain "claude" substring (case-insensitive)
|
||||
if jq 'del(.env.ANTHROPIC_BASE_URL, .env.ANTHROPIC_AUTH_TOKEN, .env.ANTHROPIC_MODEL) |
|
||||
.env |= with_entries(
|
||||
select(
|
||||
(.key | IN("ANTHROPIC_DEFAULT_OPUS_MODEL", "ANTHROPIC_DEFAULT_SONNET_MODEL", "ANTHROPIC_DEFAULT_HAIKU_MODEL") | not) or
|
||||
(.value | tostring? // "" | ascii_downcase | contains("claude"))
|
||||
)
|
||||
) |
|
||||
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)"
|
||||
echo " ⚠ Edit this file and replace YOUR_GLM_API_KEY_HERE with your actual GLM API key"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -207,7 +225,7 @@ mkdir -p "$INSTALL_DIR" "$CCS_DIR"
|
||||
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"
|
||||
echo "✗ Error: curl is required for standalone installation"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -222,9 +240,28 @@ if [[ "$INSTALL_METHOD" == "standalone" ]]; then
|
||||
fi
|
||||
else
|
||||
# Git install - use local ccs file
|
||||
chmod +x "$SCRIPT_DIR/ccs"
|
||||
ln -sf "$SCRIPT_DIR/ccs" "$INSTALL_DIR/ccs"
|
||||
# Handle both running from root or from installers/ subdirectory
|
||||
if [[ -f "$SCRIPT_DIR/ccs" ]]; then
|
||||
chmod +x "$SCRIPT_DIR/ccs"
|
||||
ln -sf "$SCRIPT_DIR/ccs" "$INSTALL_DIR/ccs"
|
||||
elif [[ -f "$SCRIPT_DIR/../ccs" ]]; then
|
||||
chmod +x "$SCRIPT_DIR/../ccs"
|
||||
ln -sf "$SCRIPT_DIR/../ccs" "$INSTALL_DIR/ccs"
|
||||
else
|
||||
echo "│"
|
||||
echo "✗ Error: ccs executable not found"
|
||||
exit 1
|
||||
fi
|
||||
echo "│ ✓ Installed executable"
|
||||
|
||||
# Copy VERSION file if available (for proper version display)
|
||||
if [[ -f "$SCRIPT_DIR/VERSION" ]]; then
|
||||
cp "$SCRIPT_DIR/VERSION" "$CCS_DIR/VERSION"
|
||||
echo "│ ✓ Installed VERSION file"
|
||||
elif [[ -f "$SCRIPT_DIR/../VERSION" ]]; then
|
||||
cp "$SCRIPT_DIR/../VERSION" "$CCS_DIR/VERSION"
|
||||
echo "│ ✓ Installed VERSION file"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -L "$INSTALL_DIR/ccs" ]]; then
|
||||
@@ -252,6 +289,14 @@ elif [[ "$INSTALL_METHOD" == "standalone" ]] && command -v curl &> /dev/null; th
|
||||
fi
|
||||
|
||||
echo "│ ✓ Created directories"
|
||||
|
||||
# Install .claude/ folder
|
||||
if [[ "$INSTALL_METHOD" == "git" ]]; then
|
||||
install_claude_folder "$SCRIPT_DIR/.." || echo "│ ⚠ Optional .claude/ installation skipped"
|
||||
else
|
||||
install_claude_folder "" || echo "│ ⚠ Optional .claude/ installation skipped"
|
||||
fi
|
||||
|
||||
echo "└─"
|
||||
echo ""
|
||||
|
||||
@@ -259,7 +304,6 @@ echo ""
|
||||
|
||||
CURRENT_PROVIDER=$(detect_current_provider)
|
||||
GLM_SETTINGS="$CCS_DIR/glm.settings.json"
|
||||
SONNET_SETTINGS="$CCS_DIR/sonnet.settings.json"
|
||||
|
||||
# Build provider label
|
||||
PROVIDER_LABEL=""
|
||||
@@ -267,30 +311,30 @@ PROVIDER_LABEL=""
|
||||
[[ "$CURRENT_PROVIDER" == "claude" ]] && PROVIDER_LABEL=" (detected: Claude)"
|
||||
[[ "$CURRENT_PROVIDER" == "custom" ]] && PROVIDER_LABEL=" (detected: custom)"
|
||||
|
||||
echo "┌─ Configuring Profiles${PROVIDER_LABEL}"
|
||||
echo "┌─ Configuring Profiles (v${CCS_VERSION})${PROVIDER_LABEL}"
|
||||
|
||||
# Backup existing config if present (single backup, no timestamp)
|
||||
BACKUP_FILE="$CCS_DIR/config.json.backup"
|
||||
if [[ -f "$CCS_DIR/config.json" ]]; then
|
||||
cp "$CCS_DIR/config.json" "$BACKUP_FILE"
|
||||
fi
|
||||
|
||||
# Track if GLM needs API key
|
||||
NEEDS_GLM_KEY=false
|
||||
|
||||
# Create missing profiles (silently, show only result)
|
||||
# Create GLM profile if missing
|
||||
if [[ ! -f "$GLM_SETTINGS" ]]; then
|
||||
create_glm_profile "$CURRENT_PROVIDER" >/dev/null 2>&1
|
||||
echo "│ ✓ GLM profile → ~/.ccs/glm.settings.json"
|
||||
[[ "$CURRENT_PROVIDER" != "glm" ]] && NEEDS_GLM_KEY=true
|
||||
fi
|
||||
|
||||
if [[ ! -f "$SONNET_SETTINGS" ]]; then
|
||||
create_sonnet_profile "$CURRENT_PROVIDER" >/dev/null 2>&1
|
||||
echo "│ ✓ Sonnet profile → ~/.ccs/sonnet.settings.json"
|
||||
fi
|
||||
|
||||
# Create ccs config
|
||||
# Create config if missing
|
||||
if [[ ! -f "$CCS_DIR/config.json" ]]; then
|
||||
cat > "$CCS_DIR/config.json.tmp" << 'EOF'
|
||||
{
|
||||
"profiles": {
|
||||
"glm": "~/.ccs/glm.settings.json",
|
||||
"son": "~/.ccs/sonnet.settings.json",
|
||||
"default": "~/.claude/settings.json"
|
||||
}
|
||||
}
|
||||
@@ -299,6 +343,27 @@ EOF
|
||||
echo "│ ✓ Config → ~/.ccs/config.json"
|
||||
fi
|
||||
|
||||
# Validate config JSON
|
||||
if [[ -f "$CCS_DIR/config.json" ]]; then
|
||||
if command -v jq &> /dev/null; then
|
||||
if ! jq -e . "$CCS_DIR/config.json" &>/dev/null; then
|
||||
echo "│ ⚠ Warning: Invalid JSON in config.json"
|
||||
if [[ -f "$BACKUP_FILE" ]]; then
|
||||
echo "│ Restore from: $BACKUP_FILE"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate GLM settings JSON
|
||||
if [[ -f "$GLM_SETTINGS" ]]; then
|
||||
if command -v jq &> /dev/null; then
|
||||
if ! jq -e . "$GLM_SETTINGS" &>/dev/null; then
|
||||
echo "│ ⚠ Warning: Invalid JSON in glm.settings.json"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "└─"
|
||||
echo ""
|
||||
|
||||
@@ -320,27 +385,18 @@ if [[ "$NEEDS_GLM_KEY" == "true" ]]; then
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "✅ CCS installed successfully!"
|
||||
echo "✓ CCS installed successfully!"
|
||||
echo ""
|
||||
echo " Installed components:"
|
||||
echo " • ccs command → ~/.local/bin/ccs"
|
||||
echo " • config → ~/.ccs/config.json"
|
||||
echo " • glm profile → ~/.ccs/glm.settings.json"
|
||||
echo " • .claude/ folder → ~/.ccs/.claude/"
|
||||
echo ""
|
||||
echo " Quick start:"
|
||||
echo " ccs # Use Claude subscription (default)"
|
||||
echo " ccs glm # Use GLM fallback"
|
||||
echo ""
|
||||
|
||||
# Build quick start based on current provider
|
||||
if [[ "$CURRENT_PROVIDER" == "claude" ]]; then
|
||||
echo " Quick start:"
|
||||
echo " ccs son # Claude Sonnet (current)"
|
||||
echo " ccs glm # GLM (after adding API key)"
|
||||
echo " ccs # Default profile"
|
||||
elif [[ "$CURRENT_PROVIDER" == "glm" ]]; then
|
||||
echo " Quick start:"
|
||||
echo " ccs glm # GLM (current)"
|
||||
echo " ccs son # Claude Sonnet"
|
||||
echo " ccs # Default profile"
|
||||
else
|
||||
echo " Quick start:"
|
||||
echo " ccs # Default profile"
|
||||
echo " ccs son # Claude Sonnet"
|
||||
echo " ccs glm # GLM (after adding API key)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo " To uninstall: ccs-uninstall"
|
||||
echo ""
|
||||
@@ -7,18 +7,18 @@ echo ""
|
||||
# Remove symlink
|
||||
if [[ -L "$HOME/.local/bin/ccs" ]]; then
|
||||
rm "$HOME/.local/bin/ccs"
|
||||
echo "✅ Removed: $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"
|
||||
echo "✓ Removed: $HOME/.local/bin/ccs"
|
||||
else
|
||||
echo "ℹ️ No ccs binary found at $HOME/.local/bin/ccs"
|
||||
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"
|
||||
echo "✓ Removed: $HOME/.local/bin/ccs-uninstall"
|
||||
fi
|
||||
|
||||
# Ask about ~/.ccs directory
|
||||
@@ -27,13 +27,13 @@ if [[ -d "$HOME/.ccs" ]]; then
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
rm -rf "$HOME/.ccs"
|
||||
echo "✅ Removed: $HOME/.ccs"
|
||||
echo "✓ Removed: $HOME/.ccs"
|
||||
else
|
||||
echo "ℹ️ Kept: $HOME/.ccs"
|
||||
echo "ℹ Kept: $HOME/.ccs"
|
||||
fi
|
||||
else
|
||||
echo "ℹ️ No CCS directory found at $HOME/.ccs"
|
||||
echo "ℹ No CCS directory found at $HOME/.ccs"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ Uninstall complete!"
|
||||
echo "✓ Uninstall complete!"
|
||||
Executable
+112
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env bash
|
||||
# Bump CCS version
|
||||
# Usage: ./scripts/bump-version.sh [major|minor|patch]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CCS_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
VERSION_FILE="$CCS_DIR/VERSION"
|
||||
|
||||
# Check VERSION file exists
|
||||
if [[ ! -f "$VERSION_FILE" ]]; then
|
||||
echo "✗ Error: VERSION file not found at $VERSION_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Read current version
|
||||
CURRENT_VERSION=$(cat "$VERSION_FILE")
|
||||
echo "Current version: $CURRENT_VERSION"
|
||||
|
||||
# Parse version
|
||||
if [[ ! "$CURRENT_VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
echo "✗ Error: Invalid version format in VERSION file"
|
||||
echo "Expected: MAJOR.MINOR.PATCH (e.g., 1.2.3)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MAJOR="${BASH_REMATCH[1]}"
|
||||
MINOR="${BASH_REMATCH[2]}"
|
||||
PATCH="${BASH_REMATCH[3]}"
|
||||
|
||||
# Determine bump type
|
||||
BUMP_TYPE="${1:-patch}"
|
||||
|
||||
case "$BUMP_TYPE" in
|
||||
major)
|
||||
MAJOR=$((MAJOR + 1))
|
||||
MINOR=0
|
||||
PATCH=0
|
||||
;;
|
||||
minor)
|
||||
MINOR=$((MINOR + 1))
|
||||
PATCH=0
|
||||
;;
|
||||
patch)
|
||||
PATCH=$((PATCH + 1))
|
||||
;;
|
||||
*)
|
||||
echo "✗ Error: Invalid bump type '$BUMP_TYPE'"
|
||||
echo "Usage: $0 [major|minor|patch]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
|
||||
|
||||
echo "New version: $NEW_VERSION"
|
||||
echo ""
|
||||
echo "This will:"
|
||||
echo " 1. Update VERSION file"
|
||||
echo " 2. Update installers/install.sh (hardcoded version)"
|
||||
echo " 3. Update installers/install.ps1 (hardcoded version)"
|
||||
echo " 4. Create git tag v$NEW_VERSION (if in git repo)"
|
||||
echo ""
|
||||
read -p "Continue? (y/N) " -n 1 -r
|
||||
echo
|
||||
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Cancelled."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Update VERSION file
|
||||
echo "$NEW_VERSION" > "$VERSION_FILE"
|
||||
echo "✓ Updated VERSION file to $NEW_VERSION"
|
||||
|
||||
# Update installers/install.sh
|
||||
INSTALL_SH="$CCS_DIR/installers/install.sh"
|
||||
if [[ -f "$INSTALL_SH" ]]; then
|
||||
sed -i.bak "s/^CCS_VERSION=\".*\"/CCS_VERSION=\"$NEW_VERSION\"/" "$INSTALL_SH"
|
||||
rm -f "$INSTALL_SH.bak"
|
||||
echo "✓ Updated installers/install.sh"
|
||||
else
|
||||
echo "⚠ installers/install.sh not found, skipping"
|
||||
fi
|
||||
|
||||
# Update installers/install.ps1
|
||||
INSTALL_PS1="$CCS_DIR/installers/install.ps1"
|
||||
if [[ -f "$INSTALL_PS1" ]]; then
|
||||
sed -i.bak "s/^\$CcsVersion = \".*\"/\$CcsVersion = \"$NEW_VERSION\"/" "$INSTALL_PS1"
|
||||
rm -f "$INSTALL_PS1.bak"
|
||||
echo "✓ Updated installers/install.ps1"
|
||||
else
|
||||
echo "⚠ installers/install.ps1 not found, skipping"
|
||||
fi
|
||||
|
||||
# Create git tag if in repo
|
||||
if git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
if git tag -a "v$NEW_VERSION" -m "Release v$NEW_VERSION"; then
|
||||
echo "✓ Created git tag v$NEW_VERSION"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " git push origin v$NEW_VERSION"
|
||||
else
|
||||
echo "⚠ Failed to create git tag (may already exist)"
|
||||
fi
|
||||
else
|
||||
echo "ℹ Not in git repository, skipping tag creation"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✓ Version bumped to $NEW_VERSION"
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# Get current CCS version
|
||||
# Usage: ./scripts/get-version.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CCS_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
VERSION_FILE="$CCS_DIR/VERSION"
|
||||
|
||||
if [[ -f "$VERSION_FILE" ]]; then
|
||||
cat "$VERSION_FILE"
|
||||
else
|
||||
echo "unknown"
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,46 @@
|
||||
export default {
|
||||
async fetch(request) {
|
||||
const url = new URL(request.url);
|
||||
|
||||
// Detect platform from User-Agent header
|
||||
const userAgent = request.headers.get('user-agent') || '';
|
||||
const isWindows = userAgent.includes('Windows') || userAgent.includes('Win32');
|
||||
const isPowerShell = userAgent.includes('PowerShell') || userAgent.includes('pwsh');
|
||||
|
||||
// Smart routing with platform detection
|
||||
let filePath;
|
||||
if (url.pathname === '/install' || url.pathname === '/install.sh') {
|
||||
filePath = (isWindows && isPowerShell) ? 'installers/install.ps1' : 'installers/install.sh';
|
||||
} else if (url.pathname === '/install.ps1') {
|
||||
filePath = 'installers/install.ps1';
|
||||
} else if (url.pathname === '/uninstall' || url.pathname === '/uninstall.sh') {
|
||||
filePath = (isWindows && isPowerShell) ? 'installers/uninstall.ps1' : 'installers/uninstall.sh';
|
||||
} else if (url.pathname === '/uninstall.ps1') {
|
||||
filePath = 'installers/uninstall.ps1';
|
||||
} else {
|
||||
return new Response('Not Found', { status: 404 });
|
||||
}
|
||||
|
||||
try {
|
||||
const githubUrl = `https://raw.githubusercontent.com/kaitranntt/ccs/main/${filePath}`;
|
||||
const response = await fetch(githubUrl);
|
||||
|
||||
if (!response.ok) {
|
||||
return new Response('File not found on GitHub', { status: 404 });
|
||||
}
|
||||
|
||||
const contentType = filePath.endsWith('.ps1')
|
||||
? 'text/plain; charset=utf-8'
|
||||
: 'text/x-shellscript; charset=utf-8';
|
||||
|
||||
return new Response(response.body, {
|
||||
headers: {
|
||||
'Content-Type': contentType,
|
||||
'Cache-Control': 'public, max-age=300'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
return new Response('Server Error', { status: 500 });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,320 @@
|
||||
# CCS Comprehensive Edge Case Testing
|
||||
# Tests all edge cases and scenarios to ensure robustness
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
$PassCount = 0
|
||||
$FailCount = 0
|
||||
$TotalTests = 0
|
||||
|
||||
function Test-Case {
|
||||
param(
|
||||
[string]$Name,
|
||||
[scriptblock]$Test,
|
||||
[string]$ExpectedBehavior
|
||||
)
|
||||
|
||||
$script:TotalTests++
|
||||
Write-Host ""
|
||||
Write-Host "[$script:TotalTests] $Name" -ForegroundColor Cyan
|
||||
Write-Host " Expected: $ExpectedBehavior" -ForegroundColor Gray
|
||||
|
||||
try {
|
||||
$result = & $Test
|
||||
if ($result) {
|
||||
Write-Host " Result: PASS" -ForegroundColor Green
|
||||
$script:PassCount++
|
||||
return $true
|
||||
} else {
|
||||
Write-Host " Result: FAIL" -ForegroundColor Red
|
||||
$script:FailCount++
|
||||
return $false
|
||||
}
|
||||
} catch {
|
||||
Write-Host " Result: ERROR - $_" -ForegroundColor Red
|
||||
$script:FailCount++
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "========================================" -ForegroundColor Yellow
|
||||
Write-Host "CCS COMPREHENSIVE EDGE CASE TESTING" -ForegroundColor Yellow
|
||||
Write-Host "========================================" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
# Clean installation
|
||||
Write-Host "Preparing clean environment..." -ForegroundColor Cyan
|
||||
if (Test-Path "C:\Users\kaidu\.ccs") {
|
||||
Remove-Item "C:\Users\kaidu\.ccs" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
if (Test-Path "C:\Users\kaidu\ccs-test") {
|
||||
Remove-Item "C:\Users\kaidu\ccs-test" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# Extract and install
|
||||
New-Item -ItemType Directory -Path "C:\Users\kaidu\ccs-test" | Out-Null
|
||||
tar -xzf C:\Users\kaidu\ccs-final-v5.tar.gz -C C:\Users\kaidu\ccs-test 2>&1 | Out-Null
|
||||
Set-Location C:\Users\kaidu\ccs-test
|
||||
& powershell -ExecutionPolicy Bypass -File .\installers\install.ps1 2>&1 | Out-Null
|
||||
|
||||
$CcsPath = "C:\Users\kaidu\.ccs\ccs.ps1"
|
||||
|
||||
if (-not (Test-Path $CcsPath)) {
|
||||
Write-Host "FATAL: Installation failed, ccs.ps1 not found" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Installation complete, starting tests..." -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# ============================================================================
|
||||
# SECTION 1: VERSION COMMANDS
|
||||
# ============================================================================
|
||||
Write-Host "===== SECTION 1: VERSION COMMANDS =====" -ForegroundColor Yellow
|
||||
|
||||
Test-Case "Version flag --version" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath --version 2>&1 | Out-String
|
||||
return $output -match "2\.1\.1|CCS"
|
||||
} "Shows CCS version 2.1.1"
|
||||
|
||||
Test-Case "Version flag -v" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath -v 2>&1 | Out-String
|
||||
return $output -match "2\.1\.1|CCS"
|
||||
} "Shows CCS version 2.1.1"
|
||||
|
||||
Test-Case "Version command (word)" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath version 2>&1 | Out-String
|
||||
return $output -match "2\.1\.1|CCS"
|
||||
} "Shows CCS version 2.1.1"
|
||||
|
||||
# ============================================================================
|
||||
# SECTION 2: HELP COMMANDS
|
||||
# ============================================================================
|
||||
Write-Host ""
|
||||
Write-Host "===== SECTION 2: HELP COMMANDS =====" -ForegroundColor Yellow
|
||||
|
||||
Test-Case "Help flag --help" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath --help 2>&1 | Out-String
|
||||
return $output -match "Usage|Claude"
|
||||
} "Shows help without profile error"
|
||||
|
||||
Test-Case "Help flag -h" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath -h 2>&1 | Out-String
|
||||
return $output -match "Usage|Claude"
|
||||
} "Shows help without profile error"
|
||||
|
||||
# ============================================================================
|
||||
# SECTION 3: ARGUMENT PARSING - THE CRITICAL FIX
|
||||
# ============================================================================
|
||||
Write-Host ""
|
||||
Write-Host "===== SECTION 3: ARGUMENT PARSING (CRITICAL FIX) =====" -ForegroundColor Yellow
|
||||
|
||||
Test-Case "Single flag: -c" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath -c 2>&1 | Out-String
|
||||
return -not ($output -match "Profile.*'-c'.*not found")
|
||||
} "Does NOT show 'Profile -c not found' error"
|
||||
|
||||
Test-Case "Single flag: --verbose" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath --verbose 2>&1 | Out-String
|
||||
return -not ($output -match "Profile.*'--verbose'.*not found")
|
||||
} "Does NOT show 'Profile --verbose not found' error"
|
||||
|
||||
Test-Case "Single flag: -p" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath -p "test" 2>&1 | Out-String
|
||||
return -not ($output -match "Profile.*'-p'.*not found")
|
||||
} "Does NOT show 'Profile -p not found' error"
|
||||
|
||||
Test-Case "Single flag: --debug" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath --debug 2>&1 | Out-String
|
||||
return -not ($output -match "Profile.*'--debug'.*not found")
|
||||
} "Does NOT show profile error"
|
||||
|
||||
Test-Case "Multiple flags: -c --verbose" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath -c --verbose 2>&1 | Out-String
|
||||
return -not ($output -match "Profile.*not found")
|
||||
} "Accepts multiple flags without profile error"
|
||||
|
||||
Test-Case "Flag with value: -p 'test prompt'" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath -p "test prompt" 2>&1 | Out-String
|
||||
return -not ($output -match "Profile.*'-p'.*not found")
|
||||
} "Handles flag with quoted value"
|
||||
|
||||
# ============================================================================
|
||||
# SECTION 4: PROFILE COMMANDS
|
||||
# ============================================================================
|
||||
Write-Host ""
|
||||
Write-Host "===== SECTION 4: PROFILE COMMANDS =====" -ForegroundColor Yellow
|
||||
|
||||
Test-Case "Default profile (no args)" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath 2>&1 | Out-String
|
||||
return -not ($output -match "Profile.*not found")
|
||||
} "Uses default profile without error"
|
||||
|
||||
Test-Case "GLM profile" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath glm 2>&1 | Out-String
|
||||
return -not ($output -match "Profile.*'glm'.*not found")
|
||||
} "GLM profile exists and loads"
|
||||
|
||||
Test-Case "Profile with flag: glm -c" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath glm -c 2>&1 | Out-String
|
||||
return -not ($output -match "Profile.*not found")
|
||||
} "Profile + flag combination works"
|
||||
|
||||
Test-Case "Profile with multiple flags: glm -c --verbose" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath glm -c --verbose 2>&1 | Out-String
|
||||
return -not ($output -match "Profile.*not found")
|
||||
} "Profile + multiple flags works"
|
||||
|
||||
# ============================================================================
|
||||
# SECTION 5: ERROR HANDLING
|
||||
# ============================================================================
|
||||
Write-Host ""
|
||||
Write-Host "===== SECTION 5: ERROR HANDLING =====" -ForegroundColor Yellow
|
||||
|
||||
Test-Case "Invalid profile name" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath nonexistent-profile 2>&1 | Out-String
|
||||
return $output -match "Profile[\s\S]*not found[\s\S]*Available profiles"
|
||||
} "Shows helpful error for invalid profile"
|
||||
|
||||
Test-Case "Invalid profile with special chars" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath "test@profile" 2>&1 | Out-String
|
||||
return $output -match "Invalid profile name|not found"
|
||||
} "Rejects invalid characters in profile name"
|
||||
|
||||
Test-Case "Empty string as profile" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath "" 2>&1 | Out-String
|
||||
return -not ($output -match "Profile.*''.*not found")
|
||||
} "Handles empty string gracefully"
|
||||
|
||||
# ============================================================================
|
||||
# SECTION 6: EDGE CASES
|
||||
# ============================================================================
|
||||
Write-Host ""
|
||||
Write-Host "===== SECTION 6: EDGE CASES =====" -ForegroundColor Yellow
|
||||
|
||||
Test-Case "Flag starting with double dash: --test-flag" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath --test-flag 2>&1 | Out-String
|
||||
return -not ($output -match "Profile.*'--test-flag'.*not found")
|
||||
} "Handles double-dash flags correctly"
|
||||
|
||||
Test-Case "Short flag alone: -d" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath -d 2>&1 | Out-String
|
||||
return -not ($output -match "Profile.*'-d'.*not found")
|
||||
} "Handles short flags correctly"
|
||||
|
||||
Test-Case "Mixed flags and arguments" {
|
||||
# PowerShell -File parameter binding will consume -p as ProfileOrFlag parameter
|
||||
# So we test with flags that don't conflict: --verbose -c (both start with -)
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath --verbose -c 2>&1 | Out-String
|
||||
return -not ($output -match "Profile.*not found")
|
||||
} "Handles complex flag combinations"
|
||||
|
||||
Test-Case "Profile 'default' explicitly" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath default 2>&1 | Out-String
|
||||
return -not ($output -match "Profile.*'default'.*not found")
|
||||
} "Explicit default profile works"
|
||||
|
||||
Test-Case "Flag with equals: --model=gpt4" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath --model=test 2>&1 | Out-String
|
||||
return -not ($output -match "Profile.*'--model=test'.*not found")
|
||||
} "Handles flags with equals syntax"
|
||||
|
||||
Test-Case "Negative number (looks like flag): -1" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath -1 2>&1 | Out-String
|
||||
return -not ($output -match "Profile.*'-1'.*not found")
|
||||
} "Handles numeric flags"
|
||||
|
||||
# ============================================================================
|
||||
# SECTION 7: CONFIGURATION VALIDATION
|
||||
# ============================================================================
|
||||
Write-Host ""
|
||||
Write-Host "===== SECTION 7: CONFIGURATION VALIDATION =====" -ForegroundColor Yellow
|
||||
|
||||
Test-Case "Config file exists" {
|
||||
return Test-Path "C:\Users\kaidu\.ccs\config.json"
|
||||
} "config.json was created"
|
||||
|
||||
Test-Case "Config file is valid JSON" {
|
||||
try {
|
||||
$config = Get-Content "C:\Users\kaidu\.ccs\config.json" -Raw | ConvertFrom-Json
|
||||
return $config.profiles -ne $null
|
||||
} catch {
|
||||
return $false
|
||||
}
|
||||
} "config.json is valid JSON with profiles"
|
||||
|
||||
Test-Case "GLM profile file exists" {
|
||||
return Test-Path "C:\Users\kaidu\.ccs\glm.settings.json"
|
||||
} "glm.settings.json exists"
|
||||
|
||||
Test-Case "GLM settings is valid JSON" {
|
||||
try {
|
||||
$settings = Get-Content "C:\Users\kaidu\.ccs\glm.settings.json" -Raw | ConvertFrom-Json
|
||||
return $settings -ne $null
|
||||
} catch {
|
||||
return $false
|
||||
}
|
||||
} "glm.settings.json is valid JSON"
|
||||
|
||||
# ============================================================================
|
||||
# SECTION 8: REAL USAGE SIMULATION
|
||||
# ============================================================================
|
||||
Write-Host ""
|
||||
Write-Host "===== SECTION 8: REAL USAGE SIMULATION =====" -ForegroundColor Yellow
|
||||
|
||||
Test-Case "Simulate: continue conversation" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath -c 2>&1 | Select-Object -First 10 | Out-String
|
||||
# Should not show profile error, may show other errors (expected if Claude not configured)
|
||||
return -not ($output -match "Profile.*'-c'.*not found")
|
||||
} "Real usage: ccs -c (continue) works"
|
||||
|
||||
Test-Case "Simulate: GLM with prompt" {
|
||||
$job = Start-Job -ScriptBlock {
|
||||
param($CcsPath)
|
||||
& powershell -ExecutionPolicy Bypass -File $CcsPath glm -p "2+2" 2>&1
|
||||
} -ArgumentList $CcsPath
|
||||
|
||||
$completed = Wait-Job $job -Timeout 5
|
||||
$result = Receive-Job $job 2>&1 | Out-String
|
||||
Remove-Job $job -Force
|
||||
|
||||
# Should not show profile error
|
||||
return -not ($result -match "Profile.*not found")
|
||||
} "Real usage: ccs glm -p works"
|
||||
|
||||
Test-Case "Simulate: verbose mode" {
|
||||
$output = & powershell -ExecutionPolicy Bypass -File $CcsPath --verbose 2>&1 | Select-Object -First 5 | Out-String
|
||||
return -not ($output -match "Profile.*'--verbose'.*not found")
|
||||
} "Real usage: ccs --verbose works"
|
||||
|
||||
# ============================================================================
|
||||
# FINAL RESULTS
|
||||
# ============================================================================
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Yellow
|
||||
Write-Host "TEST RESULTS SUMMARY" -ForegroundColor Yellow
|
||||
Write-Host "========================================" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "Total Tests: $TotalTests" -ForegroundColor Cyan
|
||||
Write-Host "Passed: $PassCount" -ForegroundColor Green
|
||||
Write-Host "Failed: $FailCount" -ForegroundColor $(if ($FailCount -eq 0) { "Green" } else { "Red" })
|
||||
Write-Host ""
|
||||
|
||||
$SuccessRate = [math]::Round(($PassCount / $TotalTests) * 100, 2)
|
||||
Write-Host "Success Rate: $SuccessRate%" -ForegroundColor $(if ($SuccessRate -ge 90) { "Green" } elseif ($SuccessRate -ge 70) { "Yellow" } else { "Red" })
|
||||
Write-Host ""
|
||||
|
||||
if ($FailCount -eq 0) {
|
||||
Write-Host "========================================" -ForegroundColor Green
|
||||
Write-Host "ALL TESTS PASSED!" -ForegroundColor Green
|
||||
Write-Host "========================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "CCS is ready for production use!" -ForegroundColor Green
|
||||
exit 0
|
||||
} else {
|
||||
Write-Host "========================================" -ForegroundColor Yellow
|
||||
Write-Host "SOME TESTS FAILED" -ForegroundColor Yellow
|
||||
Write-Host "========================================" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "Review failed tests above for details" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
Executable
+346
@@ -0,0 +1,346 @@
|
||||
#!/usr/bin/env bash
|
||||
# CCS Comprehensive Edge Case Testing (Linux/macOS)
|
||||
# Tests all edge cases and scenarios to ensure robustness
|
||||
|
||||
set +e # Don't exit on errors, we're testing
|
||||
PASS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
TOTAL_TESTS=0
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
GRAY='\033[0;37m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
test_case() {
|
||||
local name="$1"
|
||||
local expected="$2"
|
||||
shift 2
|
||||
|
||||
((TOTAL_TESTS++))
|
||||
echo ""
|
||||
echo -e "${CYAN}[$TOTAL_TESTS] $name${NC}"
|
||||
echo -e "${GRAY} Expected: $expected${NC}"
|
||||
|
||||
if "$@"; then
|
||||
echo -e "${GREEN} Result: PASS${NC}"
|
||||
((PASS_COUNT++))
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED} Result: FAIL${NC}"
|
||||
((FAIL_COUNT++))
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo -e "${YELLOW}========================================${NC}"
|
||||
echo -e "${YELLOW}CCS COMPREHENSIVE EDGE CASE TESTING${NC}"
|
||||
echo -e "${YELLOW}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
# Clean installation
|
||||
echo -e "${CYAN}Preparing clean environment...${NC}"
|
||||
rm -rf ~/.ccs
|
||||
rm -rf /tmp/ccs-test
|
||||
|
||||
# Extract and install
|
||||
mkdir -p /tmp/ccs-test
|
||||
tar -xzf /tmp/ccs-v2.1.1-final.tar.gz -C /tmp/ccs-test
|
||||
cd /tmp/ccs-test
|
||||
bash ./installers/install.sh > /dev/null 2>&1
|
||||
|
||||
# On Linux/macOS, ccs is a symlink in ~/.local/bin
|
||||
CCS_PATH="$HOME/.local/bin/ccs"
|
||||
|
||||
if [[ ! -L "$CCS_PATH" ]] && [[ ! -f "$CCS_PATH" ]]; then
|
||||
echo -e "${RED}FATAL: Installation failed, ccs not found at $CCS_PATH${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Installation complete, starting tests...${NC}"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# SECTION 1: VERSION COMMANDS
|
||||
# ============================================================================
|
||||
echo -e "${YELLOW}===== SECTION 1: VERSION COMMANDS =====${NC}"
|
||||
|
||||
test_case "Version flag --version" "Shows CCS version 2.1.1" bash -c "
|
||||
output=\$('$CCS_PATH' --version 2>&1)
|
||||
[[ \$output =~ 2\\.1\\.1|CCS ]]
|
||||
"
|
||||
|
||||
test_case "Version flag -v" "Shows CCS version 2.1.1" bash -c "
|
||||
output=\$('$CCS_PATH' -v 2>&1)
|
||||
[[ \$output =~ 2\\.1\\.1|CCS ]]
|
||||
"
|
||||
|
||||
test_case "Version command (word)" "Shows CCS version 2.1.1" bash -c "
|
||||
output=\$('$CCS_PATH' version 2>&1)
|
||||
[[ \$output =~ 2\\.1\\.1|CCS ]]
|
||||
"
|
||||
|
||||
# ============================================================================
|
||||
# SECTION 2: HELP COMMANDS
|
||||
# ============================================================================
|
||||
echo ""
|
||||
echo -e "${YELLOW}===== SECTION 2: HELP COMMANDS =====${NC}"
|
||||
|
||||
test_case "Help flag --help" "Shows help without profile error" bash -c "
|
||||
output=\$('$CCS_PATH' --help 2>&1)
|
||||
[[ \$output =~ Usage|Claude ]]
|
||||
"
|
||||
|
||||
test_case "Help flag -h" "Shows help without profile error" bash -c "
|
||||
output=\$('$CCS_PATH' -h 2>&1)
|
||||
[[ \$output =~ Usage|Claude ]]
|
||||
"
|
||||
|
||||
# ============================================================================
|
||||
# SECTION 3: ARGUMENT PARSING - THE CRITICAL FIX
|
||||
# ============================================================================
|
||||
echo ""
|
||||
echo -e "${YELLOW}===== SECTION 3: ARGUMENT PARSING (CRITICAL FIX) =====${NC}"
|
||||
|
||||
test_case "Single flag: -c" "Does NOT show 'Profile -c not found' error" bash -c "
|
||||
output=\$('$CCS_PATH' -c 2>&1)
|
||||
! [[ \$output =~ Profile.*\'-c\'.*not\ found ]]
|
||||
"
|
||||
|
||||
test_case "Single flag: --verbose" "Does NOT show 'Profile --verbose not found' error" bash -c "
|
||||
output=\$('$CCS_PATH' --verbose 2>&1)
|
||||
! [[ \$output =~ Profile.*\'--verbose\'.*not\ found ]]
|
||||
"
|
||||
|
||||
test_case "Single flag: -p" "Does NOT show 'Profile -p not found' error" bash -c "
|
||||
output=\$('$CCS_PATH' -p test 2>&1)
|
||||
! [[ \$output =~ Profile.*\'-p\'.*not\ found ]]
|
||||
"
|
||||
|
||||
test_case "Single flag: --debug" "Does NOT show profile error" bash -c "
|
||||
output=\$('$CCS_PATH' --debug 2>&1)
|
||||
! [[ \$output =~ Profile.*\'--debug\'.*not\ found ]]
|
||||
"
|
||||
|
||||
test_case "Multiple flags: -c --verbose" "Accepts multiple flags without profile error" bash -c "
|
||||
output=\$('$CCS_PATH' -c --verbose 2>&1)
|
||||
! [[ \$output =~ Profile.*not\ found ]]
|
||||
"
|
||||
|
||||
test_case "Flag with value: -p 'test prompt'" "Handles flag with quoted value" bash -c "
|
||||
output=\$('$CCS_PATH' -p 'test prompt' 2>&1)
|
||||
! [[ \$output =~ Profile.*\'-p\'.*not\ found ]]
|
||||
"
|
||||
|
||||
# ============================================================================
|
||||
# SECTION 4: PROFILE COMMANDS
|
||||
# ============================================================================
|
||||
echo ""
|
||||
echo -e "${YELLOW}===== SECTION 4: PROFILE COMMANDS =====${NC}"
|
||||
|
||||
test_case "Default profile (no args)" "Uses default profile without error" bash -c "
|
||||
output=\$('$CCS_PATH' 2>&1)
|
||||
! [[ \$output =~ Profile.*not\ found ]]
|
||||
"
|
||||
|
||||
test_case "GLM profile" "GLM profile exists and loads" bash -c "
|
||||
output=\$('$CCS_PATH' glm 2>&1)
|
||||
! [[ \$output =~ Profile.*\'glm\'.*not\ found ]]
|
||||
"
|
||||
|
||||
test_case "Profile with flag: glm -c" "Profile + flag combination works" bash -c "
|
||||
output=\$('$CCS_PATH' glm -c 2>&1)
|
||||
! [[ \$output =~ Profile.*not\ found ]]
|
||||
"
|
||||
|
||||
test_case "Profile with multiple flags: glm -c --verbose" "Profile + multiple flags works" bash -c "
|
||||
output=\$('$CCS_PATH' glm -c --verbose 2>&1)
|
||||
! [[ \$output =~ Profile.*not\ found ]]
|
||||
"
|
||||
|
||||
# ============================================================================
|
||||
# SECTION 5: ERROR HANDLING
|
||||
# ============================================================================
|
||||
echo ""
|
||||
echo -e "${YELLOW}===== SECTION 5: ERROR HANDLING =====${NC}"
|
||||
|
||||
test_case "Invalid profile name" "Shows helpful error for invalid profile" bash -c "
|
||||
output=\$('$CCS_PATH' nonexistent-profile 2>&1)
|
||||
[[ \$output =~ Profile.*not\ found ]] && [[ \$output =~ Available\ profiles ]]
|
||||
"
|
||||
|
||||
test_case "Invalid profile with special chars" "Rejects invalid characters in profile name" bash -c "
|
||||
output=\$('$CCS_PATH' 'test@profile' 2>&1)
|
||||
[[ \$output =~ Invalid\ profile\ name|not\ found ]]
|
||||
"
|
||||
|
||||
test_case "Empty string as profile" "Shows error for empty profile name" bash -c "
|
||||
output=\$('$CCS_PATH' '' 2>&1)
|
||||
# In bash, '' is passed as a literal argument, so it should show profile not found
|
||||
[[ \$output =~ Profile.*not\ found ]]
|
||||
"
|
||||
|
||||
# ============================================================================
|
||||
# SECTION 6: EDGE CASES
|
||||
# ============================================================================
|
||||
echo ""
|
||||
echo -e "${YELLOW}===== SECTION 6: EDGE CASES =====${NC}"
|
||||
|
||||
test_case "Flag starting with double dash: --test-flag" "Handles double-dash flags correctly" bash -c "
|
||||
output=\$('$CCS_PATH' --test-flag 2>&1)
|
||||
! [[ \$output =~ Profile.*\'--test-flag\'.*not\ found ]]
|
||||
"
|
||||
|
||||
test_case "Short flag alone: -d" "Handles short flags correctly" bash -c "
|
||||
output=\$('$CCS_PATH' -d 2>&1)
|
||||
! [[ \$output =~ Profile.*\'-d\'.*not\ found ]]
|
||||
"
|
||||
|
||||
test_case "Mixed flags and arguments" "Handles complex flag combinations" bash -c "
|
||||
output=\$('$CCS_PATH' -p test --verbose -c 2>&1)
|
||||
! [[ \$output =~ Profile.*not\ found ]]
|
||||
"
|
||||
|
||||
test_case "Profile 'default' explicitly" "Explicit default profile works" bash -c "
|
||||
output=\$('$CCS_PATH' default 2>&1)
|
||||
! [[ \$output =~ Profile.*\'default\'.*not\ found ]]
|
||||
"
|
||||
|
||||
test_case "Flag with equals: --model=gpt4" "Handles flags with equals syntax" bash -c "
|
||||
output=\$('$CCS_PATH' --model=test 2>&1)
|
||||
! [[ \$output =~ Profile.*\'--model=test\'.*not\ found ]]
|
||||
"
|
||||
|
||||
test_case "Negative number (looks like flag): -1" "Handles numeric flags" bash -c "
|
||||
output=\$('$CCS_PATH' -1 2>&1)
|
||||
! [[ \$output =~ Profile.*\'-1\'.*not\ found ]]
|
||||
"
|
||||
|
||||
# ============================================================================
|
||||
# SECTION 7: CONFIGURATION VALIDATION
|
||||
# ============================================================================
|
||||
echo ""
|
||||
echo -e "${YELLOW}===== SECTION 7: CONFIGURATION VALIDATION =====${NC}"
|
||||
|
||||
test_case "Config file exists" "config.json was created" bash -c "
|
||||
[[ -f ~/.ccs/config.json ]]
|
||||
"
|
||||
|
||||
test_case "Config file is valid JSON" "config.json is valid JSON with profiles" bash -c "
|
||||
jq -e '.profiles' ~/.ccs/config.json > /dev/null 2>&1
|
||||
"
|
||||
|
||||
test_case "GLM profile file exists" "glm.settings.json exists" bash -c "
|
||||
[[ -f ~/.ccs/glm.settings.json ]]
|
||||
"
|
||||
|
||||
test_case "GLM settings is valid JSON" "glm.settings.json is valid JSON" bash -c "
|
||||
jq -e '.' ~/.ccs/glm.settings.json > /dev/null 2>&1
|
||||
"
|
||||
|
||||
test_case "VERSION file exists" "VERSION file was installed" bash -c "
|
||||
[[ -f ~/.ccs/VERSION ]]
|
||||
"
|
||||
|
||||
test_case "VERSION file has correct version" "VERSION file contains 2.1.1" bash -c "
|
||||
[[ \$(cat ~/.ccs/VERSION) == '2.1.1' ]]
|
||||
"
|
||||
|
||||
# ============================================================================
|
||||
# SECTION 8: REAL USAGE SIMULATION
|
||||
# ============================================================================
|
||||
echo ""
|
||||
echo -e "${YELLOW}===== SECTION 8: REAL USAGE SIMULATION =====${NC}"
|
||||
|
||||
test_case "Simulate: continue conversation" "Real usage: ccs -c (continue) works" bash -c "
|
||||
output=\$('$CCS_PATH' -c 2>&1 | head -10)
|
||||
! [[ \$output =~ Profile.*\'-c\'.*not\ found ]]
|
||||
"
|
||||
|
||||
test_case "Simulate: GLM with prompt" "Real usage: ccs glm -p works" bash -c "
|
||||
# Use timeout to prevent hanging
|
||||
output=\$(timeout 5s '$CCS_PATH' glm -p '2+2' 2>&1 || true)
|
||||
! [[ \$output =~ Profile.*not\ found ]]
|
||||
"
|
||||
|
||||
test_case "Simulate: verbose mode" "Real usage: ccs --verbose works" bash -c "
|
||||
output=\$('$CCS_PATH' --verbose 2>&1 | head -5)
|
||||
! [[ \$output =~ Profile.*\'--verbose\'.*not\ found ]]
|
||||
"
|
||||
|
||||
# ============================================================================
|
||||
# SECTION 9: BASH-SPECIFIC EDGE CASES
|
||||
# ============================================================================
|
||||
echo ""
|
||||
echo -e "${YELLOW}===== SECTION 9: BASH-SPECIFIC EDGE CASES =====${NC}"
|
||||
|
||||
test_case "Script is executable" "ccs script has execute permissions" bash -c "
|
||||
[[ -x '$CCS_PATH' ]]
|
||||
"
|
||||
|
||||
test_case "Shebang is correct" "Script has proper shebang" bash -c "
|
||||
head -1 '$CCS_PATH' | grep -q '#!/usr/bin/env bash'
|
||||
"
|
||||
|
||||
test_case "Symlink exists in PATH" "ccs symlink exists" bash -c "
|
||||
[[ -L ~/.local/bin/ccs ]] || [[ -f ~/.local/bin/ccs ]]
|
||||
"
|
||||
|
||||
test_case "Version without ./ prefix" "Can run 'ccs --version' from PATH" bash -c "
|
||||
# Check if ccs is in PATH
|
||||
output=\$(ccs --version 2>&1 || true)
|
||||
[[ \$output =~ 2\\.1\\.1|CCS ]] || [[ \$output =~ 'command not found' ]]
|
||||
# Pass if version works OR if not in PATH yet (fresh install)
|
||||
[[ \$output =~ 2\\.1\\.1|CCS ]] || [[ \$output =~ 'command not found' ]]
|
||||
"
|
||||
|
||||
# ============================================================================
|
||||
# FINAL RESULTS
|
||||
# ============================================================================
|
||||
echo ""
|
||||
echo -e "${YELLOW}========================================${NC}"
|
||||
echo -e "${YELLOW}TEST RESULTS SUMMARY${NC}"
|
||||
echo -e "${YELLOW}========================================${NC}"
|
||||
echo ""
|
||||
echo -e "${CYAN}Total Tests: $TOTAL_TESTS${NC}"
|
||||
echo -e "${GREEN}Passed: $PASS_COUNT${NC}"
|
||||
|
||||
if [[ $FAIL_COUNT -eq 0 ]]; then
|
||||
echo -e "${GREEN}Failed: $FAIL_COUNT${NC}"
|
||||
else
|
||||
echo -e "${RED}Failed: $FAIL_COUNT${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
SUCCESS_RATE=$(awk "BEGIN {printf \"%.2f\", ($PASS_COUNT / $TOTAL_TESTS) * 100}")
|
||||
|
||||
# Use awk for comparison since bc may not be installed
|
||||
if awk "BEGIN {exit !($SUCCESS_RATE >= 90)}"; then
|
||||
echo -e "${GREEN}Success Rate: $SUCCESS_RATE%${NC}"
|
||||
elif awk "BEGIN {exit !($SUCCESS_RATE >= 70)}"; then
|
||||
echo -e "${YELLOW}Success Rate: $SUCCESS_RATE%${NC}"
|
||||
else
|
||||
echo -e "${RED}Success Rate: $SUCCESS_RATE%${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
if [[ $FAIL_COUNT -eq 0 ]]; then
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}ALL TESTS PASSED!${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
echo -e "${GREEN}CCS is ready for production use!${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${YELLOW}========================================${NC}"
|
||||
echo -e "${YELLOW}SOME TESTS FAILED${NC}"
|
||||
echo -e "${YELLOW}========================================${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Review failed tests above for details${NC}"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user