@kaitranntt/ccs 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/ccs CHANGED
@@ -2,9 +2,11 @@
2
2
  set -euo pipefail
3
3
 
4
4
  # Version (updated by scripts/bump-version.sh)
5
- CCS_VERSION="3.0.0"
5
+ CCS_VERSION="3.0.2"
6
6
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
7
  readonly CONFIG_FILE="${CCS_CONFIG:-$HOME/.ccs/config.json}"
8
+ readonly PROFILES_JSON="$HOME/.ccs/profiles.json"
9
+ readonly INSTANCES_DIR="$HOME/.ccs/instances"
8
10
 
9
11
  # --- Color/Format Functions ---
10
12
  setup_colors() {
@@ -13,12 +15,13 @@ setup_colors() {
13
15
  ([[ -t 1 || -t 2 ]] && [[ -z "${NO_COLOR:-}" ]]) || \
14
16
  ([[ -n "${TERM:-}" && "${TERM}" != "dumb" ]] && [[ -z "${NO_COLOR:-}" ]]); then
15
17
  RED='\033[0;31m'
18
+ GREEN='\033[0;32m'
16
19
  YELLOW='\033[1;33m'
17
20
  CYAN='\033[0;36m'
18
21
  BOLD='\033[1m'
19
22
  RESET='\033[0m'
20
23
  else
21
- RED='' YELLOW='' CYAN='' BOLD='' RESET=''
24
+ RED='' GREEN='' YELLOW='' CYAN='' BOLD='' RESET=''
22
25
  fi
23
26
  }
24
27
 
@@ -37,53 +40,40 @@ show_help() {
37
40
  echo ""
38
41
  echo -e "${CYAN}Usage:${RESET}"
39
42
  echo -e " ${YELLOW}ccs${RESET} [profile] [claude-args...]"
43
+ echo -e " ${YELLOW}ccs auth${RESET} <command> [options]"
40
44
  echo -e " ${YELLOW}ccs${RESET} [flags]"
41
45
  echo ""
42
46
  echo -e "${CYAN}Description:${RESET}"
43
- echo -e " Switch between Claude models instantly. Stop hitting rate limits."
44
- echo -e " Maps profile names to Claude settings files via ~/.ccs/config.json"
45
- echo ""
46
- echo -e "${CYAN}Profile Switching:${RESET}"
47
- echo -e " ${YELLOW}ccs${RESET} Use default profile"
48
- echo -e " ${YELLOW}ccs glm${RESET} Switch to GLM profile"
49
- echo -e " ${YELLOW}ccs kimi${RESET} Switch to Kimi profile"
50
- echo -e " ${YELLOW}ccs glm${RESET} \"debug this code\" Switch to GLM and run command"
51
- echo -e " ${YELLOW}ccs kimi${RESET} \"write tests\" Switch to Kimi and run command"
52
- echo -e " ${YELLOW}ccs glm${RESET} --verbose Switch to GLM with Claude flags"
53
- echo -e " ${YELLOW}ccs kimi${RESET} --verbose Switch to Kimi with Claude flags"
47
+ echo -e " Switch between multiple Claude accounts (work, personal, team) and"
48
+ echo -e " alternative models (GLM, Kimi) instantly. Concurrent sessions with"
49
+ echo -e " auto-recovery. Zero downtime."
54
50
  echo ""
55
- echo -e "${CYAN}Flags:${RESET}"
56
- echo -e " ${YELLOW}-h, --help${RESET} Show this help message"
57
- echo -e " ${YELLOW}-v, --version${RESET} Show version and installation info"
58
- echo ""
59
- echo -e "${CYAN}Configuration:${RESET}"
60
- echo -e " Config File: ~/.ccs/config.json"
61
- echo -e " Settings: ~/.ccs/*.settings.json"
62
- echo -e " Environment: CCS_CONFIG (override config path)"
51
+ echo -e "${CYAN}Model Switching:${RESET}"
52
+ echo -e " ${YELLOW}ccs${RESET} Use default Claude account"
53
+ echo -e " ${YELLOW}ccs glm${RESET} Switch to GLM 4.6 model"
54
+ echo -e " ${YELLOW}ccs kimi${RESET} Switch to Kimi for Coding"
55
+ echo -e " ${YELLOW}ccs glm${RESET} \"debug this code\" Use GLM and run command"
63
56
  echo ""
64
- echo -e "${CYAN}Examples:${RESET}"
65
- echo -e " # Use default Claude subscription"
66
- echo -e " ${YELLOW}ccs${RESET} \"Review this architecture\""
57
+ echo -e "${CYAN}Account Management:${RESET}"
58
+ echo -e " ${YELLOW}ccs auth --help${RESET} Manage multiple Claude accounts"
59
+ echo -e " ${YELLOW}ccs work${RESET} Switch to work account"
60
+ echo -e " ${YELLOW}ccs personal${RESET} Switch to personal account"
67
61
  echo ""
68
- echo -e " # Switch to GLM for cost-effective tasks"
69
- echo -e " ${YELLOW}ccs glm${RESET} \"Write unit tests\""
62
+ echo -e "${CYAN}Diagnostics:${RESET}"
63
+ echo -e " ${YELLOW}ccs doctor${RESET} Run health check and diagnostics"
70
64
  echo ""
71
- echo -e " # Switch to Kimi for alternative option"
72
- echo -e " ${YELLOW}ccs kimi${RESET} \"Write integration tests\""
73
- echo ""
74
- echo -e " # Use with verbose output"
75
- echo -e " ${YELLOW}ccs glm${RESET} --verbose \"Debug error\""
76
- echo -e " ${YELLOW}ccs kimi${RESET} --verbose \"Review code\""
65
+ echo -e "${CYAN}Flags:${RESET}"
66
+ echo -e " ${YELLOW}-h, --help${RESET} Show this help message"
67
+ echo -e " ${YELLOW}-v, --version${RESET} Show version and installation info"
77
68
  echo ""
78
- echo -e "${YELLOW}Uninstall:${RESET}"
79
- echo -e " macOS/Linux: curl -fsSL ccs.kaitran.ca/uninstall | bash"
80
- echo -e " Windows: irm ccs.kaitran.ca/uninstall | iex"
81
- echo -e " npm: npm uninstall -g @kaitranntt/ccs"
69
+ echo -e "${CYAN}Configuration:${RESET}"
70
+ echo -e " Config: ~/.ccs/config.json"
71
+ echo -e " Profiles: ~/.ccs/profiles.json"
72
+ echo -e " Settings: ~/.ccs/*.settings.json"
82
73
  echo ""
83
74
  echo -e "${CYAN}Documentation:${RESET}"
84
75
  echo -e " GitHub: ${CYAN}https://github.com/kaitranntt/ccs${RESET}"
85
76
  echo -e " Docs: https://github.com/kaitranntt/ccs/blob/main/README.md"
86
- echo -e " Issues: https://github.com/kaitranntt/ccs/issues"
87
77
  echo ""
88
78
  echo -e "${CYAN}License:${RESET} MIT"
89
79
  }
@@ -96,6 +86,210 @@ command -v jq &>/dev/null || {
96
86
  exit 1
97
87
  }
98
88
 
89
+ # --- Auto-Recovery Functions ---
90
+
91
+ ensure_ccs_directory() {
92
+ [[ -d "$HOME/.ccs" ]] && return 0
93
+
94
+ mkdir -p "$HOME/.ccs" 2>/dev/null || {
95
+ msg_error "Cannot create ~/.ccs/ directory. Check permissions."
96
+ return 1
97
+ }
98
+
99
+ echo "[i] Auto-recovery: Created ~/.ccs/ directory"
100
+ return 0
101
+ }
102
+
103
+ ensure_config_json() {
104
+ local config_file="$HOME/.ccs/config.json"
105
+
106
+ # Check if exists and valid
107
+ if [[ -f "$config_file" ]]; then
108
+ jq empty "$config_file" 2>/dev/null && return 0
109
+
110
+ # Corrupted - backup and recreate
111
+ local backup_file="${config_file}.backup.$(date +%s)"
112
+ mv "$config_file" "$backup_file" 2>/dev/null
113
+ echo "[i] Auto-recovery: Backed up corrupted config.json"
114
+ fi
115
+
116
+ # Create default config
117
+ cat > "$config_file" <<'EOF'
118
+ {
119
+ "profiles": {
120
+ "glm": "~/.ccs/glm.settings.json",
121
+ "kimi": "~/.ccs/kimi.settings.json",
122
+ "default": "~/.claude/settings.json"
123
+ }
124
+ }
125
+ EOF
126
+
127
+ echo "[i] Auto-recovery: Created ~/.ccs/config.json"
128
+ return 0
129
+ }
130
+
131
+ ensure_claude_settings() {
132
+ local claude_dir="$HOME/.claude"
133
+ local settings_file="$claude_dir/settings.json"
134
+
135
+ # Create ~/.claude/ if missing
136
+ if [[ ! -d "$claude_dir" ]]; then
137
+ mkdir -p "$claude_dir" 2>/dev/null || return 1
138
+ echo "[i] Auto-recovery: Created ~/.claude/ directory"
139
+ fi
140
+
141
+ # Create settings.json if missing
142
+ if [[ ! -f "$settings_file" ]]; then
143
+ echo '{}' > "$settings_file" 2>/dev/null || return 1
144
+ echo "[i] Auto-recovery: Created ~/.claude/settings.json"
145
+ echo "[i] Next step: Run 'claude /login' to authenticate"
146
+ return 0
147
+ fi
148
+
149
+ return 0
150
+ }
151
+
152
+ # Run auto-recovery
153
+ auto_recover() {
154
+ ensure_ccs_directory || return 1
155
+ ensure_config_json || return 1
156
+ ensure_claude_settings || return 1
157
+ return 0
158
+ }
159
+
160
+ # --- Doctor Command ---
161
+
162
+ doctor_check() {
163
+ local check_name="$1"
164
+ local status="$2" # success, warning, error
165
+ local message="${3:-}"
166
+
167
+ case "$status" in
168
+ success)
169
+ echo -e "${GREEN}[OK]${RESET} $check_name"
170
+ ;;
171
+ warning)
172
+ echo -e "${YELLOW}[!]${RESET} $check_name${message:+: $message}"
173
+ ;;
174
+ error)
175
+ echo -e "${RED}[X]${RESET} $check_name: $message"
176
+ ;;
177
+ esac
178
+ }
179
+
180
+ doctor_run() {
181
+ echo -e "${CYAN}Running CCS Health Check...${RESET}"
182
+ echo ""
183
+
184
+ local has_errors=false
185
+
186
+ # Check Claude CLI
187
+ if command -v "$(detect_claude_cli)" &>/dev/null; then
188
+ doctor_check "Claude CLI" "success"
189
+ else
190
+ doctor_check "Claude CLI" "error" "Not found in PATH"
191
+ has_errors=true
192
+ fi
193
+
194
+ # Check ~/.ccs/
195
+ if [[ -d "$HOME/.ccs" ]]; then
196
+ doctor_check "CCS Directory" "success"
197
+ else
198
+ doctor_check "CCS Directory" "error" "~/.ccs/ not found"
199
+ has_errors=true
200
+ fi
201
+
202
+ # Check config.json
203
+ if [[ -f "$CONFIG_FILE" ]]; then
204
+ if jq empty "$CONFIG_FILE" 2>/dev/null; then
205
+ doctor_check "config.json" "success"
206
+ else
207
+ doctor_check "config.json" "error" "Invalid JSON"
208
+ has_errors=true
209
+ fi
210
+ else
211
+ doctor_check "config.json" "error" "Not found"
212
+ has_errors=true
213
+ fi
214
+
215
+ # Check glm.settings.json
216
+ local glm_file="$HOME/.ccs/glm.settings.json"
217
+ if [[ -f "$glm_file" ]]; then
218
+ if jq empty "$glm_file" 2>/dev/null; then
219
+ doctor_check "glm.settings.json" "success"
220
+ else
221
+ doctor_check "glm.settings.json" "error" "Invalid JSON"
222
+ has_errors=true
223
+ fi
224
+ else
225
+ doctor_check "glm.settings.json" "error" "Not found"
226
+ has_errors=true
227
+ fi
228
+
229
+ # Check kimi.settings.json
230
+ local kimi_file="$HOME/.ccs/kimi.settings.json"
231
+ if [[ -f "$kimi_file" ]]; then
232
+ if jq empty "$kimi_file" 2>/dev/null; then
233
+ doctor_check "kimi.settings.json" "success"
234
+ else
235
+ doctor_check "kimi.settings.json" "error" "Invalid JSON"
236
+ has_errors=true
237
+ fi
238
+ else
239
+ doctor_check "kimi.settings.json" "error" "Not found"
240
+ has_errors=true
241
+ fi
242
+
243
+ # Check ~/.claude/settings.json
244
+ if [[ -f "$HOME/.claude/settings.json" ]]; then
245
+ if jq empty "$HOME/.claude/settings.json" 2>/dev/null; then
246
+ doctor_check "Claude Settings" "success"
247
+ else
248
+ doctor_check "Claude Settings" "warning" "Invalid JSON"
249
+ fi
250
+ else
251
+ doctor_check "Claude Settings" "warning" "Not found - run 'claude /login'"
252
+ fi
253
+
254
+ # Check profiles
255
+ if [[ -f "$CONFIG_FILE" ]]; then
256
+ local profile_count=$(jq -r '.profiles | length' "$CONFIG_FILE" 2>/dev/null || echo "0")
257
+ doctor_check "Profiles" "success" "($profile_count configured)"
258
+ fi
259
+
260
+ # Check instances
261
+ if [[ -d "$INSTANCES_DIR" ]]; then
262
+ local instance_count=$(find "$INSTANCES_DIR" -maxdepth 1 -type d 2>/dev/null | wc -l)
263
+ instance_count=$((instance_count - 1)) # Exclude parent dir
264
+ doctor_check "Instances" "success" "($instance_count account profiles)"
265
+ else
266
+ doctor_check "Instances" "success" "(no account profiles)"
267
+ fi
268
+
269
+ # Check permissions
270
+ local test_file="$HOME/.ccs/.permission-test"
271
+ if echo "test" > "$test_file" 2>/dev/null; then
272
+ rm -f "$test_file" 2>/dev/null
273
+ doctor_check "Permissions" "success"
274
+ else
275
+ doctor_check "Permissions" "error" "Cannot write to ~/.ccs/"
276
+ has_errors=true
277
+ fi
278
+
279
+ # Summary
280
+ echo ""
281
+ echo -e "${CYAN}═══════════════════════════════════════════${RESET}"
282
+ if $has_errors; then
283
+ echo -e "${RED}Status: Installation has errors${RESET}"
284
+ echo "Run: npm install -g @kaitranntt/ccs --force"
285
+ else
286
+ echo -e "${GREEN}✓ All checks passed!${RESET}"
287
+ fi
288
+ echo ""
289
+
290
+ $has_errors && exit 1 || exit 0
291
+ }
292
+
99
293
  # --- Claude CLI Detection Logic ---
100
294
 
101
295
  detect_claude_cli() {
@@ -124,219 +318,600 @@ Solutions:
124
318
  Restart your terminal after installation."
125
319
  }
126
320
 
127
- # WIP: .claude/ integration testing incomplete
128
- # Feature disabled until testing complete
129
- # install_commands_and_skills() {
130
- : <<'COMMENTED_OUT'
131
- # Try both possible locations for .claude directory
132
- local source_dir=""
133
- local possible_dirs=(
134
- "$SCRIPT_DIR/.claude" # Development: tools/ccs/.claude
135
- "$HOME/.ccs/.claude" # Installed: ~/.ccs/.claude
136
- )
137
-
138
- for dir in "${possible_dirs[@]}"; do
139
- if [[ -d "$dir" ]]; then
140
- source_dir="$dir"
141
- break
321
+ show_version() {
322
+ echo -e "${BOLD}CCS (Claude Code Switch) v${CCS_VERSION}${RESET}"
323
+ echo ""
324
+ echo -e "${CYAN}Installation:${RESET}"
325
+
326
+ # Simple location - just show what 'command -v' returns
327
+ local location=$(command -v ccs 2>/dev/null || echo "(not installed)")
328
+ echo -e " ${CYAN}Location:${RESET} ${location}"
329
+
330
+ # Simple config display
331
+ local config="${CCS_CONFIG:-$HOME/.ccs/config.json}"
332
+ echo -e " ${CYAN}Config:${RESET} ${config}"
333
+ echo ""
334
+
335
+ echo -e "${CYAN}Documentation:${RESET} https://github.com/kaitranntt/ccs"
336
+ echo -e "${CYAN}License:${RESET} MIT"
337
+ echo ""
338
+ echo -e "${YELLOW}Run 'ccs --help' for usage information${RESET}"
339
+ }
340
+
341
+ # --- Profile Registry Functions (Phase 4) ---
342
+
343
+ # Initialize empty registry if missing
344
+ init_profiles_json() {
345
+ [[ -f "$PROFILES_JSON" ]] && return 0
346
+
347
+ local init_data='{
348
+ "version": "2.0.0",
349
+ "profiles": {},
350
+ "default": null
351
+ }'
352
+
353
+ echo "$init_data" > "$PROFILES_JSON"
354
+ chmod 0600 "$PROFILES_JSON"
355
+ }
356
+
357
+ # Read entire profiles.json
358
+ read_profiles_json() {
359
+ init_profiles_json
360
+ cat "$PROFILES_JSON"
361
+ }
362
+
363
+ # Write entire profiles.json (atomic)
364
+ write_profiles_json() {
365
+ local content="$1"
366
+ local temp_file="$PROFILES_JSON.tmp"
367
+
368
+ echo "$content" > "$temp_file" || {
369
+ msg_error "Failed to write profiles registry"
370
+ return 1
371
+ }
372
+
373
+ chmod 0600 "$temp_file"
374
+ mv "$temp_file" "$PROFILES_JSON" || {
375
+ rm -f "$temp_file"
376
+ msg_error "Failed to update profiles registry"
377
+ return 1
378
+ }
379
+ }
380
+
381
+ # Check if profile exists
382
+ profile_exists() {
383
+ local profile_name="$1"
384
+ init_profiles_json
385
+
386
+ local exists=$(jq -r ".profiles.\"$profile_name\" // empty" "$PROFILES_JSON")
387
+ [[ -n "$exists" ]]
388
+ }
389
+
390
+ # Create new profile
391
+ register_profile() {
392
+ local profile_name="$1"
393
+ local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
394
+
395
+ init_profiles_json
396
+
397
+ # Check if exists
398
+ profile_exists "$profile_name" && {
399
+ msg_error "Profile already exists: $profile_name"
400
+ return 1
401
+ }
402
+
403
+ # Read current data
404
+ local data=$(read_profiles_json)
405
+
406
+ # Add new profile
407
+ data=$(echo "$data" | jq \
408
+ --arg name "$profile_name" \
409
+ --arg timestamp "$timestamp" \
410
+ '.profiles[$name] = {
411
+ "type": "account",
412
+ "created": $timestamp,
413
+ "last_used": null
414
+ }')
415
+
416
+ # Note: No longer auto-set as default
417
+ # Users must explicitly run: ccs auth default <profile>
418
+ # Default always stays on implicit 'default' profile (uses ~/.claude/)
419
+
420
+ write_profiles_json "$data"
421
+ }
422
+
423
+ # Delete profile
424
+ unregister_profile() {
425
+ local profile_name="$1"
426
+
427
+ init_profiles_json
428
+
429
+ profile_exists "$profile_name" || return 0 # Idempotent
430
+
431
+ local data=$(read_profiles_json)
432
+
433
+ # Remove profile
434
+ data=$(echo "$data" | jq --arg name "$profile_name" 'del(.profiles[$name])')
435
+
436
+ # Update default if it was the deleted profile
437
+ local current_default=$(echo "$data" | jq -r '.default // empty')
438
+ if [[ "$current_default" == "$profile_name" ]]; then
439
+ # Set to first remaining profile or null
440
+ local first_profile=$(echo "$data" | jq -r '.profiles | keys[0] // empty')
441
+ data=$(echo "$data" | jq --arg first "$first_profile" '
442
+ .default = if $first != "" then $first else null end
443
+ ')
444
+ fi
445
+
446
+ write_profiles_json "$data"
447
+ }
448
+
449
+ # Update last_used timestamp
450
+ touch_profile() {
451
+ local profile_name="$1"
452
+ local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
453
+
454
+ profile_exists "$profile_name" || return 0 # Silent fail if not exists
455
+
456
+ local data=$(read_profiles_json)
457
+
458
+ data=$(echo "$data" | jq \
459
+ --arg name "$profile_name" \
460
+ --arg timestamp "$timestamp" \
461
+ '.profiles[$name].last_used = $timestamp')
462
+
463
+ write_profiles_json "$data"
464
+ }
465
+
466
+ # Get default profile
467
+ get_default_profile() {
468
+ init_profiles_json
469
+ jq -r '.default // empty' "$PROFILES_JSON"
470
+ }
471
+
472
+ # Set default profile
473
+ set_default_profile() {
474
+ local profile_name="$1"
475
+
476
+ profile_exists "$profile_name" || {
477
+ msg_error "Profile not found: $profile_name"
478
+ return 1
479
+ }
480
+
481
+ local data=$(read_profiles_json)
482
+ data=$(echo "$data" | jq --arg name "$profile_name" '.default = $name')
483
+
484
+ write_profiles_json "$data"
485
+ }
486
+
487
+ # --- Instance Management Functions (Phase 2) ---
488
+
489
+ # Sanitize profile name for filesystem
490
+ sanitize_profile_name() {
491
+ local name="$1"
492
+ # Replace unsafe chars with dash, lowercase
493
+ echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_-]/-/g'
494
+ }
495
+
496
+ # Initialize new instance directory
497
+ initialize_instance() {
498
+ local instance_path="$1"
499
+
500
+ # Create base directory
501
+ mkdir -m 0700 -p "$instance_path"
502
+
503
+ # Create subdirectories
504
+ local subdirs=(session-env todos logs file-history shell-snapshots debug .anthropic commands skills)
505
+ for dir in "${subdirs[@]}"; do
506
+ mkdir -m 0700 -p "$instance_path/$dir"
507
+ done
508
+
509
+ # Copy global configs (optional)
510
+ copy_global_configs "$instance_path"
511
+ }
512
+
513
+ # Validate instance structure (auto-repair)
514
+ validate_instance() {
515
+ local instance_path="$1"
516
+ local required_dirs=(session-env todos logs file-history shell-snapshots debug .anthropic)
517
+
518
+ for dir in "${required_dirs[@]}"; do
519
+ if [[ ! -d "$instance_path/$dir" ]]; then
520
+ mkdir -m 0700 -p "$instance_path/$dir"
142
521
  fi
143
522
  done
523
+ }
144
524
 
145
- local target_dir="$HOME/.claude"
525
+ # Copy global Claude configs to instance
526
+ copy_global_configs() {
527
+ local instance_path="$1"
528
+ local global_claude="$HOME/.claude"
146
529
 
147
- echo "┌─ Installing CCS Commands & Skills"
148
- echo "│ Source: $source_dir"
149
- echo "│ Target: $target_dir"
150
- echo "│"
530
+ # Copy settings.json
531
+ [[ -f "$global_claude/settings.json" ]] && \
532
+ cp "$global_claude/settings.json" "$instance_path/settings.json" 2>/dev/null || true
151
533
 
152
- # Check if source directory exists
153
- if [[ ! -d "$source_dir" ]]; then
154
- echo "|"
155
- msg_error "Source directory not found.
534
+ # Copy commands/
535
+ [[ -d "$global_claude/commands" ]] && \
536
+ cp -r "$global_claude/commands" "$instance_path/" 2>/dev/null || true
156
537
 
157
- Checked locations:
158
- - $SCRIPT_DIR/.claude (development)
159
- - $HOME/.ccs/.claude (installed)
538
+ # Copy skills/
539
+ [[ -d "$global_claude/skills" ]] && \
540
+ cp -r "$global_claude/skills" "$instance_path/" 2>/dev/null || true
541
+ }
160
542
 
161
- Solution:
162
- 1. If developing: Ensure you're in the CCS repository
163
- 2. If installed: Reinstall CCS with: curl -fsSL ccs.kaitran.ca/install | bash"
164
- return 1
543
+ # Ensure instance exists (lazy initialization)
544
+ ensure_instance() {
545
+ local profile_name="$1"
546
+ local safe_name=$(sanitize_profile_name "$profile_name")
547
+ local instance_path="$INSTANCES_DIR/$safe_name"
548
+
549
+ # Create if missing
550
+ if [[ ! -d "$instance_path" ]]; then
551
+ initialize_instance "$instance_path"
165
552
  fi
166
553
 
167
- # Create target directories if they don't exist
168
- mkdir -p "$target_dir/commands"
169
- mkdir -p "$target_dir/skills"
170
-
171
- local installed_count=0
172
- local skipped_count=0
173
-
174
- # Install commands
175
- if [[ -d "$source_dir/commands" ]]; then
176
- echo "│ Installing commands..."
177
- for cmd_file in "$source_dir/commands"/*.md; do
178
- if [[ -f "$cmd_file" ]]; then
179
- local cmd_name=$(basename "$cmd_file" .md)
180
- local target_file="$target_dir/commands/$cmd_name.md"
181
-
182
- if [[ -f "$target_file" ]]; then
183
- echo "| | [i] Skipping existing command: $cmd_name.md"
184
- skipped_count=$((skipped_count + 1))
185
- else
186
- if cp "$cmd_file" "$target_file"; then
187
- echo "| | [OK] Installed command: $cmd_name.md"
188
- installed_count=$((installed_count + 1))
189
- else
190
- echo "| | [X] Failed to install command: $cmd_name.md"
191
- fi
192
- fi
193
- fi
194
- done
195
- else
196
- echo "| [i] No commands directory found"
554
+ # Validate structure
555
+ validate_instance "$instance_path"
556
+
557
+ echo "$instance_path"
558
+ }
559
+
560
+ # --- Profile Detection Logic (Phase 1) ---
561
+
562
+ # List available profiles for error messages
563
+ list_available_profiles() {
564
+ local lines=()
565
+
566
+ # Settings-based profiles
567
+ if [[ -f "$CONFIG_FILE" ]]; then
568
+ local settings_profiles=$(jq -r '.profiles | keys[]' "$CONFIG_FILE" 2>/dev/null || true)
569
+ if [[ -n "$settings_profiles" ]]; then
570
+ lines+=("Settings-based profiles (GLM, Kimi, etc.):")
571
+ while IFS= read -r name; do
572
+ lines+=(" - $name")
573
+ done <<< "$settings_profiles"
574
+ fi
197
575
  fi
198
576
 
199
- echo "|"
200
-
201
- # Install skills
202
- if [[ -d "$source_dir/skills" ]]; then
203
- echo "| Installing skills..."
204
- for skill_dir in "$source_dir/skills"/*; do
205
- if [[ -d "$skill_dir" ]]; then
206
- local skill_name=$(basename "$skill_dir")
207
- local target_skill_dir="$target_dir/skills/$skill_name"
208
-
209
- if [[ -d "$target_skill_dir" ]]; then
210
- echo "| | [i] Skipping existing skill: $skill_name"
211
- skipped_count=$((skipped_count + 1))
212
- else
213
- if cp -r "$skill_dir" "$target_skill_dir"; then
214
- echo "| | [OK] Installed skill: $skill_name"
215
- installed_count=$((installed_count + 1))
216
- else
217
- echo "| | [X] Failed to install skill: $skill_name"
218
- fi
219
- fi
220
- fi
221
- done
577
+ # Account-based profiles
578
+ if [[ -f "$PROFILES_JSON" ]]; then
579
+ local account_profiles=$(jq -r '.profiles | keys[]' "$PROFILES_JSON" 2>/dev/null || true)
580
+ local default_profile=$(jq -r '.default // empty' "$PROFILES_JSON" 2>/dev/null || true)
581
+
582
+ if [[ -n "$account_profiles" ]]; then
583
+ lines+=("Account-based profiles:")
584
+ while IFS= read -r name; do
585
+ local is_default=""
586
+ [[ "$name" == "$default_profile" ]] && is_default=" [DEFAULT]"
587
+ lines+=(" - $name$is_default")
588
+ done <<< "$account_profiles"
589
+ fi
590
+ fi
591
+
592
+ if [[ ${#lines[@]} -eq 0 ]]; then
593
+ echo " (no profiles configured)"
594
+ echo " Run \"ccs auth create <profile>\" to create your first account profile."
222
595
  else
223
- echo "| [i] No skills directory found"
596
+ printf '%s\n' "${lines[@]}"
224
597
  fi
598
+ }
225
599
 
226
- echo "└─"
227
- echo ""
228
- echo "[OK] Installation complete!"
229
- echo " Installed: $installed_count items"
230
- echo " Skipped: $skipped_count items (already exist)"
231
- echo ""
232
- echo "You can now use the /ccs command in Claude CLI for task delegation."
233
- echo "Example: /ccs glm /plan 'add user authentication'"
234
- COMMENTED_OUT
235
- # }
236
-
237
- # WIP: .claude/ integration testing incomplete
238
- # Feature disabled until testing complete
239
- # uninstall_commands_and_skills() {
240
- : <<'COMMENTED_OUT'
241
- local target_dir="$HOME/.claude"
242
- local removed_count=0
243
- local not_found_count=0
244
-
245
- echo "┌─ Uninstalling CCS Commands & Skills"
246
- echo "│ Target: $target_dir"
247
- echo ""
248
-
249
- # Check if target directory exists
250
- if [[ ! -d "$target_dir" ]]; then
251
- echo "|"
252
- echo "│ [i] Claude directory not found: $target_dir"
253
- echo "│ Nothing to uninstall."
254
- echo "└─"
255
- echo ""
256
- echo "[OK] Uninstall complete!"
257
- echo " Removed: 0 items (nothing was installed)"
600
+ # Detect profile type and return info
601
+ # Sets global variables: PROFILE_TYPE, PROFILE_PATH/INSTANCE_PATH
602
+ detect_profile_type() {
603
+ local profile_name="$1"
604
+
605
+ # Special case: 'default' resolves to default profile
606
+ if [[ "$profile_name" == "default" ]]; then
607
+ # Check account-based default first
608
+ if [[ -f "$PROFILES_JSON" ]]; then
609
+ local default_account=$(jq -r '.default // empty' "$PROFILES_JSON" 2>/dev/null || true)
610
+ if [[ -n "$default_account" ]] && profile_exists "$default_account"; then
611
+ PROFILE_TYPE="account"
612
+ PROFILE_NAME="$default_account"
613
+ return 0
614
+ fi
615
+ fi
616
+
617
+ # Check settings-based default
618
+ if [[ -f "$CONFIG_FILE" ]]; then
619
+ local default_settings=$(jq -r '.profiles.default // empty' "$CONFIG_FILE" 2>/dev/null || true)
620
+ if [[ -n "$default_settings" ]]; then
621
+ PROFILE_TYPE="settings"
622
+ PROFILE_PATH="$default_settings"
623
+ PROFILE_NAME="default"
624
+ return 0
625
+ fi
626
+ fi
627
+
628
+ # No default configured, use Claude's defaults
629
+ PROFILE_TYPE="default"
630
+ PROFILE_NAME="default"
258
631
  return 0
259
632
  fi
260
633
 
261
- # Remove commands
262
- local commands_dir="$target_dir/commands"
263
- if [[ -d "$commands_dir" ]]; then
264
- echo "│ Removing commands..."
265
- for cmd_file in "$commands_dir"/ccs.md; do
266
- if [[ -f "$cmd_file" ]]; then
267
- local cmd_name=$(basename "$cmd_file" .md)
268
- if rm "$cmd_file"; then
269
- echo "| | [OK] Removed command: $cmd_name.md"
270
- removed_count=$((removed_count + 1))
271
- else
272
- echo "| | [X] Failed to remove command: $cmd_name.md"
273
- fi
274
- else
275
- echo "| | [i] CCS command not found"
276
- not_found_count=$((not_found_count + 1))
277
- fi
278
- done
279
- else
280
- echo "│ [i] Commands directory not found"
281
- not_found_count=$((not_found_count + 1))
634
+ # Priority 1: Check settings-based profiles (backward compatibility)
635
+ if [[ -f "$CONFIG_FILE" ]]; then
636
+ local settings_path=$(jq -r ".profiles.\"$profile_name\" // empty" "$CONFIG_FILE" 2>/dev/null || true)
637
+ if [[ -n "$settings_path" ]]; then
638
+ PROFILE_TYPE="settings"
639
+ PROFILE_PATH="$settings_path"
640
+ PROFILE_NAME="$profile_name"
641
+ return 0
642
+ fi
282
643
  fi
283
644
 
284
- echo "|"
285
-
286
- # Remove skills
287
- local skills_dir="$target_dir/skills"
288
- if [[ -d "$skills_dir" ]]; then
289
- echo "| Removing skills..."
290
- for skill_dir in "$skills_dir"/ccs-delegation; do
291
- if [[ -d "$skill_dir" ]]; then
292
- local skill_name=$(basename "$skill_dir")
293
- if rm -rf "$skill_dir"; then
294
- echo "| | [OK] Removed skill: $skill_name"
295
- removed_count=$((removed_count + 1))
296
- else
297
- echo "| | [X] Failed to remove skill: $skill_name"
298
- fi
299
- else
300
- echo "| | [i] CCS skill not found"
301
- not_found_count=$((not_found_count + 1))
302
- fi
303
- done
304
- else
305
- echo "│ [i] Skills directory not found"
306
- not_found_count=$((not_found_count + 1))
645
+ # Priority 2: Check account-based profiles
646
+ if [[ -f "$PROFILES_JSON" ]] && profile_exists "$profile_name"; then
647
+ PROFILE_TYPE="account"
648
+ PROFILE_NAME="$profile_name"
649
+ return 0
307
650
  fi
308
651
 
309
- echo "└─"
652
+ # Not found
653
+ PROFILE_TYPE="error"
654
+ return 1
655
+ }
656
+
657
+ # --- Auth Commands (Phase 3) ---
658
+
659
+ auth_help() {
660
+ echo -e "${BOLD}CCS Account Management${RESET}"
661
+ echo ""
662
+ echo -e "${CYAN}Usage:${RESET}"
663
+ echo -e " ${YELLOW}ccs auth${RESET} <command> [options]"
310
664
  echo ""
311
- echo "[OK] Uninstall complete!"
312
- echo " Removed: $removed_count items"
313
- echo " Not found: $not_found_count items (already removed)"
665
+ echo -e "${CYAN}Commands:${RESET}"
666
+ echo -e " ${YELLOW}create <profile>${RESET} Create new profile and login"
667
+ echo -e " ${YELLOW}list${RESET} List all saved profiles"
668
+ echo -e " ${YELLOW}show <profile>${RESET} Show profile details"
669
+ echo -e " ${YELLOW}remove <profile>${RESET} Remove saved profile"
670
+ echo -e " ${YELLOW}default <profile>${RESET} Set default profile"
671
+ echo ""
672
+ echo -e "${CYAN}Examples:${RESET}"
673
+ echo -e " ${YELLOW}ccs auth create work${RESET} # Create & login to work profile"
674
+ echo -e " ${YELLOW}ccs auth default work${RESET} # Set work as default"
675
+ echo -e " ${YELLOW}ccs auth list${RESET} # List all profiles"
676
+ echo -e " ${YELLOW}ccs work \"review code\"${RESET} # Use work profile"
677
+ echo -e " ${YELLOW}ccs \"review code\"${RESET} # Use default profile"
314
678
  echo ""
315
- echo "The /ccs command is no longer available in Claude CLI."
316
- echo "To reinstall: ccs --install"
317
- COMMENTED_OUT
318
- # }
679
+ echo -e "${CYAN}Note:${RESET}"
680
+ echo -e " By default, ${YELLOW}ccs${RESET} uses Claude CLI defaults from ~/.claude/"
681
+ echo -e " Use ${YELLOW}ccs auth default <profile>${RESET} to change the default profile."
682
+ echo ""
683
+ }
319
684
 
320
- show_version() {
321
- echo -e "${BOLD}CCS (Claude Code Switch) v${CCS_VERSION}${RESET}"
685
+ auth_create() {
686
+ local profile_name=""
687
+ local force=false
688
+
689
+ # Parse arguments
690
+ while [[ $# -gt 0 ]]; do
691
+ case "$1" in
692
+ --force) force=true ;;
693
+ -*) msg_error "Unknown option: $1"; return 1 ;;
694
+ *) profile_name="$1" ;;
695
+ esac
696
+ shift
697
+ done
698
+
699
+ # Validate profile name
700
+ [[ -z "$profile_name" ]] && {
701
+ msg_error "Profile name is required"
702
+ echo ""
703
+ echo "Usage: ${YELLOW}ccs auth create <profile> [--force]${RESET}"
704
+ return 1
705
+ }
706
+
707
+ # Check if exists
708
+ if ! $force && profile_exists "$profile_name"; then
709
+ msg_error "Profile already exists: $profile_name"
710
+ echo "Use ${YELLOW}--force${RESET} to overwrite"
711
+ return 1
712
+ fi
713
+
714
+ # Create instance
715
+ echo "[i] Creating profile: $profile_name"
716
+ local instance_path=$(ensure_instance "$profile_name")
717
+ echo "[i] Instance directory: $instance_path"
322
718
  echo ""
323
- echo -e "${CYAN}Installation:${RESET}"
324
719
 
325
- # Simple location - just show what 'command -v' returns
326
- local location=$(command -v ccs 2>/dev/null || echo "(not installed)")
327
- echo -e " ${CYAN}Location:${RESET} ${location}"
720
+ # Register profile
721
+ register_profile "$profile_name"
328
722
 
329
- # Simple config display
330
- local config="${CCS_CONFIG:-$HOME/.ccs/config.json}"
331
- echo -e " ${CYAN}Config:${RESET} ${config}"
723
+ # Launch Claude for login
724
+ echo -e "${YELLOW}[i] Starting Claude in isolated instance...${RESET}"
725
+ echo -e "${YELLOW}[i] You will be prompted to login with your account.${RESET}"
332
726
  echo ""
333
727
 
334
- echo -e "${CYAN}Documentation:${RESET} https://github.com/kaitranntt/ccs"
335
- echo -e "${CYAN}License:${RESET} MIT"
728
+ CLAUDE_CONFIG_DIR="$instance_path" $(detect_claude_cli) || {
729
+ msg_error "Login failed or cancelled"
730
+ echo "To retry: ${YELLOW}ccs auth create $profile_name --force${RESET}"
731
+ return 1
732
+ }
733
+
734
+ echo ""
735
+ echo -e "${GREEN}[OK] Profile created successfully${RESET}"
736
+ echo ""
737
+ echo " Profile: $profile_name"
738
+ echo " Instance: $instance_path"
739
+ echo ""
740
+ echo "Usage:"
741
+ echo " ${YELLOW}ccs $profile_name \"your prompt here\"${RESET} # Use this specific profile"
742
+ echo ""
743
+ echo "To set as default (so you can use just \"ccs\"):"
744
+ echo " ${YELLOW}ccs auth default $profile_name${RESET}"
745
+ echo ""
746
+ }
747
+
748
+ auth_list() {
749
+ local verbose=false
750
+ [[ "${1:-}" == "--verbose" ]] && verbose=true
751
+
752
+ # Read profiles.json
753
+ [[ ! -f "$PROFILES_JSON" ]] && {
754
+ echo -e "${YELLOW}No account profiles found${RESET}"
755
+ echo ""
756
+ echo "To create your first profile:"
757
+ echo " ${YELLOW}ccs auth create <profile>${RESET}"
758
+ return 0
759
+ }
760
+
761
+ local profiles=$(jq -r '.profiles | keys[]' "$PROFILES_JSON" 2>/dev/null || true)
762
+ local default_profile=$(jq -r '.default // empty' "$PROFILES_JSON" 2>/dev/null || true)
763
+
764
+ [[ -z "$profiles" ]] && {
765
+ echo -e "${YELLOW}No account profiles found${RESET}"
766
+ return 0
767
+ }
768
+
769
+ echo -e "${BOLD}Saved Account Profiles:${RESET}"
770
+ echo ""
771
+
772
+ # Display profiles
773
+ while IFS= read -r profile; do
774
+ local is_default=false
775
+ [[ "$profile" == "$default_profile" ]] && is_default=true
776
+
777
+ if $is_default; then
778
+ echo -e "${GREEN}[*] ${CYAN}$profile${GREEN} (default)${RESET}"
779
+ else
780
+ echo -e "[ ] ${CYAN}$profile${RESET}"
781
+ fi
782
+
783
+ local type=$(jq -r ".profiles.\"$profile\".type // \"account\"" "$PROFILES_JSON" 2>/dev/null || true)
784
+ echo " Type: $type"
785
+
786
+ if $verbose; then
787
+ local created=$(jq -r ".profiles.\"$profile\".created" "$PROFILES_JSON" 2>/dev/null || true)
788
+ local last_used=$(jq -r ".profiles.\"$profile\".last_used // \"Never\"" "$PROFILES_JSON" 2>/dev/null || true)
789
+ echo " Created: $created"
790
+ echo " Last used: $last_used"
791
+ fi
792
+
793
+ echo ""
794
+ done <<< "$profiles"
795
+ }
796
+
797
+ auth_show() {
798
+ local profile_name="${1:-}"
799
+
800
+ [[ -z "$profile_name" ]] && {
801
+ msg_error "Profile name is required"
802
+ echo "Usage: ${YELLOW}ccs auth show <profile>${RESET}"
803
+ return 1
804
+ }
805
+
806
+ # Check if exists
807
+ profile_exists "$profile_name" || {
808
+ msg_error "Profile not found: $profile_name"
809
+ return 1
810
+ }
811
+
812
+ local default_profile=$(jq -r '.default // empty' "$PROFILES_JSON" 2>/dev/null || true)
813
+ local is_default=false
814
+ [[ "$profile_name" == "$default_profile" ]] && is_default=true
815
+
816
+ echo -e "${BOLD}Profile: $profile_name${RESET}"
817
+ echo ""
818
+
819
+ local type=$(jq -r ".profiles.\"$profile_name\".type // \"account\"" "$PROFILES_JSON" 2>/dev/null || true)
820
+ local created=$(jq -r ".profiles.\"$profile_name\".created" "$PROFILES_JSON" 2>/dev/null || true)
821
+ local last_used=$(jq -r ".profiles.\"$profile_name\".last_used // \"Never\"" "$PROFILES_JSON" 2>/dev/null || true)
822
+ local instance_path="$INSTANCES_DIR/$(sanitize_profile_name "$profile_name")"
823
+
824
+ echo " Type: $type"
825
+ echo " Default: $($is_default && echo "Yes" || echo "No")"
826
+ echo " Instance: $instance_path"
827
+ echo " Created: $created"
828
+ echo " Last used: $last_used"
829
+ echo ""
830
+ }
831
+
832
+ auth_remove() {
833
+ local profile_name=""
834
+ local force=false
835
+
836
+ while [[ $# -gt 0 ]]; do
837
+ case "$1" in
838
+ --force) force=true ;;
839
+ *) profile_name="$1" ;;
840
+ esac
841
+ shift
842
+ done
843
+
844
+ [[ -z "$profile_name" ]] && {
845
+ msg_error "Profile name is required"
846
+ echo "Usage: ${YELLOW}ccs auth remove <profile> --force${RESET}"
847
+ return 1
848
+ }
849
+
850
+ profile_exists "$profile_name" || {
851
+ msg_error "Profile not found: $profile_name"
852
+ return 1
853
+ }
854
+
855
+ $force || {
856
+ msg_error "Removal requires --force flag for safety"
857
+ echo "Run: ${YELLOW}ccs auth remove $profile_name --force${RESET}"
858
+ return 1
859
+ }
860
+
861
+ # Delete instance directory
862
+ local instance_path="$INSTANCES_DIR/$(sanitize_profile_name "$profile_name")"
863
+ rm -rf "$instance_path"
864
+
865
+ # Remove from registry
866
+ unregister_profile "$profile_name"
867
+
868
+ echo -e "${GREEN}[OK] Profile removed successfully${RESET}"
869
+ echo " Profile: $profile_name"
870
+ echo ""
871
+ }
872
+
873
+ auth_default() {
874
+ local profile_name="${1:-}"
875
+
876
+ [[ -z "$profile_name" ]] && {
877
+ msg_error "Profile name is required"
878
+ echo "Usage: ${YELLOW}ccs auth default <profile>${RESET}"
879
+ return 1
880
+ }
881
+
882
+ profile_exists "$profile_name" || {
883
+ msg_error "Profile not found: $profile_name"
884
+ return 1
885
+ }
886
+
887
+ set_default_profile "$profile_name"
888
+
889
+ echo -e "${GREEN}[OK] Default profile set${RESET}"
890
+ echo " Profile: $profile_name"
891
+ echo ""
892
+ echo "Now you can use:"
893
+ echo " ${YELLOW}ccs \"your prompt\"${RESET} # Uses $profile_name profile"
336
894
  echo ""
337
- echo -e "${YELLOW}Run 'ccs --help' for usage information${RESET}"
338
895
  }
339
896
 
897
+ handle_auth_commands() {
898
+ shift # Remove 'auth'
899
+
900
+ local subcommand="${1:-}"
901
+ shift || true
902
+
903
+ case "$subcommand" in
904
+ create) auth_create "$@" ;;
905
+ list) auth_list "$@" ;;
906
+ show) auth_show "$@" ;;
907
+ remove) auth_remove "$@" ;;
908
+ default) auth_default "$@" ;;
909
+ *) auth_help ;;
910
+ esac
911
+ }
912
+
913
+ # --- Main Execution Logic ---
914
+
340
915
  # Special case: version command (check BEFORE profile detection)
341
916
  if [[ $# -gt 0 ]] && [[ "${1}" == "version" || "${1}" == "--version" || "${1}" == "-v" ]]; then
342
917
  show_version
@@ -345,37 +920,28 @@ fi
345
920
 
346
921
  # Special case: help command (check BEFORE profile detection)
347
922
  if [[ $# -gt 0 ]] && [[ "${1}" == "--help" || "${1}" == "-h" || "${1}" == "help" ]]; then
348
- setup_colors
349
923
  show_help
350
924
  exit 0
351
925
  fi
352
926
 
353
- # Special case: install command (check BEFORE profile detection)
354
- if [[ $# -gt 0 ]] && [[ "${1}" == "--install" ]]; then
355
- echo ""
356
- echo "Feature not available"
357
- echo ""
358
- echo "The --install flag is currently under development."
359
- echo ".claude/ integration testing is not complete."
360
- echo ""
361
- echo "For updates: https://github.com/kaitranntt/ccs/issues"
362
- echo ""
363
- exit 0
927
+ # Special case: auth commands
928
+ if [[ $# -gt 0 ]] && [[ "${1}" == "auth" ]]; then
929
+ handle_auth_commands "$@"
930
+ exit $?
364
931
  fi
365
932
 
366
- # Special case: uninstall command (check BEFORE profile detection)
367
- if [[ $# -gt 0 ]] && [[ "${1}" == "--uninstall" ]]; then
368
- echo ""
369
- echo "Feature not available"
370
- echo ""
371
- echo "The --uninstall flag is currently under development."
372
- echo ".claude/ integration testing is not complete."
373
- echo ""
374
- echo "For updates: https://github.com/kaitranntt/ccs/issues"
375
- echo ""
376
- exit 0
933
+ # Special case: doctor command
934
+ if [[ $# -gt 0 ]] && [[ "${1}" == "doctor" || "${1}" == "--doctor" ]]; then
935
+ doctor_run
936
+ exit $?
377
937
  fi
378
938
 
939
+ # Run auto-recovery before main logic
940
+ auto_recover || {
941
+ msg_error "Auto-recovery failed. Check permissions."
942
+ exit 1
943
+ }
944
+
379
945
  # Smart profile detection: if first arg starts with '-', it's a flag not a profile
380
946
  if [[ $# -eq 0 ]] || [[ "${1}" =~ ^- ]]; then
381
947
  # No args or first arg is a flag → use default profile
@@ -385,29 +951,6 @@ else
385
951
  PROFILE="${1}"
386
952
  fi
387
953
 
388
- # Check config exists
389
- if [[ ! -f "$CONFIG_FILE" ]]; then
390
- msg_error "Config file not found: $CONFIG_FILE
391
-
392
- Solutions:
393
- 1. Reinstall CCS:
394
- curl -fsSL ccs.kaitran.ca/install | bash
395
-
396
- 2. Or create config manually:
397
- mkdir -p ~/.ccs
398
- cat > ~/.ccs/config.json << 'EOF'
399
- {
400
- \"profiles\": {
401
- \"glm\": \"~/.ccs/glm.settings.json\",
402
- \"kimi\": \"~/.ccs/kimi.settings.json\",
403
- \"default\": \"~/.claude/settings.json\"
404
- }
405
- }
406
- EOF"
407
- exit 1
408
- fi
409
-
410
-
411
954
  # Validate profile name (alphanumeric, dash, underscore only)
412
955
  if [[ "$PROFILE" =~ [^a-zA-Z0-9_-] ]]; then
413
956
  msg_error "Invalid profile name: $PROFILE
@@ -416,44 +959,12 @@ Use only alphanumeric characters, dash, or underscore."
416
959
  exit 1
417
960
  fi
418
961
 
419
- # Single check gets profile path, validates JSON + structure in one step
420
- SETTINGS_PATH=$(jq -r --arg profile "$PROFILE" '.profiles[$profile] // empty' "$CONFIG_FILE" 2>/dev/null)
421
-
422
- if [[ -z "$SETTINGS_PATH" ]]; then
423
- # Could be: invalid JSON, no profiles object, or profile not found
424
- # Show helpful error based on what we can detect
425
- if ! jq -e . "$CONFIG_FILE" &>/dev/null; then
426
- msg_error "Invalid JSON in $CONFIG_FILE
427
-
428
- Fix the JSON syntax or reinstall:
429
- curl -fsSL ccs.kaitran.ca/install | bash"
430
- elif ! jq -e '.profiles' "$CONFIG_FILE" &>/dev/null; then
431
- msg_error "Config must have 'profiles' object
432
-
433
- See config/config.example.json for correct format
434
- Or reinstall:
435
- curl -fsSL ccs.kaitran.ca/install | bash"
436
- else
437
- AVAILABLE_PROFILES=$(jq -r '.profiles | keys[]' "$CONFIG_FILE" 2>/dev/null | sed 's/^/ - /')
438
- msg_error "Profile '$PROFILE' not found in $CONFIG_FILE
962
+ # Detect profile type
963
+ if ! detect_profile_type "$PROFILE"; then
964
+ msg_error "Profile '$PROFILE' not found
439
965
 
440
966
  Available profiles:
441
- $AVAILABLE_PROFILES"
442
- fi
443
- exit 1
444
- fi
445
-
446
- # Expand ~ in path
447
- SETTINGS_PATH="${SETTINGS_PATH/#\~/$HOME}"
448
-
449
- # Validate settings file exists
450
- if [[ ! -f "$SETTINGS_PATH" ]]; then
451
- msg_error "Settings file not found: $SETTINGS_PATH
452
-
453
- Solutions:
454
- 1. Create the settings file for profile '$PROFILE'
455
- 2. Update the path in $CONFIG_FILE
456
- 3. Or reinstall: curl -fsSL ccs.kaitran.ca/install | bash"
967
+ $(list_available_profiles)"
457
968
  exit 1
458
969
  fi
459
970
 
@@ -465,9 +976,45 @@ fi
465
976
  # Detect Claude CLI executable
466
977
  CLAUDE_CLI=$(detect_claude_cli)
467
978
 
468
- # Execute Claude with the profile settings
469
- # If claude is not found, exec will fail and show an error
470
- if ! exec "$CLAUDE_CLI" --settings "$SETTINGS_PATH" "$@"; then
471
- show_claude_not_found_error
472
- exit 1
473
- fi
979
+ # Execute based on profile type (Phase 5)
980
+ case "$PROFILE_TYPE" in
981
+ account)
982
+ # Account-based profile: use CLAUDE_CONFIG_DIR
983
+ INSTANCE_PATH=$(ensure_instance "$PROFILE_NAME")
984
+ touch_profile "$PROFILE_NAME" # Update last_used
985
+
986
+ # Execute Claude with isolated config
987
+ CLAUDE_CONFIG_DIR="$INSTANCE_PATH" exec "$CLAUDE_CLI" "$@" || {
988
+ show_claude_not_found_error
989
+ exit 1
990
+ }
991
+ ;;
992
+
993
+ settings)
994
+ # Settings-based profile: use --settings flag
995
+ SETTINGS_PATH="${PROFILE_PATH/#\~/$HOME}"
996
+
997
+ [[ ! -f "$SETTINGS_PATH" ]] && {
998
+ msg_error "Settings file not found: $SETTINGS_PATH"
999
+ exit 1
1000
+ }
1001
+
1002
+ exec "$CLAUDE_CLI" --settings "$SETTINGS_PATH" "$@" || {
1003
+ show_claude_not_found_error
1004
+ exit 1
1005
+ }
1006
+ ;;
1007
+
1008
+ default)
1009
+ # Default: no special handling
1010
+ exec "$CLAUDE_CLI" "$@" || {
1011
+ show_claude_not_found_error
1012
+ exit 1
1013
+ }
1014
+ ;;
1015
+
1016
+ *)
1017
+ msg_error "Unknown profile type: $PROFILE_TYPE"
1018
+ exit 1
1019
+ ;;
1020
+ esac