@kaitranntt/ccs 3.4.6 → 3.5.0

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,12 +2,32 @@
2
2
  set -euo pipefail
3
3
 
4
4
  # Version (updated by scripts/bump-version.sh)
5
- CCS_VERSION="3.4.6"
5
+ CCS_VERSION="3.5.0"
6
6
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
7
  readonly CONFIG_FILE="${CCS_CONFIG:-$HOME/.ccs/config.json}"
8
8
  readonly PROFILES_JSON="$HOME/.ccs/profiles.json"
9
9
  readonly INSTANCES_DIR="$HOME/.ccs/instances"
10
10
 
11
+ # Determine dependency location (git vs installed)
12
+ # Git: lib/ccs and dependencies are in same dir (lib/)
13
+ # Installed: lib/ccs is symlinked from ~/.local/bin/ccs, dependencies in ~/.ccs/lib/
14
+ if [[ -f "$SCRIPT_DIR/error-codes.sh" ]]; then
15
+ # Git install - files in same directory
16
+ DEP_DIR="$SCRIPT_DIR"
17
+ else
18
+ # Standalone install - files in ~/.ccs/lib/
19
+ DEP_DIR="$HOME/.ccs/lib"
20
+ fi
21
+
22
+ # Source error codes
23
+ source "$DEP_DIR/error-codes.sh"
24
+
25
+ # Source progress indicators
26
+ source "$DEP_DIR/progress-indicator.sh"
27
+
28
+ # Source interactive prompts
29
+ source "$DEP_DIR/prompt.sh"
30
+
11
31
  # --- Color/Format Functions ---
12
32
  setup_colors() {
13
33
  # Enable colors if: FORCE_COLOR set OR (TTY detected AND NO_COLOR not set) OR (TERM supports colors AND NO_COLOR not set)
@@ -27,14 +47,121 @@ setup_colors() {
27
47
 
28
48
  msg_error() {
29
49
  echo "" >&2
30
- echo -e "${RED}${BOLD}╔═════════════════════════════════════════════╗${RESET}" >&2
31
- echo -e "${RED}${BOLD} ERROR ║${RESET}" >&2
32
- echo -e "${RED}${BOLD}╚═════════════════════════════════════════════╝${RESET}" >&2
50
+ echo -e "${RED}${BOLD}=============================================${RESET}" >&2
51
+ echo -e "${RED}${BOLD} ERROR${RESET}" >&2
52
+ echo -e "${RED}${BOLD}=============================================${RESET}" >&2
33
53
  echo "" >&2
34
54
  echo -e "${RED}$1${RESET}" >&2
35
55
  echo "" >&2
36
56
  }
37
57
 
58
+ # Enhanced error message with error codes
59
+ show_enhanced_error() {
60
+ local error_code="$1"
61
+ local short_msg="$2"
62
+ local context="${3:-}"
63
+ local suggestions="${4:-}"
64
+
65
+ echo "" >&2
66
+ echo -e "${RED}[X] $short_msg${RESET}" >&2
67
+ echo "" >&2
68
+
69
+ [[ -n "$context" ]] && {
70
+ echo -e "$context" >&2
71
+ echo "" >&2
72
+ }
73
+
74
+ [[ -n "$suggestions" ]] && {
75
+ echo -e "${YELLOW}Solutions:${RESET}" >&2
76
+ echo -e "$suggestions" >&2
77
+ echo "" >&2
78
+ }
79
+
80
+ echo -e "${YELLOW}Error: $error_code${RESET}" >&2
81
+ echo -e "${YELLOW}$(get_error_doc_url "$error_code")${RESET}" >&2
82
+ echo "" >&2
83
+ }
84
+
85
+ # Calculate Levenshtein distance between two strings
86
+ levenshtein_distance() {
87
+ local a="$1"
88
+ local b="$2"
89
+ local len_a=${#a}
90
+ local len_b=${#b}
91
+
92
+ # Return early for empty strings
93
+ [[ $len_a -eq 0 ]] && { echo "$len_b"; return; }
94
+ [[ $len_b -eq 0 ]] && { echo "$len_a"; return; }
95
+
96
+ # Initialize matrix using associative array
97
+ declare -A matrix
98
+
99
+ # Initialize first row and column
100
+ for ((j=0; j<=len_a; j++)); do
101
+ matrix[0,$j]=$j
102
+ done
103
+ for ((i=0; i<=len_b; i++)); do
104
+ matrix[$i,0]=$i
105
+ done
106
+
107
+ # Fill matrix
108
+ for ((i=1; i<=len_b; i++)); do
109
+ for ((j=1; j<=len_a; j++)); do
110
+ if [[ "${a:j-1:1}" == "${b:i-1:1}" ]]; then
111
+ matrix[$i,$j]=${matrix[$((i-1)),$((j-1))]}
112
+ else
113
+ local sub=${matrix[$((i-1)),$((j-1))]}
114
+ local ins=${matrix[$i,$((j-1))]}
115
+ local del=${matrix[$((i-1)),$j]}
116
+ local min=$sub
117
+ [[ $ins -lt $min ]] && min=$ins
118
+ [[ $del -lt $min ]] && min=$del
119
+ matrix[$i,$j]=$((min + 1))
120
+ fi
121
+ done
122
+ done
123
+
124
+ echo "${matrix[$len_b,$len_a]}"
125
+ }
126
+
127
+ # Find similar strings using fuzzy matching
128
+ find_similar_strings() {
129
+ local target="$1"
130
+ shift
131
+ local candidates=("$@")
132
+ local max_distance=2
133
+ local target_lower="${target,,}"
134
+
135
+ declare -A distances
136
+ local matches=()
137
+
138
+ # Calculate distances
139
+ for candidate in "${candidates[@]}"; do
140
+ local candidate_lower="${candidate,,}"
141
+ local dist=$(levenshtein_distance "$target_lower" "$candidate_lower")
142
+ if [[ $dist -le $max_distance && $dist -gt 0 ]]; then
143
+ distances["$candidate"]=$dist
144
+ matches+=("$candidate")
145
+ fi
146
+ done
147
+
148
+ # Sort by distance (simple bubble sort for small arrays)
149
+ for ((i=0; i<${#matches[@]}; i++)); do
150
+ for ((j=i+1; j<${#matches[@]}; j++)); do
151
+ if [[ ${distances[${matches[i]}]} -gt ${distances[${matches[j]}]} ]]; then
152
+ local temp="${matches[i]}"
153
+ matches[i]="${matches[j]}"
154
+ matches[j]="$temp"
155
+ fi
156
+ done
157
+ done
158
+
159
+ # Return first 3 matches
160
+ for ((i=0; i<${#matches[@]} && i<3; i++)); do
161
+ echo "${matches[i]}"
162
+ done
163
+ }
164
+
38
165
  show_help() {
39
166
  echo -e "${BOLD}CCS (Claude Code Switch) - Instant profile switching for Claude CLI${RESET}"
40
167
  echo ""
@@ -66,6 +193,7 @@ show_help() {
66
193
  echo -e "${CYAN}Flags:${RESET}"
67
194
  echo -e " ${YELLOW}-h, --help${RESET} Show this help message"
68
195
  echo -e " ${YELLOW}-v, --version${RESET} Show version and installation info"
196
+ echo -e " ${YELLOW}--shell-completion${RESET} Install shell auto-completion"
69
197
  echo ""
70
198
  echo -e "${CYAN}Configuration:${RESET}"
71
199
  echo -e " Config: ~/.ccs/config.json"
@@ -78,6 +206,22 @@ show_help() {
78
206
  echo -e " Skills: ~/.ccs/shared/skills/"
79
207
  echo -e " Note: Commands, skills, and agents are symlinked across all profiles"
80
208
  echo ""
209
+ echo -e "${CYAN}Examples:${RESET}"
210
+ echo -e " Quick start:"
211
+ echo -e " ${YELLOW}\$ ccs${RESET} # Use default account"
212
+ echo -e " ${YELLOW}\$ ccs glm \"implement API\"${RESET} # Cost-optimized model"
213
+ echo ""
214
+ echo -e " Multi-account workflow:"
215
+ echo -e " ${YELLOW}\$ ccs auth create work${RESET} # Create work profile"
216
+ echo -e " ${YELLOW}\$ ccs work \"review PR\"${RESET} # Use work account"
217
+ echo ""
218
+ echo -e " For more: ${CYAN}https://github.com/kaitranntt/ccs#usage${RESET}"
219
+ echo ""
220
+ echo -e "${YELLOW}Uninstall:${RESET}"
221
+ echo " npm: npm uninstall -g @kaitranntt/ccs"
222
+ echo " macOS/Linux: curl -fsSL ccs.kaitran.ca/uninstall | bash"
223
+ echo " Windows: irm ccs.kaitran.ca/uninstall | iex"
224
+ echo ""
81
225
  echo -e "${CYAN}Documentation:${RESET}"
82
226
  echo -e " GitHub: ${CYAN}https://github.com/kaitranntt/ccs${RESET}"
83
227
  echo -e " Docs: https://github.com/kaitranntt/ccs/blob/main/README.md"
@@ -189,96 +333,137 @@ doctor_run() {
189
333
  echo ""
190
334
 
191
335
  local has_errors=false
336
+ local total_checks=9
337
+ local current_check=0
192
338
 
193
339
  # Check Claude CLI
340
+ current_check=$((current_check + 1))
341
+ show_progress_step $current_check $total_checks "Checking Claude CLI"
194
342
  if command -v "$(detect_claude_cli)" &>/dev/null; then
343
+ clear_progress
195
344
  doctor_check "Claude CLI" "success"
196
345
  else
346
+ clear_progress
197
347
  doctor_check "Claude CLI" "error" "Not found in PATH"
198
348
  has_errors=true
199
349
  fi
200
350
 
201
351
  # Check ~/.ccs/
352
+ current_check=$((current_check + 1))
353
+ show_progress_step $current_check $total_checks "Checking CCS directory"
202
354
  if [[ -d "$HOME/.ccs" ]]; then
355
+ clear_progress
203
356
  doctor_check "CCS Directory" "success"
204
357
  else
358
+ clear_progress
205
359
  doctor_check "CCS Directory" "error" "~/.ccs/ not found"
206
360
  has_errors=true
207
361
  fi
208
362
 
209
363
  # Check config.json
364
+ current_check=$((current_check + 1))
365
+ show_progress_step $current_check $total_checks "Checking config.json"
210
366
  if [[ -f "$CONFIG_FILE" ]]; then
211
367
  if jq empty "$CONFIG_FILE" 2>/dev/null; then
368
+ clear_progress
212
369
  doctor_check "config.json" "success"
213
370
  else
371
+ clear_progress
214
372
  doctor_check "config.json" "error" "Invalid JSON"
215
373
  has_errors=true
216
374
  fi
217
375
  else
376
+ clear_progress
218
377
  doctor_check "config.json" "error" "Not found"
219
378
  has_errors=true
220
379
  fi
221
380
 
222
381
  # Check glm.settings.json
382
+ current_check=$((current_check + 1))
383
+ show_progress_step $current_check $total_checks "Checking glm.settings.json"
223
384
  local glm_file="$HOME/.ccs/glm.settings.json"
224
385
  if [[ -f "$glm_file" ]]; then
225
386
  if jq empty "$glm_file" 2>/dev/null; then
387
+ clear_progress
226
388
  doctor_check "glm.settings.json" "success"
227
389
  else
390
+ clear_progress
228
391
  doctor_check "glm.settings.json" "error" "Invalid JSON"
229
392
  has_errors=true
230
393
  fi
231
394
  else
395
+ clear_progress
232
396
  doctor_check "glm.settings.json" "error" "Not found"
233
397
  has_errors=true
234
398
  fi
235
399
 
236
400
  # Check kimi.settings.json
401
+ current_check=$((current_check + 1))
402
+ show_progress_step $current_check $total_checks "Checking kimi.settings.json"
237
403
  local kimi_file="$HOME/.ccs/kimi.settings.json"
238
404
  if [[ -f "$kimi_file" ]]; then
239
405
  if jq empty "$kimi_file" 2>/dev/null; then
406
+ clear_progress
240
407
  doctor_check "kimi.settings.json" "success"
241
408
  else
409
+ clear_progress
242
410
  doctor_check "kimi.settings.json" "error" "Invalid JSON"
243
411
  has_errors=true
244
412
  fi
245
413
  else
414
+ clear_progress
246
415
  doctor_check "kimi.settings.json" "error" "Not found"
247
416
  has_errors=true
248
417
  fi
249
418
 
250
419
  # Check ~/.claude/settings.json
420
+ current_check=$((current_check + 1))
421
+ show_progress_step $current_check $total_checks "Checking Claude settings"
251
422
  if [[ -f "$HOME/.claude/settings.json" ]]; then
252
423
  if jq empty "$HOME/.claude/settings.json" 2>/dev/null; then
424
+ clear_progress
253
425
  doctor_check "Claude Settings" "success"
254
426
  else
427
+ clear_progress
255
428
  doctor_check "Claude Settings" "warning" "Invalid JSON"
256
429
  fi
257
430
  else
431
+ clear_progress
258
432
  doctor_check "Claude Settings" "warning" "Not found - run 'claude /login'"
259
433
  fi
260
434
 
261
435
  # Check profiles
436
+ current_check=$((current_check + 1))
437
+ show_progress_step $current_check $total_checks "Checking profiles"
262
438
  if [[ -f "$CONFIG_FILE" ]]; then
263
439
  local profile_count=$(jq -r '.profiles | length' "$CONFIG_FILE" 2>/dev/null || echo "0")
440
+ clear_progress
264
441
  doctor_check "Profiles" "success" "($profile_count configured)"
265
442
  fi
266
443
 
267
444
  # Check instances
445
+ current_check=$((current_check + 1))
446
+ show_progress_step $current_check $total_checks "Checking instances"
268
447
  if [[ -d "$INSTANCES_DIR" ]]; then
269
448
  local instance_count=$(find "$INSTANCES_DIR" -maxdepth 1 -type d 2>/dev/null | wc -l)
270
449
  instance_count=$((instance_count - 1)) # Exclude parent dir
450
+ clear_progress
271
451
  doctor_check "Instances" "success" "($instance_count account profiles)"
272
452
  else
453
+ clear_progress
273
454
  doctor_check "Instances" "success" "(no account profiles)"
274
455
  fi
275
456
 
276
457
  # Check permissions
458
+ current_check=$((current_check + 1))
459
+ show_progress_step $current_check $total_checks "Checking permissions"
277
460
  local test_file="$HOME/.ccs/.permission-test"
278
461
  if echo "test" > "$test_file" 2>/dev/null; then
279
462
  rm -f "$test_file" 2>/dev/null
463
+ clear_progress
280
464
  doctor_check "Permissions" "success"
281
465
  else
466
+ clear_progress
282
467
  doctor_check "Permissions" "error" "Cannot write to ~/.ccs/"
283
468
  has_errors=true
284
469
  fi
@@ -629,6 +814,27 @@ ensure_instance() {
629
814
  # --- Profile Detection Logic (Phase 1) ---
630
815
 
631
816
  # List available profiles for error messages
817
+ # Get all profile names (for fuzzy matching)
818
+ get_all_profile_names() {
819
+ local names=()
820
+
821
+ # Settings-based profiles
822
+ if [[ -f "$CONFIG_FILE" ]]; then
823
+ while IFS= read -r name; do
824
+ names+=("$name")
825
+ done < <(jq -r '.profiles | keys[]' "$CONFIG_FILE" 2>/dev/null || true)
826
+ fi
827
+
828
+ # Account-based profiles
829
+ if [[ -f "$PROFILES_JSON" ]]; then
830
+ while IFS= read -r name; do
831
+ names+=("$name")
832
+ done < <(jq -r '.profiles | keys[]' "$PROFILES_JSON" 2>/dev/null || true)
833
+ fi
834
+
835
+ printf '%s\n' "${names[@]}"
836
+ }
837
+
632
838
  list_available_profiles() {
633
839
  local lines=()
634
840
 
@@ -745,6 +951,12 @@ auth_help() {
745
951
  echo -e " ${YELLOW}ccs work \"review code\"${RESET} # Use work profile"
746
952
  echo -e " ${YELLOW}ccs \"review code\"${RESET} # Use default profile"
747
953
  echo ""
954
+ echo -e "${CYAN}Options:${RESET}"
955
+ echo -e " ${YELLOW}--force${RESET} Allow overwriting existing profile (create)"
956
+ echo -e " ${YELLOW}--yes, -y${RESET} Skip confirmation prompts (remove)"
957
+ echo -e " ${YELLOW}--json${RESET} Output in JSON format (list, show)"
958
+ echo -e " ${YELLOW}--verbose${RESET} Show additional details (list)"
959
+ echo ""
748
960
  echo -e "${CYAN}Note:${RESET}"
749
961
  echo -e " By default, ${YELLOW}ccs${RESET} uses Claude CLI defaults from ~/.claude/"
750
962
  echo -e " Use ${YELLOW}ccs auth default <profile>${RESET} to change the default profile."
@@ -816,10 +1028,24 @@ auth_create() {
816
1028
 
817
1029
  auth_list() {
818
1030
  local verbose=false
819
- [[ "${1:-}" == "--verbose" ]] && verbose=true
1031
+ local json=false
1032
+
1033
+ # Parse arguments
1034
+ while [[ $# -gt 0 ]]; do
1035
+ case "$1" in
1036
+ --verbose) verbose=true ;;
1037
+ --json) json=true ;;
1038
+ *) ;;
1039
+ esac
1040
+ shift
1041
+ done
820
1042
 
821
1043
  # Read profiles.json
822
1044
  [[ ! -f "$PROFILES_JSON" ]] && {
1045
+ if $json; then
1046
+ echo "{\"version\":\"$CCS_VERSION\",\"profiles\":[]}"
1047
+ return 0
1048
+ fi
823
1049
  echo -e "${YELLOW}No account profiles found${RESET}"
824
1050
  echo ""
825
1051
  echo "To create your first profile:"
@@ -827,9 +1053,33 @@ auth_list() {
827
1053
  return 0
828
1054
  }
829
1055
 
830
- local profiles=$(jq -r '.profiles | keys[]' "$PROFILES_JSON" 2>/dev/null || true)
831
1056
  local default_profile=$(jq -r '.default // empty' "$PROFILES_JSON" 2>/dev/null || true)
832
1057
 
1058
+ # JSON output mode
1059
+ if $json; then
1060
+ jq -n \
1061
+ --arg version "$CCS_VERSION" \
1062
+ --arg default "$default_profile" \
1063
+ --argjson data "$(cat "$PROFILES_JSON")" \
1064
+ '{
1065
+ version: $version,
1066
+ profiles: [
1067
+ $data.profiles | to_entries[] | {
1068
+ name: .key,
1069
+ type: (.value.type // "account"),
1070
+ is_default: (.key == $default),
1071
+ created: .value.created,
1072
+ last_used: (.value.last_used // null),
1073
+ instance_path: ($ENV.INSTANCES_DIR + "/" + .key)
1074
+ }
1075
+ ]
1076
+ }'
1077
+ return 0
1078
+ fi
1079
+
1080
+ # Human-readable output
1081
+ local profiles=$(jq -r '.profiles | keys[]' "$PROFILES_JSON" 2>/dev/null || true)
1082
+
833
1083
  [[ -z "$profiles" ]] && {
834
1084
  echo -e "${YELLOW}No account profiles found${RESET}"
835
1085
  return 0
@@ -864,11 +1114,22 @@ auth_list() {
864
1114
  }
865
1115
 
866
1116
  auth_show() {
867
- local profile_name="${1:-}"
1117
+ local profile_name=""
1118
+ local json=false
1119
+
1120
+ # Parse arguments
1121
+ while [[ $# -gt 0 ]]; do
1122
+ case "$1" in
1123
+ --json) json=true ;;
1124
+ -*) msg_error "Unknown option: $1"; return 1 ;;
1125
+ *) profile_name="$1" ;;
1126
+ esac
1127
+ shift
1128
+ done
868
1129
 
869
1130
  [[ -z "$profile_name" ]] && {
870
1131
  msg_error "Profile name is required"
871
- echo "Usage: ${YELLOW}ccs auth show <profile>${RESET}"
1132
+ echo "Usage: ${YELLOW}ccs auth show <profile> [--json]${RESET}"
872
1133
  return 1
873
1134
  }
874
1135
 
@@ -882,29 +1143,59 @@ auth_show() {
882
1143
  local is_default=false
883
1144
  [[ "$profile_name" == "$default_profile" ]] && is_default=true
884
1145
 
885
- echo -e "${BOLD}Profile: $profile_name${RESET}"
886
- echo ""
887
-
888
1146
  local type=$(jq -r ".profiles.\"$profile_name\".type // \"account\"" "$PROFILES_JSON" 2>/dev/null || true)
889
1147
  local created=$(jq -r ".profiles.\"$profile_name\".created" "$PROFILES_JSON" 2>/dev/null || true)
890
- local last_used=$(jq -r ".profiles.\"$profile_name\".last_used // \"Never\"" "$PROFILES_JSON" 2>/dev/null || true)
1148
+ local last_used=$(jq -r ".profiles.\"$profile_name\".last_used // null" "$PROFILES_JSON" 2>/dev/null || true)
891
1149
  local instance_path="$INSTANCES_DIR/$(sanitize_profile_name "$profile_name")"
892
1150
 
1151
+ # Count sessions
1152
+ local session_count=0
1153
+ if [[ -d "$instance_path/session-env" ]]; then
1154
+ session_count=$(find "$instance_path/session-env" -name "*.json" 2>/dev/null | wc -l | tr -d ' ')
1155
+ fi
1156
+
1157
+ # JSON output mode
1158
+ if $json; then
1159
+ jq -n \
1160
+ --arg name "$profile_name" \
1161
+ --arg type "$type" \
1162
+ --argjson is_default "$is_default" \
1163
+ --arg created "$created" \
1164
+ --arg last_used "$last_used" \
1165
+ --arg instance_path "$instance_path" \
1166
+ --argjson session_count "$session_count" \
1167
+ '{
1168
+ name: $name,
1169
+ type: $type,
1170
+ is_default: $is_default,
1171
+ created: $created,
1172
+ last_used: $last_used,
1173
+ instance_path: $instance_path,
1174
+ session_count: $session_count
1175
+ }'
1176
+ return 0
1177
+ fi
1178
+
1179
+ # Human-readable output
1180
+ echo -e "${BOLD}Profile: $profile_name${RESET}"
1181
+ echo ""
1182
+
893
1183
  echo " Type: $type"
894
1184
  echo " Default: $($is_default && echo "Yes" || echo "No")"
895
1185
  echo " Instance: $instance_path"
896
1186
  echo " Created: $created"
897
- echo " Last used: $last_used"
1187
+ [[ "$last_used" != "null" ]] && echo " Last used: $last_used" || echo " Last used: Never"
898
1188
  echo ""
899
1189
  }
900
1190
 
901
1191
  auth_remove() {
902
1192
  local profile_name=""
903
- local force=false
904
1193
 
1194
+ # Parse arguments
905
1195
  while [[ $# -gt 0 ]]; do
906
1196
  case "$1" in
907
- --force) force=true ;;
1197
+ --yes|-y) export CCS_YES=1 ;; # Auto-confirm (export for confirm_action)
1198
+ -*) msg_error "Unknown option: $1"; return 1 ;;
908
1199
  *) profile_name="$1" ;;
909
1200
  esac
910
1201
  shift
@@ -912,7 +1203,8 @@ auth_remove() {
912
1203
 
913
1204
  [[ -z "$profile_name" ]] && {
914
1205
  msg_error "Profile name is required"
915
- echo "Usage: ${YELLOW}ccs auth remove <profile> --force${RESET}"
1206
+ echo ""
1207
+ echo "Usage: ${YELLOW}ccs auth remove <profile> [--yes]${RESET}"
916
1208
  return 1
917
1209
  }
918
1210
 
@@ -921,14 +1213,28 @@ auth_remove() {
921
1213
  return 1
922
1214
  }
923
1215
 
924
- $force || {
925
- msg_error "Removal requires --force flag for safety"
926
- echo "Run: ${YELLOW}ccs auth remove $profile_name --force${RESET}"
927
- return 1
928
- }
1216
+ # Get instance path and session count for impact display
1217
+ local instance_path="$INSTANCES_DIR/$(sanitize_profile_name "$profile_name")"
1218
+ local session_count=0
1219
+
1220
+ if [[ -d "$instance_path/session-env" ]]; then
1221
+ session_count=$(find "$instance_path/session-env" -name "*.json" 2>/dev/null | wc -l | tr -d ' ')
1222
+ fi
1223
+
1224
+ # Display impact
1225
+ echo ""
1226
+ echo "Profile '${CYAN}$profile_name${RESET}' will be permanently deleted."
1227
+ echo " Instance path: $instance_path"
1228
+ echo " Sessions: $session_count conversation$([ "$session_count" -ne 1 ] && echo "s" || echo "")"
1229
+ echo ""
1230
+
1231
+ # Interactive confirmation (or --yes flag)
1232
+ if ! confirm_action "Delete this profile?" "no"; then
1233
+ echo "[i] Cancelled"
1234
+ return 0
1235
+ fi
929
1236
 
930
1237
  # Delete instance directory
931
- local instance_path="$INSTANCES_DIR/$(sanitize_profile_name "$profile_name")"
932
1238
  rm -rf "$instance_path"
933
1239
 
934
1240
  # Remove from registry
@@ -979,6 +1285,184 @@ handle_auth_commands() {
979
1285
  esac
980
1286
  }
981
1287
 
1288
+ # --- Shell Completion Installer ---
1289
+
1290
+ install_shell_completion() {
1291
+ shift # Remove --shell-completion
1292
+
1293
+ echo -e "${BOLD}Shell Completion Installer${RESET}"
1294
+ echo ""
1295
+
1296
+ # Parse flags for manual shell selection
1297
+ local target_shell=""
1298
+ for arg in "$@"; do
1299
+ case "$arg" in
1300
+ --bash) target_shell="bash" ;;
1301
+ --zsh) target_shell="zsh" ;;
1302
+ --fish) target_shell="fish" ;;
1303
+ *) ;;
1304
+ esac
1305
+ done
1306
+
1307
+ # Auto-detect shell if not specified
1308
+ if [[ -z "$target_shell" ]]; then
1309
+ if [[ -n "$BASH_VERSION" ]]; then
1310
+ target_shell="bash"
1311
+ elif [[ -n "$ZSH_VERSION" ]]; then
1312
+ target_shell="zsh"
1313
+ elif [[ -n "$FISH_VERSION" ]]; then
1314
+ target_shell="fish"
1315
+ else
1316
+ echo -e "${RED}[X] Could not detect shell${RESET}" >&2
1317
+ echo "" >&2
1318
+ echo -e "${YELLOW}Usage:${RESET}" >&2
1319
+ echo " ccs --shell-completion # Auto-detect shell" >&2
1320
+ echo " ccs --shell-completion --bash # Install for bash" >&2
1321
+ echo " ccs --shell-completion --zsh # Install for zsh" >&2
1322
+ echo " ccs --shell-completion --fish # Install for fish" >&2
1323
+ echo "" >&2
1324
+ return 1
1325
+ fi
1326
+ fi
1327
+
1328
+ # Ensure completion files exist in ~/.ccs/completions/
1329
+ local completions_dir="$HOME/.ccs/completions"
1330
+ if [[ ! -d "$completions_dir" ]]; then
1331
+ mkdir -p "$completions_dir"
1332
+ fi
1333
+
1334
+ # Copy from scripts if not present
1335
+ local script_dir="$(dirname "$0")"
1336
+ if [[ -f "$script_dir/../scripts/completion/ccs.bash" ]]; then
1337
+ cp "$script_dir/../scripts/completion/ccs.bash" "$completions_dir/" 2>/dev/null || true
1338
+ cp "$script_dir/../scripts/completion/ccs.zsh" "$completions_dir/" 2>/dev/null || true
1339
+ cp "$script_dir/../scripts/completion/ccs.fish" "$completions_dir/" 2>/dev/null || true
1340
+ fi
1341
+
1342
+ # Install based on target shell
1343
+ case "$target_shell" in
1344
+ bash)
1345
+ local rc_file="$HOME/.bashrc"
1346
+ local completion_file="$completions_dir/ccs.bash"
1347
+ local marker="# CCS shell completion"
1348
+
1349
+ if [[ ! -f "$completion_file" ]]; then
1350
+ echo -e "${RED}[X] Completion file not found: $completion_file${RESET}" >&2
1351
+ echo " Please reinstall CCS." >&2
1352
+ return 1
1353
+ fi
1354
+
1355
+ # Check if already installed
1356
+ if grep -q "$marker" "$rc_file" 2>/dev/null; then
1357
+ echo -e "${GREEN}[OK] Shell completion already installed${RESET}"
1358
+ echo ""
1359
+ return 0
1360
+ fi
1361
+
1362
+ # Append to .bashrc
1363
+ {
1364
+ echo ""
1365
+ echo "$marker"
1366
+ echo "source \"$completion_file\""
1367
+ } >> "$rc_file"
1368
+
1369
+ echo -e "${GREEN}[OK] Shell completion installed successfully!${RESET}"
1370
+ echo ""
1371
+ echo "Added to $rc_file"
1372
+ echo ""
1373
+ echo -e "${CYAN}To activate:${RESET}"
1374
+ echo " source ~/.bashrc"
1375
+ echo ""
1376
+ echo -e "${CYAN}Then test:${RESET}"
1377
+ echo " ccs <TAB> # See available profiles"
1378
+ echo " ccs auth <TAB> # See auth subcommands"
1379
+ echo ""
1380
+ ;;
1381
+
1382
+ zsh)
1383
+ local rc_file="$HOME/.zshrc"
1384
+ local completion_dir="$HOME/.zsh/completion"
1385
+ local completion_file="$completions_dir/ccs.zsh"
1386
+ local marker="# CCS shell completion"
1387
+
1388
+ if [[ ! -f "$completion_file" ]]; then
1389
+ echo -e "${RED}[X] Completion file not found: $completion_file${RESET}" >&2
1390
+ echo " Please reinstall CCS." >&2
1391
+ return 1
1392
+ fi
1393
+
1394
+ # Create zsh completion directory
1395
+ mkdir -p "$completion_dir"
1396
+
1397
+ # Copy to zsh completion directory
1398
+ cp "$completion_file" "$completion_dir/_ccs"
1399
+
1400
+ # Check if already installed
1401
+ if grep -q "$marker" "$rc_file" 2>/dev/null; then
1402
+ echo -e "${GREEN}[OK] Shell completion already installed${RESET}"
1403
+ echo ""
1404
+ return 0
1405
+ fi
1406
+
1407
+ # Append to .zshrc
1408
+ {
1409
+ echo ""
1410
+ echo "$marker"
1411
+ echo "fpath=(~/.zsh/completion \$fpath)"
1412
+ echo "autoload -Uz compinit && compinit"
1413
+ } >> "$rc_file"
1414
+
1415
+ echo -e "${GREEN}[OK] Shell completion installed successfully!${RESET}"
1416
+ echo ""
1417
+ echo "Added to $rc_file"
1418
+ echo ""
1419
+ echo -e "${CYAN}To activate:${RESET}"
1420
+ echo " source ~/.zshrc"
1421
+ echo ""
1422
+ echo -e "${CYAN}Then test:${RESET}"
1423
+ echo " ccs <TAB> # See available profiles"
1424
+ echo " ccs auth <TAB> # See auth subcommands"
1425
+ echo ""
1426
+ ;;
1427
+
1428
+ fish)
1429
+ local fish_dir="$HOME/.config/fish/completions"
1430
+ local completion_file="$completions_dir/ccs.fish"
1431
+
1432
+ if [[ ! -f "$completion_file" ]]; then
1433
+ echo -e "${RED}[X] Completion file not found: $completion_file${RESET}" >&2
1434
+ echo " Please reinstall CCS." >&2
1435
+ return 1
1436
+ fi
1437
+
1438
+ # Create fish completion directory
1439
+ mkdir -p "$fish_dir"
1440
+
1441
+ # Copy to fish completion directory (fish auto-loads from here)
1442
+ cp "$completion_file" "$fish_dir/ccs.fish"
1443
+
1444
+ echo -e "${GREEN}[OK] Shell completion installed successfully!${RESET}"
1445
+ echo ""
1446
+ echo "Installed to $fish_dir/ccs.fish"
1447
+ echo ""
1448
+ echo -e "${CYAN}To activate:${RESET}"
1449
+ echo " Fish auto-loads completions (no reload needed)"
1450
+ echo ""
1451
+ echo -e "${CYAN}Then test:${RESET}"
1452
+ echo " ccs <TAB> # See available profiles"
1453
+ echo " ccs auth <TAB> # See auth subcommands"
1454
+ echo ""
1455
+ ;;
1456
+
1457
+ *)
1458
+ echo -e "${RED}[X] Unsupported shell: $target_shell${RESET}" >&2
1459
+ return 1
1460
+ ;;
1461
+ esac
1462
+
1463
+ return 0
1464
+ }
1465
+
982
1466
  # --- Main Execution Logic ---
983
1467
 
984
1468
  # Special case: version command (check BEFORE profile detection)
@@ -999,6 +1483,12 @@ if [[ $# -gt 0 ]] && [[ "${1}" == "auth" ]]; then
999
1483
  exit $?
1000
1484
  fi
1001
1485
 
1486
+ # Special case: shell completion installer
1487
+ if [[ $# -gt 0 ]] && [[ "${1}" == "--shell-completion" ]]; then
1488
+ install_shell_completion "$@"
1489
+ exit $?
1490
+ fi
1491
+
1002
1492
  # Special case: doctor command
1003
1493
  if [[ $# -gt 0 ]] && [[ "${1}" == "doctor" || "${1}" == "--doctor" ]]; then
1004
1494
  doctor_run
@@ -1030,10 +1520,36 @@ fi
1030
1520
 
1031
1521
  # Detect profile type
1032
1522
  if ! detect_profile_type "$PROFILE"; then
1033
- msg_error "Profile '$PROFILE' not found
1523
+ # Get suggestions using fuzzy matching
1524
+ mapfile -t all_profiles < <(get_all_profile_names)
1525
+ mapfile -t suggestions < <(find_similar_strings "$PROFILE" "${all_profiles[@]}")
1034
1526
 
1035
- Available profiles:
1036
- $(list_available_profiles)"
1527
+ echo "" >&2
1528
+ echo -e "${RED}[X] Profile '$PROFILE' not found${RESET}" >&2
1529
+ echo "" >&2
1530
+
1531
+ # Show suggestions if any
1532
+ if [[ ${#suggestions[@]} -gt 0 ]]; then
1533
+ echo -e "${YELLOW}Did you mean:${RESET}" >&2
1534
+ for suggestion in "${suggestions[@]}"; do
1535
+ echo " $suggestion" >&2
1536
+ done
1537
+ echo "" >&2
1538
+ fi
1539
+
1540
+ echo -e "${CYAN}Available profiles:${RESET}" >&2
1541
+ list_available_profiles >&2
1542
+ echo "" >&2
1543
+ echo -e "${YELLOW}Solutions:${RESET}" >&2
1544
+ echo " # Use existing profile" >&2
1545
+ echo " ccs <profile> \"your prompt\"" >&2
1546
+ echo "" >&2
1547
+ echo " # Create new account profile" >&2
1548
+ echo " ccs auth create <name>" >&2
1549
+ echo "" >&2
1550
+ echo -e "${YELLOW}Error: $E_PROFILE_NOT_FOUND${RESET}" >&2
1551
+ echo -e "${YELLOW}$(get_error_doc_url "$E_PROFILE_NOT_FOUND")${RESET}" >&2
1552
+ echo "" >&2
1037
1553
  exit 1
1038
1554
  fi
1039
1555