@kaitranntt/ccs 3.0.0 → 3.0.1

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